1 """GNUmed EMR structure editors
2
3 This module contains widgets to create and edit EMR structural
4 elements (issues, enconters, episodes).
5
6 This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
7 and Karsten <Karsten.Hilbert@gmx.net>.
8 """
9
10 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
11 __license__ = "GPL v2 or later"
12
13
14 import sys
15 import time
16 import logging
17 import datetime as pydt
18
19
20
21 import wx
22
23
24
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27 from Gnumed.pycommon import gmI18N
28 from Gnumed.pycommon import gmExceptions
29 from Gnumed.pycommon import gmCfg
30 from Gnumed.pycommon import gmDateTime
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmDispatcher
33 from Gnumed.pycommon import gmMatchProvider
34
35 from Gnumed.business import gmEMRStructItems
36 from Gnumed.business import gmSurgery
37 from Gnumed.business import gmPerson
38
39 from Gnumed.wxpython import gmPhraseWheel
40 from Gnumed.wxpython import gmGuiHelpers
41 from Gnumed.wxpython import gmListWidgets
42 from Gnumed.wxpython import gmEditArea
43 from Gnumed.wxpython import gmPatSearchWidgets
44
45
46 _log = logging.getLogger('gm.ui')
47
48
49
51 """Spin time in seconds."""
52 if time2spin == 0:
53 return
54 sleep_time = 0.1
55 total_rounds = int(time2spin / sleep_time)
56 if total_rounds < 1:
57 return
58 rounds = 0
59 while rounds < total_rounds:
60 wx.Yield()
61 time.sleep(sleep_time)
62 rounds += 1
63
64
65
76
77 def delete(procedure=None):
78 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
79 return True
80
81 gmDispatcher.send (
82 signal = u'statustext',
83 msg = _('Cannot delete performed procedure.'),
84 beep = True
85 )
86 return False
87
88 def refresh(lctrl):
89 procs = emr.get_performed_procedures()
90
91 items = [
92 [
93 u'%s%s' % (
94 p['clin_when'].strftime('%Y-%m-%d'),
95 gmTools.bool2subst (
96 p['is_ongoing'],
97 _(' (ongoing)'),
98 gmTools.coalesce (
99 initial = p['clin_end'],
100 instead = u'',
101 template_initial = u' - %s',
102 function_initial = ('strftime', u'%Y-%m-%d')
103 )
104 )
105 ),
106 p['clin_where'],
107 p['episode'],
108 p['performed_procedure']
109 ] for p in procs
110 ]
111 lctrl.set_string_items(items = items)
112 lctrl.set_data(data = procs)
113
114 gmListWidgets.get_choices_from_list (
115 parent = parent,
116 msg = _('\nSelect the procedure you want to edit !\n'),
117 caption = _('Editing performed procedures ...'),
118 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
119 single_selection = True,
120 edit_callback = edit,
121 new_callback = edit,
122 delete_callback = delete,
123 refresh_callback = refresh
124 )
125
137
138 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
139
140 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
141
150
152 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
153 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
154 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
155 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
156 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
157
158
159 mp = gmMatchProvider.cMatchProvider_SQL2 (
160 queries = [
161 u"""
162 SELECT DISTINCT ON (data) data, location
163 FROM (
164 SELECT
165 clin_where as data,
166 clin_where as location
167 FROM
168 clin.procedure
169 WHERE
170 clin_where %(fragment_condition)s
171
172 UNION ALL
173
174 SELECT
175 narrative as data,
176 narrative as location
177 FROM
178 clin.hospital_stay
179 WHERE
180 narrative %(fragment_condition)s
181 ) as union_result
182 ORDER BY data
183 LIMIT 25"""
184 ]
185 )
186 mp.setThresholds(2, 4, 6)
187 self._PRW_location.matcher = mp
188
189
190 mp = gmMatchProvider.cMatchProvider_SQL2 (
191 queries = [
192 u"""
193 select distinct on (narrative) narrative, narrative
194 from clin.procedure
195 where narrative %(fragment_condition)s
196 order by narrative
197 limit 25
198 """ ]
199 )
200 mp.setThresholds(2, 4, 6)
201 self._PRW_procedure.matcher = mp
202
204 stay = self._PRW_hospital_stay.GetData()
205 if stay is None:
206 self._PRW_hospital_stay.SetText()
207 self._PRW_location.Enable(True)
208 self._PRW_episode.Enable(True)
209 self._LBL_hospital_details.SetLabel(u'')
210 else:
211 self._PRW_location.SetText()
212 self._PRW_location.Enable(False)
213 self._PRW_episode.SetText()
214 self._PRW_episode.Enable(False)
215 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
216
218 if self._PRW_location.GetValue().strip() == u'':
219 self._PRW_hospital_stay.Enable(True)
220
221 else:
222 self._PRW_hospital_stay.SetText()
223 self._PRW_hospital_stay.Enable(False)
224 self._PRW_hospital_stay.display_as_valid(True)
225
226
238
261
262
263
321
356
358 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
359
360 if self._DPRW_end.GetData() is None:
361 self.data['clin_end'] = None
362 else:
363 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
364
365 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
366
367 if self._PRW_hospital_stay.GetData() is None:
368 self.data['pk_hospital_stay'] = None
369 self.data['clin_where'] = self._PRW_location.GetValue().strip()
370 self.data['pk_episode'] = self._PRW_episode.GetData()
371 else:
372 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
373 self.data['clin_where'] = None
374 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
375 self.data['pk_episode'] = stay['pk_episode']
376
377 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
378
379 self.data.save()
380 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
381
382 return True
383
385 self._DPRW_date.SetText()
386 self._DPRW_end.SetText()
387 self._CHBOX_ongoing.SetValue(False)
388 self._CHBOX_ongoing.Enable(True)
389 self._PRW_hospital_stay.SetText()
390 self._PRW_location.SetText()
391 self._PRW_episode.SetText()
392 self._PRW_procedure.SetText()
393 self._PRW_codes.SetText()
394
395 self._PRW_procedure.SetFocus()
396
427
439
440
441
446
462
463
464
475
476 def delete(stay=None):
477 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
478 return True
479 gmDispatcher.send (
480 signal = u'statustext',
481 msg = _('Cannot delete hospitalization.'),
482 beep = True
483 )
484 return False
485
486 def refresh(lctrl):
487 stays = emr.get_hospital_stays()
488 items = [
489 [
490 s['admission'].strftime('%Y-%m-%d'),
491 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
492 s['episode'],
493 gmTools.coalesce(s['hospital'], u'')
494 ] for s in stays
495 ]
496 lctrl.set_string_items(items = items)
497 lctrl.set_data(data = stays)
498
499 gmListWidgets.get_choices_from_list (
500 parent = parent,
501 msg = _("The patient's hospitalizations:\n"),
502 caption = _('Editing hospitalizations ...'),
503 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
504 single_selection = True,
505 edit_callback = edit,
506 new_callback = edit,
507 delete_callback = delete,
508 refresh_callback = refresh
509 )
510
511
523
525 """Phrasewheel to allow selection of a hospitalization."""
527
528 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
529
530 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
531
532 mp = gmMatchProvider.cMatchProvider_SQL2 (
533 queries = [
534 u"""
535 select
536 pk_hospital_stay,
537 descr
538 from (
539 select distinct on (pk_hospital_stay)
540 pk_hospital_stay,
541 descr
542 from
543 (select
544 pk_hospital_stay,
545 (
546 to_char(admission, 'YYYY-Mon-DD')
547 || coalesce((' (' || hospital || '):'), ': ')
548 || episode
549 || coalesce((' (' || health_issue || ')'), '')
550 ) as descr
551 from
552 clin.v_pat_hospital_stays
553 where
554 %(ctxt_pat)s
555
556 hospital %(fragment_condition)s
557 or
558 episode %(fragment_condition)s
559 or
560 health_issue %(fragment_condition)s
561 ) as the_stays
562 ) as distinct_stays
563 order by descr
564 limit 25
565 """ ],
566 context = ctxt
567 )
568 mp.setThresholds(3, 4, 6)
569 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
570
571 self.matcher = mp
572 self.selection_only = True
573
574 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
575
576 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
577
581
582
583
585
586 valid = True
587
588 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
589 valid = False
590 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
591
592 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
593 if self._PRW_discharge.date is not None:
594 if not self._PRW_discharge.date > self._PRW_admission.date:
595 valid = False
596 self._PRW_discharge.display_as_valid(False)
597 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
598
599 if self._PRW_episode.GetValue().strip() == u'':
600 valid = False
601 self._PRW_episode.display_as_valid(False)
602 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
603
604 return (valid is True)
605
618
628
634
644
646 print "this was not expected to be used in this edit area"
647
648
649
658
659 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
660
662 if parent is None:
663 parent = wx.GetApp().GetTopWindow()
664
665
666 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
667 if dlg.ShowModal() == wx.ID_OK:
668 dlg.Destroy()
669 return True
670 dlg.Destroy()
671 return False
672
675
676 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
677
678 if patient is None:
679 patient = gmPerson.gmCurrentPatient()
680
681 if not patient.connected:
682 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
683 return False
684
685 if parent is None:
686 parent = wx.GetApp().GetTopWindow()
687
688 emr = patient.get_emr()
689
690
691 def refresh(lctrl):
692 if encounters is None:
693 encs = emr.get_encounters()
694 else:
695 encs = encounters
696
697 items = [
698 [
699 e['started'].strftime('%x %H:%M'),
700 e['last_affirmed'].strftime('%H:%M'),
701 e['l10n_type'],
702 gmTools.coalesce(e['reason_for_encounter'], u''),
703 gmTools.coalesce(e['assessment_of_encounter'], u''),
704 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
705 e['pk_encounter']
706 ] for e in encs
707 ]
708 lctrl.set_string_items(items = items)
709 lctrl.set_data(data = encs)
710 active_pk = emr.active_encounter['pk_encounter']
711 for idx in range(len(encs)):
712 e = encs[idx]
713 if e['pk_encounter'] == active_pk:
714 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
715
716 def new():
717 cfg_db = gmCfg.cCfgSQL()
718
719 enc_type = cfg_db.get2 (
720 option = u'encounter.default_type',
721 workplace = gmSurgery.gmCurrentPractice().active_workplace,
722 bias = u'user',
723 default = u'in surgery'
724 )
725 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
726 return edit_encounter(parent = parent, encounter = enc)
727
728 def edit(enc=None):
729 return edit_encounter(parent = parent, encounter = enc)
730
731 def edit_active(enc=None):
732 return edit_encounter(parent = parent, encounter = emr.active_encounter)
733
734 def start_new(enc=None):
735 start_new_encounter(emr = emr)
736 return True
737
738 return gmListWidgets.get_choices_from_list (
739 parent = parent,
740 msg = _("The patient's encounters.\n"),
741 caption = _('Encounters ...'),
742 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
743 can_return_empty = False,
744 single_selection = single_selection,
745 refresh_callback = refresh,
746 edit_callback = edit,
747 new_callback = new,
748 ignore_OK_button = ignore_OK_button,
749 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
750 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
751 )
752
754 """This is used as the callback when the EMR detects that the
755 patient was here rather recently and wants to ask the
756 provider whether to continue the recent encounter.
757 """
758 if parent is None:
759 parent = wx.GetApp().GetTopWindow()
760
761 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
762 parent = None,
763 id = -1,
764 caption = caption,
765 question = msg,
766 button_defs = [
767 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
768 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
769 ],
770 show_checkbox = False
771 )
772
773 result = dlg.ShowModal()
774 dlg.Destroy()
775
776 if result == wx.ID_YES:
777 return True
778
779 return False
780
782
783 if parent is None:
784 parent = wx.GetApp().GetTopWindow()
785
786
787 def edit(enc_type=None):
788 return edit_encounter_type(parent = parent, encounter_type = enc_type)
789
790 def delete(enc_type=None):
791 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
792 return True
793 gmDispatcher.send (
794 signal = u'statustext',
795 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
796 beep = True
797 )
798 return False
799
800 def refresh(lctrl):
801 enc_types = gmEMRStructItems.get_encounter_types()
802 lctrl.set_string_items(items = enc_types)
803
804 gmListWidgets.get_choices_from_list (
805 parent = parent,
806 msg = _('\nSelect the encounter type you want to edit !\n'),
807 caption = _('Managing encounter types ...'),
808 columns = [_('Local name'), _('Encounter type')],
809 single_selection = True,
810 edit_callback = edit,
811 new_callback = edit,
812 delete_callback = delete,
813 refresh_callback = refresh
814 )
815
825
827
829 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
830
831 cmd = u"""
832 SELECT DISTINCT ON (list_label)
833 pk_encounter
834 AS data,
835 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
836 AS list_label,
837 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
838 AS field_label
839 FROM
840 clin.v_pat_encounters
841 WHERE
842 (
843 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
844 OR
845 l10n_type %(fragment_condition)s
846 OR
847 type %(fragment_condition)s
848 ) %(ctxt_patient)s
849 ORDER BY
850 list_label
851 LIMIT
852 30
853 """
854 context = {'ctxt_patient': {
855 'where_part': u'AND pk_patient = %(patient)s',
856 'placeholder': u'patient'
857 }}
858
859 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
860 self.matcher._SQL_data2match = u"""
861 SELECT
862 pk_encounter
863 AS data,
864 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
865 AS list_label,
866 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
867 AS field_label
868 FROM
869 clin.v_pat_encounters
870 WHERE
871 pk_encounter = %(pk)s
872 """
873 self.matcher.setThresholds(1, 3, 5)
874
875 self.selection_only = True
876
877 self.set_context(context = 'patient', val = None)
878
885
896
898 """Phrasewheel to allow selection of encounter type.
899
900 - user input interpreted as encounter type in English or local language
901 - data returned is pk of corresponding encounter type or None
902 """
904
905 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
906
907 mp = gmMatchProvider.cMatchProvider_SQL2 (
908 queries = [
909 u"""
910 SELECT
911 data,
912 field_label,
913 list_label
914 FROM (
915 SELECT DISTINCT ON (data) *
916 FROM (
917 SELECT
918 pk AS data,
919 _(description) AS field_label,
920 case
921 when _(description) = description then _(description)
922 else _(description) || ' (' || description || ')'
923 end AS list_label
924 FROM
925 clin.encounter_type
926 WHERE
927 _(description) %(fragment_condition)s
928 OR
929 description %(fragment_condition)s
930 ) AS q_distinct_pk
931 ) AS q_ordered
932 ORDER BY
933 list_label
934 """ ]
935 )
936 mp.setThresholds(2, 4, 6)
937
938 self.matcher = mp
939 self.selection_only = True
940 self.picklist_delay = 50
941
942 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
943
945
950
951
952
953
954
984
997
1007
1009 self._TCTRL_l10n_name.SetValue(u'')
1010 self._TCTRL_name.SetValue(u'')
1011 self._TCTRL_name.Enable(True)
1012
1014 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1015 self._TCTRL_name.SetValue(self.data['description'])
1016
1017 self._TCTRL_name.Enable(False)
1018
1020 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1021 self._TCTRL_name.SetValue(self.data['description'])
1022 self._TCTRL_name.Enable(True)
1023
1024
1025
1026
1027
1028
1029 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1030
1032
1034 try:
1035 self.__encounter = kwargs['encounter']
1036 del kwargs['encounter']
1037 except KeyError:
1038 self.__encounter = None
1039
1040 try:
1041 msg = kwargs['msg']
1042 del kwargs['msg']
1043 except KeyError:
1044 msg = None
1045
1046 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1047
1048 self.refresh(msg = msg)
1049
1050
1051
1052 - def refresh(self, encounter=None, msg=None):
1053
1054 if msg is not None:
1055 self._LBL_instructions.SetLabel(msg)
1056
1057 if encounter is not None:
1058 self.__encounter = encounter
1059
1060 if self.__encounter is None:
1061 return True
1062
1063
1064
1065 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1066 self._LBL_patient.SetLabel(pat.get_description_gender())
1067
1068 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1069
1070 fts = gmDateTime.cFuzzyTimestamp (
1071 timestamp = self.__encounter['started'],
1072 accuracy = gmDateTime.acc_minutes
1073 )
1074 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1075
1076 fts = gmDateTime.cFuzzyTimestamp (
1077 timestamp = self.__encounter['last_affirmed'],
1078 accuracy = gmDateTime.acc_minutes
1079 )
1080 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1081
1082
1083 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1084 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1085 self._PRW_rfe_codes.SetText(val, data)
1086
1087
1088 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1089 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1090 self._PRW_aoe_codes.SetText(val, data)
1091
1092
1093 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1094 self._PRW_end.SetFocus()
1095 else:
1096 self._TCTRL_aoe.SetFocus()
1097
1098 return True
1099
1101
1102 if self._PRW_encounter_type.GetData() is None:
1103 self._PRW_encounter_type.SetBackgroundColour('pink')
1104 self._PRW_encounter_type.Refresh()
1105 self._PRW_encounter_type.SetFocus()
1106 return False
1107 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1108 self._PRW_encounter_type.Refresh()
1109
1110
1111 if self._PRW_start.GetValue().strip() == u'':
1112 self._PRW_start.SetBackgroundColour('pink')
1113 self._PRW_start.Refresh()
1114 self._PRW_start.SetFocus()
1115 return False
1116 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1117 self._PRW_start.SetBackgroundColour('pink')
1118 self._PRW_start.Refresh()
1119 self._PRW_start.SetFocus()
1120 return False
1121 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1122 self._PRW_start.Refresh()
1123
1124
1125
1126
1127
1128
1129
1130 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1131 self._PRW_end.SetBackgroundColour('pink')
1132 self._PRW_end.Refresh()
1133 self._PRW_end.SetFocus()
1134 return False
1135 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1136 self._PRW_end.Refresh()
1137
1138 return True
1139
1141 if not self.__is_valid_for_save():
1142 return False
1143
1144 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1145 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1146 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1147 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1148 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1149 self.__encounter.save_payload()
1150
1151 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1152 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1153
1154 return True
1155
1156
1158
1160 encounter = kwargs['encounter']
1161 del kwargs['encounter']
1162
1163 try:
1164 button_defs = kwargs['button_defs']
1165 del kwargs['button_defs']
1166 except KeyError:
1167 button_defs = None
1168
1169 try:
1170 msg = kwargs['msg']
1171 del kwargs['msg']
1172 except KeyError:
1173 msg = None
1174
1175 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1176 self.SetSize((450, 280))
1177 self.SetMinSize((450, 280))
1178
1179 if button_defs is not None:
1180 self._BTN_save.SetLabel(button_defs[0][0])
1181 self._BTN_save.SetToolTipString(button_defs[0][1])
1182 self._BTN_close.SetLabel(button_defs[1][0])
1183 self._BTN_close.SetToolTipString(button_defs[1][1])
1184 self.Refresh()
1185
1186 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1187
1188 self.Fit()
1189
1196
1197 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1198
1200
1205
1207 self._TCTRL_encounter.SetValue(u'')
1208 self._TCTRL_encounter.SetToolTipString(u'')
1209 self._BTN_new.Enable(False)
1210 self._BTN_list.Enable(False)
1211
1213 pat = gmPerson.gmCurrentPatient()
1214 if not pat.connected:
1215 self.clear()
1216 return
1217
1218 enc = pat.get_emr().active_encounter
1219 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1220 self._TCTRL_encounter.SetToolTipString (
1221 _('The active encounter of the current patient:\n\n%s') %
1222 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1223 )
1224 self._BTN_new.Enable(True)
1225 self._BTN_list.Enable(True)
1226
1228 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1229
1230 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1231
1232
1233 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1234 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1235 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1236
1237
1238
1240 wx.CallAfter(self.clear)
1241
1243 wx.CallAfter(self.refresh)
1244 return True
1245
1251
1257
1262
1263
1264
1274
1344
1346 """Prepare changing health issue for an episode.
1347
1348 Checks for two-open-episodes conflict. When this
1349 function succeeds, the pk_health_issue has been set
1350 on the episode instance and the episode should - for
1351 all practical purposes - be ready for save_payload().
1352 """
1353
1354 if not episode['episode_open']:
1355 episode['pk_health_issue'] = target_issue['pk_health_issue']
1356 if save_to_backend:
1357 episode.save_payload()
1358 return True
1359
1360
1361 if target_issue is None:
1362 episode['pk_health_issue'] = None
1363 if save_to_backend:
1364 episode.save_payload()
1365 return True
1366
1367
1368 db_cfg = gmCfg.cCfgSQL()
1369 epi_ttl = int(db_cfg.get2 (
1370 option = u'episode.ttl',
1371 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1372 bias = 'user',
1373 default = 60
1374 ))
1375 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1376 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1377 existing_epi = target_issue.get_open_episode()
1378
1379
1380 if existing_epi is None:
1381 episode['pk_health_issue'] = target_issue['pk_health_issue']
1382 if save_to_backend:
1383 episode.save_payload()
1384 return True
1385
1386
1387 if existing_epi['pk_episode'] == episode['pk_episode']:
1388 episode['pk_health_issue'] = target_issue['pk_health_issue']
1389 if save_to_backend:
1390 episode.save_payload()
1391 return True
1392
1393
1394 move_range = episode.get_access_range()
1395 exist_range = existing_epi.get_access_range()
1396 question = _(
1397 'You want to associate the running episode:\n\n'
1398 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1399 'with the health issue:\n\n'
1400 ' "%(issue_name)s"\n\n'
1401 'There already is another episode running\n'
1402 'for this health issue:\n\n'
1403 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1404 'However, there can only be one running\n'
1405 'episode per health issue.\n\n'
1406 'Which episode do you want to close ?'
1407 ) % {
1408 'new_epi_name': episode['description'],
1409 'new_epi_start': move_range[0].strftime('%m/%y'),
1410 'new_epi_end': move_range[1].strftime('%m/%y'),
1411 'issue_name': target_issue['description'],
1412 'old_epi_name': existing_epi['description'],
1413 'old_epi_start': exist_range[0].strftime('%m/%y'),
1414 'old_epi_end': exist_range[1].strftime('%m/%y')
1415 }
1416 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1417 parent = None,
1418 id = -1,
1419 caption = _('Resolving two-running-episodes conflict'),
1420 question = question,
1421 button_defs = [
1422 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1423 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1424 ]
1425 )
1426 decision = dlg.ShowModal()
1427
1428 if decision == wx.ID_CANCEL:
1429
1430 return False
1431
1432 elif decision == wx.ID_YES:
1433
1434 existing_epi['episode_open'] = False
1435 existing_epi.save_payload()
1436
1437 elif decision == wx.ID_NO:
1438
1439 episode['episode_open'] = False
1440
1441 else:
1442 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1443
1444 episode['pk_health_issue'] = target_issue['pk_health_issue']
1445 if save_to_backend:
1446 episode.save_payload()
1447 return True
1448
1472
1474 """Let user select an episode *description*.
1475
1476 The user can select an episode description from the previously
1477 used descriptions across all episodes across all patients.
1478
1479 Selection is done with a phrasewheel so the user can
1480 type the episode name and matches will be shown. Typing
1481 "*" will show the entire list of episodes.
1482
1483 If the user types a description not existing yet a
1484 new episode description will be returned.
1485 """
1487
1488 mp = gmMatchProvider.cMatchProvider_SQL2 (
1489 queries = [
1490 u"""
1491 SELECT DISTINCT ON (description)
1492 description
1493 AS data,
1494 description
1495 AS field_label,
1496 description || ' ('
1497 || CASE
1498 WHEN is_open IS TRUE THEN _('ongoing')
1499 ELSE _('closed')
1500 END
1501 || ')'
1502 AS list_label
1503 FROM
1504 clin.episode
1505 WHERE
1506 description %(fragment_condition)s
1507 ORDER BY description
1508 LIMIT 30
1509 """
1510 ]
1511 )
1512 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1513 self.matcher = mp
1514
1516 """Let user select an episode.
1517
1518 The user can select an episode from the existing episodes of a
1519 patient. Selection is done with a phrasewheel so the user
1520 can type the episode name and matches will be shown. Typing
1521 "*" will show the entire list of episodes. Closed episodes
1522 will be marked as such. If the user types an episode name not
1523 in the list of existing episodes a new episode can be created
1524 from it if the programmer activated that feature.
1525
1526 If keyword <patient_id> is set to None or left out the control
1527 will listen to patient change signals and therefore act on
1528 gmPerson.gmCurrentPatient() changes.
1529 """
1531
1532 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1533
1534 mp = gmMatchProvider.cMatchProvider_SQL2 (
1535 queries = [
1536 u"""(
1537
1538 select
1539 pk_episode
1540 as data,
1541 description
1542 as field_label,
1543 coalesce (
1544 description || ' - ' || health_issue,
1545 description
1546 ) as list_label,
1547 1 as rank
1548 from
1549 clin.v_pat_episodes
1550 where
1551 episode_open is true and
1552 description %(fragment_condition)s
1553 %(ctxt_pat)s
1554
1555 ) union all (
1556
1557 select
1558 pk_episode
1559 as data,
1560 description
1561 as field_label,
1562 coalesce (
1563 description || _(' (closed)') || ' - ' || health_issue,
1564 description || _(' (closed)')
1565 ) as list_label,
1566 2 as rank
1567 from
1568 clin.v_pat_episodes
1569 where
1570 description %(fragment_condition)s and
1571 episode_open is false
1572 %(ctxt_pat)s
1573
1574 )
1575
1576 order by rank, list_label
1577 limit 30"""
1578 ],
1579 context = ctxt
1580 )
1581
1582 try:
1583 kwargs['patient_id']
1584 except KeyError:
1585 kwargs['patient_id'] = None
1586
1587 if kwargs['patient_id'] is None:
1588 self.use_current_patient = True
1589 self.__register_patient_change_signals()
1590 pat = gmPerson.gmCurrentPatient()
1591 if pat.connected:
1592 mp.set_context('pat', pat.ID)
1593 else:
1594 self.use_current_patient = False
1595 self.__patient_id = int(kwargs['patient_id'])
1596 mp.set_context('pat', self.__patient_id)
1597
1598 del kwargs['patient_id']
1599
1600 gmPhraseWheel.cPhraseWheel.__init__ (
1601 self,
1602 *args,
1603 **kwargs
1604 )
1605 self.matcher = mp
1606
1607
1608
1610 if self.use_current_patient:
1611 return False
1612 self.__patient_id = int(patient_id)
1613 self.set_context('pat', self.__patient_id)
1614 return True
1615
1616 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1619
1621
1622 epi_name = self.GetValue().strip()
1623 if epi_name == u'':
1624 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1625 _log.debug('cannot create episode without name')
1626 return
1627
1628 if self.use_current_patient:
1629 pat = gmPerson.gmCurrentPatient()
1630 else:
1631 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1632
1633 emr = pat.get_emr()
1634 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1635 if epi is None:
1636 self.data = {}
1637 else:
1638 self.SetText (
1639 value = epi_name,
1640 data = epi['pk_episode']
1641 )
1642
1645
1646
1647
1651
1654
1656 if self.use_current_patient:
1657 patient = gmPerson.gmCurrentPatient()
1658 self.set_context('pat', patient.ID)
1659 return True
1660
1661 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1662
1663 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1664
1677
1678
1679
1681
1682 errors = False
1683
1684 if len(self._PRW_description.GetValue().strip()) == 0:
1685 errors = True
1686 self._PRW_description.display_as_valid(False)
1687 self._PRW_description.SetFocus()
1688 else:
1689 self._PRW_description.display_as_valid(True)
1690 self._PRW_description.Refresh()
1691
1692 return not errors
1693
1695
1696 pat = gmPerson.gmCurrentPatient()
1697 emr = pat.get_emr()
1698
1699 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1700 epi['summary'] = self._TCTRL_status.GetValue().strip()
1701 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1702 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1703
1704 issue_name = self._PRW_issue.GetValue().strip()
1705 if len(issue_name) != 0:
1706 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1707 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1708
1709 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1710 gmDispatcher.send (
1711 signal = 'statustext',
1712 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1713 epi['description'],
1714 issue['description']
1715 )
1716 )
1717 gmEMRStructItems.delete_episode(episode = epi)
1718 return False
1719
1720 epi.save()
1721
1722 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1723
1724 self.data = epi
1725 return True
1726
1728
1729 self.data['description'] = self._PRW_description.GetValue().strip()
1730 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1731 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1732 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1733
1734 issue_name = self._PRW_issue.GetValue().strip()
1735 if len(issue_name) == 0:
1736 self.data['pk_health_issue'] = None
1737 else:
1738 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1739 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1740
1741 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1742 gmDispatcher.send (
1743 signal = 'statustext',
1744 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1745 self.data['description'],
1746 issue['description']
1747 )
1748 )
1749 return False
1750
1751 self.data.save()
1752 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1753
1754 return True
1755
1768
1787
1789 self._refresh_as_new()
1790
1791
1792
1802
1804
1805 if parent is None:
1806 parent = wx.GetApp().GetTopWindow()
1807
1808 def refresh(lctrl):
1809 issues = emr.get_health_issues()
1810 items = [
1811 [
1812 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1813 i['description'],
1814 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1815 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1816 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1817 ] for i in issues
1818 ]
1819 lctrl.set_string_items(items = items)
1820 lctrl.set_data(data = issues)
1821
1822 return gmListWidgets.get_choices_from_list (
1823 parent = parent,
1824 msg = _('\nSelect the health issues !\n'),
1825 caption = _('Showing health issues ...'),
1826 columns = [u'', _('Health issue'), u'', u'', u''],
1827 single_selection = False,
1828
1829
1830
1831 refresh_callback = refresh
1832 )
1833
1835
1836
1837
1839
1840 issues = kwargs['issues']
1841 del kwargs['issues']
1842
1843 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1844
1845 self.SetTitle(_('Select the health issues you are interested in ...'))
1846 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1847
1848 for issue in issues:
1849 if issue['is_confidential']:
1850 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1851 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1852 else:
1853 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1854
1855 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1856 if issue['clinically_relevant']:
1857 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1858 if issue['is_active']:
1859 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1860 if issue['is_cause_of_death']:
1861 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1862
1863 self._LCTRL_items.set_column_widths()
1864 self._LCTRL_items.set_data(data = issues)
1865
1867 """Let the user select a health issue.
1868
1869 The user can select a health issue from the existing issues
1870 of a patient. Selection is done with a phrasewheel so the user
1871 can type the issue name and matches will be shown. Typing
1872 "*" will show the entire list of issues. Inactive issues
1873 will be marked as such. If the user types an issue name not
1874 in the list of existing issues a new issue can be created
1875 from it if the programmer activated that feature.
1876
1877 If keyword <patient_id> is set to None or left out the control
1878 will listen to patient change signals and therefore act on
1879 gmPerson.gmCurrentPatient() changes.
1880 """
1882
1883 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1884
1885 mp = gmMatchProvider.cMatchProvider_SQL2 (
1886
1887 queries = [
1888 u"""
1889 SELECT
1890 data,
1891 field_label,
1892 list_label
1893 FROM ((
1894 SELECT
1895 pk_health_issue AS data,
1896 description AS field_label,
1897 description AS list_label
1898 FROM clin.v_health_issues
1899 WHERE
1900 is_active IS true
1901 AND
1902 description %(fragment_condition)s
1903 AND
1904 %(ctxt_pat)s
1905
1906 ) UNION (
1907
1908 SELECT
1909 pk_health_issue AS data,
1910 description AS field_label,
1911 description || _(' (inactive)') AS list_label
1912 FROM clin.v_health_issues
1913 WHERE
1914 is_active IS false
1915 AND
1916 description %(fragment_condition)s
1917 AND
1918 %(ctxt_pat)s
1919 )) AS union_query
1920 ORDER BY
1921 list_label"""],
1922 context = ctxt
1923 )
1924
1925 try: kwargs['patient_id']
1926 except KeyError: kwargs['patient_id'] = None
1927
1928 if kwargs['patient_id'] is None:
1929 self.use_current_patient = True
1930 self.__register_patient_change_signals()
1931 pat = gmPerson.gmCurrentPatient()
1932 if pat.connected:
1933 mp.set_context('pat', pat.ID)
1934 else:
1935 self.use_current_patient = False
1936 self.__patient_id = int(kwargs['patient_id'])
1937 mp.set_context('pat', self.__patient_id)
1938
1939 del kwargs['patient_id']
1940
1941 gmPhraseWheel.cPhraseWheel.__init__ (
1942 self,
1943 *args,
1944 **kwargs
1945 )
1946 self.matcher = mp
1947
1948
1949
1951 if self.use_current_patient:
1952 return False
1953 self.__patient_id = int(patient_id)
1954 self.set_context('pat', self.__patient_id)
1955 return True
1956
1958 issue_name = self.GetValue().strip()
1959 if issue_name == u'':
1960 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
1961 _log.debug('cannot create health issue without name')
1962 return
1963
1964 if self.use_current_patient:
1965 pat = gmPerson.gmCurrentPatient()
1966 else:
1967 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1968
1969 emr = pat.get_emr()
1970 issue = emr.add_health_issue(issue_name = issue_name)
1971
1972 if issue is None:
1973 self.data = {}
1974 else:
1975 self.SetText (
1976 value = issue_name,
1977 data = issue['pk_health_issue']
1978 )
1979
1982
1983
1984
1988
1991
1993 if self.use_current_patient:
1994 patient = gmPerson.gmCurrentPatient()
1995 self.set_context('pat', patient.ID)
1996 return True
1997
1998 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
1999
2001
2003 try:
2004 msg = kwargs['message']
2005 except KeyError:
2006 msg = None
2007 del kwargs['message']
2008 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
2009 if msg is not None:
2010 self._lbl_message.SetLabel(label=msg)
2011
2022
2023 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2024
2025 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2026 """Panel encapsulating health issue edit area functionality."""
2027
2029
2030 try:
2031 issue = kwargs['issue']
2032 except KeyError:
2033 issue = None
2034
2035 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2036
2037 gmEditArea.cGenericEditAreaMixin.__init__(self)
2038
2039
2040 mp = gmMatchProvider.cMatchProvider_SQL2 (
2041 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2042 )
2043 mp.setThresholds(1, 3, 5)
2044 self._PRW_condition.matcher = mp
2045
2046 mp = gmMatchProvider.cMatchProvider_SQL2 (
2047 queries = [u"""
2048 select distinct on (grouping) grouping, grouping from (
2049
2050 select rank, grouping from ((
2051
2052 select
2053 grouping,
2054 1 as rank
2055 from
2056 clin.health_issue
2057 where
2058 grouping %%(fragment_condition)s
2059 and
2060 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2061
2062 ) union (
2063
2064 select
2065 grouping,
2066 2 as rank
2067 from
2068 clin.health_issue
2069 where
2070 grouping %%(fragment_condition)s
2071
2072 )) as union_result
2073
2074 order by rank
2075
2076 ) as order_result
2077
2078 limit 50""" % gmPerson.gmCurrentPatient().ID
2079 ]
2080 )
2081 mp.setThresholds(1, 3, 5)
2082 self._PRW_grouping.matcher = mp
2083
2084 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2085 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2086
2087 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2088 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2089
2090 self._PRW_year_noted.Enable(True)
2091
2092 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2093
2094 self.data = issue
2095
2096
2097
2117
2119 pat = gmPerson.gmCurrentPatient()
2120 emr = pat.get_emr()
2121
2122 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2123
2124 side = u''
2125 if self._ChBOX_left.GetValue():
2126 side += u's'
2127 if self._ChBOX_right.GetValue():
2128 side += u'd'
2129 issue['laterality'] = side
2130
2131 issue['summary'] = self._TCTRL_status.GetValue().strip()
2132 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2133 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2134 issue['is_active'] = self._ChBOX_active.GetValue()
2135 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2136 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2137 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2138
2139 age_noted = self._PRW_age_noted.GetData()
2140 if age_noted is not None:
2141 issue['age_noted'] = age_noted
2142
2143 issue.save()
2144
2145 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2146
2147 self.data = issue
2148 return True
2149
2151
2152 self.data['description'] = self._PRW_condition.GetValue().strip()
2153
2154 side = u''
2155 if self._ChBOX_left.GetValue():
2156 side += u's'
2157 if self._ChBOX_right.GetValue():
2158 side += u'd'
2159 self.data['laterality'] = side
2160
2161 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2162 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2163 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2164 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2165 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2166 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2167 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2168
2169 age_noted = self._PRW_age_noted.GetData()
2170 if age_noted is not None:
2171 self.data['age_noted'] = age_noted
2172
2173 self.data.save()
2174 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2175
2176 return True
2177
2179 self._PRW_condition.SetText()
2180 self._ChBOX_left.SetValue(0)
2181 self._ChBOX_right.SetValue(0)
2182 self._PRW_codes.SetText()
2183 self._on_leave_codes()
2184 self._PRW_certainty.SetText()
2185 self._PRW_grouping.SetText()
2186 self._TCTRL_status.SetValue(u'')
2187 self._PRW_age_noted.SetText()
2188 self._PRW_year_noted.SetText()
2189 self._ChBOX_active.SetValue(0)
2190 self._ChBOX_relevant.SetValue(1)
2191 self._ChBOX_confidential.SetValue(0)
2192 self._ChBOX_caused_death.SetValue(0)
2193
2194 return True
2195
2236
2238 return self._refresh_as_new()
2239
2240
2241
2243 if not self._PRW_codes.IsModified():
2244 return True
2245
2246 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2247
2249
2250 if not self._PRW_age_noted.IsModified():
2251 return True
2252
2253 str_age = self._PRW_age_noted.GetValue().strip()
2254
2255 if str_age == u'':
2256 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2257 return True
2258
2259 age = gmDateTime.str2interval(str_interval = str_age)
2260
2261 if age is None:
2262 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2263 self._PRW_age_noted.SetBackgroundColour('pink')
2264 self._PRW_age_noted.Refresh()
2265 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2266 return True
2267
2268 pat = gmPerson.gmCurrentPatient()
2269 if pat['dob'] is not None:
2270 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2271
2272 if age >= max_age:
2273 gmDispatcher.send (
2274 signal = 'statustext',
2275 msg = _(
2276 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2277 ) % (age, pat.get_medical_age())
2278 )
2279 self._PRW_age_noted.SetBackgroundColour('pink')
2280 self._PRW_age_noted.Refresh()
2281 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2282 return True
2283
2284 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2285 self._PRW_age_noted.Refresh()
2286 self._PRW_age_noted.SetData(data=age)
2287
2288 if pat['dob'] is not None:
2289 fts = gmDateTime.cFuzzyTimestamp (
2290 timestamp = pat['dob'] + age,
2291 accuracy = gmDateTime.acc_months
2292 )
2293 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2294
2295
2296
2297
2298
2299 return True
2300
2302
2303 if not self._PRW_year_noted.IsModified():
2304 return True
2305
2306 year_noted = self._PRW_year_noted.GetData()
2307
2308 if year_noted is None:
2309 if self._PRW_year_noted.GetValue().strip() == u'':
2310 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2311 return True
2312 self._PRW_year_noted.SetBackgroundColour('pink')
2313 self._PRW_year_noted.Refresh()
2314 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2315 return True
2316
2317 year_noted = year_noted.get_pydt()
2318
2319 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2320 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2321 self._PRW_year_noted.SetBackgroundColour('pink')
2322 self._PRW_year_noted.Refresh()
2323 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2324 return True
2325
2326 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2327 self._PRW_year_noted.Refresh()
2328
2329 pat = gmPerson.gmCurrentPatient()
2330 if pat['dob'] is not None:
2331 issue_age = year_noted - pat['dob']
2332 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2333 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2334
2335 return True
2336
2338 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2339 return True
2340
2342 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2343 return True
2344
2345
2346
2348
2350
2351 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2352
2353 self.selection_only = False
2354
2355 mp = gmMatchProvider.cMatchProvider_FixedList (
2356 aSeq = [
2357 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2358 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2359 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2360 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2361 ]
2362 )
2363 mp.setThresholds(1, 2, 4)
2364 self.matcher = mp
2365
2366 self.SetToolTipString(_(
2367 "The diagnostic classification or grading of this assessment.\n"
2368 "\n"
2369 "This documents how certain one is about this being a true diagnosis."
2370 ))
2371
2372
2373
2374 if __name__ == '__main__':
2375
2376 from Gnumed.business import gmPersonSearch
2377
2378
2380 """
2381 Test application for testing EMR struct widgets
2382 """
2383
2385 """
2386 Create test application UI
2387 """
2388 frame = wx.Frame (
2389 None,
2390 -4,
2391 'Testing EMR struct widgets',
2392 size=wx.Size(600, 400),
2393 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2394 )
2395 filemenu= wx.Menu()
2396 filemenu.AppendSeparator()
2397 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2398
2399
2400 menuBar = wx.MenuBar()
2401 menuBar.Append(filemenu,"&File")
2402
2403 frame.SetMenuBar(menuBar)
2404
2405 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2406 wx.DefaultPosition, wx.DefaultSize, 0 )
2407
2408
2409 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2410
2411
2412 self.__pat = gmPerson.gmCurrentPatient()
2413
2414 frame.Show(1)
2415 return 1
2416
2418 """
2419 Close test aplication
2420 """
2421 self.ExitMainLoop ()
2422
2432
2441
2442
2443
2444
2445
2453
2459
2461 app = wx.PyWidgetTester(size = (400, 40))
2462 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2463 app.MainLoop()
2464
2466 app = wx.PyWidgetTester(size = (400, 40))
2467 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2468
2469 app.MainLoop()
2470
2472 app = wx.PyWidgetTester(size = (200, 300))
2473 edit_health_issue(parent=app.frame, issue=None)
2474
2476 app = wx.PyWidgetTester(size = (200, 300))
2477 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2478 app.MainLoop()
2479
2481 app = wx.PyWidgetTester(size = (200, 300))
2482 edit_procedure(parent=app.frame)
2483
2484
2485 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2486
2487 gmI18N.activate_locale()
2488 gmI18N.install_domain()
2489 gmDateTime.init()
2490
2491
2492 pat = gmPersonSearch.ask_for_patient()
2493 if pat is None:
2494 print "No patient. Exiting gracefully..."
2495 sys.exit(0)
2496 gmPatSearchWidgets.set_active_patient(patient=pat)
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514 test_edit_procedure()
2515
2516
2517