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):
1227
1229
1230 epi_name = self.GetValue().strip()
1231 if epi_name == u'':
1232 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1233 _log.debug('cannot create episode without name')
1234 return
1235
1236 if self.use_current_patient:
1237 pat = gmPerson.gmCurrentPatient()
1238 else:
1239 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1240
1241 emr = pat.get_emr()
1242 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1243 if epi is None:
1244 self.data = None
1245 else:
1246 self.data = epi['pk_episode']
1247
1250
1251
1252
1256
1259
1261 if self.use_current_patient:
1262 patient = gmPerson.gmCurrentPatient()
1263 self.set_context('pat', patient.ID)
1264 return True
1265
1266 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1267
1280
1281
1282
1284
1285 errors = False
1286
1287 if len(self._PRW_description.GetValue().strip()) == 0:
1288 errors = True
1289 self._PRW_description.display_as_valid(False)
1290 self._PRW_description.SetFocus()
1291 else:
1292 self._PRW_description.display_as_valid(True)
1293 self._PRW_description.Refresh()
1294
1295 return not errors
1296
1298
1299 pat = gmPerson.gmCurrentPatient()
1300 emr = pat.get_emr()
1301
1302 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1303 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1304 epi['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1305
1306 issue_name = self._PRW_issue.GetValue().strip()
1307 if len(issue_name) != 0:
1308 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1309 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1310
1311 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1312 gmDispatcher.send (
1313 signal = 'statustext',
1314 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1315 epi['description'],
1316 issue['description']
1317 )
1318 )
1319 gmEMRStructItems.delete_episode(episode = epi)
1320 return False
1321
1322 epi.save()
1323
1324 self.data = epi
1325 return True
1326
1328
1329 self.data['description'] = self._PRW_description.GetValue().strip()
1330 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1331 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1332
1333 issue_name = self._PRW_issue.GetValue().strip()
1334 if len(issue_name) != 0:
1335 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1336 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1337
1338 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1339 gmDispatcher.send (
1340 signal = 'statustext',
1341 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1342 self.data['description'],
1343 issue['description']
1344 )
1345 )
1346 return False
1347
1348 self.data.save()
1349 return True
1350
1361
1375
1377 self._refresh_as_new()
1378
1379
1380
1390
1392
1393
1394
1396
1397 issues = kwargs['issues']
1398 del kwargs['issues']
1399
1400 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1401
1402 self.SetTitle(_('Select the health issues you are interested in ...'))
1403 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1404
1405 for issue in issues:
1406 if issue['is_confidential']:
1407 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1408 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1409 else:
1410 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1411
1412 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1413 if issue['clinically_relevant']:
1414 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1415 if issue['is_active']:
1416 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1417 if issue['is_cause_of_death']:
1418 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1419
1420 self._LCTRL_items.set_column_widths()
1421 self._LCTRL_items.set_data(data = issues)
1422
1424 """Let the user select a health issue.
1425
1426 The user can select a health issue from the existing issues
1427 of a patient. Selection is done with a phrasewheel so the user
1428 can type the issue name and matches will be shown. Typing
1429 "*" will show the entire list of issues. Inactive issues
1430 will be marked as such. If the user types an issue name not
1431 in the list of existing issues a new issue can be created
1432 from it if the programmer activated that feature.
1433
1434 If keyword <patient_id> is set to None or left out the control
1435 will listen to patient change signals and therefore act on
1436 gmPerson.gmCurrentPatient() changes.
1437 """
1439
1440 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1441
1442 mp = gmMatchProvider.cMatchProvider_SQL2 (
1443
1444 queries = [u"""
1445 (select pk_health_issue, description, 1
1446 from clin.v_health_issues where
1447 is_active is true and
1448 description %(fragment_condition)s and
1449 %(ctxt_pat)s
1450 order by description)
1451
1452 union
1453
1454 (select pk_health_issue, description || _(' (inactive)'), 2
1455 from clin.v_health_issues where
1456 is_active is false and
1457 description %(fragment_condition)s and
1458 %(ctxt_pat)s
1459 order by description)"""
1460 ],
1461 context = ctxt
1462 )
1463
1464 try: kwargs['patient_id']
1465 except KeyError: kwargs['patient_id'] = None
1466
1467 if kwargs['patient_id'] is None:
1468 self.use_current_patient = True
1469 self.__register_patient_change_signals()
1470 pat = gmPerson.gmCurrentPatient()
1471 if pat.connected:
1472 mp.set_context('pat', pat.ID)
1473 else:
1474 self.use_current_patient = False
1475 self.__patient_id = int(kwargs['patient_id'])
1476 mp.set_context('pat', self.__patient_id)
1477
1478 del kwargs['patient_id']
1479
1480 gmPhraseWheel.cPhraseWheel.__init__ (
1481 self,
1482 *args,
1483 **kwargs
1484 )
1485 self.matcher = mp
1486
1487
1488
1490 if self.use_current_patient:
1491 return False
1492 self.__patient_id = int(patient_id)
1493 self.set_context('pat', self.__patient_id)
1494 return True
1495
1496 - def GetData(self, can_create=False, is_open=False):
1514
1515
1516
1520
1523
1525 if self.use_current_patient:
1526 patient = gmPerson.gmCurrentPatient()
1527 self.set_context('pat', patient.ID)
1528 return True
1529
1531
1533 try:
1534 msg = kwargs['message']
1535 except KeyError:
1536 msg = None
1537 del kwargs['message']
1538 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1539 if msg is not None:
1540 self._lbl_message.SetLabel(label=msg)
1541
1552
1553 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1554 """Panel encapsulating health issue edit area functionality."""
1555
1557
1558 try:
1559 issue = kwargs['issue']
1560 except KeyError:
1561 issue = None
1562
1563 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1564
1565 gmEditArea.cGenericEditAreaMixin.__init__(self)
1566
1567
1568 mp = gmMatchProvider.cMatchProvider_SQL2 (
1569 queries = [u"select distinct on (description) description, description from clin.health_issue where description %(fragment_condition)s limit 50"]
1570 )
1571 mp.setThresholds(1, 3, 5)
1572 self._PRW_condition.matcher = mp
1573
1574 mp = gmMatchProvider.cMatchProvider_SQL2 (
1575 queries = [u"""
1576 select distinct on (grouping) grouping, grouping from (
1577
1578 select rank, grouping from ((
1579
1580 select
1581 grouping,
1582 1 as rank
1583 from
1584 clin.health_issue
1585 where
1586 grouping %%(fragment_condition)s
1587 and
1588 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1589
1590 ) union (
1591
1592 select
1593 grouping,
1594 2 as rank
1595 from
1596 clin.health_issue
1597 where
1598 grouping %%(fragment_condition)s
1599
1600 )) as union_result
1601
1602 order by rank
1603
1604 ) as order_result
1605
1606 limit 50""" % gmPerson.gmCurrentPatient().ID
1607 ]
1608 )
1609 mp.setThresholds(1, 3, 5)
1610 self._PRW_grouping.matcher = mp
1611
1612 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1613 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1614
1615 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1616 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1617
1618 self.data = issue
1619
1620
1621
1641
1643 pat = gmPerson.gmCurrentPatient()
1644 emr = pat.get_emr()
1645
1646 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1647
1648 side = u''
1649 if self._ChBOX_left.GetValue():
1650 side += u's'
1651 if self._ChBOX_right.GetValue():
1652 side += u'd'
1653 issue['laterality'] = side
1654
1655 issue['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1656 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1657 issue['is_active'] = self._ChBOX_active.GetValue()
1658 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1659 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1660 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1661
1662 age_noted = self._PRW_age_noted.GetData()
1663 if age_noted is not None:
1664 issue['age_noted'] = age_noted
1665
1666 issue.save()
1667
1668 narr = self._TCTRL_notes.GetValue().strip()
1669 if narr != u'':
1670 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = issue['pk_health_issue'])
1671 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1672
1673 self.data = issue
1674
1675 return True
1676
1678
1679
1680 self.data['description'] = self._PRW_condition.GetValue().strip()
1681
1682 side = u''
1683 if self._ChBOX_left.GetValue():
1684 side += u's'
1685 if self._ChBOX_right.GetValue():
1686 side += u'd'
1687 self.data['laterality'] = side
1688
1689 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1690 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1691 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1692 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1693 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1694 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1695
1696 age_noted = self._PRW_age_noted.GetData()
1697 if age_noted is not None:
1698 self.data['age_noted'] = age_noted
1699
1700 self.data.save()
1701
1702 narr = self._TCTRL_notes.GetValue().strip()
1703 if narr != '':
1704 pat = gmPerson.gmCurrentPatient()
1705 emr = pat.get_emr()
1706 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = self.data['pk_health_issue'])
1707 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1708
1709
1710 return True
1711
1713 self._PRW_condition.SetText()
1714 self._ChBOX_left.SetValue(0)
1715 self._ChBOX_right.SetValue(0)
1716 self._PRW_classification.SetText()
1717 self._PRW_grouping.SetText()
1718 self._TCTRL_notes.SetValue(u'')
1719 self._PRW_age_noted.SetText()
1720 self._PRW_year_noted.SetText()
1721 self._ChBOX_active.SetValue(0)
1722 self._ChBOX_relevant.SetValue(1)
1723 self._ChBOX_is_operation.SetValue(0)
1724 self._ChBOX_confidential.SetValue(0)
1725 self._ChBOX_caused_death.SetValue(0)
1726
1727 return True
1728
1730 self._PRW_condition.SetText(self.data['description'])
1731
1732 lat = gmTools.coalesce(self.data['laterality'], '')
1733 if lat.find('s') == -1:
1734 self._ChBOX_left.SetValue(0)
1735 else:
1736 self._ChBOX_left.SetValue(1)
1737 if lat.find('d') == -1:
1738 self._ChBOX_right.SetValue(0)
1739 else:
1740 self._ChBOX_right.SetValue(1)
1741
1742 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification'])
1743 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
1744 self._TCTRL_notes.SetValue('')
1745
1746 if self.data['age_noted'] is None:
1747 self._PRW_age_noted.SetText()
1748 else:
1749 self._PRW_age_noted.SetText (
1750 value = '%sd' % self.data['age_noted'].days,
1751 data = self.data['age_noted']
1752 )
1753
1754 self._ChBOX_active.SetValue(self.data['is_active'])
1755 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
1756 self._ChBOX_is_operation.SetValue(0)
1757 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
1758 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
1759
1760
1761
1762
1763
1764 return True
1765
1767 return self._refresh_as_new()
1768
1769
1770
1772
1773 if not self._PRW_age_noted.IsModified():
1774 return True
1775
1776 str_age = self._PRW_age_noted.GetValue().strip()
1777
1778 if str_age == u'':
1779 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1780 return True
1781
1782 age = gmDateTime.str2interval(str_interval = str_age)
1783
1784 if age is None:
1785 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
1786 self._PRW_age_noted.SetBackgroundColour('pink')
1787 self._PRW_age_noted.Refresh()
1788 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1789 return True
1790
1791 pat = gmPerson.gmCurrentPatient()
1792 if pat['dob'] is not None:
1793 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1794
1795 if age >= max_age:
1796 gmDispatcher.send (
1797 signal = 'statustext',
1798 msg = _(
1799 'Health issue cannot have been noted at age %s. Patient is only %s old.'
1800 ) % (age, pat.get_medical_age())
1801 )
1802 self._PRW_age_noted.SetBackgroundColour('pink')
1803 self._PRW_age_noted.Refresh()
1804 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1805 return True
1806
1807 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1808 self._PRW_age_noted.Refresh()
1809 self._PRW_age_noted.SetData(data=age)
1810
1811 if pat['dob'] is not None:
1812 fts = gmDateTime.cFuzzyTimestamp (
1813 timestamp = pat['dob'] + age,
1814 accuracy = gmDateTime.acc_months
1815 )
1816 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
1817
1818
1819
1820
1821
1822 return True
1823
1825
1826 if not self._PRW_year_noted.IsModified():
1827 return True
1828
1829 year_noted = self._PRW_year_noted.GetData()
1830
1831 if year_noted is None:
1832 if self._PRW_year_noted.GetValue().strip() == u'':
1833 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1834 return True
1835 self._PRW_year_noted.SetBackgroundColour('pink')
1836 self._PRW_year_noted.Refresh()
1837 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1838 return True
1839
1840 year_noted = year_noted.get_pydt()
1841
1842 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
1843 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
1844 self._PRW_year_noted.SetBackgroundColour('pink')
1845 self._PRW_year_noted.Refresh()
1846 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1847 return True
1848
1849 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1850 self._PRW_year_noted.Refresh()
1851
1852 pat = gmPerson.gmCurrentPatient()
1853 if pat['dob'] is not None:
1854 issue_age = year_noted - pat['dob']
1855 str_age = gmDateTime.format_interval_medically(interval = issue_age)
1856 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
1857
1858 return True
1859
1861 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1862 return True
1863
1865 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1866 return True
1867
1868
1869
1871
1873
1874 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1875
1876 self.selection_only = False
1877
1878 mp = gmMatchProvider.cMatchProvider_FixedList (
1879 aSeq = [
1880 {'data': u'A', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
1881 {'data': u'B', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
1882 {'data': u'C', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
1883 {'data': u'D', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
1884 ]
1885 )
1886 mp.setThresholds(1, 2, 4)
1887 self.matcher = mp
1888
1889 self.SetToolTipString(_(
1890 "The diagnostic classification or grading of this assessment.\n"
1891 "\n"
1892 "This documents how certain one is about this being a true diagnosis."
1893 ))
1894
1895
1896
1897 if __name__ == '__main__':
1898
1899
1901 """
1902 Test application for testing EMR struct widgets
1903 """
1904
1906 """
1907 Create test application UI
1908 """
1909 frame = wx.Frame (
1910 None,
1911 -4,
1912 'Testing EMR struct widgets',
1913 size=wx.Size(600, 400),
1914 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1915 )
1916 filemenu= wx.Menu()
1917 filemenu.AppendSeparator()
1918 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
1919
1920
1921 menuBar = wx.MenuBar()
1922 menuBar.Append(filemenu,"&File")
1923
1924 frame.SetMenuBar(menuBar)
1925
1926 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1927 wx.DefaultPosition, wx.DefaultSize, 0 )
1928
1929
1930 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
1931
1932
1933 self.__pat = gmPerson.gmCurrentPatient()
1934
1935 frame.Show(1)
1936 return 1
1937
1939 """
1940 Close test aplication
1941 """
1942 self.ExitMainLoop ()
1943
1945 app = wx.PyWidgetTester(size = (200, 300))
1946 emr = pat.get_emr()
1947 enc = emr.active_encounter
1948
1949 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
1950 app.frame.Show(True)
1951 app.MainLoop()
1952 return
1953
1955 app = wx.PyWidgetTester(size = (200, 300))
1956 emr = pat.get_emr()
1957 enc = emr.active_encounter
1958
1959
1960 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
1961 dlg.ShowModal()
1962
1963
1964
1965
1966
1968 app = wx.PyWidgetTester(size = (200, 300))
1969 emr = pat.get_emr()
1970 epi = emr.get_episodes()[0]
1971 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1972 app.frame.Show(True)
1973 app.MainLoop()
1974
1980
1982 app = wx.PyWidgetTester(size = (400, 40))
1983 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1984 app.MainLoop()
1985
1987 app = wx.PyWidgetTester(size = (400, 40))
1988 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1989
1990 app.MainLoop()
1991
1993 app = wx.PyWidgetTester(size = (200, 300))
1994 edit_health_issue(parent=app.frame, issue=None)
1995
1997 app = wx.PyWidgetTester(size = (200, 300))
1998 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1999 app.MainLoop()
2000
2002 app = wx.PyWidgetTester(size = (200, 300))
2003 edit_procedure(parent=app.frame)
2004
2005
2006 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2007
2008 gmI18N.activate_locale()
2009 gmI18N.install_domain()
2010 gmDateTime.init()
2011
2012
2013 pat = gmPerson.ask_for_patient()
2014 if pat is None:
2015 print "No patient. Exiting gracefully..."
2016 sys.exit(0)
2017 gmPatSearchWidgets.set_active_patient(patient=pat)
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035 test_edit_procedure()
2036
2037
2038