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 self._PRW_admission.date is not None:
595 if not self._PRW_discharge.date > self._PRW_admission.date:
596 valid = False
597 self._PRW_discharge.display_as_valid(False)
598 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
599
600 if self._PRW_episode.GetValue().strip() == u'':
601 valid = False
602 self._PRW_episode.display_as_valid(False)
603 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
604
605 return (valid is True)
606
619
629
635
645
647 print "this was not expected to be used in this edit area"
648
649
650
659
660 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
661
663 if parent is None:
664 parent = wx.GetApp().GetTopWindow()
665
666
667 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
668 if dlg.ShowModal() == wx.ID_OK:
669 dlg.Destroy()
670 return True
671 dlg.Destroy()
672 return False
673
676
677 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
678
679 if patient is None:
680 patient = gmPerson.gmCurrentPatient()
681
682 if not patient.connected:
683 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
684 return False
685
686 if parent is None:
687 parent = wx.GetApp().GetTopWindow()
688
689 emr = patient.get_emr()
690
691
692 def refresh(lctrl):
693 if encounters is None:
694 encs = emr.get_encounters()
695 else:
696 encs = encounters
697
698 items = [
699 [
700 e['started'].strftime('%x %H:%M'),
701 e['last_affirmed'].strftime('%H:%M'),
702 e['l10n_type'],
703 gmTools.coalesce(e['reason_for_encounter'], u''),
704 gmTools.coalesce(e['assessment_of_encounter'], u''),
705 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
706 e['pk_encounter']
707 ] for e in encs
708 ]
709 lctrl.set_string_items(items = items)
710 lctrl.set_data(data = encs)
711 active_pk = emr.active_encounter['pk_encounter']
712 for idx in range(len(encs)):
713 e = encs[idx]
714 if e['pk_encounter'] == active_pk:
715 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
716
717 def new():
718 cfg_db = gmCfg.cCfgSQL()
719
720 enc_type = cfg_db.get2 (
721 option = u'encounter.default_type',
722 workplace = gmSurgery.gmCurrentPractice().active_workplace,
723 bias = u'user',
724 default = u'in surgery'
725 )
726 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
727 return edit_encounter(parent = parent, encounter = enc)
728
729 def edit(enc=None):
730 return edit_encounter(parent = parent, encounter = enc)
731
732 def edit_active(enc=None):
733 return edit_encounter(parent = parent, encounter = emr.active_encounter)
734
735 def start_new(enc=None):
736 start_new_encounter(emr = emr)
737 return True
738
739 return gmListWidgets.get_choices_from_list (
740 parent = parent,
741 msg = _("The patient's encounters.\n"),
742 caption = _('Encounters ...'),
743 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
744 can_return_empty = False,
745 single_selection = single_selection,
746 refresh_callback = refresh,
747 edit_callback = edit,
748 new_callback = new,
749 ignore_OK_button = ignore_OK_button,
750 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
751 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
752 )
753
755 """This is used as the callback when the EMR detects that the
756 patient was here rather recently and wants to ask the
757 provider whether to continue the recent encounter.
758 """
759 if parent is None:
760 parent = wx.GetApp().GetTopWindow()
761
762 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
763 parent = None,
764 id = -1,
765 caption = caption,
766 question = msg,
767 button_defs = [
768 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
769 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
770 ],
771 show_checkbox = False
772 )
773
774 result = dlg.ShowModal()
775 dlg.Destroy()
776
777 if result == wx.ID_YES:
778 return True
779
780 return False
781
783
784 if parent is None:
785 parent = wx.GetApp().GetTopWindow()
786
787
788 def edit(enc_type=None):
789 return edit_encounter_type(parent = parent, encounter_type = enc_type)
790
791 def delete(enc_type=None):
792 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
793 return True
794 gmDispatcher.send (
795 signal = u'statustext',
796 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
797 beep = True
798 )
799 return False
800
801 def refresh(lctrl):
802 enc_types = gmEMRStructItems.get_encounter_types()
803 lctrl.set_string_items(items = enc_types)
804
805 gmListWidgets.get_choices_from_list (
806 parent = parent,
807 msg = _('\nSelect the encounter type you want to edit !\n'),
808 caption = _('Managing encounter types ...'),
809 columns = [_('Local name'), _('Encounter type')],
810 single_selection = True,
811 edit_callback = edit,
812 new_callback = edit,
813 delete_callback = delete,
814 refresh_callback = refresh
815 )
816
826
828
830 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
831
832 cmd = u"""
833 SELECT DISTINCT ON (list_label)
834 pk_encounter
835 AS data,
836 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
837 AS list_label,
838 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
839 AS field_label
840 FROM
841 clin.v_pat_encounters
842 WHERE
843 (
844 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
845 OR
846 l10n_type %(fragment_condition)s
847 OR
848 type %(fragment_condition)s
849 ) %(ctxt_patient)s
850 ORDER BY
851 list_label
852 LIMIT
853 30
854 """
855 context = {'ctxt_patient': {
856 'where_part': u'AND pk_patient = %(patient)s',
857 'placeholder': u'patient'
858 }}
859
860 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
861 self.matcher._SQL_data2match = u"""
862 SELECT
863 pk_encounter
864 AS data,
865 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
866 AS list_label,
867 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
868 AS field_label
869 FROM
870 clin.v_pat_encounters
871 WHERE
872 pk_encounter = %(pk)s
873 """
874 self.matcher.setThresholds(1, 3, 5)
875
876 self.selection_only = True
877
878 self.set_context(context = 'patient', val = None)
879
886
897
899 """Phrasewheel to allow selection of encounter type.
900
901 - user input interpreted as encounter type in English or local language
902 - data returned is pk of corresponding encounter type or None
903 """
905
906 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
907
908 mp = gmMatchProvider.cMatchProvider_SQL2 (
909 queries = [
910 u"""
911 SELECT
912 data,
913 field_label,
914 list_label
915 FROM (
916 SELECT DISTINCT ON (data) *
917 FROM (
918 SELECT
919 pk AS data,
920 _(description) AS field_label,
921 case
922 when _(description) = description then _(description)
923 else _(description) || ' (' || description || ')'
924 end AS list_label
925 FROM
926 clin.encounter_type
927 WHERE
928 _(description) %(fragment_condition)s
929 OR
930 description %(fragment_condition)s
931 ) AS q_distinct_pk
932 ) AS q_ordered
933 ORDER BY
934 list_label
935 """ ]
936 )
937 mp.setThresholds(2, 4, 6)
938
939 self.matcher = mp
940 self.selection_only = True
941 self.picklist_delay = 50
942
943 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
944
946
951
952
953
954
955
985
998
1008
1010 self._TCTRL_l10n_name.SetValue(u'')
1011 self._TCTRL_name.SetValue(u'')
1012 self._TCTRL_name.Enable(True)
1013
1015 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1016 self._TCTRL_name.SetValue(self.data['description'])
1017
1018 self._TCTRL_name.Enable(False)
1019
1021 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1022 self._TCTRL_name.SetValue(self.data['description'])
1023 self._TCTRL_name.Enable(True)
1024
1025
1026
1027
1028
1029
1030 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1031
1033
1035 try:
1036 self.__encounter = kwargs['encounter']
1037 del kwargs['encounter']
1038 except KeyError:
1039 self.__encounter = None
1040
1041 try:
1042 msg = kwargs['msg']
1043 del kwargs['msg']
1044 except KeyError:
1045 msg = None
1046
1047 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1048
1049 self.refresh(msg = msg)
1050
1051
1052
1053 - def refresh(self, encounter=None, msg=None):
1054
1055 if msg is not None:
1056 self._LBL_instructions.SetLabel(msg)
1057
1058 if encounter is not None:
1059 self.__encounter = encounter
1060
1061 if self.__encounter is None:
1062 return True
1063
1064
1065
1066 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1067 self._LBL_patient.SetLabel(pat.get_description_gender())
1068
1069 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1070
1071 fts = gmDateTime.cFuzzyTimestamp (
1072 timestamp = self.__encounter['started'],
1073 accuracy = gmDateTime.acc_minutes
1074 )
1075 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1076
1077 fts = gmDateTime.cFuzzyTimestamp (
1078 timestamp = self.__encounter['last_affirmed'],
1079 accuracy = gmDateTime.acc_minutes
1080 )
1081 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1082
1083
1084 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1085 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1086 self._PRW_rfe_codes.SetText(val, data)
1087
1088
1089 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1090 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1091 self._PRW_aoe_codes.SetText(val, data)
1092
1093
1094 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1095 self._PRW_end.SetFocus()
1096 else:
1097 self._TCTRL_aoe.SetFocus()
1098
1099 return True
1100
1102
1103 if self._PRW_encounter_type.GetData() is None:
1104 self._PRW_encounter_type.SetBackgroundColour('pink')
1105 self._PRW_encounter_type.Refresh()
1106 self._PRW_encounter_type.SetFocus()
1107 return False
1108 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1109 self._PRW_encounter_type.Refresh()
1110
1111
1112 if self._PRW_start.GetValue().strip() == u'':
1113 self._PRW_start.SetBackgroundColour('pink')
1114 self._PRW_start.Refresh()
1115 self._PRW_start.SetFocus()
1116 return False
1117 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1118 self._PRW_start.SetBackgroundColour('pink')
1119 self._PRW_start.Refresh()
1120 self._PRW_start.SetFocus()
1121 return False
1122 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1123 self._PRW_start.Refresh()
1124
1125
1126
1127
1128
1129
1130
1131 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1132 self._PRW_end.SetBackgroundColour('pink')
1133 self._PRW_end.Refresh()
1134 self._PRW_end.SetFocus()
1135 return False
1136 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1137 self._PRW_end.Refresh()
1138
1139 return True
1140
1142 if not self.__is_valid_for_save():
1143 return False
1144
1145 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1146 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1147 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1148 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1149 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1150 self.__encounter.save_payload()
1151
1152 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1153 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1154
1155 return True
1156
1157
1159
1161 encounter = kwargs['encounter']
1162 del kwargs['encounter']
1163
1164 try:
1165 button_defs = kwargs['button_defs']
1166 del kwargs['button_defs']
1167 except KeyError:
1168 button_defs = None
1169
1170 try:
1171 msg = kwargs['msg']
1172 del kwargs['msg']
1173 except KeyError:
1174 msg = None
1175
1176 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1177 self.SetSize((450, 280))
1178 self.SetMinSize((450, 280))
1179
1180 if button_defs is not None:
1181 self._BTN_save.SetLabel(button_defs[0][0])
1182 self._BTN_save.SetToolTipString(button_defs[0][1])
1183 self._BTN_close.SetLabel(button_defs[1][0])
1184 self._BTN_close.SetToolTipString(button_defs[1][1])
1185 self.Refresh()
1186
1187 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1188
1189 self.Fit()
1190
1197
1199 start = self._PRW_encounter_start.GetData()
1200 if start is None:
1201 return
1202 start = start.get_pydt()
1203
1204 end = self._PRW_encounter_end.GetData()
1205 if end is None:
1206 fts = gmDateTime.cFuzzyTimestamp (
1207 timestamp = start,
1208 accuracy = gmDateTime.acc_minutes
1209 )
1210 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1211 return
1212 end = end.get_pydt()
1213
1214 if start > end:
1215 end = end.replace (
1216 year = start.year,
1217 month = start.month,
1218 day = start.day
1219 )
1220 fts = gmDateTime.cFuzzyTimestamp (
1221 timestamp = end,
1222 accuracy = gmDateTime.acc_minutes
1223 )
1224 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1225 return
1226
1227 emr = self.__pat.get_emr()
1228 if start != emr.active_encounter['started']:
1229 end = end.replace (
1230 year = start.year,
1231 month = start.month,
1232 day = start.day
1233 )
1234 fts = gmDateTime.cFuzzyTimestamp (
1235 timestamp = end,
1236 accuracy = gmDateTime.acc_minutes
1237 )
1238 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1239 return
1240
1241 return
1242
1243
1244 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1245
1247
1252
1254 self._TCTRL_encounter.SetValue(u'')
1255 self._TCTRL_encounter.SetToolTipString(u'')
1256 self._BTN_new.Enable(False)
1257 self._BTN_list.Enable(False)
1258
1260 pat = gmPerson.gmCurrentPatient()
1261 if not pat.connected:
1262 self.clear()
1263 return
1264
1265 enc = pat.get_emr().active_encounter
1266 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1267 self._TCTRL_encounter.SetToolTipString (
1268 _('The active encounter of the current patient:\n\n%s') %
1269 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1270 )
1271 self._BTN_new.Enable(True)
1272 self._BTN_list.Enable(True)
1273
1275 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1276
1277 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1278
1279
1280 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1281 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1282 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1283
1284
1285
1287 wx.CallAfter(self.clear)
1288
1290 wx.CallAfter(self.refresh)
1291 return True
1292
1298
1304
1309
1310
1311
1321
1391
1393 """Prepare changing health issue for an episode.
1394
1395 Checks for two-open-episodes conflict. When this
1396 function succeeds, the pk_health_issue has been set
1397 on the episode instance and the episode should - for
1398 all practical purposes - be ready for save_payload().
1399 """
1400
1401 if not episode['episode_open']:
1402 episode['pk_health_issue'] = target_issue['pk_health_issue']
1403 if save_to_backend:
1404 episode.save_payload()
1405 return True
1406
1407
1408 if target_issue is None:
1409 episode['pk_health_issue'] = None
1410 if save_to_backend:
1411 episode.save_payload()
1412 return True
1413
1414
1415 db_cfg = gmCfg.cCfgSQL()
1416 epi_ttl = int(db_cfg.get2 (
1417 option = u'episode.ttl',
1418 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1419 bias = 'user',
1420 default = 60
1421 ))
1422 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1423 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1424 existing_epi = target_issue.get_open_episode()
1425
1426
1427 if existing_epi is None:
1428 episode['pk_health_issue'] = target_issue['pk_health_issue']
1429 if save_to_backend:
1430 episode.save_payload()
1431 return True
1432
1433
1434 if existing_epi['pk_episode'] == episode['pk_episode']:
1435 episode['pk_health_issue'] = target_issue['pk_health_issue']
1436 if save_to_backend:
1437 episode.save_payload()
1438 return True
1439
1440
1441 move_range = episode.get_access_range()
1442 exist_range = existing_epi.get_access_range()
1443 question = _(
1444 'You want to associate the running episode:\n\n'
1445 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1446 'with the health issue:\n\n'
1447 ' "%(issue_name)s"\n\n'
1448 'There already is another episode running\n'
1449 'for this health issue:\n\n'
1450 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1451 'However, there can only be one running\n'
1452 'episode per health issue.\n\n'
1453 'Which episode do you want to close ?'
1454 ) % {
1455 'new_epi_name': episode['description'],
1456 'new_epi_start': move_range[0].strftime('%m/%y'),
1457 'new_epi_end': move_range[1].strftime('%m/%y'),
1458 'issue_name': target_issue['description'],
1459 'old_epi_name': existing_epi['description'],
1460 'old_epi_start': exist_range[0].strftime('%m/%y'),
1461 'old_epi_end': exist_range[1].strftime('%m/%y')
1462 }
1463 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1464 parent = None,
1465 id = -1,
1466 caption = _('Resolving two-running-episodes conflict'),
1467 question = question,
1468 button_defs = [
1469 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1470 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1471 ]
1472 )
1473 decision = dlg.ShowModal()
1474
1475 if decision == wx.ID_CANCEL:
1476
1477 return False
1478
1479 elif decision == wx.ID_YES:
1480
1481 existing_epi['episode_open'] = False
1482 existing_epi.save_payload()
1483
1484 elif decision == wx.ID_NO:
1485
1486 episode['episode_open'] = False
1487
1488 else:
1489 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1490
1491 episode['pk_health_issue'] = target_issue['pk_health_issue']
1492 if save_to_backend:
1493 episode.save_payload()
1494 return True
1495
1519
1521 """Let user select an episode *description*.
1522
1523 The user can select an episode description from the previously
1524 used descriptions across all episodes across all patients.
1525
1526 Selection is done with a phrasewheel so the user can
1527 type the episode name and matches will be shown. Typing
1528 "*" will show the entire list of episodes.
1529
1530 If the user types a description not existing yet a
1531 new episode description will be returned.
1532 """
1534
1535 mp = gmMatchProvider.cMatchProvider_SQL2 (
1536 queries = [
1537 u"""
1538 SELECT DISTINCT ON (description)
1539 description
1540 AS data,
1541 description
1542 AS field_label,
1543 description || ' ('
1544 || CASE
1545 WHEN is_open IS TRUE THEN _('ongoing')
1546 ELSE _('closed')
1547 END
1548 || ')'
1549 AS list_label
1550 FROM
1551 clin.episode
1552 WHERE
1553 description %(fragment_condition)s
1554 ORDER BY description
1555 LIMIT 30
1556 """
1557 ]
1558 )
1559 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1560 self.matcher = mp
1561
1563 """Let user select an episode.
1564
1565 The user can select an episode from the existing episodes of a
1566 patient. Selection is done with a phrasewheel so the user
1567 can type the episode name and matches will be shown. Typing
1568 "*" will show the entire list of episodes. Closed episodes
1569 will be marked as such. If the user types an episode name not
1570 in the list of existing episodes a new episode can be created
1571 from it if the programmer activated that feature.
1572
1573 If keyword <patient_id> is set to None or left out the control
1574 will listen to patient change signals and therefore act on
1575 gmPerson.gmCurrentPatient() changes.
1576 """
1578
1579 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1580
1581 mp = gmMatchProvider.cMatchProvider_SQL2 (
1582 queries = [
1583 u"""(
1584
1585 select
1586 pk_episode
1587 as data,
1588 description
1589 as field_label,
1590 coalesce (
1591 description || ' - ' || health_issue,
1592 description
1593 ) as list_label,
1594 1 as rank
1595 from
1596 clin.v_pat_episodes
1597 where
1598 episode_open is true and
1599 description %(fragment_condition)s
1600 %(ctxt_pat)s
1601
1602 ) union all (
1603
1604 select
1605 pk_episode
1606 as data,
1607 description
1608 as field_label,
1609 coalesce (
1610 description || _(' (closed)') || ' - ' || health_issue,
1611 description || _(' (closed)')
1612 ) as list_label,
1613 2 as rank
1614 from
1615 clin.v_pat_episodes
1616 where
1617 description %(fragment_condition)s and
1618 episode_open is false
1619 %(ctxt_pat)s
1620
1621 )
1622
1623 order by rank, list_label
1624 limit 30"""
1625 ],
1626 context = ctxt
1627 )
1628
1629 try:
1630 kwargs['patient_id']
1631 except KeyError:
1632 kwargs['patient_id'] = None
1633
1634 if kwargs['patient_id'] is None:
1635 self.use_current_patient = True
1636 self.__register_patient_change_signals()
1637 pat = gmPerson.gmCurrentPatient()
1638 if pat.connected:
1639 mp.set_context('pat', pat.ID)
1640 else:
1641 self.use_current_patient = False
1642 self.__patient_id = int(kwargs['patient_id'])
1643 mp.set_context('pat', self.__patient_id)
1644
1645 del kwargs['patient_id']
1646
1647 gmPhraseWheel.cPhraseWheel.__init__ (
1648 self,
1649 *args,
1650 **kwargs
1651 )
1652 self.matcher = mp
1653
1654
1655
1657 if self.use_current_patient:
1658 return False
1659 self.__patient_id = int(patient_id)
1660 self.set_context('pat', self.__patient_id)
1661 return True
1662
1663 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1666
1668
1669 epi_name = self.GetValue().strip()
1670 if epi_name == u'':
1671 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1672 _log.debug('cannot create episode without name')
1673 return
1674
1675 if self.use_current_patient:
1676 pat = gmPerson.gmCurrentPatient()
1677 else:
1678 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1679
1680 emr = pat.get_emr()
1681 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1682 if epi is None:
1683 self.data = {}
1684 else:
1685 self.SetText (
1686 value = epi_name,
1687 data = epi['pk_episode']
1688 )
1689
1692
1693
1694
1698
1701
1703 if self.use_current_patient:
1704 patient = gmPerson.gmCurrentPatient()
1705 self.set_context('pat', patient.ID)
1706 return True
1707
1708 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1709
1710 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1711
1724
1725
1726
1728
1729 errors = False
1730
1731 if len(self._PRW_description.GetValue().strip()) == 0:
1732 errors = True
1733 self._PRW_description.display_as_valid(False)
1734 self._PRW_description.SetFocus()
1735 else:
1736 self._PRW_description.display_as_valid(True)
1737 self._PRW_description.Refresh()
1738
1739 return not errors
1740
1742
1743 pat = gmPerson.gmCurrentPatient()
1744 emr = pat.get_emr()
1745
1746 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1747 epi['summary'] = self._TCTRL_status.GetValue().strip()
1748 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1749 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1750
1751 issue_name = self._PRW_issue.GetValue().strip()
1752 if len(issue_name) != 0:
1753 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1754 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1755
1756 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1757 gmDispatcher.send (
1758 signal = 'statustext',
1759 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1760 epi['description'],
1761 issue['description']
1762 )
1763 )
1764 gmEMRStructItems.delete_episode(episode = epi)
1765 return False
1766
1767 epi.save()
1768
1769 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1770
1771 self.data = epi
1772 return True
1773
1775
1776 self.data['description'] = self._PRW_description.GetValue().strip()
1777 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1778 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1779 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1780
1781 issue_name = self._PRW_issue.GetValue().strip()
1782 if len(issue_name) == 0:
1783 self.data['pk_health_issue'] = None
1784 else:
1785 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1786 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1787
1788 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1789 gmDispatcher.send (
1790 signal = 'statustext',
1791 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1792 self.data['description'],
1793 issue['description']
1794 )
1795 )
1796 return False
1797
1798 self.data.save()
1799 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1800
1801 return True
1802
1815
1834
1836 self._refresh_as_new()
1837
1838
1839
1849
1851
1852 if parent is None:
1853 parent = wx.GetApp().GetTopWindow()
1854
1855 def refresh(lctrl):
1856 issues = emr.get_health_issues()
1857 items = [
1858 [
1859 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1860 i['description'],
1861 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1862 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1863 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1864 ] for i in issues
1865 ]
1866 lctrl.set_string_items(items = items)
1867 lctrl.set_data(data = issues)
1868
1869 return gmListWidgets.get_choices_from_list (
1870 parent = parent,
1871 msg = _('\nSelect the health issues !\n'),
1872 caption = _('Showing health issues ...'),
1873 columns = [u'', _('Health issue'), u'', u'', u''],
1874 single_selection = False,
1875
1876
1877
1878 refresh_callback = refresh
1879 )
1880
1882
1883
1884
1886
1887 issues = kwargs['issues']
1888 del kwargs['issues']
1889
1890 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1891
1892 self.SetTitle(_('Select the health issues you are interested in ...'))
1893 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1894
1895 for issue in issues:
1896 if issue['is_confidential']:
1897 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1898 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1899 else:
1900 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1901
1902 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1903 if issue['clinically_relevant']:
1904 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1905 if issue['is_active']:
1906 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1907 if issue['is_cause_of_death']:
1908 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1909
1910 self._LCTRL_items.set_column_widths()
1911 self._LCTRL_items.set_data(data = issues)
1912
1914 """Let the user select a health issue.
1915
1916 The user can select a health issue from the existing issues
1917 of a patient. Selection is done with a phrasewheel so the user
1918 can type the issue name and matches will be shown. Typing
1919 "*" will show the entire list of issues. Inactive issues
1920 will be marked as such. If the user types an issue name not
1921 in the list of existing issues a new issue can be created
1922 from it if the programmer activated that feature.
1923
1924 If keyword <patient_id> is set to None or left out the control
1925 will listen to patient change signals and therefore act on
1926 gmPerson.gmCurrentPatient() changes.
1927 """
1929
1930 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1931
1932 mp = gmMatchProvider.cMatchProvider_SQL2 (
1933
1934 queries = [
1935 u"""
1936 SELECT
1937 data,
1938 field_label,
1939 list_label
1940 FROM ((
1941 SELECT
1942 pk_health_issue AS data,
1943 description AS field_label,
1944 description AS list_label
1945 FROM clin.v_health_issues
1946 WHERE
1947 is_active IS true
1948 AND
1949 description %(fragment_condition)s
1950 AND
1951 %(ctxt_pat)s
1952
1953 ) UNION (
1954
1955 SELECT
1956 pk_health_issue AS data,
1957 description AS field_label,
1958 description || _(' (inactive)') AS list_label
1959 FROM clin.v_health_issues
1960 WHERE
1961 is_active IS false
1962 AND
1963 description %(fragment_condition)s
1964 AND
1965 %(ctxt_pat)s
1966 )) AS union_query
1967 ORDER BY
1968 list_label"""],
1969 context = ctxt
1970 )
1971
1972 try: kwargs['patient_id']
1973 except KeyError: kwargs['patient_id'] = None
1974
1975 if kwargs['patient_id'] is None:
1976 self.use_current_patient = True
1977 self.__register_patient_change_signals()
1978 pat = gmPerson.gmCurrentPatient()
1979 if pat.connected:
1980 mp.set_context('pat', pat.ID)
1981 else:
1982 self.use_current_patient = False
1983 self.__patient_id = int(kwargs['patient_id'])
1984 mp.set_context('pat', self.__patient_id)
1985
1986 del kwargs['patient_id']
1987
1988 gmPhraseWheel.cPhraseWheel.__init__ (
1989 self,
1990 *args,
1991 **kwargs
1992 )
1993 self.matcher = mp
1994
1995
1996
1998 if self.use_current_patient:
1999 return False
2000 self.__patient_id = int(patient_id)
2001 self.set_context('pat', self.__patient_id)
2002 return True
2003
2005 issue_name = self.GetValue().strip()
2006 if issue_name == u'':
2007 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2008 _log.debug('cannot create health issue without name')
2009 return
2010
2011 if self.use_current_patient:
2012 pat = gmPerson.gmCurrentPatient()
2013 else:
2014 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2015
2016 emr = pat.get_emr()
2017 issue = emr.add_health_issue(issue_name = issue_name)
2018
2019 if issue is None:
2020 self.data = {}
2021 else:
2022 self.SetText (
2023 value = issue_name,
2024 data = issue['pk_health_issue']
2025 )
2026
2029
2030
2031
2035
2038
2040 if self.use_current_patient:
2041 patient = gmPerson.gmCurrentPatient()
2042 self.set_context('pat', patient.ID)
2043 return True
2044
2045 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2046
2048
2050 try:
2051 msg = kwargs['message']
2052 except KeyError:
2053 msg = None
2054 del kwargs['message']
2055 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
2056 if msg is not None:
2057 self._lbl_message.SetLabel(label=msg)
2058
2069
2070 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2071
2072 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2073 """Panel encapsulating health issue edit area functionality."""
2074
2076
2077 try:
2078 issue = kwargs['issue']
2079 except KeyError:
2080 issue = None
2081
2082 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2083
2084 gmEditArea.cGenericEditAreaMixin.__init__(self)
2085
2086
2087 mp = gmMatchProvider.cMatchProvider_SQL2 (
2088 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2089 )
2090 mp.setThresholds(1, 3, 5)
2091 self._PRW_condition.matcher = mp
2092
2093 mp = gmMatchProvider.cMatchProvider_SQL2 (
2094 queries = [u"""
2095 select distinct on (grouping) grouping, grouping from (
2096
2097 select rank, grouping from ((
2098
2099 select
2100 grouping,
2101 1 as rank
2102 from
2103 clin.health_issue
2104 where
2105 grouping %%(fragment_condition)s
2106 and
2107 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2108
2109 ) union (
2110
2111 select
2112 grouping,
2113 2 as rank
2114 from
2115 clin.health_issue
2116 where
2117 grouping %%(fragment_condition)s
2118
2119 )) as union_result
2120
2121 order by rank
2122
2123 ) as order_result
2124
2125 limit 50""" % gmPerson.gmCurrentPatient().ID
2126 ]
2127 )
2128 mp.setThresholds(1, 3, 5)
2129 self._PRW_grouping.matcher = mp
2130
2131 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2132 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2133
2134 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2135 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2136
2137 self._PRW_year_noted.Enable(True)
2138
2139 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2140
2141 self.data = issue
2142
2143
2144
2164
2166 pat = gmPerson.gmCurrentPatient()
2167 emr = pat.get_emr()
2168
2169 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2170
2171 side = u''
2172 if self._ChBOX_left.GetValue():
2173 side += u's'
2174 if self._ChBOX_right.GetValue():
2175 side += u'd'
2176 issue['laterality'] = side
2177
2178 issue['summary'] = self._TCTRL_status.GetValue().strip()
2179 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2180 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2181 issue['is_active'] = self._ChBOX_active.GetValue()
2182 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2183 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2184 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2185
2186 age_noted = self._PRW_age_noted.GetData()
2187 if age_noted is not None:
2188 issue['age_noted'] = age_noted
2189
2190 issue.save()
2191
2192 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2193
2194 self.data = issue
2195 return True
2196
2198
2199 self.data['description'] = self._PRW_condition.GetValue().strip()
2200
2201 side = u''
2202 if self._ChBOX_left.GetValue():
2203 side += u's'
2204 if self._ChBOX_right.GetValue():
2205 side += u'd'
2206 self.data['laterality'] = side
2207
2208 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2209 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2210 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2211 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2212 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2213 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2214 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2215
2216 age_noted = self._PRW_age_noted.GetData()
2217 if age_noted is not None:
2218 self.data['age_noted'] = age_noted
2219
2220 self.data.save()
2221 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2222
2223 return True
2224
2226 self._PRW_condition.SetText()
2227 self._ChBOX_left.SetValue(0)
2228 self._ChBOX_right.SetValue(0)
2229 self._PRW_codes.SetText()
2230 self._on_leave_codes()
2231 self._PRW_certainty.SetText()
2232 self._PRW_grouping.SetText()
2233 self._TCTRL_status.SetValue(u'')
2234 self._PRW_age_noted.SetText()
2235 self._PRW_year_noted.SetText()
2236 self._ChBOX_active.SetValue(1)
2237 self._ChBOX_relevant.SetValue(1)
2238 self._ChBOX_confidential.SetValue(0)
2239 self._ChBOX_caused_death.SetValue(0)
2240
2241 return True
2242
2283
2285 return self._refresh_as_new()
2286
2287
2288
2290 if not self._PRW_codes.IsModified():
2291 return True
2292
2293 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2294
2296
2297 if not self._PRW_age_noted.IsModified():
2298 return True
2299
2300 str_age = self._PRW_age_noted.GetValue().strip()
2301
2302 if str_age == u'':
2303 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2304 return True
2305
2306 age = gmDateTime.str2interval(str_interval = str_age)
2307
2308 if age is None:
2309 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2310 self._PRW_age_noted.SetBackgroundColour('pink')
2311 self._PRW_age_noted.Refresh()
2312 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2313 return True
2314
2315 pat = gmPerson.gmCurrentPatient()
2316 if pat['dob'] is not None:
2317 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2318
2319 if age >= max_age:
2320 gmDispatcher.send (
2321 signal = 'statustext',
2322 msg = _(
2323 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2324 ) % (age, pat.get_medical_age())
2325 )
2326 self._PRW_age_noted.SetBackgroundColour('pink')
2327 self._PRW_age_noted.Refresh()
2328 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2329 return True
2330
2331 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2332 self._PRW_age_noted.Refresh()
2333 self._PRW_age_noted.SetData(data=age)
2334
2335 if pat['dob'] is not None:
2336 fts = gmDateTime.cFuzzyTimestamp (
2337 timestamp = pat['dob'] + age,
2338 accuracy = gmDateTime.acc_months
2339 )
2340 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2341
2342
2343
2344
2345
2346 return True
2347
2349
2350 if not self._PRW_year_noted.IsModified():
2351 return True
2352
2353 year_noted = self._PRW_year_noted.GetData()
2354
2355 if year_noted is None:
2356 if self._PRW_year_noted.GetValue().strip() == u'':
2357 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2358 return True
2359 self._PRW_year_noted.SetBackgroundColour('pink')
2360 self._PRW_year_noted.Refresh()
2361 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2362 return True
2363
2364 year_noted = year_noted.get_pydt()
2365
2366 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2367 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2368 self._PRW_year_noted.SetBackgroundColour('pink')
2369 self._PRW_year_noted.Refresh()
2370 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2371 return True
2372
2373 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2374 self._PRW_year_noted.Refresh()
2375
2376 pat = gmPerson.gmCurrentPatient()
2377 if pat['dob'] is not None:
2378 issue_age = year_noted - pat['dob']
2379 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2380 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2381
2382 return True
2383
2385 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2386 return True
2387
2389 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2390 return True
2391
2392
2393
2395
2397
2398 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2399
2400 self.selection_only = False
2401
2402 mp = gmMatchProvider.cMatchProvider_FixedList (
2403 aSeq = [
2404 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2405 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2406 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2407 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2408 ]
2409 )
2410 mp.setThresholds(1, 2, 4)
2411 self.matcher = mp
2412
2413 self.SetToolTipString(_(
2414 "The diagnostic classification or grading of this assessment.\n"
2415 "\n"
2416 "This documents how certain one is about this being a true diagnosis."
2417 ))
2418
2419
2420
2421 if __name__ == '__main__':
2422
2423 from Gnumed.business import gmPersonSearch
2424
2425
2427 """
2428 Test application for testing EMR struct widgets
2429 """
2430
2432 """
2433 Create test application UI
2434 """
2435 frame = wx.Frame (
2436 None,
2437 -4,
2438 'Testing EMR struct widgets',
2439 size=wx.Size(600, 400),
2440 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2441 )
2442 filemenu= wx.Menu()
2443 filemenu.AppendSeparator()
2444 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2445
2446
2447 menuBar = wx.MenuBar()
2448 menuBar.Append(filemenu,"&File")
2449
2450 frame.SetMenuBar(menuBar)
2451
2452 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2453 wx.DefaultPosition, wx.DefaultSize, 0 )
2454
2455
2456 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2457
2458
2459 self.__pat = gmPerson.gmCurrentPatient()
2460
2461 frame.Show(1)
2462 return 1
2463
2465 """
2466 Close test aplication
2467 """
2468 self.ExitMainLoop ()
2469
2479
2488
2489
2490
2491
2492
2500
2506
2508 app = wx.PyWidgetTester(size = (400, 40))
2509 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2510 app.MainLoop()
2511
2513 app = wx.PyWidgetTester(size = (400, 40))
2514 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2515
2516 app.MainLoop()
2517
2519 app = wx.PyWidgetTester(size = (200, 300))
2520 edit_health_issue(parent=app.frame, issue=None)
2521
2523 app = wx.PyWidgetTester(size = (200, 300))
2524 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2525 app.MainLoop()
2526
2528 app = wx.PyWidgetTester(size = (200, 300))
2529 edit_procedure(parent=app.frame)
2530
2531
2532 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2533
2534 gmI18N.activate_locale()
2535 gmI18N.install_domain()
2536 gmDateTime.init()
2537
2538
2539 pat = gmPersonSearch.ask_for_patient()
2540 if pat is None:
2541 print "No patient. Exiting gracefully..."
2542 sys.exit(0)
2543 gmPatSearchWidgets.set_active_patient(patient=pat)
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561 test_edit_procedure()
2562
2563
2564