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 __version__ = "$Revision: 1.114 $"
11 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
12 __license__ = "GPL"
13
14
15 import sys, re, datetime as pydt, logging, time
16
17
18
19 import wx
20 import wx.lib.pubsub as wxps
21
22
23
24 if __name__ == '__main__':
25 sys.path.insert(0, '../../')
26 from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions
27 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery
28 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets
29 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg
30 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
31 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl, wxgEncounterEditAreaDlg
32 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
33 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
34
35
36 _log = logging.getLogger('gm.ui')
37 _log.info(__version__)
38
39
40
51
52 def delete(procedure=None):
53 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
54 return True
55
56 gmDispatcher.send (
57 signal = u'statustext',
58 msg = _('Cannot delete performed procedure.'),
59 beep = True
60 )
61 return False
62
63 def refresh(lctrl):
64 procs = emr.get_performed_procedures()
65
66 items = [
67 [
68 p['clin_when'].strftime('%Y-%m-%d'),
69 p['clin_where'],
70 p['episode'],
71 p['performed_procedure']
72 ] for p in procs
73 ]
74 lctrl.set_string_items(items = items)
75 lctrl.set_data(data = procs)
76
77 gmListWidgets.get_choices_from_list (
78 parent = parent,
79 msg = _('\nSelect the procedure you want to edit !\n'),
80 caption = _('Editing performed procedures ...'),
81 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
82 single_selection = True,
83 edit_callback = edit,
84 new_callback = edit,
85 delete_callback = delete,
86 refresh_callback = refresh
87 )
88
89 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
90
102
103 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
104
113
115 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
116 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
117
118
119 mp = gmMatchProvider.cMatchProvider_SQL2 (
120 queries = [
121 u"""
122 select distinct on (clin_where) clin_where, clin_where
123 from clin.procedure
124 where clin_where %(fragment_condition)s
125 order by clin_where
126 limit 25
127 """ ]
128 )
129 mp.setThresholds(2, 4, 6)
130 self._PRW_location.matcher = mp
131
132
133 mp = gmMatchProvider.cMatchProvider_SQL2 (
134 queries = [
135 u"""
136 select distinct on (narrative) narrative, narrative
137 from clin.procedure
138 where narrative %(fragment_condition)s
139 order by narrative
140 limit 25
141 """ ]
142 )
143 mp.setThresholds(2, 4, 6)
144 self._PRW_procedure.matcher = mp
145
147 if self._PRW_hospital_stay.GetData() is None:
148 self._PRW_hospital_stay.SetText()
149 self._PRW_episode.Enable(True)
150 else:
151 self._PRW_location.SetText()
152 self._PRW_episode.SetText()
153 self._PRW_episode.Enable(False)
154
156 if self._PRW_location.GetValue().strip() == u'':
157 return
158
159 self._PRW_hospital_stay.SetText()
160 self._PRW_episode.Enable(True)
161
162
163
165
166 has_errors = False
167
168 if not self._DPRW_date.is_valid_timestamp():
169 self._DPRW_date.display_as_valid(False)
170 has_errors = True
171 else:
172 self._DPRW_date.display_as_valid(True)
173
174 if self._PRW_hospital_stay.GetData() is None:
175 if self._PRW_episode.GetData() is None:
176 self._PRW_episode.display_as_valid(False)
177 has_errors = True
178 else:
179 self._PRW_episode.display_as_valid(True)
180 else:
181 self._PRW_episode.display_as_valid(True)
182
183 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
184 self._PRW_procedure.display_as_valid(False)
185 has_errors = True
186 else:
187 self._PRW_procedure.display_as_valid(True)
188
189 invalid_location = (
190 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'')
191 or
192 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'')
193 )
194 if invalid_location:
195 self._PRW_hospital_stay.display_as_valid(False)
196 self._PRW_location.display_as_valid(False)
197 has_errors = True
198 else:
199 self._PRW_hospital_stay.display_as_valid(True)
200 self._PRW_location.display_as_valid(True)
201
202 wxps.Publisher().sendMessage (
203 topic = 'statustext',
204 data = {'msg': _('Cannot save procedure.'), 'beep': True}
205 )
206
207 return (has_errors is False)
208
232
234 self.data['clin_when'] = self._DPRW_date.data.get_pydt()
235
236 if self._PRW_hospital_stay.GetData() is None:
237 self.data['pk_hospital_stay'] = None
238 self.data['clin_where'] = self._PRW_location.GetValue().strip()
239 self.data['pk_episode'] = self._PRW_episode.GetData()
240 else:
241 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
242 self.data['clin_where'] = None
243 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
244 self.data['pk_episode'] = stay['pk_episode']
245
246 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
247
248 self.data.save()
249 return True
250
259
273
285
289
290
291
302
303 def delete(stay=None):
304 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
305 return True
306 gmDispatcher.send (
307 signal = u'statustext',
308 msg = _('Cannot delete hospital stay.'),
309 beep = True
310 )
311 return False
312
313 def refresh(lctrl):
314 stays = emr.get_hospital_stays()
315 items = [
316 [
317 s['admission'].strftime('%Y-%m-%d'),
318 gmTools.coalesce(s['discharge'], u''),
319 s['episode'],
320 gmTools.coalesce(s['hospital'], u'')
321 ] for s in stays
322 ]
323 lctrl.set_string_items(items = items)
324 lctrl.set_data(data = stays)
325
326 gmListWidgets.get_choices_from_list (
327 parent = parent,
328 msg = _('\nSelect the hospital stay you want to edit !\n'),
329 caption = _('Editing hospital stays ...'),
330 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
331 single_selection = True,
332 edit_callback = edit,
333 new_callback = edit,
334 delete_callback = delete,
335 refresh_callback = refresh
336 )
337
338
350
352 """Phrasewheel to allow selection of a hospital stay.
353 """
355
356 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
357
358 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
359
360 mp = gmMatchProvider.cMatchProvider_SQL2 (
361 queries = [
362 u"""
363 select
364 pk_hospital_stay,
365 descr
366 from (
367 select distinct on (pk_hospital_stay)
368 pk_hospital_stay,
369 descr
370 from
371 (select
372 pk_hospital_stay,
373 (
374 to_char(admission, 'YYYY-Mon-DD')
375 || coalesce((' (' || hospital || '):'), ': ')
376 || episode
377 || coalesce((' (' || health_issue || ')'), '')
378 ) as descr
379 from
380 clin.v_pat_hospital_stays
381 where
382 %(ctxt_pat)s
383
384 hospital %(fragment_condition)s
385 or
386 episode %(fragment_condition)s
387 or
388 health_issue %(fragment_condition)s
389 ) as the_stays
390 ) as distinct_stays
391 order by descr
392 limit 25
393 """ ],
394 context = ctxt
395 )
396 mp.setThresholds(3, 4, 6)
397 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
398
399 self.matcher = mp
400 self.selection_only = True
401
402 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
403
404 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
405
409
410
411
413 if not self._DP_admission.GetValue().IsValid():
414 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
415 wxps.Publisher().sendMessage (
416 topic = 'statustext',
417 data = {'msg': _('Missing admission data. Cannot save hospital stay.'), 'beep': True}
418 )
419 return False
420 else:
421 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
422
423 if self._DP_discharge.GetValue().IsValid():
424 if not self._DP_discharge.GetValue().IsLaterThan(self._DP_admission.GetValue()):
425 self._DP_discharge.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
426 wxps.Publisher().sendMessage (
427 topic = 'statustext',
428 data = {'msg': _('Discharge date must be empty or later than admission. Cannot save hospital stay.'), 'beep': True}
429 )
430 return False
431
432 return True
433
451
462
467
468
480
482 print "this was not expected to be used in this edit area"
483
484
485
494
496
497 if parent is None:
498 parent = wx.GetApp().GetTopWindow()
499
500
501 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
502 dlg.ShowModal()
503
504 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None):
505
506 if patient is None:
507 patient = gmPerson.gmCurrentPatient()
508
509 if not patient.connected:
510 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
511 return False
512
513 if parent is None:
514 parent = wx.GetApp().GetTopWindow()
515
516 emr = patient.get_emr()
517
518
519 def refresh(lctrl):
520 if encounters is not None:
521 encs = encounters
522 else:
523 encs = emr.get_encounters()
524
525 items = [
526 [
527 e['started'].strftime('%x %H:%M'),
528 e['last_affirmed'].strftime('%H:%M'),
529 e['l10n_type'],
530 gmTools.coalesce(e['reason_for_encounter'], u''),
531 gmTools.coalesce(e['assessment_of_encounter'], u''),
532 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
533 e['pk_encounter']
534 ] for e in encs
535 ]
536
537 lctrl.set_string_items(items = items)
538 lctrl.set_data(data = encs)
539
540 def edit(enc = None):
541 return edit_encounter(parent = parent, encounter = enc)
542
543 return gmListWidgets.get_choices_from_list (
544 parent = parent,
545 msg = _('\nBelow find the relevant encounters of the patient.\n'),
546 caption = _('Encounters ...'),
547 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
548 can_return_empty = True,
549 single_selection = single_selection,
550 refresh_callback = refresh,
551 edit_callback = edit
552 )
553
555 """This is used as the callback when the EMR detects that the
556 patient was here rather recently and wants to ask the
557 provider whether to continue the recent encounter.
558 """
559 if parent is None:
560 parent = wx.GetApp().GetTopWindow()
561
562 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
563 parent = None,
564 id = -1,
565 caption = caption,
566 question = msg,
567 button_defs = [
568 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
569 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
570 ],
571 show_checkbox = False
572 )
573
574 result = dlg.ShowModal()
575 dlg.Destroy()
576
577 if result == wx.ID_YES:
578 return True
579
580 return False
581
583
584 if parent is None:
585 parent = wx.GetApp().GetTopWindow()
586
587
588 def edit(enc_type=None):
589 return edit_encounter_type(parent = parent, encounter_type = enc_type)
590
591 def delete(enc_type=None):
592 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
593 return True
594 gmDispatcher.send (
595 signal = u'statustext',
596 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
597 beep = True
598 )
599 return False
600
601 def refresh(lctrl):
602 enc_types = gmEMRStructItems.get_encounter_types()
603 lctrl.set_string_items(items = enc_types)
604
605 gmListWidgets.get_choices_from_list (
606 parent = parent,
607 msg = _('\nSelect the encounter type you want to edit !\n'),
608 caption = _('Managing encounter types ...'),
609 columns = [_('Local name'), _('Encounter type')],
610 single_selection = True,
611 edit_callback = edit,
612 new_callback = edit,
613 delete_callback = delete,
614 refresh_callback = refresh
615 )
616
626
628 """Phrasewheel to allow selection of encounter type.
629
630 - user input interpreted as encounter type in English or local language
631 - data returned is pk of corresponding encounter type or None
632 """
634
635 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
636
637 mp = gmMatchProvider.cMatchProvider_SQL2 (
638 queries = [
639 u"""
640 select pk, l10n_description from (
641 select distinct on (pk) * from (
642 (select
643 pk,
644 _(description) as l10n_description,
645 1 as rank
646 from
647 clin.encounter_type
648 where
649 _(description) %(fragment_condition)s
650
651 ) union all (
652
653 select
654 pk,
655 _(description) as l10n_description,
656 2 as rank
657 from
658 clin.encounter_type
659 where
660 description %(fragment_condition)s
661 )
662 ) as q_distinct_pk
663 ) as q_ordered order by rank, l10n_description
664 """ ]
665 )
666 mp.setThresholds(2, 4, 6)
667
668 self.matcher = mp
669 self.selection_only = True
670 self.picklist_delay = 50
671
673
678
679
680
681
682
712
725
735
737 self._TCTRL_l10n_name.SetValue(u'')
738 self._TCTRL_name.SetValue(u'')
739 self._TCTRL_name.Enable(True)
740
742 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
743 self._TCTRL_name.SetValue(self.data['description'])
744
745 self._TCTRL_name.Enable(False)
746
748 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
749 self._TCTRL_name.SetValue(self.data['description'])
750 self._TCTRL_name.Enable(True)
751
752
753
754
755
756
758
760 try:
761 self.__encounter = kwargs['encounter']
762 del kwargs['encounter']
763 except KeyError:
764 self.__encounter = None
765
766 try:
767 msg = kwargs['msg']
768 del kwargs['msg']
769 except KeyError:
770 msg = None
771
772 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
773
774 self.refresh(msg = msg)
775
776
777
778 - def refresh(self, encounter=None, msg=None):
817
819
820 if self._PRW_encounter_type.GetData() is None:
821 self._PRW_encounter_type.SetBackgroundColour('pink')
822 self._PRW_encounter_type.Refresh()
823 self._PRW_encounter_type.SetFocus()
824 return False
825 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
826 self._PRW_encounter_type.Refresh()
827
828 if not self._PRW_start.is_valid_timestamp():
829 self._PRW_start.SetFocus()
830 return False
831
832 if not self._PRW_end.is_valid_timestamp():
833 self._PRW_end.SetFocus()
834 return False
835
836 return True
837
839 if not self.__is_valid_for_save():
840 return False
841
842 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
843 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
844 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
845 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
846 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
847 self.__encounter.save_payload()
848
849 return True
850
851
853
855 encounter = kwargs['encounter']
856 del kwargs['encounter']
857
858 try:
859 button_defs = kwargs['button_defs']
860 del kwargs['button_defs']
861 except KeyError:
862 button_defs = None
863
864 try:
865 msg = kwargs['msg']
866 del kwargs['msg']
867 except KeyError:
868 msg = None
869
870 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
871 self.SetSize((450, 280))
872 self.SetMinSize((450, 280))
873
874 if button_defs is not None:
875 self._BTN_save.SetLabel(button_defs[0][0])
876 self._BTN_save.SetToolTipString(button_defs[0][1])
877 self._BTN_close.SetLabel(button_defs[1][0])
878 self._BTN_close.SetToolTipString(button_defs[1][1])
879 self.Refresh()
880
881 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
882
883 self.Fit()
884
891
892
893
903
973
975 """Prepare changing health issue for an episode.
976
977 Checks for two-open-episodes conflict. When this
978 function succeeds, the pk_health_issue has been set
979 on the episode instance and the episode should - for
980 all practical purposes - be ready for save_payload().
981 """
982
983 if not episode['episode_open']:
984 episode['pk_health_issue'] = target_issue['pk_health_issue']
985 if save_to_backend:
986 episode.save_payload()
987 return True
988
989
990 if target_issue is None:
991 episode['pk_health_issue'] = None
992 if save_to_backend:
993 episode.save_payload()
994 return True
995
996
997 db_cfg = gmCfg.cCfgSQL()
998 epi_ttl = int(db_cfg.get2 (
999 option = u'episode.ttl',
1000 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1001 bias = 'user',
1002 default = 60
1003 ))
1004 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1005 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1006 existing_epi = target_issue.get_open_episode()
1007
1008
1009 if existing_epi is None:
1010 episode['pk_health_issue'] = target_issue['pk_health_issue']
1011 if save_to_backend:
1012 episode.save_payload()
1013 return True
1014
1015
1016 if existing_epi['pk_episode'] == episode['pk_episode']:
1017 episode['pk_health_issue'] = target_issue['pk_health_issue']
1018 if save_to_backend:
1019 episode.save_payload()
1020 return True
1021
1022
1023 move_range = episode.get_access_range()
1024 exist_range = existing_epi.get_access_range()
1025 question = _(
1026 'You want to associate the running episode:\n\n'
1027 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1028 'with the health issue:\n\n'
1029 ' "%(issue_name)s"\n\n'
1030 'There already is another episode running\n'
1031 'for this health issue:\n\n'
1032 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1033 'However, there can only be one running\n'
1034 'episode per health issue.\n\n'
1035 'Which episode do you want to close ?'
1036 ) % {
1037 'new_epi_name': episode['description'],
1038 'new_epi_start': move_range[0].strftime('%m/%y'),
1039 'new_epi_end': move_range[1].strftime('%m/%y'),
1040 'issue_name': target_issue['description'],
1041 'old_epi_name': existing_epi['description'],
1042 'old_epi_start': exist_range[0].strftime('%m/%y'),
1043 'old_epi_end': exist_range[1].strftime('%m/%y')
1044 }
1045 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1046 parent = None,
1047 id = -1,
1048 caption = _('Resolving two-running-episodes conflict'),
1049 question = question,
1050 button_defs = [
1051 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1052 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1053 ]
1054 )
1055 decision = dlg.ShowModal()
1056
1057 if decision == wx.ID_CANCEL:
1058
1059 return False
1060
1061 elif decision == wx.ID_YES:
1062
1063 existing_epi['episode_open'] = False
1064 existing_epi.save_payload()
1065
1066 elif decision == wx.ID_NO:
1067
1068 episode['episode_open'] = False
1069
1070 else:
1071 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1072
1073 episode['pk_health_issue'] = target_issue['pk_health_issue']
1074 if save_to_backend:
1075 episode.save_payload()
1076 return True
1077
1101
1103 """Let user select an episode *description*.
1104
1105 The user can select an episode description from the previously
1106 used descriptions across all episodes across all patients.
1107
1108 Selection is done with a phrasewheel so the user can
1109 type the episode name and matches will be shown. Typing
1110 "*" will show the entire list of episodes.
1111
1112 If the user types a description not existing yet a
1113 new episode description will be returned.
1114 """
1116
1117 mp = gmMatchProvider.cMatchProvider_SQL2 (
1118 queries = [u"""
1119 select distinct on (description) description, description, 1
1120 from clin.episode
1121 where description %(fragment_condition)s
1122 order by description
1123 limit 30"""
1124 ]
1125 )
1126 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1127 self.matcher = mp
1128
1130 """Let user select an episode.
1131
1132 The user can select an episode from the existing episodes of a
1133 patient. Selection is done with a phrasewheel so the user
1134 can type the episode name and matches will be shown. Typing
1135 "*" will show the entire list of episodes. Closed episodes
1136 will be marked as such. If the user types an episode name not
1137 in the list of existing episodes a new episode can be created
1138 from it if the programmer activated that feature.
1139
1140 If keyword <patient_id> is set to None or left out the control
1141 will listen to patient change signals and therefore act on
1142 gmPerson.gmCurrentPatient() changes.
1143 """
1145
1146 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1147
1148 mp = gmMatchProvider.cMatchProvider_SQL2 (
1149 queries = [
1150 u"""(
1151
1152 select
1153 pk_episode,
1154 coalesce (
1155 description || ' - ' || health_issue,
1156 description
1157 ) as description,
1158 1 as rank
1159 from
1160 clin.v_pat_episodes
1161 where
1162 episode_open is true and
1163 description %(fragment_condition)s
1164 %(ctxt_pat)s
1165
1166 ) union all (
1167
1168 select
1169 pk_episode,
1170 coalesce (
1171 description || _(' (closed)') || ' - ' || health_issue,
1172 description || _(' (closed)')
1173 ) as description,
1174 2 as rank
1175 from
1176 clin.v_pat_episodes
1177 where
1178 description %(fragment_condition)s and
1179 episode_open is false
1180 %(ctxt_pat)s
1181
1182 )
1183 order by rank, description
1184 limit 30"""
1185 ],
1186 context = ctxt
1187 )
1188
1189 try:
1190 kwargs['patient_id']
1191 except KeyError:
1192 kwargs['patient_id'] = None
1193
1194 if kwargs['patient_id'] is None:
1195 self.use_current_patient = True
1196 self.__register_patient_change_signals()
1197 pat = gmPerson.gmCurrentPatient()
1198 if pat.connected:
1199 mp.set_context('pat', pat.ID)
1200 else:
1201 self.use_current_patient = False
1202 self.__patient_id = int(kwargs['patient_id'])
1203 mp.set_context('pat', self.__patient_id)
1204
1205 del kwargs['patient_id']
1206
1207 gmPhraseWheel.cPhraseWheel.__init__ (
1208 self,
1209 *args,
1210 **kwargs
1211 )
1212 self.matcher = mp
1213
1214
1215
1217 if self.use_current_patient:
1218 return False
1219 self.__patient_id = int(patient_id)
1220 self.set_context('pat', self.__patient_id)
1221 return True
1222
1223 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1224
1225 self.__is_open = is_open
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
1247
1249
1250 epi_name = self.GetValue().strip()
1251 if epi_name == u'':
1252 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1253 _log.debug('cannot create episode without name')
1254 return
1255
1256 if self.use_current_patient:
1257 pat = gmPerson.gmCurrentPatient()
1258 else:
1259 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1260
1261 emr = pat.get_emr()
1262 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open)
1263 if epi is None:
1264 self.data = None
1265 else:
1266 self.data = epi['pk_episode']
1267
1270
1271
1272
1276
1279
1281 if self.use_current_patient:
1282 patient = gmPerson.gmCurrentPatient()
1283 self.set_context('pat', patient.ID)
1284 return True
1285
1286 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1287
1300
1301
1302
1304
1305 errors = False
1306
1307 if len(self._PRW_description.GetValue().strip()) == 0:
1308 errors = True
1309 self._PRW_description.display_as_valid(False)
1310 self._PRW_description.SetFocus()
1311 else:
1312 self._PRW_description.display_as_valid(True)
1313 self._PRW_description.Refresh()
1314
1315 return not errors
1316
1318
1319 pat = gmPerson.gmCurrentPatient()
1320 emr = pat.get_emr()
1321
1322 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1323 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1324 epi['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1325
1326 issue_name = self._PRW_issue.GetValue().strip()
1327 if len(issue_name) != 0:
1328 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1329 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1330
1331 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1332 gmDispatcher.send (
1333 signal = 'statustext',
1334 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1335 epi['description'],
1336 issue['description']
1337 )
1338 )
1339 gmEMRStructItems.delete_episode(episode = epi)
1340 return False
1341
1342 epi.save()
1343
1344 self.data = epi
1345 return True
1346
1348
1349 self.data['description'] = self._PRW_description.GetValue().strip()
1350 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1351 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1352
1353 issue_name = self._PRW_issue.GetValue().strip()
1354 if len(issue_name) != 0:
1355 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1356 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1357
1358 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1359 gmDispatcher.send (
1360 signal = 'statustext',
1361 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1362 self.data['description'],
1363 issue['description']
1364 )
1365 )
1366 return False
1367
1368 self.data.save()
1369 return True
1370
1381
1395
1397 self._refresh_as_new()
1398
1399
1400
1410
1412
1413
1414
1416
1417 issues = kwargs['issues']
1418 del kwargs['issues']
1419
1420 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1421
1422 self.SetTitle(_('Select the health issues you are interested in ...'))
1423 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1424
1425 for issue in issues:
1426 if issue['is_confidential']:
1427 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1428 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1429 else:
1430 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1431
1432 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1433 if issue['clinically_relevant']:
1434 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1435 if issue['is_active']:
1436 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1437 if issue['is_cause_of_death']:
1438 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1439
1440 self._LCTRL_items.set_column_widths()
1441 self._LCTRL_items.set_data(data = issues)
1442
1444 """Let the user select a health issue.
1445
1446 The user can select a health issue from the existing issues
1447 of a patient. Selection is done with a phrasewheel so the user
1448 can type the issue name and matches will be shown. Typing
1449 "*" will show the entire list of issues. Inactive issues
1450 will be marked as such. If the user types an issue name not
1451 in the list of existing issues a new issue can be created
1452 from it if the programmer activated that feature.
1453
1454 If keyword <patient_id> is set to None or left out the control
1455 will listen to patient change signals and therefore act on
1456 gmPerson.gmCurrentPatient() changes.
1457 """
1459
1460 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1461
1462 mp = gmMatchProvider.cMatchProvider_SQL2 (
1463
1464 queries = [u"""
1465 (select pk_health_issue, description, 1
1466 from clin.v_health_issues where
1467 is_active is true and
1468 description %(fragment_condition)s and
1469 %(ctxt_pat)s
1470 order by description)
1471
1472 union
1473
1474 (select pk_health_issue, description || _(' (inactive)'), 2
1475 from clin.v_health_issues where
1476 is_active is false and
1477 description %(fragment_condition)s and
1478 %(ctxt_pat)s
1479 order by description)"""
1480 ],
1481 context = ctxt
1482 )
1483
1484 try: kwargs['patient_id']
1485 except KeyError: kwargs['patient_id'] = None
1486
1487 if kwargs['patient_id'] is None:
1488 self.use_current_patient = True
1489 self.__register_patient_change_signals()
1490 pat = gmPerson.gmCurrentPatient()
1491 if pat.connected:
1492 mp.set_context('pat', pat.ID)
1493 else:
1494 self.use_current_patient = False
1495 self.__patient_id = int(kwargs['patient_id'])
1496 mp.set_context('pat', self.__patient_id)
1497
1498 del kwargs['patient_id']
1499
1500 gmPhraseWheel.cPhraseWheel.__init__ (
1501 self,
1502 *args,
1503 **kwargs
1504 )
1505 self.matcher = mp
1506
1507
1508
1510 if self.use_current_patient:
1511 return False
1512 self.__patient_id = int(patient_id)
1513 self.set_context('pat', self.__patient_id)
1514 return True
1515
1516 - def GetData(self, can_create=False, is_open=False):
1534
1535
1536
1540
1543
1545 if self.use_current_patient:
1546 patient = gmPerson.gmCurrentPatient()
1547 self.set_context('pat', patient.ID)
1548 return True
1549
1551
1553 try:
1554 msg = kwargs['message']
1555 except KeyError:
1556 msg = None
1557 del kwargs['message']
1558 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1559 if msg is not None:
1560 self._lbl_message.SetLabel(label=msg)
1561
1572
1573 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1574 """Panel encapsulating health issue edit area functionality."""
1575
1577
1578 try:
1579 issue = kwargs['issue']
1580 except KeyError:
1581 issue = None
1582
1583 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1584
1585 gmEditArea.cGenericEditAreaMixin.__init__(self)
1586
1587
1588 mp = gmMatchProvider.cMatchProvider_SQL2 (
1589 queries = [u"select distinct on (description) description, description from clin.health_issue where description %(fragment_condition)s limit 50"]
1590 )
1591 mp.setThresholds(1, 3, 5)
1592 self._PRW_condition.matcher = mp
1593
1594 mp = gmMatchProvider.cMatchProvider_SQL2 (
1595 queries = [u"""
1596 select distinct on (grouping) grouping, grouping from (
1597
1598 select rank, grouping from ((
1599
1600 select
1601 grouping,
1602 1 as rank
1603 from
1604 clin.health_issue
1605 where
1606 grouping %%(fragment_condition)s
1607 and
1608 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1609
1610 ) union (
1611
1612 select
1613 grouping,
1614 2 as rank
1615 from
1616 clin.health_issue
1617 where
1618 grouping %%(fragment_condition)s
1619
1620 )) as union_result
1621
1622 order by rank
1623
1624 ) as order_result
1625
1626 limit 50""" % gmPerson.gmCurrentPatient().ID
1627 ]
1628 )
1629 mp.setThresholds(1, 3, 5)
1630 self._PRW_grouping.matcher = mp
1631
1632 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1633 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1634
1635 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1636 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1637
1638 self.data = issue
1639
1640
1641
1661
1663 pat = gmPerson.gmCurrentPatient()
1664 emr = pat.get_emr()
1665
1666 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1667
1668 side = u''
1669 if self._ChBOX_left.GetValue():
1670 side += u's'
1671 if self._ChBOX_right.GetValue():
1672 side += u'd'
1673 issue['laterality'] = side
1674
1675 issue['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1676 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1677 issue['is_active'] = self._ChBOX_active.GetValue()
1678 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1679 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1680 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1681
1682 age_noted = self._PRW_age_noted.GetData()
1683 if age_noted is not None:
1684 issue['age_noted'] = age_noted
1685
1686 issue.save()
1687
1688 narr = self._TCTRL_notes.GetValue().strip()
1689 if narr != u'':
1690 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = issue['pk_health_issue'])
1691 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1692
1693 self.data = issue
1694
1695 return True
1696
1698
1699
1700 self.data['description'] = self._PRW_condition.GetValue().strip()
1701
1702 side = u''
1703 if self._ChBOX_left.GetValue():
1704 side += u's'
1705 if self._ChBOX_right.GetValue():
1706 side += u'd'
1707 self.data['laterality'] = side
1708
1709 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1710 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1711 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1712 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1713 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1714 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1715
1716 age_noted = self._PRW_age_noted.GetData()
1717 if age_noted is not None:
1718 self.data['age_noted'] = age_noted
1719
1720 self.data.save()
1721
1722 narr = self._TCTRL_notes.GetValue().strip()
1723 if narr != '':
1724 pat = gmPerson.gmCurrentPatient()
1725 emr = pat.get_emr()
1726 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = self.data['pk_health_issue'])
1727 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1728
1729
1730 return True
1731
1733 self._PRW_condition.SetText()
1734 self._ChBOX_left.SetValue(0)
1735 self._ChBOX_right.SetValue(0)
1736 self._PRW_classification.SetText()
1737 self._PRW_grouping.SetText()
1738 self._TCTRL_notes.SetValue(u'')
1739 self._PRW_age_noted.SetText()
1740 self._PRW_year_noted.SetText()
1741 self._ChBOX_active.SetValue(0)
1742 self._ChBOX_relevant.SetValue(1)
1743 self._ChBOX_is_operation.SetValue(0)
1744 self._ChBOX_confidential.SetValue(0)
1745 self._ChBOX_caused_death.SetValue(0)
1746
1747 return True
1748
1750 self._PRW_condition.SetText(self.data['description'])
1751
1752 lat = gmTools.coalesce(self.data['laterality'], '')
1753 if lat.find('s') == -1:
1754 self._ChBOX_left.SetValue(0)
1755 else:
1756 self._ChBOX_left.SetValue(1)
1757 if lat.find('d') == -1:
1758 self._ChBOX_right.SetValue(0)
1759 else:
1760 self._ChBOX_right.SetValue(1)
1761
1762 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification'])
1763 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
1764 self._TCTRL_notes.SetValue('')
1765
1766 if self.data['age_noted'] is None:
1767 self._PRW_age_noted.SetText()
1768 else:
1769 self._PRW_age_noted.SetText (
1770 value = '%sd' % self.data['age_noted'].days,
1771 data = self.data['age_noted']
1772 )
1773
1774 self._ChBOX_active.SetValue(self.data['is_active'])
1775 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
1776 self._ChBOX_is_operation.SetValue(0)
1777 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
1778 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
1779
1780
1781
1782
1783
1784 return True
1785
1787 return self._refresh_as_new()
1788
1789
1790
1792
1793 if not self._PRW_age_noted.IsModified():
1794 return True
1795
1796 str_age = self._PRW_age_noted.GetValue().strip()
1797
1798 if str_age == u'':
1799 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1800 return True
1801
1802 age = gmDateTime.str2interval(str_interval = str_age)
1803
1804 if age is None:
1805 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
1806 self._PRW_age_noted.SetBackgroundColour('pink')
1807 self._PRW_age_noted.Refresh()
1808 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1809 return True
1810
1811 pat = gmPerson.gmCurrentPatient()
1812 if pat['dob'] is not None:
1813 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1814
1815 if age >= max_age:
1816 gmDispatcher.send (
1817 signal = 'statustext',
1818 msg = _(
1819 'Health issue cannot have been noted at age %s. Patient is only %s old.'
1820 ) % (age, pat.get_medical_age())
1821 )
1822 self._PRW_age_noted.SetBackgroundColour('pink')
1823 self._PRW_age_noted.Refresh()
1824 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1825 return True
1826
1827 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1828 self._PRW_age_noted.Refresh()
1829 self._PRW_age_noted.SetData(data=age)
1830
1831 if pat['dob'] is not None:
1832 fts = gmDateTime.cFuzzyTimestamp (
1833 timestamp = pat['dob'] + age,
1834 accuracy = gmDateTime.acc_months
1835 )
1836 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
1837
1838
1839
1840
1841
1842 return True
1843
1845
1846 if not self._PRW_year_noted.IsModified():
1847 return True
1848
1849 year_noted = self._PRW_year_noted.GetData()
1850
1851 if year_noted is None:
1852 if self._PRW_year_noted.GetValue().strip() == u'':
1853 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1854 return True
1855 self._PRW_year_noted.SetBackgroundColour('pink')
1856 self._PRW_year_noted.Refresh()
1857 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1858 return True
1859
1860 year_noted = year_noted.get_pydt()
1861
1862 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
1863 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
1864 self._PRW_year_noted.SetBackgroundColour('pink')
1865 self._PRW_year_noted.Refresh()
1866 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1867 return True
1868
1869 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1870 self._PRW_year_noted.Refresh()
1871
1872 pat = gmPerson.gmCurrentPatient()
1873 if pat['dob'] is not None:
1874 issue_age = year_noted - pat['dob']
1875 str_age = gmDateTime.format_interval_medically(interval = issue_age)
1876 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
1877
1878 return True
1879
1881 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1882 return True
1883
1885 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1886 return True
1887
1888
1889
1891
1893
1894 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1895
1896 self.selection_only = False
1897
1898 mp = gmMatchProvider.cMatchProvider_FixedList (
1899 aSeq = [
1900 {'data': u'A', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
1901 {'data': u'B', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
1902 {'data': u'C', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
1903 {'data': u'D', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
1904 ]
1905 )
1906 mp.setThresholds(1, 2, 4)
1907 self.matcher = mp
1908
1909 self.SetToolTipString(_(
1910 "The diagnostic classification or grading of this assessment.\n"
1911 "\n"
1912 "This documents how certain one is about this being a true diagnosis."
1913 ))
1914
1915
1916
1917 if __name__ == '__main__':
1918
1919
1921 """
1922 Test application for testing EMR struct widgets
1923 """
1924
1926 """
1927 Create test application UI
1928 """
1929 frame = wx.Frame (
1930 None,
1931 -4,
1932 'Testing EMR struct widgets',
1933 size=wx.Size(600, 400),
1934 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1935 )
1936 filemenu= wx.Menu()
1937 filemenu.AppendSeparator()
1938 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
1939
1940
1941 menuBar = wx.MenuBar()
1942 menuBar.Append(filemenu,"&File")
1943
1944 frame.SetMenuBar(menuBar)
1945
1946 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1947 wx.DefaultPosition, wx.DefaultSize, 0 )
1948
1949
1950 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
1951
1952
1953 self.__pat = gmPerson.gmCurrentPatient()
1954
1955 frame.Show(1)
1956 return 1
1957
1959 """
1960 Close test aplication
1961 """
1962 self.ExitMainLoop ()
1963
1965 app = wx.PyWidgetTester(size = (200, 300))
1966 emr = pat.get_emr()
1967 enc = emr.active_encounter
1968
1969 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
1970 app.frame.Show(True)
1971 app.MainLoop()
1972 return
1973
1975 app = wx.PyWidgetTester(size = (200, 300))
1976 emr = pat.get_emr()
1977 enc = emr.active_encounter
1978
1979
1980 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
1981 dlg.ShowModal()
1982
1983
1984
1985
1986
1988 app = wx.PyWidgetTester(size = (200, 300))
1989 emr = pat.get_emr()
1990 epi = emr.get_episodes()[0]
1991 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1992 app.frame.Show(True)
1993 app.MainLoop()
1994
2000
2002 app = wx.PyWidgetTester(size = (400, 40))
2003 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2004 app.MainLoop()
2005
2007 app = wx.PyWidgetTester(size = (400, 40))
2008 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2009
2010 app.MainLoop()
2011
2013 app = wx.PyWidgetTester(size = (200, 300))
2014 edit_health_issue(parent=app.frame, issue=None)
2015
2017 app = wx.PyWidgetTester(size = (200, 300))
2018 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2019 app.MainLoop()
2020
2022 app = wx.PyWidgetTester(size = (200, 300))
2023 edit_procedure(parent=app.frame)
2024
2025
2026 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2027
2028 gmI18N.activate_locale()
2029 gmI18N.install_domain()
2030 gmDateTime.init()
2031
2032
2033 pat = gmPerson.ask_for_patient()
2034 if pat is None:
2035 print "No patient. Exiting gracefully..."
2036 sys.exit(0)
2037 gmPatSearchWidgets.set_active_patient(patient=pat)
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055 test_edit_procedure()
2056
2057
2058