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 (data)
833 pk_encounter
834 AS data,
835 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
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 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
843 OR
844 l10n_type %(fragment_condition)s
845 OR
846 type %(fragment_condition)s
847 %(ctxt_patient)s
848 ORDER BY
849 list_label
850 LIMIT
851 30
852 """
853 context = {'ctxt_patient': {
854 'where_part': u'AND pk_patient = %(patient)s',
855 'placeholder': u'patient'
856 }}
857
858 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
859 self.matcher._SQL_data2match = u"""
860 SELECT
861 pk_encounter
862 AS data,
863 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
864 AS list_label,
865 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
866 AS field_label
867 FROM
868 clin.v_pat_encounters
869 WHERE
870 pk_encounter = %(pk)s
871 """
872 self.matcher.setThresholds(1, 3, 5)
873 self.selection_only = True
874
875 self.set_context(context = 'patient', val = None)
876
883
894
896 """Phrasewheel to allow selection of encounter type.
897
898 - user input interpreted as encounter type in English or local language
899 - data returned is pk of corresponding encounter type or None
900 """
902
903 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
904
905 mp = gmMatchProvider.cMatchProvider_SQL2 (
906 queries = [
907 u"""
908 SELECT
909 data,
910 field_label,
911 list_label
912 FROM (
913 SELECT DISTINCT ON (data) *
914 FROM (
915 SELECT
916 pk AS data,
917 _(description) AS field_label,
918 case
919 when _(description) = description then _(description)
920 else _(description) || ' (' || description || ')'
921 end AS list_label
922 FROM
923 clin.encounter_type
924 WHERE
925 _(description) %(fragment_condition)s
926 OR
927 description %(fragment_condition)s
928 ) AS q_distinct_pk
929 ) AS q_ordered
930 ORDER BY
931 list_label
932 """ ]
933 )
934 mp.setThresholds(2, 4, 6)
935
936 self.matcher = mp
937 self.selection_only = True
938 self.picklist_delay = 50
939
940 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
941
943
948
949
950
951
952
982
995
1005
1007 self._TCTRL_l10n_name.SetValue(u'')
1008 self._TCTRL_name.SetValue(u'')
1009 self._TCTRL_name.Enable(True)
1010
1012 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1013 self._TCTRL_name.SetValue(self.data['description'])
1014
1015 self._TCTRL_name.Enable(False)
1016
1018 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1019 self._TCTRL_name.SetValue(self.data['description'])
1020 self._TCTRL_name.Enable(True)
1021
1022
1023
1024
1025
1026
1027 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1028
1030
1032 try:
1033 self.__encounter = kwargs['encounter']
1034 del kwargs['encounter']
1035 except KeyError:
1036 self.__encounter = None
1037
1038 try:
1039 msg = kwargs['msg']
1040 del kwargs['msg']
1041 except KeyError:
1042 msg = None
1043
1044 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1045
1046 self.refresh(msg = msg)
1047
1048
1049
1050 - def refresh(self, encounter=None, msg=None):
1051
1052 if msg is not None:
1053 self._LBL_instructions.SetLabel(msg)
1054
1055 if encounter is not None:
1056 self.__encounter = encounter
1057
1058 if self.__encounter is None:
1059 return True
1060
1061
1062
1063 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1064 self._LBL_patient.SetLabel(pat.get_description_gender())
1065
1066 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1067
1068 fts = gmDateTime.cFuzzyTimestamp (
1069 timestamp = self.__encounter['started'],
1070 accuracy = gmDateTime.acc_minutes
1071 )
1072 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1073
1074 fts = gmDateTime.cFuzzyTimestamp (
1075 timestamp = self.__encounter['last_affirmed'],
1076 accuracy = gmDateTime.acc_minutes
1077 )
1078 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1079
1080
1081 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1082 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1083 self._PRW_rfe_codes.SetText(val, data)
1084
1085
1086 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1087 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1088 self._PRW_aoe_codes.SetText(val, data)
1089
1090
1091 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1092 self._PRW_end.SetFocus()
1093 else:
1094 self._TCTRL_aoe.SetFocus()
1095
1096 return True
1097
1099
1100 if self._PRW_encounter_type.GetData() is None:
1101 self._PRW_encounter_type.SetBackgroundColour('pink')
1102 self._PRW_encounter_type.Refresh()
1103 self._PRW_encounter_type.SetFocus()
1104 return False
1105 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1106 self._PRW_encounter_type.Refresh()
1107
1108
1109 if self._PRW_start.GetValue().strip() == u'':
1110 self._PRW_start.SetBackgroundColour('pink')
1111 self._PRW_start.Refresh()
1112 self._PRW_start.SetFocus()
1113 return False
1114 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1115 self._PRW_start.SetBackgroundColour('pink')
1116 self._PRW_start.Refresh()
1117 self._PRW_start.SetFocus()
1118 return False
1119 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1120 self._PRW_start.Refresh()
1121
1122
1123
1124
1125
1126
1127
1128 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1129 self._PRW_end.SetBackgroundColour('pink')
1130 self._PRW_end.Refresh()
1131 self._PRW_end.SetFocus()
1132 return False
1133 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1134 self._PRW_end.Refresh()
1135
1136 return True
1137
1139 if not self.__is_valid_for_save():
1140 return False
1141
1142 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1143 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1144 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1145 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1146 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1147 self.__encounter.save_payload()
1148
1149 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1150 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1151
1152 return True
1153
1154
1156
1158 encounter = kwargs['encounter']
1159 del kwargs['encounter']
1160
1161 try:
1162 button_defs = kwargs['button_defs']
1163 del kwargs['button_defs']
1164 except KeyError:
1165 button_defs = None
1166
1167 try:
1168 msg = kwargs['msg']
1169 del kwargs['msg']
1170 except KeyError:
1171 msg = None
1172
1173 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1174 self.SetSize((450, 280))
1175 self.SetMinSize((450, 280))
1176
1177 if button_defs is not None:
1178 self._BTN_save.SetLabel(button_defs[0][0])
1179 self._BTN_save.SetToolTipString(button_defs[0][1])
1180 self._BTN_close.SetLabel(button_defs[1][0])
1181 self._BTN_close.SetToolTipString(button_defs[1][1])
1182 self.Refresh()
1183
1184 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1185
1186 self.Fit()
1187
1194
1195 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1196
1198
1203
1205 self._TCTRL_encounter.SetValue(u'')
1206 self._TCTRL_encounter.SetToolTipString(u'')
1207 self._BTN_new.Enable(False)
1208 self._BTN_list.Enable(False)
1209
1211 pat = gmPerson.gmCurrentPatient()
1212 if not pat.connected:
1213 self.clear()
1214 return
1215
1216 enc = pat.get_emr().active_encounter
1217 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1218 self._TCTRL_encounter.SetToolTipString (
1219 _('The active encounter of the current patient:\n\n%s') %
1220 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1221 )
1222 self._BTN_new.Enable(True)
1223 self._BTN_list.Enable(True)
1224
1226 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1227
1228 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1229
1230
1231 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1232 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1233 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1234
1235
1236
1238 wx.CallAfter(self.clear)
1239
1241 wx.CallAfter(self.refresh)
1242 return True
1243
1249
1255
1260
1261
1262
1272
1342
1344 """Prepare changing health issue for an episode.
1345
1346 Checks for two-open-episodes conflict. When this
1347 function succeeds, the pk_health_issue has been set
1348 on the episode instance and the episode should - for
1349 all practical purposes - be ready for save_payload().
1350 """
1351
1352 if not episode['episode_open']:
1353 episode['pk_health_issue'] = target_issue['pk_health_issue']
1354 if save_to_backend:
1355 episode.save_payload()
1356 return True
1357
1358
1359 if target_issue is None:
1360 episode['pk_health_issue'] = None
1361 if save_to_backend:
1362 episode.save_payload()
1363 return True
1364
1365
1366 db_cfg = gmCfg.cCfgSQL()
1367 epi_ttl = int(db_cfg.get2 (
1368 option = u'episode.ttl',
1369 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1370 bias = 'user',
1371 default = 60
1372 ))
1373 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1374 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1375 existing_epi = target_issue.get_open_episode()
1376
1377
1378 if existing_epi is None:
1379 episode['pk_health_issue'] = target_issue['pk_health_issue']
1380 if save_to_backend:
1381 episode.save_payload()
1382 return True
1383
1384
1385 if existing_epi['pk_episode'] == episode['pk_episode']:
1386 episode['pk_health_issue'] = target_issue['pk_health_issue']
1387 if save_to_backend:
1388 episode.save_payload()
1389 return True
1390
1391
1392 move_range = episode.get_access_range()
1393 exist_range = existing_epi.get_access_range()
1394 question = _(
1395 'You want to associate the running episode:\n\n'
1396 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1397 'with the health issue:\n\n'
1398 ' "%(issue_name)s"\n\n'
1399 'There already is another episode running\n'
1400 'for this health issue:\n\n'
1401 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1402 'However, there can only be one running\n'
1403 'episode per health issue.\n\n'
1404 'Which episode do you want to close ?'
1405 ) % {
1406 'new_epi_name': episode['description'],
1407 'new_epi_start': move_range[0].strftime('%m/%y'),
1408 'new_epi_end': move_range[1].strftime('%m/%y'),
1409 'issue_name': target_issue['description'],
1410 'old_epi_name': existing_epi['description'],
1411 'old_epi_start': exist_range[0].strftime('%m/%y'),
1412 'old_epi_end': exist_range[1].strftime('%m/%y')
1413 }
1414 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1415 parent = None,
1416 id = -1,
1417 caption = _('Resolving two-running-episodes conflict'),
1418 question = question,
1419 button_defs = [
1420 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1421 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1422 ]
1423 )
1424 decision = dlg.ShowModal()
1425
1426 if decision == wx.ID_CANCEL:
1427
1428 return False
1429
1430 elif decision == wx.ID_YES:
1431
1432 existing_epi['episode_open'] = False
1433 existing_epi.save_payload()
1434
1435 elif decision == wx.ID_NO:
1436
1437 episode['episode_open'] = False
1438
1439 else:
1440 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1441
1442 episode['pk_health_issue'] = target_issue['pk_health_issue']
1443 if save_to_backend:
1444 episode.save_payload()
1445 return True
1446
1470
1472 """Let user select an episode *description*.
1473
1474 The user can select an episode description from the previously
1475 used descriptions across all episodes across all patients.
1476
1477 Selection is done with a phrasewheel so the user can
1478 type the episode name and matches will be shown. Typing
1479 "*" will show the entire list of episodes.
1480
1481 If the user types a description not existing yet a
1482 new episode description will be returned.
1483 """
1485
1486 mp = gmMatchProvider.cMatchProvider_SQL2 (
1487 queries = [
1488 u"""
1489 SELECT DISTINCT ON (description)
1490 description
1491 AS data,
1492 description
1493 AS field_label,
1494 description || ' ('
1495 || CASE
1496 WHEN is_open IS TRUE THEN _('ongoing')
1497 ELSE _('closed')
1498 END
1499 || ')'
1500 AS list_label
1501 FROM
1502 clin.episode
1503 WHERE
1504 description %(fragment_condition)s
1505 ORDER BY description
1506 LIMIT 30
1507 """
1508 ]
1509 )
1510 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1511 self.matcher = mp
1512
1514 """Let user select an episode.
1515
1516 The user can select an episode from the existing episodes of a
1517 patient. Selection is done with a phrasewheel so the user
1518 can type the episode name and matches will be shown. Typing
1519 "*" will show the entire list of episodes. Closed episodes
1520 will be marked as such. If the user types an episode name not
1521 in the list of existing episodes a new episode can be created
1522 from it if the programmer activated that feature.
1523
1524 If keyword <patient_id> is set to None or left out the control
1525 will listen to patient change signals and therefore act on
1526 gmPerson.gmCurrentPatient() changes.
1527 """
1529
1530 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1531
1532 mp = gmMatchProvider.cMatchProvider_SQL2 (
1533 queries = [
1534 u"""(
1535
1536 select
1537 pk_episode
1538 as data,
1539 description
1540 as field_label,
1541 coalesce (
1542 description || ' - ' || health_issue,
1543 description
1544 ) as list_label,
1545 1 as rank
1546 from
1547 clin.v_pat_episodes
1548 where
1549 episode_open is true and
1550 description %(fragment_condition)s
1551 %(ctxt_pat)s
1552
1553 ) union all (
1554
1555 select
1556 pk_episode
1557 as data,
1558 description
1559 as field_label,
1560 coalesce (
1561 description || _(' (closed)') || ' - ' || health_issue,
1562 description || _(' (closed)')
1563 ) as list_label,
1564 2 as rank
1565 from
1566 clin.v_pat_episodes
1567 where
1568 description %(fragment_condition)s and
1569 episode_open is false
1570 %(ctxt_pat)s
1571
1572 )
1573
1574 order by rank, list_label
1575 limit 30"""
1576 ],
1577 context = ctxt
1578 )
1579
1580 try:
1581 kwargs['patient_id']
1582 except KeyError:
1583 kwargs['patient_id'] = None
1584
1585 if kwargs['patient_id'] is None:
1586 self.use_current_patient = True
1587 self.__register_patient_change_signals()
1588 pat = gmPerson.gmCurrentPatient()
1589 if pat.connected:
1590 mp.set_context('pat', pat.ID)
1591 else:
1592 self.use_current_patient = False
1593 self.__patient_id = int(kwargs['patient_id'])
1594 mp.set_context('pat', self.__patient_id)
1595
1596 del kwargs['patient_id']
1597
1598 gmPhraseWheel.cPhraseWheel.__init__ (
1599 self,
1600 *args,
1601 **kwargs
1602 )
1603 self.matcher = mp
1604
1605
1606
1608 if self.use_current_patient:
1609 return False
1610 self.__patient_id = int(patient_id)
1611 self.set_context('pat', self.__patient_id)
1612 return True
1613
1614 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1617
1619
1620 epi_name = self.GetValue().strip()
1621 if epi_name == u'':
1622 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1623 _log.debug('cannot create episode without name')
1624 return
1625
1626 if self.use_current_patient:
1627 pat = gmPerson.gmCurrentPatient()
1628 else:
1629 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1630
1631 emr = pat.get_emr()
1632 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1633 if epi is None:
1634 self.data = {}
1635 else:
1636 self.SetText (
1637 value = epi_name,
1638 data = epi['pk_episode']
1639 )
1640
1643
1644
1645
1649
1652
1654 if self.use_current_patient:
1655 patient = gmPerson.gmCurrentPatient()
1656 self.set_context('pat', patient.ID)
1657 return True
1658
1659 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1660
1661 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1662
1675
1676
1677
1679
1680 errors = False
1681
1682 if len(self._PRW_description.GetValue().strip()) == 0:
1683 errors = True
1684 self._PRW_description.display_as_valid(False)
1685 self._PRW_description.SetFocus()
1686 else:
1687 self._PRW_description.display_as_valid(True)
1688 self._PRW_description.Refresh()
1689
1690 return not errors
1691
1693
1694 pat = gmPerson.gmCurrentPatient()
1695 emr = pat.get_emr()
1696
1697 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1698 epi['summary'] = self._TCTRL_status.GetValue().strip()
1699 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1700 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1701
1702 issue_name = self._PRW_issue.GetValue().strip()
1703 if len(issue_name) != 0:
1704 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1705 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1706
1707 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1708 gmDispatcher.send (
1709 signal = 'statustext',
1710 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1711 epi['description'],
1712 issue['description']
1713 )
1714 )
1715 gmEMRStructItems.delete_episode(episode = epi)
1716 return False
1717
1718 epi.save()
1719
1720 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1721
1722 self.data = epi
1723 return True
1724
1726
1727 self.data['description'] = self._PRW_description.GetValue().strip()
1728 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1729 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1730 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1731
1732 issue_name = self._PRW_issue.GetValue().strip()
1733 if len(issue_name) == 0:
1734 self.data['pk_health_issue'] = None
1735 else:
1736 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1737 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1738
1739 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1740 gmDispatcher.send (
1741 signal = 'statustext',
1742 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1743 self.data['description'],
1744 issue['description']
1745 )
1746 )
1747 return False
1748
1749 self.data.save()
1750 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1751
1752 return True
1753
1766
1785
1787 self._refresh_as_new()
1788
1789
1790
1800
1802
1803 if parent is None:
1804 parent = wx.GetApp().GetTopWindow()
1805
1806 def refresh(lctrl):
1807 issues = emr.get_health_issues()
1808 items = [
1809 [
1810 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1811 i['description'],
1812 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1813 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1814 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1815 ] for i in issues
1816 ]
1817 lctrl.set_string_items(items = items)
1818 lctrl.set_data(data = issues)
1819
1820 return gmListWidgets.get_choices_from_list (
1821 parent = parent,
1822 msg = _('\nSelect the health issues !\n'),
1823 caption = _('Showing health issues ...'),
1824 columns = [u'', _('Health issue'), u'', u'', u''],
1825 single_selection = False,
1826
1827
1828
1829 refresh_callback = refresh
1830 )
1831
1833
1834
1835
1837
1838 issues = kwargs['issues']
1839 del kwargs['issues']
1840
1841 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1842
1843 self.SetTitle(_('Select the health issues you are interested in ...'))
1844 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1845
1846 for issue in issues:
1847 if issue['is_confidential']:
1848 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1849 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1850 else:
1851 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1852
1853 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1854 if issue['clinically_relevant']:
1855 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1856 if issue['is_active']:
1857 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1858 if issue['is_cause_of_death']:
1859 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1860
1861 self._LCTRL_items.set_column_widths()
1862 self._LCTRL_items.set_data(data = issues)
1863
1865 """Let the user select a health issue.
1866
1867 The user can select a health issue from the existing issues
1868 of a patient. Selection is done with a phrasewheel so the user
1869 can type the issue name and matches will be shown. Typing
1870 "*" will show the entire list of issues. Inactive issues
1871 will be marked as such. If the user types an issue name not
1872 in the list of existing issues a new issue can be created
1873 from it if the programmer activated that feature.
1874
1875 If keyword <patient_id> is set to None or left out the control
1876 will listen to patient change signals and therefore act on
1877 gmPerson.gmCurrentPatient() changes.
1878 """
1880
1881 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1882
1883 mp = gmMatchProvider.cMatchProvider_SQL2 (
1884
1885 queries = [
1886 u"""
1887 SELECT
1888 data,
1889 field_label,
1890 list_label
1891 FROM ((
1892 SELECT
1893 pk_health_issue AS data,
1894 description AS field_label,
1895 description AS list_label
1896 FROM clin.v_health_issues
1897 WHERE
1898 is_active IS true
1899 AND
1900 description %(fragment_condition)s
1901 AND
1902 %(ctxt_pat)s
1903
1904 ) UNION (
1905
1906 SELECT
1907 pk_health_issue AS data,
1908 description AS field_label,
1909 description || _(' (inactive)') AS list_label
1910 FROM clin.v_health_issues
1911 WHERE
1912 is_active IS false
1913 AND
1914 description %(fragment_condition)s
1915 AND
1916 %(ctxt_pat)s
1917 )) AS union_query
1918 ORDER BY
1919 list_label"""],
1920 context = ctxt
1921 )
1922
1923 try: kwargs['patient_id']
1924 except KeyError: kwargs['patient_id'] = None
1925
1926 if kwargs['patient_id'] is None:
1927 self.use_current_patient = True
1928 self.__register_patient_change_signals()
1929 pat = gmPerson.gmCurrentPatient()
1930 if pat.connected:
1931 mp.set_context('pat', pat.ID)
1932 else:
1933 self.use_current_patient = False
1934 self.__patient_id = int(kwargs['patient_id'])
1935 mp.set_context('pat', self.__patient_id)
1936
1937 del kwargs['patient_id']
1938
1939 gmPhraseWheel.cPhraseWheel.__init__ (
1940 self,
1941 *args,
1942 **kwargs
1943 )
1944 self.matcher = mp
1945
1946
1947
1949 if self.use_current_patient:
1950 return False
1951 self.__patient_id = int(patient_id)
1952 self.set_context('pat', self.__patient_id)
1953 return True
1954
1956 issue_name = self.GetValue().strip()
1957 if issue_name == u'':
1958 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
1959 _log.debug('cannot create health issue without name')
1960 return
1961
1962 if self.use_current_patient:
1963 pat = gmPerson.gmCurrentPatient()
1964 else:
1965 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1966
1967 emr = pat.get_emr()
1968 issue = emr.add_health_issue(issue_name = issue_name)
1969
1970 if issue is None:
1971 self.data = {}
1972 else:
1973 self.SetText (
1974 value = issue_name,
1975 data = issue['pk_health_issue']
1976 )
1977
1980
1981
1982
1986
1989
1991 if self.use_current_patient:
1992 patient = gmPerson.gmCurrentPatient()
1993 self.set_context('pat', patient.ID)
1994 return True
1995
1996 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
1997
1999
2001 try:
2002 msg = kwargs['message']
2003 except KeyError:
2004 msg = None
2005 del kwargs['message']
2006 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
2007 if msg is not None:
2008 self._lbl_message.SetLabel(label=msg)
2009
2020
2021 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2022
2023 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2024 """Panel encapsulating health issue edit area functionality."""
2025
2027
2028 try:
2029 issue = kwargs['issue']
2030 except KeyError:
2031 issue = None
2032
2033 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2034
2035 gmEditArea.cGenericEditAreaMixin.__init__(self)
2036
2037
2038 mp = gmMatchProvider.cMatchProvider_SQL2 (
2039 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2040 )
2041 mp.setThresholds(1, 3, 5)
2042 self._PRW_condition.matcher = mp
2043
2044 mp = gmMatchProvider.cMatchProvider_SQL2 (
2045 queries = [u"""
2046 select distinct on (grouping) grouping, grouping from (
2047
2048 select rank, grouping from ((
2049
2050 select
2051 grouping,
2052 1 as rank
2053 from
2054 clin.health_issue
2055 where
2056 grouping %%(fragment_condition)s
2057 and
2058 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2059
2060 ) union (
2061
2062 select
2063 grouping,
2064 2 as rank
2065 from
2066 clin.health_issue
2067 where
2068 grouping %%(fragment_condition)s
2069
2070 )) as union_result
2071
2072 order by rank
2073
2074 ) as order_result
2075
2076 limit 50""" % gmPerson.gmCurrentPatient().ID
2077 ]
2078 )
2079 mp.setThresholds(1, 3, 5)
2080 self._PRW_grouping.matcher = mp
2081
2082 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2083 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2084
2085 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2086 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2087
2088 self._PRW_year_noted.Enable(True)
2089
2090 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2091
2092 self.data = issue
2093
2094
2095
2115
2117 pat = gmPerson.gmCurrentPatient()
2118 emr = pat.get_emr()
2119
2120 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2121
2122 side = u''
2123 if self._ChBOX_left.GetValue():
2124 side += u's'
2125 if self._ChBOX_right.GetValue():
2126 side += u'd'
2127 issue['laterality'] = side
2128
2129 issue['summary'] = self._TCTRL_status.GetValue().strip()
2130 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2131 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2132 issue['is_active'] = self._ChBOX_active.GetValue()
2133 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2134 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2135 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2136
2137 age_noted = self._PRW_age_noted.GetData()
2138 if age_noted is not None:
2139 issue['age_noted'] = age_noted
2140
2141 issue.save()
2142
2143 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2144
2145 self.data = issue
2146 return True
2147
2149
2150 self.data['description'] = self._PRW_condition.GetValue().strip()
2151
2152 side = u''
2153 if self._ChBOX_left.GetValue():
2154 side += u's'
2155 if self._ChBOX_right.GetValue():
2156 side += u'd'
2157 self.data['laterality'] = side
2158
2159 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2160 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2161 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2162 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2163 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2164 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2165 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2166
2167 age_noted = self._PRW_age_noted.GetData()
2168 if age_noted is not None:
2169 self.data['age_noted'] = age_noted
2170
2171 self.data.save()
2172 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2173
2174 return True
2175
2177 self._PRW_condition.SetText()
2178 self._ChBOX_left.SetValue(0)
2179 self._ChBOX_right.SetValue(0)
2180 self._PRW_codes.SetText()
2181 self._on_leave_codes()
2182 self._PRW_certainty.SetText()
2183 self._PRW_grouping.SetText()
2184 self._TCTRL_status.SetValue(u'')
2185 self._PRW_age_noted.SetText()
2186 self._PRW_year_noted.SetText()
2187 self._ChBOX_active.SetValue(0)
2188 self._ChBOX_relevant.SetValue(1)
2189 self._ChBOX_confidential.SetValue(0)
2190 self._ChBOX_caused_death.SetValue(0)
2191
2192 return True
2193
2234
2236 return self._refresh_as_new()
2237
2238
2239
2241 if not self._PRW_codes.IsModified():
2242 return True
2243
2244 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2245
2247
2248 if not self._PRW_age_noted.IsModified():
2249 return True
2250
2251 str_age = self._PRW_age_noted.GetValue().strip()
2252
2253 if str_age == u'':
2254 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2255 return True
2256
2257 age = gmDateTime.str2interval(str_interval = str_age)
2258
2259 if age is None:
2260 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2261 self._PRW_age_noted.SetBackgroundColour('pink')
2262 self._PRW_age_noted.Refresh()
2263 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2264 return True
2265
2266 pat = gmPerson.gmCurrentPatient()
2267 if pat['dob'] is not None:
2268 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2269
2270 if age >= max_age:
2271 gmDispatcher.send (
2272 signal = 'statustext',
2273 msg = _(
2274 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2275 ) % (age, pat.get_medical_age())
2276 )
2277 self._PRW_age_noted.SetBackgroundColour('pink')
2278 self._PRW_age_noted.Refresh()
2279 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2280 return True
2281
2282 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2283 self._PRW_age_noted.Refresh()
2284 self._PRW_age_noted.SetData(data=age)
2285
2286 if pat['dob'] is not None:
2287 fts = gmDateTime.cFuzzyTimestamp (
2288 timestamp = pat['dob'] + age,
2289 accuracy = gmDateTime.acc_months
2290 )
2291 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2292
2293
2294
2295
2296
2297 return True
2298
2300
2301 if not self._PRW_year_noted.IsModified():
2302 return True
2303
2304 year_noted = self._PRW_year_noted.GetData()
2305
2306 if year_noted is None:
2307 if self._PRW_year_noted.GetValue().strip() == u'':
2308 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2309 return True
2310 self._PRW_year_noted.SetBackgroundColour('pink')
2311 self._PRW_year_noted.Refresh()
2312 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2313 return True
2314
2315 year_noted = year_noted.get_pydt()
2316
2317 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2318 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2319 self._PRW_year_noted.SetBackgroundColour('pink')
2320 self._PRW_year_noted.Refresh()
2321 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2322 return True
2323
2324 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2325 self._PRW_year_noted.Refresh()
2326
2327 pat = gmPerson.gmCurrentPatient()
2328 if pat['dob'] is not None:
2329 issue_age = year_noted - pat['dob']
2330 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2331 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2332
2333 return True
2334
2336 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2337 return True
2338
2340 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2341 return True
2342
2343
2344
2346
2348
2349 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2350
2351 self.selection_only = False
2352
2353 mp = gmMatchProvider.cMatchProvider_FixedList (
2354 aSeq = [
2355 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2356 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2357 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2358 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2359 ]
2360 )
2361 mp.setThresholds(1, 2, 4)
2362 self.matcher = mp
2363
2364 self.SetToolTipString(_(
2365 "The diagnostic classification or grading of this assessment.\n"
2366 "\n"
2367 "This documents how certain one is about this being a true diagnosis."
2368 ))
2369
2370
2371
2372 if __name__ == '__main__':
2373
2374 from Gnumed.business import gmPersonSearch
2375
2376
2378 """
2379 Test application for testing EMR struct widgets
2380 """
2381
2383 """
2384 Create test application UI
2385 """
2386 frame = wx.Frame (
2387 None,
2388 -4,
2389 'Testing EMR struct widgets',
2390 size=wx.Size(600, 400),
2391 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2392 )
2393 filemenu= wx.Menu()
2394 filemenu.AppendSeparator()
2395 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2396
2397
2398 menuBar = wx.MenuBar()
2399 menuBar.Append(filemenu,"&File")
2400
2401 frame.SetMenuBar(menuBar)
2402
2403 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2404 wx.DefaultPosition, wx.DefaultSize, 0 )
2405
2406
2407 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2408
2409
2410 self.__pat = gmPerson.gmCurrentPatient()
2411
2412 frame.Show(1)
2413 return 1
2414
2416 """
2417 Close test aplication
2418 """
2419 self.ExitMainLoop ()
2420
2430
2439
2440
2441
2442
2443
2451
2457
2459 app = wx.PyWidgetTester(size = (400, 40))
2460 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2461 app.MainLoop()
2462
2464 app = wx.PyWidgetTester(size = (400, 40))
2465 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2466
2467 app.MainLoop()
2468
2470 app = wx.PyWidgetTester(size = (200, 300))
2471 edit_health_issue(parent=app.frame, issue=None)
2472
2474 app = wx.PyWidgetTester(size = (200, 300))
2475 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2476 app.MainLoop()
2477
2479 app = wx.PyWidgetTester(size = (200, 300))
2480 edit_procedure(parent=app.frame)
2481
2482
2483 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2484
2485 gmI18N.activate_locale()
2486 gmI18N.install_domain()
2487 gmDateTime.init()
2488
2489
2490 pat = gmPersonSearch.ask_for_patient()
2491 if pat is None:
2492 print "No patient. Exiting gracefully..."
2493 sys.exit(0)
2494 gmPatSearchWidgets.set_active_patient(patient=pat)
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512 test_edit_procedure()
2513
2514
2515