1 """GNUmed EMR structure editors
2
3 This module contains widgets to create and edit EMR structural
4 elements (issues, enconters, episodes).
5
6 This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
7 and Karsten <Karsten.Hilbert@gmx.net>.
8 """
9
10 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
11 __license__ = "GPL v2 or later"
12
13
14 import sys
15 import time
16 import logging
17 import datetime as pydt
18
19
20
21 import wx
22
23
24
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmDateTime
30
31 if __name__ == '__main__':
32 gmI18N.activate_locale()
33 gmI18N.install_domain()
34 gmDateTime.init()
35
36 from Gnumed.pycommon import gmExceptions
37 from Gnumed.pycommon import gmCfg
38 from Gnumed.pycommon import gmTools
39 from Gnumed.pycommon import gmDispatcher
40 from Gnumed.pycommon import gmMatchProvider
41
42 from Gnumed.business import gmEMRStructItems
43 from Gnumed.business import gmPraxis
44 from Gnumed.business import gmPerson
45
46 from Gnumed.wxpython import gmPhraseWheel
47 from Gnumed.wxpython import gmGuiHelpers
48 from Gnumed.wxpython import gmListWidgets
49 from Gnumed.wxpython import gmEditArea
50
51
52 _log = logging.getLogger('gm.ui')
53
54
55
56
58 """Spin time in seconds but let wx go on."""
59 if time2spin == 0:
60 return
61 sleep_time = 0.1
62 total_rounds = int(time2spin / sleep_time)
63 if total_rounds < 1:
64 wx.Yield()
65 time.sleep(sleep_time)
66 return
67 rounds = 0
68 while rounds < total_rounds:
69 wx.Yield()
70 time.sleep(sleep_time)
71 rounds += 1
72
73
74
75
76 -def edit_episode(parent=None, episode=None, single_entry=True):
85
86
97
98 def delete(episode=None):
99 if gmEMRStructItems.delete_episode(episode = episode):
100 return True
101 gmDispatcher.send (
102 signal = 'statustext',
103 msg = _('Cannot delete episode.'),
104 beep = True
105 )
106 return False
107
108 def manage_issues(episode=None):
109 return select_health_issues(parent = None, emr = emr)
110
111 def get_tooltip(data):
112 if data is None:
113 return None
114 return data.format (
115 patient = pat,
116 with_summary = True,
117 with_codes = True,
118 with_encounters = False,
119 with_documents = False,
120 with_hospital_stays = False,
121 with_procedures = False,
122 with_family_history = False,
123 with_tests = False,
124 with_vaccinations = False,
125 with_health_issue = True
126 )
127
128 def refresh(lctrl):
129 epis = emr.get_episodes(order_by = 'description')
130 items = [
131 [ e['description'],
132 gmTools.bool2subst(e['episode_open'], _('ongoing'), _('closed'), '<unknown>'),
133 gmDateTime.pydt_strftime(e.best_guess_clinical_start_date, '%Y %b %d'),
134 gmTools.coalesce(e['health_issue'], '')
135 ] for e in epis
136 ]
137 lctrl.set_string_items(items = items)
138 lctrl.set_data(data = epis)
139
140 gmListWidgets.get_choices_from_list (
141 parent = parent,
142 msg = _('\nSelect the episode you want to edit !\n'),
143 caption = _('Editing episodes ...'),
144 columns = [_('Episode'), _('Status'), _('Started'), _('Health issue')],
145 single_selection = True,
146 edit_callback = edit,
147 new_callback = edit,
148 delete_callback = delete,
149 refresh_callback = refresh,
150 list_tooltip_callback = get_tooltip,
151 left_extra_button = (_('Manage issues'), _('Manage health issues'), manage_issues)
152 )
153
154
224
225
227 """Prepare changing health issue for an episode.
228
229 Checks for two-open-episodes conflict. When this
230 function succeeds, the pk_health_issue has been set
231 on the episode instance and the episode should - for
232 all practical purposes - be ready for save_payload().
233 """
234
235 if not episode['episode_open']:
236 episode['pk_health_issue'] = target_issue['pk_health_issue']
237 if save_to_backend:
238 episode.save_payload()
239 return True
240
241
242 if target_issue is None:
243 episode['pk_health_issue'] = None
244 if save_to_backend:
245 episode.save_payload()
246 return True
247
248
249 db_cfg = gmCfg.cCfgSQL()
250 epi_ttl = int(db_cfg.get2 (
251 option = 'episode.ttl',
252 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
253 bias = 'user',
254 default = 60
255 ))
256 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
257 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
258 existing_epi = target_issue.get_open_episode()
259
260
261 if existing_epi is None:
262 episode['pk_health_issue'] = target_issue['pk_health_issue']
263 if save_to_backend:
264 episode.save_payload()
265 return True
266
267
268 if existing_epi['pk_episode'] == episode['pk_episode']:
269 episode['pk_health_issue'] = target_issue['pk_health_issue']
270 if save_to_backend:
271 episode.save_payload()
272 return True
273
274
275 move_range = (episode.best_guess_clinical_start_date, episode.best_guess_clinical_end_date)
276 if move_range[1] is None:
277 move_range_end = '?'
278 else:
279 move_range_end = move_range[1].strftime('%m/%y')
280 exist_range = (existing_epi.best_guess_clinical_start_date, existing_epi.best_guess_clinical_end_date)
281 if exist_range[1] is None:
282 exist_range_end = '?'
283 else:
284 exist_range_end = exist_range[1].strftime('%m/%y')
285 question = _(
286 'You want to associate the running episode:\n\n'
287 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
288 'with the health issue:\n\n'
289 ' "%(issue_name)s"\n\n'
290 'There already is another episode running\n'
291 'for this health issue:\n\n'
292 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
293 'However, there can only be one running\n'
294 'episode per health issue.\n\n'
295 'Which episode do you want to close ?'
296 ) % {
297 'new_epi_name': episode['description'],
298 'new_epi_start': move_range[0].strftime('%m/%y'),
299 'new_epi_end': move_range_end,
300 'issue_name': target_issue['description'],
301 'old_epi_name': existing_epi['description'],
302 'old_epi_start': exist_range[0].strftime('%m/%y'),
303 'old_epi_end': exist_range_end
304 }
305 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
306 parent = None,
307 id = -1,
308 caption = _('Resolving two-running-episodes conflict'),
309 question = question,
310 button_defs = [
311 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
312 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
313 ]
314 )
315 decision = dlg.ShowModal()
316
317 if decision == wx.ID_CANCEL:
318
319 return False
320
321 elif decision == wx.ID_YES:
322
323 existing_epi['episode_open'] = False
324 existing_epi.save_payload()
325
326 elif decision == wx.ID_NO:
327
328 episode['episode_open'] = False
329
330 else:
331 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
332
333 episode['pk_health_issue'] = target_issue['pk_health_issue']
334 if save_to_backend:
335 episode.save_payload()
336 return True
337
338
362
363
365 """Let user select an episode *description*.
366
367 The user can select an episode description from the previously
368 used descriptions across all episodes across all patients.
369
370 Selection is done with a phrasewheel so the user can
371 type the episode name and matches will be shown. Typing
372 "*" will show the entire list of episodes.
373
374 If the user types a description not existing yet a
375 new episode description will be returned.
376 """
378
379 mp = gmMatchProvider.cMatchProvider_SQL2 (
380 queries = [
381 """
382 SELECT DISTINCT ON (description)
383 description
384 AS data,
385 description
386 AS field_label,
387 description || ' ('
388 || CASE
389 WHEN is_open IS TRUE THEN _('ongoing')
390 ELSE _('closed')
391 END
392 || ')'
393 AS list_label
394 FROM
395 clin.episode
396 WHERE
397 description %(fragment_condition)s
398 ORDER BY description
399 LIMIT 30
400 """
401 ]
402 )
403 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
404 self.matcher = mp
405
406
408 """Let user select an episode.
409
410 The user can select an episode from the existing episodes of a
411 patient. Selection is done with a phrasewheel so the user
412 can type the episode name and matches will be shown. Typing
413 "*" will show the entire list of episodes. Closed episodes
414 will be marked as such. If the user types an episode name not
415 in the list of existing episodes a new episode can be created
416 from it if the programmer activated that feature.
417
418 If keyword <patient_id> is set to None or left out the control
419 will listen to patient change signals and therefore act on
420 gmPerson.gmCurrentPatient() changes.
421 """
443
444
445
446
448 if self.use_current_patient:
449 return False
450 self.__patient_id = int(patient_id)
451 self.set_context('pat', self.__patient_id)
452 return True
453
454
455 - def GetData(self, can_create=False, as_instance=False, is_open=False):
458
459
461
462 epi_name = self.GetValue().strip()
463 if epi_name == '':
464 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create episode without name.'), beep = True)
465 _log.debug('cannot create episode without name')
466 return
467
468 if self.use_current_patient:
469 pat = gmPerson.gmCurrentPatient()
470 else:
471 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
472
473 emr = pat.emr
474 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
475 if epi is None:
476 self.data = {}
477 else:
478 self.SetText (
479 value = epi_name,
480 data = epi['pk_episode']
481 )
482
483
486
487
488
489
493
494
500
501
503 if self.use_current_patient:
504 patient = gmPerson.gmCurrentPatient()
505 self.set_context('pat', patient.ID)
506 return True
507
508
509 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
510
511 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
512
525
526
527
528
530
531 errors = False
532
533 if len(self._PRW_description.GetValue().strip()) == 0:
534 errors = True
535 self._PRW_description.display_as_valid(False)
536 self._PRW_description.SetFocus()
537 else:
538 self._PRW_description.display_as_valid(True)
539 self._PRW_description.Refresh()
540
541 return not errors
542
543
545
546 pat = gmPerson.gmCurrentPatient()
547 emr = pat.emr
548
549 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
550 epi['summary'] = self._TCTRL_status.GetValue().strip()
551 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
552 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
553
554 issue_name = self._PRW_issue.GetValue().strip()
555 if len(issue_name) != 0:
556 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
557 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
558
559 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
560 gmDispatcher.send (
561 signal = 'statustext',
562 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
563 epi['description'],
564 issue['description']
565 )
566 )
567 gmEMRStructItems.delete_episode(episode = epi)
568 return False
569
570 epi.save()
571
572 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
573
574 self.data = epi
575 return True
576
577
579
580 self.data['description'] = self._PRW_description.GetValue().strip()
581 self.data['summary'] = self._TCTRL_status.GetValue().strip()
582 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
583 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
584
585 issue_name = self._PRW_issue.GetValue().strip()
586 if len(issue_name) == 0:
587 self.data['pk_health_issue'] = None
588 else:
589 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
590 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
591
592 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
593 gmDispatcher.send (
594 signal = 'statustext',
595 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
596 self.data['description'],
597 issue['description']
598 )
599 )
600 return False
601
602 self.data.save()
603 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
604
605 return True
606
607
622
623
653
654
656 self._refresh_as_new()
657
658
659
660
672
673
681
682 def delete(issue=None):
683 if gmEMRStructItems.delete_health_issue(health_issue = issue):
684 return True
685 gmDispatcher.send (
686 signal = 'statustext',
687 msg = _('Cannot delete health issue.'),
688 beep = True
689 )
690 return False
691
692 def get_tooltip(data):
693 if data is None:
694 return None
695 patient = gmPerson.cPatient(data['pk_patient'])
696 return data.format (
697 patient = patient,
698 with_summary = True,
699 with_codes = True,
700 with_episodes = True,
701 with_encounters = True,
702 with_medications = False,
703 with_hospital_stays = False,
704 with_procedures = False,
705 with_family_history = False,
706 with_documents = False,
707 with_tests = False,
708 with_vaccinations = False
709 )
710
711 def refresh(lctrl):
712 issues = emr.get_health_issues()
713 items = [
714 [
715 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), '', ''),
716 i['description'],
717 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), '', ''),
718 gmTools.bool2subst(i['is_active'], _('active'), '', ''),
719 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), '', '')
720 ] for i in issues
721 ]
722 lctrl.set_string_items(items = items)
723 lctrl.set_data(data = issues)
724
725 return gmListWidgets.get_choices_from_list (
726 parent = parent,
727 msg = _('\nSelect the health issues !\n'),
728 caption = _('Showing health issues ...'),
729 columns = ['', _('Health issue'), '', '', ''],
730 single_selection = False,
731 edit_callback = edit,
732 new_callback = edit,
733 delete_callback = delete,
734 refresh_callback = refresh
735 )
736
738
739
740
742
743 issues = kwargs['issues']
744 del kwargs['issues']
745
746 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
747
748 self.SetTitle(_('Select the health issues you are interested in ...'))
749 self._LCTRL_items.set_columns(['', _('Health Issue'), '', '', ''])
750
751 for issue in issues:
752 if issue['is_confidential']:
753 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = _('confidential'))
754 self._LCTRL_items.SetItemTextColour(row_num, wx.Colour('RED'))
755 else:
756 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = '')
757
758 self._LCTRL_items.SetItem(index = row_num, column = 1, label = issue['description'])
759 if issue['clinically_relevant']:
760 self._LCTRL_items.SetItem(index = row_num, column = 2, label = _('relevant'))
761 if issue['is_active']:
762 self._LCTRL_items.SetItem(index = row_num, column = 3, label = _('active'))
763 if issue['is_cause_of_death']:
764 self._LCTRL_items.SetItem(index = row_num, column = 4, label = _('fatal'))
765
766 self._LCTRL_items.set_column_widths()
767 self._LCTRL_items.set_data(data = issues)
768
769
771 """Let the user select a health issue.
772
773 The user can select a health issue from the existing issues
774 of a patient. Selection is done with a phrasewheel so the user
775 can type the issue name and matches will be shown. Typing
776 "*" will show the entire list of issues. Inactive issues
777 will be marked as such. If the user types an issue name not
778 in the list of existing issues a new issue can be created
779 from it if the programmer activated that feature.
780
781 If keyword <patient_id> is set to None or left out the control
782 will listen to patient change signals and therefore act on
783 gmPerson.gmCurrentPatient() changes.
784 """
786
787 ctxt = {'ctxt_pat': {'where_part': 'pk_patient=%(pat)s', 'placeholder': 'pat'}}
788
789 mp = gmMatchProvider.cMatchProvider_SQL2 (
790
791 queries = ["""
792 SELECT
793 data,
794 field_label,
795 list_label
796 FROM ((
797 SELECT
798 pk_health_issue AS data,
799 description AS field_label,
800 description AS list_label
801 FROM clin.v_health_issues
802 WHERE
803 is_active IS true
804 AND
805 description %(fragment_condition)s
806 AND
807 %(ctxt_pat)s
808
809 ) UNION (
810
811 SELECT
812 pk_health_issue AS data,
813 description AS field_label,
814 description || _(' (inactive)') AS list_label
815 FROM clin.v_health_issues
816 WHERE
817 is_active IS false
818 AND
819 description %(fragment_condition)s
820 AND
821 %(ctxt_pat)s
822 )) AS union_query
823 ORDER BY
824 list_label"""
825 ],
826 context = ctxt
827 )
828 try: kwargs['patient_id']
829 except KeyError: kwargs['patient_id'] = None
830
831 if kwargs['patient_id'] is None:
832 self.use_current_patient = True
833 self.__register_patient_change_signals()
834 pat = gmPerson.gmCurrentPatient()
835 if pat.connected:
836 mp.set_context('pat', pat.ID)
837 else:
838 self.use_current_patient = False
839 self.__patient_id = int(kwargs['patient_id'])
840 mp.set_context('pat', self.__patient_id)
841
842 del kwargs['patient_id']
843
844 gmPhraseWheel.cPhraseWheel.__init__ (
845 self,
846 *args,
847 **kwargs
848 )
849 self.matcher = mp
850
851
852
854 if self.use_current_patient:
855 return False
856 self.__patient_id = int(patient_id)
857 self.set_context('pat', self.__patient_id)
858 return True
859
861 issue_name = self.GetValue().strip()
862 if issue_name == '':
863 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create health issue without name.'), beep = True)
864 _log.debug('cannot create health issue without name')
865 return
866
867 if self.use_current_patient:
868 pat = gmPerson.gmCurrentPatient()
869 else:
870 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
871
872 emr = pat.emr
873 issue = emr.add_health_issue(issue_name = issue_name)
874
875 if issue is None:
876 self.data = {}
877 else:
878 self.SetText (
879 value = issue_name,
880 data = issue['pk_health_issue']
881 )
882
885
907
908
909
913
916
918 if self.use_current_patient:
919 patient = gmPerson.gmCurrentPatient()
920 self.set_context('pat', patient.ID)
921 return True
922
923 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
924
947
948 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
949
950 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
951 """Panel encapsulating health issue edit area functionality."""
952
970
972
973
974 mp = gmMatchProvider.cMatchProvider_SQL2 (
975 queries = ["SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
976 )
977 mp.setThresholds(1, 3, 5)
978 self._PRW_condition.matcher = mp
979
980 mp = gmMatchProvider.cMatchProvider_SQL2 (
981 queries = ["""
982 SELECT DISTINCT ON (grouping) grouping, grouping from (
983
984 SELECT rank, grouping from ((
985
986 SELECT
987 grouping,
988 1 as rank
989 from
990 clin.health_issue
991 where
992 grouping %%(fragment_condition)s
993 and
994 (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
995
996 ) union (
997
998 SELECT
999 grouping,
1000 2 as rank
1001 from
1002 clin.health_issue
1003 where
1004 grouping %%(fragment_condition)s
1005
1006 )) as union_result
1007
1008 order by rank
1009
1010 ) as order_result
1011
1012 limit 50""" % gmPerson.gmCurrentPatient().ID
1013 ]
1014 )
1015 mp.setThresholds(1, 3, 5)
1016 self._PRW_grouping.matcher = mp
1017
1018 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1019 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1020
1021
1022
1023
1024 self._PRW_year_noted.Enable(True)
1025
1026 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
1027
1028
1029
1030
1049
1050
1052 pat = gmPerson.gmCurrentPatient()
1053 emr = pat.emr
1054
1055 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1056
1057 side = ''
1058 if self._ChBOX_left.GetValue():
1059 side += 's'
1060 if self._ChBOX_right.GetValue():
1061 side += 'd'
1062 issue['laterality'] = side
1063
1064 issue['summary'] = self._TCTRL_status.GetValue().strip()
1065 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1066 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1067 issue['is_active'] = self._ChBOX_active.GetValue()
1068 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1069 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1070 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1071
1072 age_noted = self._PRW_age_noted.GetData()
1073 if age_noted is not None:
1074 issue['age_noted'] = age_noted
1075
1076 issue.save()
1077
1078 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1079
1080 self.data = issue
1081 return True
1082
1084
1085 self.data['description'] = self._PRW_condition.GetValue().strip()
1086
1087 side = ''
1088 if self._ChBOX_left.GetValue():
1089 side += 's'
1090 if self._ChBOX_right.GetValue():
1091 side += 'd'
1092 self.data['laterality'] = side
1093
1094 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1095 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1096 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1097 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1098 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1099 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1100 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1101
1102 age_noted = self._PRW_age_noted.GetData()
1103 if age_noted is not None:
1104 self.data['age_noted'] = age_noted
1105
1106 self.data.save()
1107 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1108
1109 return True
1110
1112 self._PRW_condition.SetText()
1113 self._ChBOX_left.SetValue(0)
1114 self._ChBOX_right.SetValue(0)
1115 self._PRW_codes.SetText()
1116 self._on_leave_codes()
1117 self._PRW_certainty.SetText()
1118 self._PRW_grouping.SetText()
1119 self._TCTRL_status.SetValue('')
1120 self._PRW_age_noted.SetText()
1121 self._PRW_year_noted.SetText()
1122 self._ChBOX_active.SetValue(1)
1123 self._ChBOX_relevant.SetValue(1)
1124 self._ChBOX_confidential.SetValue(0)
1125 self._ChBOX_caused_death.SetValue(0)
1126
1127 self._PRW_condition.SetFocus()
1128 return True
1129
1168
1170 return self._refresh_as_new()
1171
1172
1173
1175 if not self._PRW_codes.IsModified():
1176 return True
1177
1178 self._TCTRL_code_details.SetValue('- ' + '\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
1179
1181
1182 if not self._PRW_age_noted.IsModified():
1183 return True
1184
1185 age_str = self._PRW_age_noted.GetValue().strip()
1186
1187 if age_str == '':
1188 return True
1189
1190 issue_age = gmDateTime.str2interval(str_interval = age_str)
1191
1192 if issue_age is None:
1193 self.StatusText = _('Cannot parse [%s] into valid interval.') % age_str
1194 self._PRW_age_noted.display_as_valid(False)
1195 return True
1196
1197 pat = gmPerson.gmCurrentPatient()
1198 if pat['dob'] is not None:
1199 max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1200 if issue_age >= max_issue_age:
1201 self.StatusText = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
1202 self._PRW_age_noted.display_as_valid(False)
1203 return True
1204
1205 self._PRW_age_noted.display_as_valid(True)
1206 self._PRW_age_noted.SetText(value = age_str, data = issue_age)
1207
1208 if pat['dob'] is not None:
1209 fts = gmDateTime.cFuzzyTimestamp (
1210 timestamp = pat['dob'] + issue_age,
1211 accuracy = gmDateTime.acc_months
1212 )
1213 self._PRW_year_noted.SetText(value = str(fts), data = fts)
1214
1215 return True
1216
1218
1219 if not self._PRW_year_noted.IsModified():
1220 return True
1221
1222 year_noted = self._PRW_year_noted.GetData()
1223
1224 if year_noted is None:
1225 if self._PRW_year_noted.GetValue().strip() == '':
1226 self._PRW_year_noted.display_as_valid(True)
1227 return True
1228 self._PRW_year_noted.display_as_valid(False)
1229 return True
1230
1231 year_noted = year_noted.get_pydt()
1232
1233 if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
1234 self.StatusText = _('Condition diagnosed in the future.')
1235 self._PRW_year_noted.display_as_valid(False)
1236 return True
1237
1238 self._PRW_year_noted.display_as_valid(True)
1239
1240 pat = gmPerson.gmCurrentPatient()
1241 if pat['dob'] is not None:
1242 issue_age = year_noted - pat['dob']
1243 age_str = gmDateTime.format_interval_medically(interval = issue_age)
1244 self._PRW_age_noted.SetText(age_str, issue_age, True)
1245
1246 return True
1247
1249 wx.CallAfter(self._PRW_year_noted.SetText, '', None, True)
1250 return True
1251
1253 wx.CallAfter(self._PRW_age_noted.SetText, '', None, True)
1254 return True
1255
1256
1257
1258
1260
1262
1263 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1264
1265 self.selection_only = False
1266
1267 mp = gmMatchProvider.cMatchProvider_FixedList (
1268 aSeq = [
1269 {'data': 'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'weight': 1},
1270 {'data': 'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'weight': 1},
1271 {'data': 'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'weight': 1},
1272 {'data': 'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'weight': 1}
1273 ]
1274 )
1275 mp.setThresholds(1, 2, 4)
1276 self.matcher = mp
1277
1278 self.SetToolTip(_(
1279 "The diagnostic classification or grading of this assessment.\n"
1280 "\n"
1281 "This documents how certain one is about this being a true diagnosis."
1282 ))
1283
1284
1285
1286
1287 if __name__ == '__main__':
1288
1289 if len(sys.argv) < 2:
1290 sys.exit()
1291
1292 if sys.argv[1] != 'test':
1293 sys.exit()
1294
1295 from Gnumed.business import gmPersonSearch
1296 from Gnumed.wxpython import gmPatSearchWidgets
1297
1298
1300 """
1301 Test application for testing EMR struct widgets
1302 """
1303
1305 """
1306 Create test application UI
1307 """
1308 frame = wx.Frame (
1309 None,
1310 -4,
1311 'Testing EMR struct widgets',
1312 size=wx.Size(600, 400),
1313 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1314 )
1315 filemenu = wx.Menu()
1316 filemenu.AppendSeparator()
1317 item = filemenu.Append(ID_EXIT, "E&xit"," Terminate test application")
1318 self.Bind(wx.EVT_MENU, self.OnCloseWindow, item)
1319
1320
1321 menuBar = wx.MenuBar()
1322 menuBar.Append(filemenu,"&File")
1323
1324 frame.SetMenuBar(menuBar)
1325
1326 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1327 wx.DefaultPosition, wx.DefaultSize, 0 )
1328
1329
1330 self.__pat = gmPerson.gmCurrentPatient()
1331
1332 frame.Show(1)
1333 return 1
1334
1336 """
1337 Close test aplication
1338 """
1339 self.ExitMainLoop ()
1340
1341
1343 app = wx.PyWidgetTester(size = (200, 300))
1344 emr = pat.emr
1345 epi = emr.get_episodes()[0]
1346 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1347 app.frame.Show(True)
1348 app.MainLoop()
1349
1355
1356
1358 frame = wx.Frame()
1359 wx.GetApp().SetTopWindow(frame)
1360 prw = cEpisodeSelectionPhraseWheel(frame)
1361
1362
1363 frame.Show(True)
1364 wx.GetApp().MainLoop()
1365
1366
1370
1371
1373 app = wx.PyWidgetTester(size = (200, 300))
1374 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1375 app.MainLoop()
1376
1377
1378
1379 branch = gmPraxis.get_praxis_branches()[0]
1380 prax = gmPraxis.gmCurrentPraxisBranch(branch)
1381 print(prax)
1382
1383
1384 app = wx.App()
1385
1386
1387 pat = gmPersonSearch.ask_for_patient()
1388 if pat is None:
1389 print("No patient. Exiting gracefully...")
1390 sys.exit(0)
1391 gmPatSearchWidgets.set_active_patient(patient=pat)
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405 test_episode_selection_prw()
1406