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
21
22
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions
26 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery, gmPersonSearch
27 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets
28 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg
29 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
30
31
32 _log = logging.getLogger('gm.ui')
33 _log.info(__version__)
34
35
36
47
48 def delete(procedure=None):
49 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
50 return True
51
52 gmDispatcher.send (
53 signal = u'statustext',
54 msg = _('Cannot delete performed procedure.'),
55 beep = True
56 )
57 return False
58
59 def refresh(lctrl):
60 procs = emr.get_performed_procedures()
61
62 items = [
63 [
64 u'%s%s' % (
65 p['clin_when'].strftime('%Y-%m-%d'),
66 gmTools.bool2subst (
67 p['is_ongoing'],
68 _(' (ongoing)'),
69 gmTools.coalesce (
70 initial = p['clin_end'],
71 instead = u'',
72 template_initial = u' - %s',
73 function_initial = ('strftime', u'%Y-%m-%d')
74 )
75 )
76 ),
77 p['clin_where'],
78 p['episode'],
79 p['performed_procedure']
80 ] for p in procs
81 ]
82 lctrl.set_string_items(items = items)
83 lctrl.set_data(data = procs)
84
85 gmListWidgets.get_choices_from_list (
86 parent = parent,
87 msg = _('\nSelect the procedure you want to edit !\n'),
88 caption = _('Editing performed procedures ...'),
89 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
90 single_selection = True,
91 edit_callback = edit,
92 new_callback = edit,
93 delete_callback = delete,
94 refresh_callback = refresh
95 )
96
108
109 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
110
111 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
112
121
123 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
124 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
125 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
126 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
127 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
128
129
130 mp = gmMatchProvider.cMatchProvider_SQL2 (
131 queries = [
132 u"""
133 SELECT DISTINCT ON (data) data, location
134 FROM (
135 SELECT
136 clin_where as data,
137 clin_where as location
138 FROM
139 clin.procedure
140 WHERE
141 clin_where %(fragment_condition)s
142
143 UNION ALL
144
145 SELECT
146 narrative as data,
147 narrative as location
148 FROM
149 clin.hospital_stay
150 WHERE
151 narrative %(fragment_condition)s
152 ) as union_result
153 ORDER BY data
154 LIMIT 25"""
155 ]
156 )
157 mp.setThresholds(2, 4, 6)
158 self._PRW_location.matcher = mp
159
160
161 mp = gmMatchProvider.cMatchProvider_SQL2 (
162 queries = [
163 u"""
164 select distinct on (narrative) narrative, narrative
165 from clin.procedure
166 where narrative %(fragment_condition)s
167 order by narrative
168 limit 25
169 """ ]
170 )
171 mp.setThresholds(2, 4, 6)
172 self._PRW_procedure.matcher = mp
173
175 stay = self._PRW_hospital_stay.GetData()
176 if stay is None:
177 self._PRW_hospital_stay.SetText()
178 self._PRW_location.Enable(True)
179 self._PRW_episode.Enable(True)
180 self._LBL_hospital_details.SetLabel(u'')
181 else:
182 self._PRW_location.SetText()
183 self._PRW_location.Enable(False)
184 self._PRW_episode.SetText()
185 self._PRW_episode.Enable(False)
186 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
187
189 if self._PRW_location.GetValue().strip() == u'':
190 self._PRW_hospital_stay.Enable(True)
191
192 else:
193 self._PRW_hospital_stay.SetText()
194 self._PRW_hospital_stay.Enable(False)
195 self._PRW_hospital_stay.display_as_valid(True)
196
197
209
232
233
234
292
327
329 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
330
331 if self._DPRW_end.GetData() is None:
332 self.data['clin_end'] = None
333 else:
334 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
335
336 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
337
338 if self._PRW_hospital_stay.GetData() is None:
339 self.data['pk_hospital_stay'] = None
340 self.data['clin_where'] = self._PRW_location.GetValue().strip()
341 self.data['pk_episode'] = self._PRW_episode.GetData()
342 else:
343 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
344 self.data['clin_where'] = None
345 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
346 self.data['pk_episode'] = stay['pk_episode']
347
348 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
349
350 self.data.save()
351 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
352
353 return True
354
356 self._DPRW_date.SetText()
357 self._DPRW_end.SetText()
358 self._CHBOX_ongoing.SetValue(False)
359 self._CHBOX_ongoing.Enable(True)
360 self._PRW_hospital_stay.SetText()
361 self._PRW_location.SetText()
362 self._PRW_episode.SetText()
363 self._PRW_procedure.SetText()
364 self._PRW_codes.SetText()
365
366 self._PRW_procedure.SetFocus()
367
398
410
411
412
417
433
434
435
446
447 def delete(stay=None):
448 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
449 return True
450 gmDispatcher.send (
451 signal = u'statustext',
452 msg = _('Cannot delete hospitalization.'),
453 beep = True
454 )
455 return False
456
457 def refresh(lctrl):
458 stays = emr.get_hospital_stays()
459 items = [
460 [
461 s['admission'].strftime('%Y-%m-%d'),
462 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
463 s['episode'],
464 gmTools.coalesce(s['hospital'], u'')
465 ] for s in stays
466 ]
467 lctrl.set_string_items(items = items)
468 lctrl.set_data(data = stays)
469
470 gmListWidgets.get_choices_from_list (
471 parent = parent,
472 msg = _("The patient's hospitalizations:\n"),
473 caption = _('Editing hospitalizations ...'),
474 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
475 single_selection = True,
476 edit_callback = edit,
477 new_callback = edit,
478 delete_callback = delete,
479 refresh_callback = refresh
480 )
481
482
494
496 """Phrasewheel to allow selection of a hospitalization."""
498
499 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
500
501 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
502
503 mp = gmMatchProvider.cMatchProvider_SQL2 (
504 queries = [
505 u"""
506 select
507 pk_hospital_stay,
508 descr
509 from (
510 select distinct on (pk_hospital_stay)
511 pk_hospital_stay,
512 descr
513 from
514 (select
515 pk_hospital_stay,
516 (
517 to_char(admission, 'YYYY-Mon-DD')
518 || coalesce((' (' || hospital || '):'), ': ')
519 || episode
520 || coalesce((' (' || health_issue || ')'), '')
521 ) as descr
522 from
523 clin.v_pat_hospital_stays
524 where
525 %(ctxt_pat)s
526
527 hospital %(fragment_condition)s
528 or
529 episode %(fragment_condition)s
530 or
531 health_issue %(fragment_condition)s
532 ) as the_stays
533 ) as distinct_stays
534 order by descr
535 limit 25
536 """ ],
537 context = ctxt
538 )
539 mp.setThresholds(3, 4, 6)
540 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
541
542 self.matcher = mp
543 self.selection_only = True
544
545 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
546
547 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
548
552
553
554
556
557 valid = True
558
559 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
560 valid = False
561 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
562
563 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
564 if self._PRW_discharge.date is not None:
565 if not self._PRW_discharge.date > self._PRW_admission.date:
566 valid = False
567 self._PRW_discharge.display_as_valid(False)
568 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
569
570 if self._PRW_episode.GetValue().strip() == u'':
571 valid = False
572 self._PRW_episode.display_as_valid(False)
573 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
574
575 return (valid is True)
576
589
599
605
615
617 print "this was not expected to be used in this edit area"
618
619
620
629
630 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
631
633 if parent is None:
634 parent = wx.GetApp().GetTopWindow()
635
636
637 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
638 if dlg.ShowModal() == wx.ID_OK:
639 dlg.Destroy()
640 return True
641 dlg.Destroy()
642 return False
643
646
647 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
648
649 if patient is None:
650 patient = gmPerson.gmCurrentPatient()
651
652 if not patient.connected:
653 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
654 return False
655
656 if parent is None:
657 parent = wx.GetApp().GetTopWindow()
658
659 emr = patient.get_emr()
660
661
662 def refresh(lctrl):
663 if encounters is None:
664 encs = emr.get_encounters()
665 else:
666 encs = encounters
667
668 items = [
669 [
670 e['started'].strftime('%x %H:%M'),
671 e['last_affirmed'].strftime('%H:%M'),
672 e['l10n_type'],
673 gmTools.coalesce(e['reason_for_encounter'], u''),
674 gmTools.coalesce(e['assessment_of_encounter'], u''),
675 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
676 e['pk_encounter']
677 ] for e in encs
678 ]
679 lctrl.set_string_items(items = items)
680 lctrl.set_data(data = encs)
681 active_pk = emr.active_encounter['pk_encounter']
682 for idx in range(len(encs)):
683 e = encs[idx]
684 if e['pk_encounter'] == active_pk:
685 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
686
687 def new():
688 cfg_db = gmCfg.cCfgSQL()
689
690 enc_type = cfg_db.get2 (
691 option = u'encounter.default_type',
692 workplace = gmSurgery.gmCurrentPractice().active_workplace,
693 bias = u'user',
694 default = u'in surgery'
695 )
696 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
697 return edit_encounter(parent = parent, encounter = enc)
698
699 def edit(enc=None):
700 return edit_encounter(parent = parent, encounter = enc)
701
702 def edit_active(enc=None):
703 return edit_encounter(parent = parent, encounter = emr.active_encounter)
704
705 def start_new(enc=None):
706 start_new_encounter(emr = emr)
707 return True
708
709 return gmListWidgets.get_choices_from_list (
710 parent = parent,
711 msg = _("The patient's encounters.\n"),
712 caption = _('Encounters ...'),
713 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
714 can_return_empty = False,
715 single_selection = single_selection,
716 refresh_callback = refresh,
717 edit_callback = edit,
718 new_callback = new,
719 ignore_OK_button = ignore_OK_button,
720 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
721 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
722 )
723
725 """This is used as the callback when the EMR detects that the
726 patient was here rather recently and wants to ask the
727 provider whether to continue the recent encounter.
728 """
729 if parent is None:
730 parent = wx.GetApp().GetTopWindow()
731
732 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
733 parent = None,
734 id = -1,
735 caption = caption,
736 question = msg,
737 button_defs = [
738 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
739 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
740 ],
741 show_checkbox = False
742 )
743
744 result = dlg.ShowModal()
745 dlg.Destroy()
746
747 if result == wx.ID_YES:
748 return True
749
750 return False
751
753
754 if parent is None:
755 parent = wx.GetApp().GetTopWindow()
756
757
758 def edit(enc_type=None):
759 return edit_encounter_type(parent = parent, encounter_type = enc_type)
760
761 def delete(enc_type=None):
762 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
763 return True
764 gmDispatcher.send (
765 signal = u'statustext',
766 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
767 beep = True
768 )
769 return False
770
771 def refresh(lctrl):
772 enc_types = gmEMRStructItems.get_encounter_types()
773 lctrl.set_string_items(items = enc_types)
774
775 gmListWidgets.get_choices_from_list (
776 parent = parent,
777 msg = _('\nSelect the encounter type you want to edit !\n'),
778 caption = _('Managing encounter types ...'),
779 columns = [_('Local name'), _('Encounter type')],
780 single_selection = True,
781 edit_callback = edit,
782 new_callback = edit,
783 delete_callback = delete,
784 refresh_callback = refresh
785 )
786
796
798
800 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
801
802 cmd = u"""
803 SELECT -- DISTINCT ON (data)
804 pk_encounter
805 AS data,
806 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
807 AS list_label,
808 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
809 AS field_label
810 FROM
811 clin.v_pat_encounters
812 WHERE
813 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
814 OR
815 l10n_type %(fragment_condition)s
816 OR
817 type %(fragment_condition)s
818 %(ctxt_patient)s
819 ORDER BY
820 list_label
821 LIMIT
822 30
823 """
824 context = {'ctxt_patient': {
825 'where_part': u'AND pk_patient = %(patient)s',
826 'placeholder': u'patient'
827 }}
828
829 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
830 self.matcher._SQL_data2match = u"""
831 SELECT
832 pk_encounter
833 AS data,
834 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
835 AS list_label,
836 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
837 AS field_label
838 FROM
839 clin.v_pat_encounters
840 WHERE
841 pk_encounter = %(pk)s
842 """
843 self.matcher.setThresholds(1, 3, 5)
844 self.selection_only = True
845
846 self.set_context(context = 'patient', val = None)
847
854
865
867 """Phrasewheel to allow selection of encounter type.
868
869 - user input interpreted as encounter type in English or local language
870 - data returned is pk of corresponding encounter type or None
871 """
873
874 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
875
876 mp = gmMatchProvider.cMatchProvider_SQL2 (
877 queries = [
878 u"""
879 SELECT
880 data,
881 field_label,
882 list_label
883 FROM (
884 SELECT DISTINCT ON (data) *
885 FROM (
886 SELECT
887 pk AS data,
888 _(description) AS field_label,
889 case
890 when _(description) = description then _(description)
891 else _(description) || ' (' || description || ')'
892 end AS list_label
893 FROM
894 clin.encounter_type
895 WHERE
896 _(description) %(fragment_condition)s
897 OR
898 description %(fragment_condition)s
899 ) AS q_distinct_pk
900 ) AS q_ordered
901 ORDER BY
902 list_label
903 """ ]
904 )
905 mp.setThresholds(2, 4, 6)
906
907 self.matcher = mp
908 self.selection_only = True
909 self.picklist_delay = 50
910
912
917
918
919
920
921
951
964
974
976 self._TCTRL_l10n_name.SetValue(u'')
977 self._TCTRL_name.SetValue(u'')
978 self._TCTRL_name.Enable(True)
979
981 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
982 self._TCTRL_name.SetValue(self.data['description'])
983
984 self._TCTRL_name.Enable(False)
985
987 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
988 self._TCTRL_name.SetValue(self.data['description'])
989 self._TCTRL_name.Enable(True)
990
991
992
993
994
995
996 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
997
999
1001 try:
1002 self.__encounter = kwargs['encounter']
1003 del kwargs['encounter']
1004 except KeyError:
1005 self.__encounter = None
1006
1007 try:
1008 msg = kwargs['msg']
1009 del kwargs['msg']
1010 except KeyError:
1011 msg = None
1012
1013 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1014
1015 self.refresh(msg = msg)
1016
1017
1018
1019 - def refresh(self, encounter=None, msg=None):
1020
1021 if msg is not None:
1022 self._LBL_instructions.SetLabel(msg)
1023
1024 if encounter is not None:
1025 self.__encounter = encounter
1026
1027 if self.__encounter is None:
1028 return True
1029
1030
1031
1032 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1033 self._LBL_patient.SetLabel(pat.get_description_gender())
1034
1035 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1036
1037 fts = gmDateTime.cFuzzyTimestamp (
1038 timestamp = self.__encounter['started'],
1039 accuracy = gmDateTime.acc_minutes
1040 )
1041 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1042
1043 fts = gmDateTime.cFuzzyTimestamp (
1044 timestamp = self.__encounter['last_affirmed'],
1045 accuracy = gmDateTime.acc_minutes
1046 )
1047 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1048
1049
1050 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1051 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1052 self._PRW_rfe_codes.SetText(val, data)
1053
1054
1055 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1056 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1057 self._PRW_aoe_codes.SetText(val, data)
1058
1059
1060 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1061 self._PRW_end.SetFocus()
1062 else:
1063 self._TCTRL_aoe.SetFocus()
1064
1065 return True
1066
1068
1069 if self._PRW_encounter_type.GetData() is None:
1070 self._PRW_encounter_type.SetBackgroundColour('pink')
1071 self._PRW_encounter_type.Refresh()
1072 self._PRW_encounter_type.SetFocus()
1073 return False
1074 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1075 self._PRW_encounter_type.Refresh()
1076
1077
1078 if self._PRW_start.GetValue().strip() == u'':
1079 self._PRW_start.SetBackgroundColour('pink')
1080 self._PRW_start.Refresh()
1081 self._PRW_start.SetFocus()
1082 return False
1083 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1084 self._PRW_start.SetBackgroundColour('pink')
1085 self._PRW_start.Refresh()
1086 self._PRW_start.SetFocus()
1087 return False
1088 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1089 self._PRW_start.Refresh()
1090
1091
1092
1093
1094
1095
1096
1097 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1098 self._PRW_end.SetBackgroundColour('pink')
1099 self._PRW_end.Refresh()
1100 self._PRW_end.SetFocus()
1101 return False
1102 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1103 self._PRW_end.Refresh()
1104
1105 return True
1106
1108 if not self.__is_valid_for_save():
1109 return False
1110
1111 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1112 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1113 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1114 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1115 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1116 self.__encounter.save_payload()
1117
1118 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1119 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1120
1121 return True
1122
1123
1125
1127 encounter = kwargs['encounter']
1128 del kwargs['encounter']
1129
1130 try:
1131 button_defs = kwargs['button_defs']
1132 del kwargs['button_defs']
1133 except KeyError:
1134 button_defs = None
1135
1136 try:
1137 msg = kwargs['msg']
1138 del kwargs['msg']
1139 except KeyError:
1140 msg = None
1141
1142 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1143 self.SetSize((450, 280))
1144 self.SetMinSize((450, 280))
1145
1146 if button_defs is not None:
1147 self._BTN_save.SetLabel(button_defs[0][0])
1148 self._BTN_save.SetToolTipString(button_defs[0][1])
1149 self._BTN_close.SetLabel(button_defs[1][0])
1150 self._BTN_close.SetToolTipString(button_defs[1][1])
1151 self.Refresh()
1152
1153 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1154
1155 self.Fit()
1156
1163
1164 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1165
1167
1172
1174 self._TCTRL_encounter.SetValue(u'')
1175 self._TCTRL_encounter.SetToolTipString(u'')
1176 self._BTN_new.Enable(False)
1177 self._BTN_list.Enable(False)
1178
1180 pat = gmPerson.gmCurrentPatient()
1181 if not pat.connected:
1182 self.clear()
1183 return
1184
1185 enc = pat.get_emr().active_encounter
1186 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1187 self._TCTRL_encounter.SetToolTipString (
1188 _('The active encounter of the current patient:\n\n%s') %
1189 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1190 )
1191 self._BTN_new.Enable(True)
1192 self._BTN_list.Enable(True)
1193
1195 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1196
1197 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1198
1199
1200 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1201 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1202 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1203
1204
1205
1207 wx.CallAfter(self.clear)
1208
1210 wx.CallAfter(self.refresh)
1211 return True
1212
1216
1220
1223
1224
1225
1235
1305
1307 """Prepare changing health issue for an episode.
1308
1309 Checks for two-open-episodes conflict. When this
1310 function succeeds, the pk_health_issue has been set
1311 on the episode instance and the episode should - for
1312 all practical purposes - be ready for save_payload().
1313 """
1314
1315 if not episode['episode_open']:
1316 episode['pk_health_issue'] = target_issue['pk_health_issue']
1317 if save_to_backend:
1318 episode.save_payload()
1319 return True
1320
1321
1322 if target_issue is None:
1323 episode['pk_health_issue'] = None
1324 if save_to_backend:
1325 episode.save_payload()
1326 return True
1327
1328
1329 db_cfg = gmCfg.cCfgSQL()
1330 epi_ttl = int(db_cfg.get2 (
1331 option = u'episode.ttl',
1332 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1333 bias = 'user',
1334 default = 60
1335 ))
1336 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1337 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1338 existing_epi = target_issue.get_open_episode()
1339
1340
1341 if existing_epi is None:
1342 episode['pk_health_issue'] = target_issue['pk_health_issue']
1343 if save_to_backend:
1344 episode.save_payload()
1345 return True
1346
1347
1348 if existing_epi['pk_episode'] == episode['pk_episode']:
1349 episode['pk_health_issue'] = target_issue['pk_health_issue']
1350 if save_to_backend:
1351 episode.save_payload()
1352 return True
1353
1354
1355 move_range = episode.get_access_range()
1356 exist_range = existing_epi.get_access_range()
1357 question = _(
1358 'You want to associate the running episode:\n\n'
1359 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1360 'with the health issue:\n\n'
1361 ' "%(issue_name)s"\n\n'
1362 'There already is another episode running\n'
1363 'for this health issue:\n\n'
1364 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1365 'However, there can only be one running\n'
1366 'episode per health issue.\n\n'
1367 'Which episode do you want to close ?'
1368 ) % {
1369 'new_epi_name': episode['description'],
1370 'new_epi_start': move_range[0].strftime('%m/%y'),
1371 'new_epi_end': move_range[1].strftime('%m/%y'),
1372 'issue_name': target_issue['description'],
1373 'old_epi_name': existing_epi['description'],
1374 'old_epi_start': exist_range[0].strftime('%m/%y'),
1375 'old_epi_end': exist_range[1].strftime('%m/%y')
1376 }
1377 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1378 parent = None,
1379 id = -1,
1380 caption = _('Resolving two-running-episodes conflict'),
1381 question = question,
1382 button_defs = [
1383 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1384 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1385 ]
1386 )
1387 decision = dlg.ShowModal()
1388
1389 if decision == wx.ID_CANCEL:
1390
1391 return False
1392
1393 elif decision == wx.ID_YES:
1394
1395 existing_epi['episode_open'] = False
1396 existing_epi.save_payload()
1397
1398 elif decision == wx.ID_NO:
1399
1400 episode['episode_open'] = False
1401
1402 else:
1403 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1404
1405 episode['pk_health_issue'] = target_issue['pk_health_issue']
1406 if save_to_backend:
1407 episode.save_payload()
1408 return True
1409
1433
1435 """Let user select an episode *description*.
1436
1437 The user can select an episode description from the previously
1438 used descriptions across all episodes across all patients.
1439
1440 Selection is done with a phrasewheel so the user can
1441 type the episode name and matches will be shown. Typing
1442 "*" will show the entire list of episodes.
1443
1444 If the user types a description not existing yet a
1445 new episode description will be returned.
1446 """
1448
1449 mp = gmMatchProvider.cMatchProvider_SQL2 (
1450 queries = [
1451 u"""
1452 SELECT DISTINCT ON (description)
1453 description
1454 AS data,
1455 description
1456 AS field_label,
1457 description || ' ('
1458 || CASE
1459 WHEN is_open IS TRUE THEN _('ongoing')
1460 ELSE _('closed')
1461 END
1462 || ')'
1463 AS list_label
1464 FROM
1465 clin.episode
1466 WHERE
1467 description %(fragment_condition)s
1468 ORDER BY description
1469 LIMIT 30
1470 """
1471 ]
1472 )
1473 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1474 self.matcher = mp
1475
1477 """Let user select an episode.
1478
1479 The user can select an episode from the existing episodes of a
1480 patient. Selection is done with a phrasewheel so the user
1481 can type the episode name and matches will be shown. Typing
1482 "*" will show the entire list of episodes. Closed episodes
1483 will be marked as such. If the user types an episode name not
1484 in the list of existing episodes a new episode can be created
1485 from it if the programmer activated that feature.
1486
1487 If keyword <patient_id> is set to None or left out the control
1488 will listen to patient change signals and therefore act on
1489 gmPerson.gmCurrentPatient() changes.
1490 """
1492
1493 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1494
1495 mp = gmMatchProvider.cMatchProvider_SQL2 (
1496 queries = [
1497 u"""(
1498
1499 select
1500 pk_episode
1501 as data,
1502 description
1503 as field_label,
1504 coalesce (
1505 description || ' - ' || health_issue,
1506 description
1507 ) as list_label,
1508 1 as rank
1509 from
1510 clin.v_pat_episodes
1511 where
1512 episode_open is true and
1513 description %(fragment_condition)s
1514 %(ctxt_pat)s
1515
1516 ) union all (
1517
1518 select
1519 pk_episode
1520 as data,
1521 description
1522 as field_label,
1523 coalesce (
1524 description || _(' (closed)') || ' - ' || health_issue,
1525 description || _(' (closed)')
1526 ) as list_label,
1527 2 as rank
1528 from
1529 clin.v_pat_episodes
1530 where
1531 description %(fragment_condition)s and
1532 episode_open is false
1533 %(ctxt_pat)s
1534
1535 )
1536
1537 order by rank, list_label
1538 limit 30"""
1539 ],
1540 context = ctxt
1541 )
1542
1543 try:
1544 kwargs['patient_id']
1545 except KeyError:
1546 kwargs['patient_id'] = None
1547
1548 if kwargs['patient_id'] is None:
1549 self.use_current_patient = True
1550 self.__register_patient_change_signals()
1551 pat = gmPerson.gmCurrentPatient()
1552 if pat.connected:
1553 mp.set_context('pat', pat.ID)
1554 else:
1555 self.use_current_patient = False
1556 self.__patient_id = int(kwargs['patient_id'])
1557 mp.set_context('pat', self.__patient_id)
1558
1559 del kwargs['patient_id']
1560
1561 gmPhraseWheel.cPhraseWheel.__init__ (
1562 self,
1563 *args,
1564 **kwargs
1565 )
1566 self.matcher = mp
1567
1568
1569
1571 if self.use_current_patient:
1572 return False
1573 self.__patient_id = int(patient_id)
1574 self.set_context('pat', self.__patient_id)
1575 return True
1576
1577 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1580
1582
1583 epi_name = self.GetValue().strip()
1584 if epi_name == u'':
1585 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1586 _log.debug('cannot create episode without name')
1587 return
1588
1589 if self.use_current_patient:
1590 pat = gmPerson.gmCurrentPatient()
1591 else:
1592 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1593
1594 emr = pat.get_emr()
1595 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1596 if epi is None:
1597 self.data = {}
1598 else:
1599 self.SetText (
1600 value = epi_name,
1601 data = epi['pk_episode']
1602 )
1603
1606
1607
1608
1612
1615
1617 if self.use_current_patient:
1618 patient = gmPerson.gmCurrentPatient()
1619 self.set_context('pat', patient.ID)
1620 return True
1621
1622 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1623
1624 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1625
1638
1639
1640
1642
1643 errors = False
1644
1645 if len(self._PRW_description.GetValue().strip()) == 0:
1646 errors = True
1647 self._PRW_description.display_as_valid(False)
1648 self._PRW_description.SetFocus()
1649 else:
1650 self._PRW_description.display_as_valid(True)
1651 self._PRW_description.Refresh()
1652
1653 return not errors
1654
1656
1657 pat = gmPerson.gmCurrentPatient()
1658 emr = pat.get_emr()
1659
1660 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1661 epi['summary'] = self._TCTRL_status.GetValue().strip()
1662 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1663 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1664
1665 issue_name = self._PRW_issue.GetValue().strip()
1666 if len(issue_name) != 0:
1667 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1668 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1669
1670 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1671 gmDispatcher.send (
1672 signal = 'statustext',
1673 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1674 epi['description'],
1675 issue['description']
1676 )
1677 )
1678 gmEMRStructItems.delete_episode(episode = epi)
1679 return False
1680
1681 epi.save()
1682
1683 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1684
1685 self.data = epi
1686 return True
1687
1689
1690 self.data['description'] = self._PRW_description.GetValue().strip()
1691 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1692 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1693 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1694
1695 issue_name = self._PRW_issue.GetValue().strip()
1696 if len(issue_name) == 0:
1697 self.data['pk_health_issue'] = None
1698 else:
1699 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1700 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1701
1702 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1703 gmDispatcher.send (
1704 signal = 'statustext',
1705 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1706 self.data['description'],
1707 issue['description']
1708 )
1709 )
1710 return False
1711
1712 self.data.save()
1713 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1714
1715 return True
1716
1729
1748
1750 self._refresh_as_new()
1751
1752
1753
1763
1765
1766 if parent is None:
1767 parent = wx.GetApp().GetTopWindow()
1768
1769 def refresh(lctrl):
1770 issues = emr.get_health_issues()
1771 items = [
1772 [
1773 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1774 i['description'],
1775 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1776 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1777 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1778 ] for i in issues
1779 ]
1780 lctrl.set_string_items(items = items)
1781 lctrl.set_data(data = issues)
1782
1783 return gmListWidgets.get_choices_from_list (
1784 parent = parent,
1785 msg = _('\nSelect the health issues !\n'),
1786 caption = _('Showing health issues ...'),
1787 columns = [u'', _('Health issue'), u'', u'', u''],
1788 single_selection = False,
1789
1790
1791
1792 refresh_callback = refresh
1793 )
1794
1796
1797
1798
1800
1801 issues = kwargs['issues']
1802 del kwargs['issues']
1803
1804 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1805
1806 self.SetTitle(_('Select the health issues you are interested in ...'))
1807 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1808
1809 for issue in issues:
1810 if issue['is_confidential']:
1811 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1812 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1813 else:
1814 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1815
1816 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1817 if issue['clinically_relevant']:
1818 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1819 if issue['is_active']:
1820 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1821 if issue['is_cause_of_death']:
1822 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1823
1824 self._LCTRL_items.set_column_widths()
1825 self._LCTRL_items.set_data(data = issues)
1826
1828 """Let the user select a health issue.
1829
1830 The user can select a health issue from the existing issues
1831 of a patient. Selection is done with a phrasewheel so the user
1832 can type the issue name and matches will be shown. Typing
1833 "*" will show the entire list of issues. Inactive issues
1834 will be marked as such. If the user types an issue name not
1835 in the list of existing issues a new issue can be created
1836 from it if the programmer activated that feature.
1837
1838 If keyword <patient_id> is set to None or left out the control
1839 will listen to patient change signals and therefore act on
1840 gmPerson.gmCurrentPatient() changes.
1841 """
1843
1844 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1845
1846 mp = gmMatchProvider.cMatchProvider_SQL2 (
1847
1848 queries = [
1849 u"""
1850 SELECT
1851 data,
1852 field_label,
1853 list_label
1854 FROM ((
1855 SELECT
1856 pk_health_issue AS data,
1857 description AS field_label,
1858 description AS list_label
1859 FROM clin.v_health_issues
1860 WHERE
1861 is_active IS true
1862 AND
1863 description %(fragment_condition)s
1864 AND
1865 %(ctxt_pat)s
1866
1867 ) UNION (
1868
1869 SELECT
1870 pk_health_issue AS data,
1871 description AS field_label,
1872 description || _(' (inactive)') AS list_label
1873 FROM clin.v_health_issues
1874 WHERE
1875 is_active IS false
1876 AND
1877 description %(fragment_condition)s
1878 AND
1879 %(ctxt_pat)s
1880 )) AS union_query
1881 ORDER BY
1882 list_label"""],
1883 context = ctxt
1884 )
1885
1886 try: kwargs['patient_id']
1887 except KeyError: kwargs['patient_id'] = None
1888
1889 if kwargs['patient_id'] is None:
1890 self.use_current_patient = True
1891 self.__register_patient_change_signals()
1892 pat = gmPerson.gmCurrentPatient()
1893 if pat.connected:
1894 mp.set_context('pat', pat.ID)
1895 else:
1896 self.use_current_patient = False
1897 self.__patient_id = int(kwargs['patient_id'])
1898 mp.set_context('pat', self.__patient_id)
1899
1900 del kwargs['patient_id']
1901
1902 gmPhraseWheel.cPhraseWheel.__init__ (
1903 self,
1904 *args,
1905 **kwargs
1906 )
1907 self.matcher = mp
1908
1909
1910
1912 if self.use_current_patient:
1913 return False
1914 self.__patient_id = int(patient_id)
1915 self.set_context('pat', self.__patient_id)
1916 return True
1917
1919 issue_name = self.GetValue().strip()
1920 if issue_name == u'':
1921 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
1922 _log.debug('cannot create health issue without name')
1923 return
1924
1925 if self.use_current_patient:
1926 pat = gmPerson.gmCurrentPatient()
1927 else:
1928 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1929
1930 emr = pat.get_emr()
1931 issue = emr.add_health_issue(issue_name = issue_name)
1932
1933 if issue is None:
1934 self.data = {}
1935 else:
1936 self.SetText (
1937 value = issue_name,
1938 data = issue['pk_health_issue']
1939 )
1940
1943
1944
1945
1949
1952
1954 if self.use_current_patient:
1955 patient = gmPerson.gmCurrentPatient()
1956 self.set_context('pat', patient.ID)
1957 return True
1958
1960
1962 try:
1963 msg = kwargs['message']
1964 except KeyError:
1965 msg = None
1966 del kwargs['message']
1967 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1968 if msg is not None:
1969 self._lbl_message.SetLabel(label=msg)
1970
1981
1982 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
1983
1984 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1985 """Panel encapsulating health issue edit area functionality."""
1986
1988
1989 try:
1990 issue = kwargs['issue']
1991 except KeyError:
1992 issue = None
1993
1994 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1995
1996 gmEditArea.cGenericEditAreaMixin.__init__(self)
1997
1998
1999 mp = gmMatchProvider.cMatchProvider_SQL2 (
2000 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2001 )
2002 mp.setThresholds(1, 3, 5)
2003 self._PRW_condition.matcher = mp
2004
2005 mp = gmMatchProvider.cMatchProvider_SQL2 (
2006 queries = [u"""
2007 select distinct on (grouping) grouping, grouping from (
2008
2009 select rank, grouping from ((
2010
2011 select
2012 grouping,
2013 1 as rank
2014 from
2015 clin.health_issue
2016 where
2017 grouping %%(fragment_condition)s
2018 and
2019 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2020
2021 ) union (
2022
2023 select
2024 grouping,
2025 2 as rank
2026 from
2027 clin.health_issue
2028 where
2029 grouping %%(fragment_condition)s
2030
2031 )) as union_result
2032
2033 order by rank
2034
2035 ) as order_result
2036
2037 limit 50""" % gmPerson.gmCurrentPatient().ID
2038 ]
2039 )
2040 mp.setThresholds(1, 3, 5)
2041 self._PRW_grouping.matcher = mp
2042
2043 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2044 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2045
2046 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2047 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2048
2049 self._PRW_year_noted.Enable(True)
2050
2051 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2052
2053 self.data = issue
2054
2055
2056
2076
2078 pat = gmPerson.gmCurrentPatient()
2079 emr = pat.get_emr()
2080
2081 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2082
2083 side = u''
2084 if self._ChBOX_left.GetValue():
2085 side += u's'
2086 if self._ChBOX_right.GetValue():
2087 side += u'd'
2088 issue['laterality'] = side
2089
2090 issue['summary'] = self._TCTRL_status.GetValue().strip()
2091 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2092 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2093 issue['is_active'] = self._ChBOX_active.GetValue()
2094 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2095 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2096 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2097
2098 age_noted = self._PRW_age_noted.GetData()
2099 if age_noted is not None:
2100 issue['age_noted'] = age_noted
2101
2102 issue.save()
2103
2104 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2105
2106 self.data = issue
2107 return True
2108
2110
2111 self.data['description'] = self._PRW_condition.GetValue().strip()
2112
2113 side = u''
2114 if self._ChBOX_left.GetValue():
2115 side += u's'
2116 if self._ChBOX_right.GetValue():
2117 side += u'd'
2118 self.data['laterality'] = side
2119
2120 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2121 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2122 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2123 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2124 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2125 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2126 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2127
2128 age_noted = self._PRW_age_noted.GetData()
2129 if age_noted is not None:
2130 self.data['age_noted'] = age_noted
2131
2132 self.data.save()
2133 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2134
2135 return True
2136
2138 self._PRW_condition.SetText()
2139 self._ChBOX_left.SetValue(0)
2140 self._ChBOX_right.SetValue(0)
2141 self._PRW_codes.SetText()
2142 self._on_leave_codes()
2143 self._PRW_certainty.SetText()
2144 self._PRW_grouping.SetText()
2145 self._TCTRL_status.SetValue(u'')
2146 self._PRW_age_noted.SetText()
2147 self._PRW_year_noted.SetText()
2148 self._ChBOX_active.SetValue(0)
2149 self._ChBOX_relevant.SetValue(1)
2150 self._ChBOX_confidential.SetValue(0)
2151 self._ChBOX_caused_death.SetValue(0)
2152
2153 return True
2154
2195
2197 return self._refresh_as_new()
2198
2199
2200
2202 if not self._PRW_codes.IsModified():
2203 return True
2204
2205 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2206
2208
2209 if not self._PRW_age_noted.IsModified():
2210 return True
2211
2212 str_age = self._PRW_age_noted.GetValue().strip()
2213
2214 if str_age == u'':
2215 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2216 return True
2217
2218 age = gmDateTime.str2interval(str_interval = str_age)
2219
2220 if age is None:
2221 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2222 self._PRW_age_noted.SetBackgroundColour('pink')
2223 self._PRW_age_noted.Refresh()
2224 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2225 return True
2226
2227 pat = gmPerson.gmCurrentPatient()
2228 if pat['dob'] is not None:
2229 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2230
2231 if age >= max_age:
2232 gmDispatcher.send (
2233 signal = 'statustext',
2234 msg = _(
2235 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2236 ) % (age, pat.get_medical_age())
2237 )
2238 self._PRW_age_noted.SetBackgroundColour('pink')
2239 self._PRW_age_noted.Refresh()
2240 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2241 return True
2242
2243 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2244 self._PRW_age_noted.Refresh()
2245 self._PRW_age_noted.SetData(data=age)
2246
2247 if pat['dob'] is not None:
2248 fts = gmDateTime.cFuzzyTimestamp (
2249 timestamp = pat['dob'] + age,
2250 accuracy = gmDateTime.acc_months
2251 )
2252 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2253
2254
2255
2256
2257
2258 return True
2259
2261
2262 if not self._PRW_year_noted.IsModified():
2263 return True
2264
2265 year_noted = self._PRW_year_noted.GetData()
2266
2267 if year_noted is None:
2268 if self._PRW_year_noted.GetValue().strip() == u'':
2269 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2270 return True
2271 self._PRW_year_noted.SetBackgroundColour('pink')
2272 self._PRW_year_noted.Refresh()
2273 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2274 return True
2275
2276 year_noted = year_noted.get_pydt()
2277
2278 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2279 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2280 self._PRW_year_noted.SetBackgroundColour('pink')
2281 self._PRW_year_noted.Refresh()
2282 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2283 return True
2284
2285 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2286 self._PRW_year_noted.Refresh()
2287
2288 pat = gmPerson.gmCurrentPatient()
2289 if pat['dob'] is not None:
2290 issue_age = year_noted - pat['dob']
2291 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2292 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2293
2294 return True
2295
2297 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2298 return True
2299
2301 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2302 return True
2303
2304
2305
2307
2309
2310 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2311
2312 self.selection_only = False
2313
2314 mp = gmMatchProvider.cMatchProvider_FixedList (
2315 aSeq = [
2316 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2317 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2318 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2319 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2320 ]
2321 )
2322 mp.setThresholds(1, 2, 4)
2323 self.matcher = mp
2324
2325 self.SetToolTipString(_(
2326 "The diagnostic classification or grading of this assessment.\n"
2327 "\n"
2328 "This documents how certain one is about this being a true diagnosis."
2329 ))
2330
2331
2332
2333 if __name__ == '__main__':
2334
2335
2337 """
2338 Test application for testing EMR struct widgets
2339 """
2340
2342 """
2343 Create test application UI
2344 """
2345 frame = wx.Frame (
2346 None,
2347 -4,
2348 'Testing EMR struct widgets',
2349 size=wx.Size(600, 400),
2350 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2351 )
2352 filemenu= wx.Menu()
2353 filemenu.AppendSeparator()
2354 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2355
2356
2357 menuBar = wx.MenuBar()
2358 menuBar.Append(filemenu,"&File")
2359
2360 frame.SetMenuBar(menuBar)
2361
2362 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2363 wx.DefaultPosition, wx.DefaultSize, 0 )
2364
2365
2366 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2367
2368
2369 self.__pat = gmPerson.gmCurrentPatient()
2370
2371 frame.Show(1)
2372 return 1
2373
2375 """
2376 Close test aplication
2377 """
2378 self.ExitMainLoop ()
2379
2389
2398
2399
2400
2401
2402
2410
2416
2418 app = wx.PyWidgetTester(size = (400, 40))
2419 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2420 app.MainLoop()
2421
2423 app = wx.PyWidgetTester(size = (400, 40))
2424 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2425
2426 app.MainLoop()
2427
2429 app = wx.PyWidgetTester(size = (200, 300))
2430 edit_health_issue(parent=app.frame, issue=None)
2431
2433 app = wx.PyWidgetTester(size = (200, 300))
2434 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2435 app.MainLoop()
2436
2438 app = wx.PyWidgetTester(size = (200, 300))
2439 edit_procedure(parent=app.frame)
2440
2441
2442 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2443
2444 gmI18N.activate_locale()
2445 gmI18N.install_domain()
2446 gmDateTime.init()
2447
2448
2449 pat = gmPersonSearch.ask_for_patient()
2450 if pat is None:
2451 print "No patient. Exiting gracefully..."
2452 sys.exit(0)
2453 gmPatSearchWidgets.set_active_patient(patient=pat)
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471 test_edit_procedure()
2472
2473
2474