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 gmPraxis
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
44
45 _log = logging.getLogger('gm.ui')
46
47
48
50 """Spin time in seconds."""
51 if time2spin == 0:
52 return
53 sleep_time = 0.1
54 total_rounds = int(time2spin / sleep_time)
55 if total_rounds < 1:
56 return
57 rounds = 0
58 while rounds < total_rounds:
59 wx.Yield()
60 time.sleep(sleep_time)
61 rounds += 1
62
63
64
75
76 def delete(procedure=None):
77 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
78 return True
79
80 gmDispatcher.send (
81 signal = u'statustext',
82 msg = _('Cannot delete performed procedure.'),
83 beep = True
84 )
85 return False
86
87 def refresh(lctrl):
88 procs = emr.get_performed_procedures()
89
90 items = [
91 [
92 u'%s%s' % (
93 p['clin_when'].strftime('%Y-%m-%d'),
94 gmTools.bool2subst (
95 p['is_ongoing'],
96 _(' (ongoing)'),
97 gmTools.coalesce (
98 initial = p['clin_end'],
99 instead = u'',
100 template_initial = u' - %s',
101 function_initial = ('strftime', u'%Y-%m-%d')
102 )
103 )
104 ),
105 p['clin_where'],
106 p['episode'],
107 p['performed_procedure']
108 ] for p in procs
109 ]
110 lctrl.set_string_items(items = items)
111 lctrl.set_data(data = procs)
112
113 gmListWidgets.get_choices_from_list (
114 parent = parent,
115 msg = _('\nSelect the procedure you want to edit !\n'),
116 caption = _('Editing performed procedures ...'),
117 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
118 single_selection = True,
119 edit_callback = edit,
120 new_callback = edit,
121 delete_callback = delete,
122 refresh_callback = refresh
123 )
124
136
137 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
138
139 -class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin):
140
149
151 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
152 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
153 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
154 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
155 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
156
157
158 mp = gmMatchProvider.cMatchProvider_SQL2 (
159 queries = [
160 u"""
161 SELECT DISTINCT ON (data) data, location
162 FROM (
163 SELECT
164 clin_where as data,
165 clin_where as location
166 FROM
167 clin.procedure
168 WHERE
169 clin_where %(fragment_condition)s
170
171 UNION ALL
172
173 SELECT
174 narrative as data,
175 narrative as location
176 FROM
177 clin.hospital_stay
178 WHERE
179 narrative %(fragment_condition)s
180 ) as union_result
181 ORDER BY data
182 LIMIT 25"""
183 ]
184 )
185 mp.setThresholds(2, 4, 6)
186 self._PRW_location.matcher = mp
187
188
189 mp = gmMatchProvider.cMatchProvider_SQL2 (
190 queries = [
191 u"""
192 select distinct on (narrative) narrative, narrative
193 from clin.procedure
194 where narrative %(fragment_condition)s
195 order by narrative
196 limit 25
197 """ ]
198 )
199 mp.setThresholds(2, 4, 6)
200 self._PRW_procedure.matcher = mp
201
203 stay = self._PRW_hospital_stay.GetData()
204 if stay is None:
205 self._PRW_hospital_stay.SetText()
206 self._PRW_location.Enable(True)
207 self._PRW_episode.Enable(True)
208 self._LBL_hospital_details.SetLabel(u'')
209 else:
210 self._PRW_location.SetText()
211 self._PRW_location.Enable(False)
212 self._PRW_episode.SetText()
213 self._PRW_episode.Enable(False)
214 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
215
217 if self._PRW_location.GetValue().strip() == u'':
218 self._PRW_hospital_stay.Enable(True)
219
220 else:
221 self._PRW_hospital_stay.SetText()
222 self._PRW_hospital_stay.Enable(False)
223 self._PRW_hospital_stay.display_as_valid(True)
224
225
237
260
261
262
320
355
357 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
358
359 if self._DPRW_end.GetData() is None:
360 self.data['clin_end'] = None
361 else:
362 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
363
364 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
365
366 if self._PRW_hospital_stay.GetData() is None:
367 self.data['pk_hospital_stay'] = None
368 self.data['clin_where'] = self._PRW_location.GetValue().strip()
369 self.data['pk_episode'] = self._PRW_episode.GetData()
370 else:
371 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
372 self.data['clin_where'] = None
373 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
374 self.data['pk_episode'] = stay['pk_episode']
375
376 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
377
378 self.data.save()
379 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
380
381 return True
382
384 self._DPRW_date.SetText()
385 self._DPRW_end.SetText()
386 self._CHBOX_ongoing.SetValue(False)
387 self._CHBOX_ongoing.Enable(True)
388 self._PRW_hospital_stay.SetText()
389 self._PRW_location.SetText()
390 self._PRW_episode.SetText()
391 self._PRW_procedure.SetText()
392 self._PRW_codes.SetText()
393
394 self._PRW_procedure.SetFocus()
395
426
438
439
440
445
461
462
463
465
466 pat = gmPerson.gmCurrentPatient()
467 emr = pat.get_emr()
468
469 if parent is None:
470 parent = wx.GetApp().GetTopWindow()
471
472 def get_tooltip(stay=None):
473 if stay is None:
474 return None
475 return stay.format (
476 include_procedures = True,
477 include_docs = True
478 )
479
480 def edit(stay=None):
481 return edit_hospital_stay(parent = parent, hospital_stay = stay)
482
483 def delete(stay=None):
484 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
485 return True
486 gmDispatcher.send (
487 signal = u'statustext',
488 msg = _('Cannot delete hospitalization.'),
489 beep = True
490 )
491 return False
492
493 def refresh(lctrl):
494 stays = emr.get_hospital_stays()
495 items = [
496 [
497 s['admission'].strftime('%Y-%m-%d'),
498 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
499 s['episode'],
500 gmTools.coalesce(s['hospital'], u'')
501 ] for s in stays
502 ]
503 lctrl.set_string_items(items = items)
504 lctrl.set_data(data = stays)
505
506 gmListWidgets.get_choices_from_list (
507 parent = parent,
508 msg = _("The patient's hospitalizations:\n"),
509 caption = _('Editing hospitalizations ...'),
510 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
511 single_selection = True,
512 edit_callback = edit,
513 new_callback = edit,
514 delete_callback = delete,
515 refresh_callback = refresh,
516 list_tooltip_callback = get_tooltip
517 )
518
519
531
533 """Phrasewheel to allow selection of a hospitalization."""
535
536 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
537
538 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
539
540 mp = gmMatchProvider.cMatchProvider_SQL2 (
541 queries = [
542 u"""
543 select
544 pk_hospital_stay,
545 descr
546 from (
547 select distinct on (pk_hospital_stay)
548 pk_hospital_stay,
549 descr
550 from
551 (select
552 pk_hospital_stay,
553 (
554 to_char(admission, 'YYYY-Mon-DD')
555 || coalesce((' (' || hospital || '):'), ': ')
556 || episode
557 || coalesce((' (' || health_issue || ')'), '')
558 ) as descr
559 from
560 clin.v_pat_hospital_stays
561 where
562 %(ctxt_pat)s
563
564 hospital %(fragment_condition)s
565 or
566 episode %(fragment_condition)s
567 or
568 health_issue %(fragment_condition)s
569 ) as the_stays
570 ) as distinct_stays
571 order by descr
572 limit 25
573 """ ],
574 context = ctxt
575 )
576 mp.setThresholds(3, 4, 6)
577 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
578
579 self.matcher = mp
580 self.selection_only = True
581
582 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
583
584 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
585
589
590
591
593
594 valid = True
595
596 if self._PRW_episode.GetValue().strip() == u'':
597 valid = False
598 self._PRW_episode.display_as_valid(False)
599 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
600 self._PRW_episode.SetFocus()
601
602 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
603 valid = False
604 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
605 self._PRW_admission.SetFocus()
606
607 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
608 if self._PRW_discharge.date is not None:
609 adm = self._PRW_admission.date
610 discharge = self._PRW_discharge.date
611
612 discharge = discharge.replace (
613 hour = adm.hour,
614 minute = adm.minute,
615 second = adm.second,
616 microsecond = adm.microsecond
617 )
618 if adm is not None:
619 if discharge == adm:
620 self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
621 elif not self._PRW_discharge.date > self._PRW_admission.date:
622 valid = False
623 self._PRW_discharge.display_as_valid(False)
624 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
625 self._PRW_discharge.SetFocus()
626
627 return (valid is True)
628
641
651
658
670
672 print "this was not expected to be used in this edit area"
673
674
675
676
685
686 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
687
689 if parent is None:
690 parent = wx.GetApp().GetTopWindow()
691
692
693 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
694 if dlg.ShowModal() == wx.ID_OK:
695 dlg.Destroy()
696 return True
697 dlg.Destroy()
698 return False
699
702
703 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
704
705 if patient is None:
706 patient = gmPerson.gmCurrentPatient()
707
708 if not patient.connected:
709 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
710 return False
711
712 if parent is None:
713 parent = wx.GetApp().GetTopWindow()
714
715 emr = patient.get_emr()
716
717
718 def refresh(lctrl):
719 if encounters is None:
720 encs = emr.get_encounters()
721 else:
722 encs = encounters
723
724 items = [
725 [
726 gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'),
727 e['last_affirmed'].strftime('%H:%M'),
728 e['l10n_type'],
729 gmTools.coalesce(e['reason_for_encounter'], u''),
730 gmTools.coalesce(e['assessment_of_encounter'], u''),
731 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
732 e['pk_encounter']
733 ] for e in encs
734 ]
735 lctrl.set_string_items(items = items)
736 lctrl.set_data(data = encs)
737 active_pk = emr.active_encounter['pk_encounter']
738 for idx in range(len(encs)):
739 e = encs[idx]
740 if e['pk_encounter'] == active_pk:
741 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
742
743 def new():
744 cfg_db = gmCfg.cCfgSQL()
745
746 enc_type = cfg_db.get2 (
747 option = u'encounter.default_type',
748 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
749 bias = u'user',
750 default = u'in surgery'
751 )
752 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
753 return edit_encounter(parent = parent, encounter = enc)
754
755 def edit(enc=None):
756 return edit_encounter(parent = parent, encounter = enc)
757
758 def edit_active(enc=None):
759 return edit_encounter(parent = parent, encounter = emr.active_encounter)
760
761 def start_new(enc=None):
762 start_new_encounter(emr = emr)
763 return True
764
765 return gmListWidgets.get_choices_from_list (
766 parent = parent,
767 msg = _("The patient's encounters.\n"),
768 caption = _('Encounters ...'),
769 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
770 can_return_empty = False,
771 single_selection = single_selection,
772 refresh_callback = refresh,
773 edit_callback = edit,
774 new_callback = new,
775 ignore_OK_button = ignore_OK_button,
776 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
777 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
778 )
779
781 """This is used as the callback when the EMR detects that the
782 patient was here rather recently and wants to ask the
783 provider whether to continue the recent encounter.
784 """
785 if parent is None:
786 parent = wx.GetApp().GetTopWindow()
787
788 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
789 parent = None,
790 id = -1,
791 caption = caption,
792 question = msg,
793 button_defs = [
794 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
795 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
796 ],
797 show_checkbox = False
798 )
799
800 result = dlg.ShowModal()
801 dlg.Destroy()
802
803 if result == wx.ID_YES:
804 return True
805
806 return False
807
809
810 if parent is None:
811 parent = wx.GetApp().GetTopWindow()
812
813
814 def edit(enc_type=None):
815 return edit_encounter_type(parent = parent, encounter_type = enc_type)
816
817 def delete(enc_type=None):
818 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
819 return True
820 gmDispatcher.send (
821 signal = u'statustext',
822 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
823 beep = True
824 )
825 return False
826
827 def refresh(lctrl):
828 enc_types = gmEMRStructItems.get_encounter_types()
829 lctrl.set_string_items(items = enc_types)
830
831 gmListWidgets.get_choices_from_list (
832 parent = parent,
833 msg = _('\nSelect the encounter type you want to edit !\n'),
834 caption = _('Managing encounter types ...'),
835 columns = [_('Local name'), _('Encounter type')],
836 single_selection = True,
837 edit_callback = edit,
838 new_callback = edit,
839 delete_callback = delete,
840 refresh_callback = refresh
841 )
842
852
854
856 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
857
858 cmd = u"""
859 SELECT DISTINCT ON (list_label)
860 pk_encounter
861 AS data,
862 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
863 AS list_label,
864 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
865 AS field_label
866 FROM
867 clin.v_pat_encounters
868 WHERE
869 (
870 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
871 OR
872 l10n_type %(fragment_condition)s
873 OR
874 type %(fragment_condition)s
875 ) %(ctxt_patient)s
876 ORDER BY
877 list_label
878 LIMIT
879 30
880 """
881 context = {'ctxt_patient': {
882 'where_part': u'AND pk_patient = %(patient)s',
883 'placeholder': u'patient'
884 }}
885
886 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
887 self.matcher._SQL_data2match = u"""
888 SELECT
889 pk_encounter
890 AS data,
891 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
892 AS list_label,
893 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
894 AS field_label
895 FROM
896 clin.v_pat_encounters
897 WHERE
898 pk_encounter = %(pk)s
899 """
900 self.matcher.setThresholds(1, 3, 5)
901
902 self.selection_only = True
903
904 self.set_context(context = 'patient', val = None)
905
912
923
925 """Phrasewheel to allow selection of encounter type.
926
927 - user input interpreted as encounter type in English or local language
928 - data returned is pk of corresponding encounter type or None
929 """
931
932 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
933
934 mp = gmMatchProvider.cMatchProvider_SQL2 (
935 queries = [
936 u"""
937 SELECT
938 data,
939 field_label,
940 list_label
941 FROM (
942 SELECT DISTINCT ON (data) *
943 FROM (
944 SELECT
945 pk AS data,
946 _(description) AS field_label,
947 case
948 when _(description) = description then _(description)
949 else _(description) || ' (' || description || ')'
950 end AS list_label
951 FROM
952 clin.encounter_type
953 WHERE
954 _(description) %(fragment_condition)s
955 OR
956 description %(fragment_condition)s
957 ) AS q_distinct_pk
958 ) AS q_ordered
959 ORDER BY
960 list_label
961 """ ]
962 )
963 mp.setThresholds(2, 4, 6)
964
965 self.matcher = mp
966 self.selection_only = True
967 self.picklist_delay = 50
968
969 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
970
972
977
978
979
980
981
1011
1024
1034
1036 self._TCTRL_l10n_name.SetValue(u'')
1037 self._TCTRL_name.SetValue(u'')
1038 self._TCTRL_name.Enable(True)
1039
1041 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1042 self._TCTRL_name.SetValue(self.data['description'])
1043
1044 self._TCTRL_name.Enable(False)
1045
1047 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1048 self._TCTRL_name.SetValue(self.data['description'])
1049 self._TCTRL_name.Enable(True)
1050
1051
1052
1053
1054
1055
1056 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1057
1059
1061 try:
1062 self.__encounter = kwargs['encounter']
1063 del kwargs['encounter']
1064 except KeyError:
1065 self.__encounter = None
1066
1067 try:
1068 msg = kwargs['msg']
1069 del kwargs['msg']
1070 except KeyError:
1071 msg = None
1072
1073 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1074
1075 self.refresh(msg = msg)
1076
1077
1078
1079 - def refresh(self, encounter=None, msg=None):
1080
1081 if msg is not None:
1082 self._LBL_instructions.SetLabel(msg)
1083
1084 if encounter is not None:
1085 self.__encounter = encounter
1086
1087 if self.__encounter is None:
1088 return True
1089
1090
1091
1092 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1093 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
1094 curr_pat = gmPerson.gmCurrentPatient()
1095 if curr_pat.connected:
1096 if curr_pat.ID == self.__encounter['pk_patient']:
1097 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
1098 else:
1099 self._LBL_patient.SetForegroundColour('red')
1100
1101 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1102
1103 fts = gmDateTime.cFuzzyTimestamp (
1104 timestamp = self.__encounter['started'],
1105 accuracy = gmDateTime.acc_minutes
1106 )
1107 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1108
1109 fts = gmDateTime.cFuzzyTimestamp (
1110 timestamp = self.__encounter['last_affirmed'],
1111 accuracy = gmDateTime.acc_minutes
1112 )
1113 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1114
1115
1116 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1117 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1118 self._PRW_rfe_codes.SetText(val, data)
1119
1120
1121 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1122 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1123 self._PRW_aoe_codes.SetText(val, data)
1124
1125
1126 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1127 self._PRW_end.SetFocus()
1128 else:
1129 self._TCTRL_aoe.SetFocus()
1130
1131 return True
1132
1134
1135 if self._PRW_encounter_type.GetData() is None:
1136 self._PRW_encounter_type.SetBackgroundColour('pink')
1137 self._PRW_encounter_type.Refresh()
1138 self._PRW_encounter_type.SetFocus()
1139 return False
1140 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1141 self._PRW_encounter_type.Refresh()
1142
1143
1144 if self._PRW_start.GetValue().strip() == u'':
1145 self._PRW_start.SetBackgroundColour('pink')
1146 self._PRW_start.Refresh()
1147 self._PRW_start.SetFocus()
1148 return False
1149 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1150 self._PRW_start.SetBackgroundColour('pink')
1151 self._PRW_start.Refresh()
1152 self._PRW_start.SetFocus()
1153 return False
1154 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1155 self._PRW_start.Refresh()
1156
1157
1158
1159
1160
1161
1162
1163 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1164 self._PRW_end.SetBackgroundColour('pink')
1165 self._PRW_end.Refresh()
1166 self._PRW_end.SetFocus()
1167 return False
1168 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1169 self._PRW_end.Refresh()
1170
1171 return True
1172
1174 if not self.__is_valid_for_save():
1175 return False
1176
1177 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1178 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1179 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1180 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1181 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1182 self.__encounter.save_payload()
1183
1184 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1185 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1186
1187 return True
1188
1189
1191
1193 encounter = kwargs['encounter']
1194 del kwargs['encounter']
1195
1196 try:
1197 button_defs = kwargs['button_defs']
1198 del kwargs['button_defs']
1199 except KeyError:
1200 button_defs = None
1201
1202 try:
1203 msg = kwargs['msg']
1204 del kwargs['msg']
1205 except KeyError:
1206 msg = None
1207
1208 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1209 self.SetSize((450, 280))
1210 self.SetMinSize((450, 280))
1211
1212 if button_defs is not None:
1213 self._BTN_save.SetLabel(button_defs[0][0])
1214 self._BTN_save.SetToolTipString(button_defs[0][1])
1215 self._BTN_close.SetLabel(button_defs[1][0])
1216 self._BTN_close.SetToolTipString(button_defs[1][1])
1217 self.Refresh()
1218
1219 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1220
1221 self.Fit()
1222
1229
1231 start = self._PRW_encounter_start.GetData()
1232 if start is None:
1233 return
1234 start = start.get_pydt()
1235
1236 end = self._PRW_encounter_end.GetData()
1237 if end is None:
1238 fts = gmDateTime.cFuzzyTimestamp (
1239 timestamp = start,
1240 accuracy = gmDateTime.acc_minutes
1241 )
1242 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1243 return
1244 end = end.get_pydt()
1245
1246 if start > end:
1247 end = end.replace (
1248 year = start.year,
1249 month = start.month,
1250 day = start.day
1251 )
1252 fts = gmDateTime.cFuzzyTimestamp (
1253 timestamp = end,
1254 accuracy = gmDateTime.acc_minutes
1255 )
1256 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1257 return
1258
1259 emr = self.__pat.get_emr()
1260 if start != emr.active_encounter['started']:
1261 end = end.replace (
1262 year = start.year,
1263 month = start.month,
1264 day = start.day
1265 )
1266 fts = gmDateTime.cFuzzyTimestamp (
1267 timestamp = end,
1268 accuracy = gmDateTime.acc_minutes
1269 )
1270 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1271 return
1272
1273 return
1274
1275
1276 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1277
1279
1284
1286 self._TCTRL_encounter.SetValue(u'')
1287 self._TCTRL_encounter.SetToolTipString(u'')
1288 self._BTN_new.Enable(False)
1289 self._BTN_list.Enable(False)
1290
1292 pat = gmPerson.gmCurrentPatient()
1293 if not pat.connected:
1294 self.clear()
1295 return
1296
1297 enc = pat.get_emr().active_encounter
1298 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1299 self._TCTRL_encounter.SetToolTipString (
1300 _('The active encounter of the current patient:\n\n%s') %
1301 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1302 )
1303 self._BTN_new.Enable(True)
1304 self._BTN_list.Enable(True)
1305
1307 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1308
1309 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1310
1311
1312 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1313 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1314 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1315
1316
1317
1319 wx.CallAfter(self.clear)
1320
1322 wx.CallAfter(self.refresh)
1323 return True
1324
1330
1336
1341
1342
1343
1353
1423
1425 """Prepare changing health issue for an episode.
1426
1427 Checks for two-open-episodes conflict. When this
1428 function succeeds, the pk_health_issue has been set
1429 on the episode instance and the episode should - for
1430 all practical purposes - be ready for save_payload().
1431 """
1432
1433 if not episode['episode_open']:
1434 episode['pk_health_issue'] = target_issue['pk_health_issue']
1435 if save_to_backend:
1436 episode.save_payload()
1437 return True
1438
1439
1440 if target_issue is None:
1441 episode['pk_health_issue'] = None
1442 if save_to_backend:
1443 episode.save_payload()
1444 return True
1445
1446
1447 db_cfg = gmCfg.cCfgSQL()
1448 epi_ttl = int(db_cfg.get2 (
1449 option = u'episode.ttl',
1450 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1451 bias = 'user',
1452 default = 60
1453 ))
1454 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1455 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1456 existing_epi = target_issue.get_open_episode()
1457
1458
1459 if existing_epi is None:
1460 episode['pk_health_issue'] = target_issue['pk_health_issue']
1461 if save_to_backend:
1462 episode.save_payload()
1463 return True
1464
1465
1466 if existing_epi['pk_episode'] == episode['pk_episode']:
1467 episode['pk_health_issue'] = target_issue['pk_health_issue']
1468 if save_to_backend:
1469 episode.save_payload()
1470 return True
1471
1472
1473 move_range = episode.get_access_range()
1474 exist_range = existing_epi.get_access_range()
1475 question = _(
1476 'You want to associate the running episode:\n\n'
1477 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1478 'with the health issue:\n\n'
1479 ' "%(issue_name)s"\n\n'
1480 'There already is another episode running\n'
1481 'for this health issue:\n\n'
1482 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1483 'However, there can only be one running\n'
1484 'episode per health issue.\n\n'
1485 'Which episode do you want to close ?'
1486 ) % {
1487 'new_epi_name': episode['description'],
1488 'new_epi_start': move_range[0].strftime('%m/%y'),
1489 'new_epi_end': move_range[1].strftime('%m/%y'),
1490 'issue_name': target_issue['description'],
1491 'old_epi_name': existing_epi['description'],
1492 'old_epi_start': exist_range[0].strftime('%m/%y'),
1493 'old_epi_end': exist_range[1].strftime('%m/%y')
1494 }
1495 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1496 parent = None,
1497 id = -1,
1498 caption = _('Resolving two-running-episodes conflict'),
1499 question = question,
1500 button_defs = [
1501 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1502 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1503 ]
1504 )
1505 decision = dlg.ShowModal()
1506
1507 if decision == wx.ID_CANCEL:
1508
1509 return False
1510
1511 elif decision == wx.ID_YES:
1512
1513 existing_epi['episode_open'] = False
1514 existing_epi.save_payload()
1515
1516 elif decision == wx.ID_NO:
1517
1518 episode['episode_open'] = False
1519
1520 else:
1521 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1522
1523 episode['pk_health_issue'] = target_issue['pk_health_issue']
1524 if save_to_backend:
1525 episode.save_payload()
1526 return True
1527
1551
1553 """Let user select an episode *description*.
1554
1555 The user can select an episode description from the previously
1556 used descriptions across all episodes across all patients.
1557
1558 Selection is done with a phrasewheel so the user can
1559 type the episode name and matches will be shown. Typing
1560 "*" will show the entire list of episodes.
1561
1562 If the user types a description not existing yet a
1563 new episode description will be returned.
1564 """
1566
1567 mp = gmMatchProvider.cMatchProvider_SQL2 (
1568 queries = [
1569 u"""
1570 SELECT DISTINCT ON (description)
1571 description
1572 AS data,
1573 description
1574 AS field_label,
1575 description || ' ('
1576 || CASE
1577 WHEN is_open IS TRUE THEN _('ongoing')
1578 ELSE _('closed')
1579 END
1580 || ')'
1581 AS list_label
1582 FROM
1583 clin.episode
1584 WHERE
1585 description %(fragment_condition)s
1586 ORDER BY description
1587 LIMIT 30
1588 """
1589 ]
1590 )
1591 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1592 self.matcher = mp
1593
1595 """Let user select an episode.
1596
1597 The user can select an episode from the existing episodes of a
1598 patient. Selection is done with a phrasewheel so the user
1599 can type the episode name and matches will be shown. Typing
1600 "*" will show the entire list of episodes. Closed episodes
1601 will be marked as such. If the user types an episode name not
1602 in the list of existing episodes a new episode can be created
1603 from it if the programmer activated that feature.
1604
1605 If keyword <patient_id> is set to None or left out the control
1606 will listen to patient change signals and therefore act on
1607 gmPerson.gmCurrentPatient() changes.
1608 """
1610
1611 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1612
1613 mp = gmMatchProvider.cMatchProvider_SQL2 (
1614 queries = [
1615 u"""(
1616
1617 select
1618 pk_episode
1619 as data,
1620 description
1621 as field_label,
1622 coalesce (
1623 description || ' - ' || health_issue,
1624 description
1625 ) as list_label,
1626 1 as rank
1627 from
1628 clin.v_pat_episodes
1629 where
1630 episode_open is true and
1631 description %(fragment_condition)s
1632 %(ctxt_pat)s
1633
1634 ) union all (
1635
1636 select
1637 pk_episode
1638 as data,
1639 description
1640 as field_label,
1641 coalesce (
1642 description || _(' (closed)') || ' - ' || health_issue,
1643 description || _(' (closed)')
1644 ) as list_label,
1645 2 as rank
1646 from
1647 clin.v_pat_episodes
1648 where
1649 description %(fragment_condition)s and
1650 episode_open is false
1651 %(ctxt_pat)s
1652
1653 )
1654
1655 order by rank, list_label
1656 limit 30"""
1657 ],
1658 context = ctxt
1659 )
1660
1661 try:
1662 kwargs['patient_id']
1663 except KeyError:
1664 kwargs['patient_id'] = None
1665
1666 if kwargs['patient_id'] is None:
1667 self.use_current_patient = True
1668 self.__register_patient_change_signals()
1669 pat = gmPerson.gmCurrentPatient()
1670 if pat.connected:
1671 mp.set_context('pat', pat.ID)
1672 else:
1673 self.use_current_patient = False
1674 self.__patient_id = int(kwargs['patient_id'])
1675 mp.set_context('pat', self.__patient_id)
1676
1677 del kwargs['patient_id']
1678
1679 gmPhraseWheel.cPhraseWheel.__init__ (
1680 self,
1681 *args,
1682 **kwargs
1683 )
1684 self.matcher = mp
1685
1686
1687
1689 if self.use_current_patient:
1690 return False
1691 self.__patient_id = int(patient_id)
1692 self.set_context('pat', self.__patient_id)
1693 return True
1694
1695 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1698
1700
1701 epi_name = self.GetValue().strip()
1702 if epi_name == u'':
1703 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1704 _log.debug('cannot create episode without name')
1705 return
1706
1707 if self.use_current_patient:
1708 pat = gmPerson.gmCurrentPatient()
1709 else:
1710 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1711
1712 emr = pat.get_emr()
1713 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1714 if epi is None:
1715 self.data = {}
1716 else:
1717 self.SetText (
1718 value = epi_name,
1719 data = epi['pk_episode']
1720 )
1721
1724
1725
1726
1730
1733
1735 if self.use_current_patient:
1736 patient = gmPerson.gmCurrentPatient()
1737 self.set_context('pat', patient.ID)
1738 return True
1739
1740 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1741
1742 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1743
1756
1757
1758
1760
1761 errors = False
1762
1763 if len(self._PRW_description.GetValue().strip()) == 0:
1764 errors = True
1765 self._PRW_description.display_as_valid(False)
1766 self._PRW_description.SetFocus()
1767 else:
1768 self._PRW_description.display_as_valid(True)
1769 self._PRW_description.Refresh()
1770
1771 return not errors
1772
1774
1775 pat = gmPerson.gmCurrentPatient()
1776 emr = pat.get_emr()
1777
1778 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1779 epi['summary'] = self._TCTRL_status.GetValue().strip()
1780 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1781 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1782
1783 issue_name = self._PRW_issue.GetValue().strip()
1784 if len(issue_name) != 0:
1785 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1786 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1787
1788 if not move_episode_to_issue(episode = epi, 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 epi['description'],
1793 issue['description']
1794 )
1795 )
1796 gmEMRStructItems.delete_episode(episode = epi)
1797 return False
1798
1799 epi.save()
1800
1801 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1802
1803 self.data = epi
1804 return True
1805
1807
1808 self.data['description'] = self._PRW_description.GetValue().strip()
1809 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1810 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1811 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1812
1813 issue_name = self._PRW_issue.GetValue().strip()
1814 if len(issue_name) == 0:
1815 self.data['pk_health_issue'] = None
1816 else:
1817 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1818 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1819
1820 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1821 gmDispatcher.send (
1822 signal = 'statustext',
1823 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1824 self.data['description'],
1825 issue['description']
1826 )
1827 )
1828 return False
1829
1830 self.data.save()
1831 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1832
1833 return True
1834
1847
1866
1868 self._refresh_as_new()
1869
1870
1871
1881
1883
1884 if parent is None:
1885 parent = wx.GetApp().GetTopWindow()
1886
1887 def refresh(lctrl):
1888 issues = emr.get_health_issues()
1889 items = [
1890 [
1891 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1892 i['description'],
1893 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1894 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1895 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1896 ] for i in issues
1897 ]
1898 lctrl.set_string_items(items = items)
1899 lctrl.set_data(data = issues)
1900
1901 return gmListWidgets.get_choices_from_list (
1902 parent = parent,
1903 msg = _('\nSelect the health issues !\n'),
1904 caption = _('Showing health issues ...'),
1905 columns = [u'', _('Health issue'), u'', u'', u''],
1906 single_selection = False,
1907
1908
1909
1910 refresh_callback = refresh
1911 )
1912
1914
1915
1916
1918
1919 issues = kwargs['issues']
1920 del kwargs['issues']
1921
1922 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1923
1924 self.SetTitle(_('Select the health issues you are interested in ...'))
1925 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1926
1927 for issue in issues:
1928 if issue['is_confidential']:
1929 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1930 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1931 else:
1932 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1933
1934 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1935 if issue['clinically_relevant']:
1936 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1937 if issue['is_active']:
1938 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1939 if issue['is_cause_of_death']:
1940 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1941
1942 self._LCTRL_items.set_column_widths()
1943 self._LCTRL_items.set_data(data = issues)
1944
1946 """Let the user select a health issue.
1947
1948 The user can select a health issue from the existing issues
1949 of a patient. Selection is done with a phrasewheel so the user
1950 can type the issue name and matches will be shown. Typing
1951 "*" will show the entire list of issues. Inactive issues
1952 will be marked as such. If the user types an issue name not
1953 in the list of existing issues a new issue can be created
1954 from it if the programmer activated that feature.
1955
1956 If keyword <patient_id> is set to None or left out the control
1957 will listen to patient change signals and therefore act on
1958 gmPerson.gmCurrentPatient() changes.
1959 """
1961
1962 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1963
1964 mp = gmMatchProvider.cMatchProvider_SQL2 (
1965
1966 queries = [
1967 u"""
1968 SELECT
1969 data,
1970 field_label,
1971 list_label
1972 FROM ((
1973 SELECT
1974 pk_health_issue AS data,
1975 description AS field_label,
1976 description AS list_label
1977 FROM clin.v_health_issues
1978 WHERE
1979 is_active IS true
1980 AND
1981 description %(fragment_condition)s
1982 AND
1983 %(ctxt_pat)s
1984
1985 ) UNION (
1986
1987 SELECT
1988 pk_health_issue AS data,
1989 description AS field_label,
1990 description || _(' (inactive)') AS list_label
1991 FROM clin.v_health_issues
1992 WHERE
1993 is_active IS false
1994 AND
1995 description %(fragment_condition)s
1996 AND
1997 %(ctxt_pat)s
1998 )) AS union_query
1999 ORDER BY
2000 list_label"""],
2001 context = ctxt
2002 )
2003
2004 try: kwargs['patient_id']
2005 except KeyError: kwargs['patient_id'] = None
2006
2007 if kwargs['patient_id'] is None:
2008 self.use_current_patient = True
2009 self.__register_patient_change_signals()
2010 pat = gmPerson.gmCurrentPatient()
2011 if pat.connected:
2012 mp.set_context('pat', pat.ID)
2013 else:
2014 self.use_current_patient = False
2015 self.__patient_id = int(kwargs['patient_id'])
2016 mp.set_context('pat', self.__patient_id)
2017
2018 del kwargs['patient_id']
2019
2020 gmPhraseWheel.cPhraseWheel.__init__ (
2021 self,
2022 *args,
2023 **kwargs
2024 )
2025 self.matcher = mp
2026
2027
2028
2030 if self.use_current_patient:
2031 return False
2032 self.__patient_id = int(patient_id)
2033 self.set_context('pat', self.__patient_id)
2034 return True
2035
2037 issue_name = self.GetValue().strip()
2038 if issue_name == u'':
2039 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2040 _log.debug('cannot create health issue without name')
2041 return
2042
2043 if self.use_current_patient:
2044 pat = gmPerson.gmCurrentPatient()
2045 else:
2046 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2047
2048 emr = pat.get_emr()
2049 issue = emr.add_health_issue(issue_name = issue_name)
2050
2051 if issue is None:
2052 self.data = {}
2053 else:
2054 self.SetText (
2055 value = issue_name,
2056 data = issue['pk_health_issue']
2057 )
2058
2061
2062
2063
2067
2070
2072 if self.use_current_patient:
2073 patient = gmPerson.gmCurrentPatient()
2074 self.set_context('pat', patient.ID)
2075 return True
2076
2077 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2078
2101
2102 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2103
2104 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2105 """Panel encapsulating health issue edit area functionality."""
2106
2108
2109 try:
2110 issue = kwargs['issue']
2111 except KeyError:
2112 issue = None
2113
2114 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2115
2116 gmEditArea.cGenericEditAreaMixin.__init__(self)
2117
2118
2119 mp = gmMatchProvider.cMatchProvider_SQL2 (
2120 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2121 )
2122 mp.setThresholds(1, 3, 5)
2123 self._PRW_condition.matcher = mp
2124
2125 mp = gmMatchProvider.cMatchProvider_SQL2 (
2126 queries = [u"""
2127 select distinct on (grouping) grouping, grouping from (
2128
2129 select rank, grouping from ((
2130
2131 select
2132 grouping,
2133 1 as rank
2134 from
2135 clin.health_issue
2136 where
2137 grouping %%(fragment_condition)s
2138 and
2139 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2140
2141 ) union (
2142
2143 select
2144 grouping,
2145 2 as rank
2146 from
2147 clin.health_issue
2148 where
2149 grouping %%(fragment_condition)s
2150
2151 )) as union_result
2152
2153 order by rank
2154
2155 ) as order_result
2156
2157 limit 50""" % gmPerson.gmCurrentPatient().ID
2158 ]
2159 )
2160 mp.setThresholds(1, 3, 5)
2161 self._PRW_grouping.matcher = mp
2162
2163 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2164 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2165
2166 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2167 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2168
2169 self._PRW_year_noted.Enable(True)
2170
2171 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2172
2173 self.data = issue
2174
2175
2176
2196
2198 pat = gmPerson.gmCurrentPatient()
2199 emr = pat.get_emr()
2200
2201 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2202
2203 side = u''
2204 if self._ChBOX_left.GetValue():
2205 side += u's'
2206 if self._ChBOX_right.GetValue():
2207 side += u'd'
2208 issue['laterality'] = side
2209
2210 issue['summary'] = self._TCTRL_status.GetValue().strip()
2211 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2212 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2213 issue['is_active'] = self._ChBOX_active.GetValue()
2214 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2215 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2216 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2217
2218 age_noted = self._PRW_age_noted.GetData()
2219 if age_noted is not None:
2220 issue['age_noted'] = age_noted
2221
2222 issue.save()
2223
2224 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2225
2226 self.data = issue
2227 return True
2228
2230
2231 self.data['description'] = self._PRW_condition.GetValue().strip()
2232
2233 side = u''
2234 if self._ChBOX_left.GetValue():
2235 side += u's'
2236 if self._ChBOX_right.GetValue():
2237 side += u'd'
2238 self.data['laterality'] = side
2239
2240 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2241 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2242 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2243 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2244 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2245 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2246 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2247
2248 age_noted = self._PRW_age_noted.GetData()
2249 if age_noted is not None:
2250 self.data['age_noted'] = age_noted
2251
2252 self.data.save()
2253 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2254
2255 return True
2256
2258 self._PRW_condition.SetText()
2259 self._ChBOX_left.SetValue(0)
2260 self._ChBOX_right.SetValue(0)
2261 self._PRW_codes.SetText()
2262 self._on_leave_codes()
2263 self._PRW_certainty.SetText()
2264 self._PRW_grouping.SetText()
2265 self._TCTRL_status.SetValue(u'')
2266 self._PRW_age_noted.SetText()
2267 self._PRW_year_noted.SetText()
2268 self._ChBOX_active.SetValue(1)
2269 self._ChBOX_relevant.SetValue(1)
2270 self._ChBOX_confidential.SetValue(0)
2271 self._ChBOX_caused_death.SetValue(0)
2272
2273 return True
2274
2315
2317 return self._refresh_as_new()
2318
2319
2320
2322 if not self._PRW_codes.IsModified():
2323 return True
2324
2325 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2326
2328
2329 if not self._PRW_age_noted.IsModified():
2330 return True
2331
2332 str_age = self._PRW_age_noted.GetValue().strip()
2333
2334 if str_age == u'':
2335 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2336 return True
2337
2338 age = gmDateTime.str2interval(str_interval = str_age)
2339
2340 if age is None:
2341 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2342 self._PRW_age_noted.SetBackgroundColour('pink')
2343 self._PRW_age_noted.Refresh()
2344 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2345 return True
2346
2347 pat = gmPerson.gmCurrentPatient()
2348 if pat['dob'] is not None:
2349 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2350
2351 if age >= max_age:
2352 gmDispatcher.send (
2353 signal = 'statustext',
2354 msg = _(
2355 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2356 ) % (age, pat.get_medical_age())
2357 )
2358 self._PRW_age_noted.SetBackgroundColour('pink')
2359 self._PRW_age_noted.Refresh()
2360 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2361 return True
2362
2363 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2364 self._PRW_age_noted.Refresh()
2365 self._PRW_age_noted.SetData(data=age)
2366
2367 if pat['dob'] is not None:
2368 fts = gmDateTime.cFuzzyTimestamp (
2369 timestamp = pat['dob'] + age,
2370 accuracy = gmDateTime.acc_months
2371 )
2372 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2373
2374
2375
2376
2377
2378 return True
2379
2381
2382 if not self._PRW_year_noted.IsModified():
2383 return True
2384
2385 year_noted = self._PRW_year_noted.GetData()
2386
2387 if year_noted is None:
2388 if self._PRW_year_noted.GetValue().strip() == u'':
2389 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2390 return True
2391 self._PRW_year_noted.SetBackgroundColour('pink')
2392 self._PRW_year_noted.Refresh()
2393 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2394 return True
2395
2396 year_noted = year_noted.get_pydt()
2397
2398 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2399 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2400 self._PRW_year_noted.SetBackgroundColour('pink')
2401 self._PRW_year_noted.Refresh()
2402 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2403 return True
2404
2405 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2406 self._PRW_year_noted.Refresh()
2407
2408 pat = gmPerson.gmCurrentPatient()
2409 if pat['dob'] is not None:
2410 issue_age = year_noted - pat['dob']
2411 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2412 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2413
2414 return True
2415
2417 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2418 return True
2419
2421 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2422 return True
2423
2424
2425
2427
2429
2430 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2431
2432 self.selection_only = False
2433
2434 mp = gmMatchProvider.cMatchProvider_FixedList (
2435 aSeq = [
2436 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2437 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2438 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2439 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2440 ]
2441 )
2442 mp.setThresholds(1, 2, 4)
2443 self.matcher = mp
2444
2445 self.SetToolTipString(_(
2446 "The diagnostic classification or grading of this assessment.\n"
2447 "\n"
2448 "This documents how certain one is about this being a true diagnosis."
2449 ))
2450
2451
2452
2453 if __name__ == '__main__':
2454
2455 from Gnumed.business import gmPersonSearch
2456 from Gnumed.wxpython import gmPatSearchWidgets
2457
2458
2460 """
2461 Test application for testing EMR struct widgets
2462 """
2463
2465 """
2466 Create test application UI
2467 """
2468 frame = wx.Frame (
2469 None,
2470 -4,
2471 'Testing EMR struct widgets',
2472 size=wx.Size(600, 400),
2473 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2474 )
2475 filemenu= wx.Menu()
2476 filemenu.AppendSeparator()
2477 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2478
2479
2480 menuBar = wx.MenuBar()
2481 menuBar.Append(filemenu,"&File")
2482
2483 frame.SetMenuBar(menuBar)
2484
2485 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2486 wx.DefaultPosition, wx.DefaultSize, 0 )
2487
2488
2489 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2490
2491
2492 self.__pat = gmPerson.gmCurrentPatient()
2493
2494 frame.Show(1)
2495 return 1
2496
2498 """
2499 Close test aplication
2500 """
2501 self.ExitMainLoop ()
2502
2512
2521
2522
2523
2524
2525
2533
2539
2541 app = wx.PyWidgetTester(size = (400, 40))
2542 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2543 app.MainLoop()
2544
2546 app = wx.PyWidgetTester(size = (400, 40))
2547 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2548
2549 app.MainLoop()
2550
2552 app = wx.PyWidgetTester(size = (200, 300))
2553 edit_health_issue(parent=app.frame, issue=None)
2554
2556 app = wx.PyWidgetTester(size = (200, 300))
2557 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2558 app.MainLoop()
2559
2561 app = wx.PyWidgetTester(size = (200, 300))
2562 edit_procedure(parent=app.frame)
2563
2564
2565 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2566
2567 gmI18N.activate_locale()
2568 gmI18N.install_domain()
2569 gmDateTime.init()
2570
2571
2572 pat = gmPersonSearch.ask_for_patient()
2573 if pat is None:
2574 print "No patient. Exiting gracefully..."
2575 sys.exit(0)
2576 gmPatSearchWidgets.set_active_patient(patient=pat)
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594 test_edit_procedure()
2595
2596
2597