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):
731
732 def edit(enc=None):
733 return edit_encounter(parent = parent, encounter = enc)
734
735 def edit_active(enc=None):
736 return edit_encounter(parent = parent, encounter = emr.active_encounter)
737
738 def start_new(enc=None):
739 start_new_encounter(emr = emr)
740 return True
741
742 def get_tooltip(data):
743 if data is None:
744 return None
745 return data.format (
746 patient = patient,
747 with_soap = False,
748 with_docs = False,
749 with_tests = False,
750 with_vaccinations = False,
751 with_rfe_aoe = True,
752 with_family_history = False,
753 by_episode=False,
754 fancy_header = True,
755 )
756
757 def refresh(lctrl):
758 if encounters is None:
759 encs = emr.get_encounters()
760 else:
761 encs = encounters
762
763 items = [
764 [
765 u'%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'), e['last_affirmed'].strftime('%H:%M')),
766 e['l10n_type'],
767 gmTools.coalesce(e['praxis_branch'], u''),
768 gmTools.coalesce(e['reason_for_encounter'], u''),
769 gmTools.coalesce(e['assessment_of_encounter'], u''),
770 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
771 e['pk_encounter']
772 ] for e in encs
773 ]
774 lctrl.set_string_items(items = items)
775 lctrl.set_data(data = encs)
776 active_pk = emr.active_encounter['pk_encounter']
777 for idx in range(len(encs)):
778 e = encs[idx]
779 if e['pk_encounter'] == active_pk:
780 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
781
782 return gmListWidgets.get_choices_from_list (
783 parent = parent,
784 msg = _("The patient's encounters.\n"),
785 caption = _('Encounters ...'),
786 columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
787 can_return_empty = False,
788 single_selection = single_selection,
789 refresh_callback = refresh,
790 edit_callback = edit,
791 new_callback = new,
792 list_tooltip_callback = get_tooltip,
793 ignore_OK_button = ignore_OK_button,
794 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
795 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
796 )
797
798
800 """This is used as the callback when the EMR detects that the
801 patient was here rather recently and wants to ask the
802 provider whether to continue the recent encounter.
803 """
804 if parent is None:
805 parent = wx.GetApp().GetTopWindow()
806
807 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
808 parent = None,
809 id = -1,
810 caption = caption,
811 question = msg,
812 button_defs = [
813 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
814 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
815 ],
816 show_checkbox = False
817 )
818
819 result = dlg.ShowModal()
820 dlg.Destroy()
821
822 if result == wx.ID_YES:
823 return True
824
825 return False
826
828
829 if parent is None:
830 parent = wx.GetApp().GetTopWindow()
831
832
833 def edit(enc_type=None):
834 return edit_encounter_type(parent = parent, encounter_type = enc_type)
835
836 def delete(enc_type=None):
837 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
838 return True
839 gmDispatcher.send (
840 signal = u'statustext',
841 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
842 beep = True
843 )
844 return False
845
846 def refresh(lctrl):
847 enc_types = gmEMRStructItems.get_encounter_types()
848 lctrl.set_string_items(items = enc_types)
849
850 gmListWidgets.get_choices_from_list (
851 parent = parent,
852 msg = _('\nSelect the encounter type you want to edit !\n'),
853 caption = _('Managing encounter types ...'),
854 columns = [_('Local name'), _('Encounter type')],
855 single_selection = True,
856 edit_callback = edit,
857 new_callback = edit,
858 delete_callback = delete,
859 refresh_callback = refresh
860 )
861
871
873
875 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
876
877 cmd = u"""
878 SELECT DISTINCT ON (list_label)
879 pk_encounter
880 AS data,
881 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
882 AS list_label,
883 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
884 AS field_label
885 FROM
886 clin.v_pat_encounters
887 WHERE
888 (
889 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
890 OR
891 l10n_type %(fragment_condition)s
892 OR
893 type %(fragment_condition)s
894 ) %(ctxt_patient)s
895 ORDER BY
896 list_label
897 LIMIT
898 30
899 """
900 context = {'ctxt_patient': {
901 'where_part': u'AND pk_patient = %(patient)s',
902 'placeholder': u'patient'
903 }}
904
905 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
906 self.matcher._SQL_data2match = u"""
907 SELECT
908 pk_encounter
909 AS data,
910 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
911 AS list_label,
912 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
913 AS field_label
914 FROM
915 clin.v_pat_encounters
916 WHERE
917 pk_encounter = %(pk)s
918 """
919 self.matcher.setThresholds(1, 3, 5)
920
921 self.selection_only = True
922
923 self.set_context(context = 'patient', val = None)
924
931
942
944 """Phrasewheel to allow selection of encounter type.
945
946 - user input interpreted as encounter type in English or local language
947 - data returned is pk of corresponding encounter type or None
948 """
950
951 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
952
953 mp = gmMatchProvider.cMatchProvider_SQL2 (
954 queries = [
955 u"""
956 SELECT
957 data,
958 field_label,
959 list_label
960 FROM (
961 SELECT DISTINCT ON (data) *
962 FROM (
963 SELECT
964 pk AS data,
965 _(description) AS field_label,
966 case
967 when _(description) = description then _(description)
968 else _(description) || ' (' || description || ')'
969 end AS list_label
970 FROM
971 clin.encounter_type
972 WHERE
973 _(description) %(fragment_condition)s
974 OR
975 description %(fragment_condition)s
976 ) AS q_distinct_pk
977 ) AS q_ordered
978 ORDER BY
979 list_label
980 """ ]
981 )
982 mp.setThresholds(2, 4, 6)
983
984 self.matcher = mp
985 self.selection_only = True
986 self.picklist_delay = 50
987
988 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
989
991
996
997
998
999
1000
1002 if self.mode == 'edit':
1003 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1004 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1005 return False
1006 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1007 return True
1008
1009 no_errors = True
1010
1011 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1012 if self._TCTRL_name.GetValue().strip() == u'':
1013 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1014 no_errors = False
1015 else:
1016 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1017 else:
1018 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1019
1020 if self._TCTRL_name.GetValue().strip() == u'':
1021 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1022 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
1023 no_errors = False
1024 else:
1025 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1026 else:
1027 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1028
1029 return no_errors
1030
1043
1053
1055 self._TCTRL_l10n_name.SetValue(u'')
1056 self._TCTRL_name.SetValue(u'')
1057 self._TCTRL_name.Enable(True)
1058
1060 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1061 self._TCTRL_name.SetValue(self.data['description'])
1062
1063 self._TCTRL_name.Enable(False)
1064
1066 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1067 self._TCTRL_name.SetValue(self.data['description'])
1068 self._TCTRL_name.Enable(True)
1069
1070
1071
1072
1073
1074
1075 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1076
1078
1080 try:
1081 self.__encounter = kwargs['encounter']
1082 del kwargs['encounter']
1083 except KeyError:
1084 self.__encounter = None
1085
1086 try:
1087 msg = kwargs['msg']
1088 del kwargs['msg']
1089 except KeyError:
1090 msg = None
1091
1092 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1093
1094 self.refresh(msg = msg)
1095
1096
1097
1098 - def refresh(self, encounter=None, msg=None):
1099
1100 if msg is not None:
1101 self._LBL_instructions.SetLabel(msg)
1102
1103 if encounter is not None:
1104 self.__encounter = encounter
1105
1106 if self.__encounter is None:
1107 return True
1108
1109
1110
1111 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1112 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
1113 curr_pat = gmPerson.gmCurrentPatient()
1114 if curr_pat.connected:
1115 if curr_pat.ID == self.__encounter['pk_patient']:
1116 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
1117 else:
1118 self._LBL_patient.SetForegroundColour('red')
1119
1120 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
1121 self._PRW_location.Enable(True)
1122 self._PRW_location.display_as_disabled(False)
1123 branch = self.__encounter.praxis_branch
1124 if branch is None:
1125 unit = self.__encounter.org_unit
1126 if unit is None:
1127 self._PRW_location.SetText(u'', data = None)
1128 else:
1129 self._PRW_location.Enable(False)
1130 self._PRW_location.display_as_disabled(True)
1131 self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
1132 else:
1133 self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
1134
1135 fts = gmDateTime.cFuzzyTimestamp (
1136 timestamp = self.__encounter['started'],
1137 accuracy = gmDateTime.acc_minutes
1138 )
1139 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1140
1141 fts = gmDateTime.cFuzzyTimestamp (
1142 timestamp = self.__encounter['last_affirmed'],
1143 accuracy = gmDateTime.acc_minutes
1144 )
1145 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1146
1147
1148 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1149 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1150 self._PRW_rfe_codes.SetText(val, data)
1151
1152
1153 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1154 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1155 self._PRW_aoe_codes.SetText(val, data)
1156
1157
1158 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1159 self._PRW_end.SetFocus()
1160 else:
1161 self._TCTRL_aoe.SetFocus()
1162
1163 return True
1164
1166
1167 if self._PRW_encounter_type.GetData() is None:
1168 self._PRW_encounter_type.SetBackgroundColour('pink')
1169 self._PRW_encounter_type.Refresh()
1170 self._PRW_encounter_type.SetFocus()
1171 return False
1172 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1173 self._PRW_encounter_type.Refresh()
1174
1175
1176 if self._PRW_start.GetValue().strip() == u'':
1177 self._PRW_start.SetBackgroundColour('pink')
1178 self._PRW_start.Refresh()
1179 self._PRW_start.SetFocus()
1180 return False
1181 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1182 self._PRW_start.SetBackgroundColour('pink')
1183 self._PRW_start.Refresh()
1184 self._PRW_start.SetFocus()
1185 return False
1186 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1187 self._PRW_start.Refresh()
1188
1189
1190
1191
1192
1193
1194
1195 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1196 self._PRW_end.SetBackgroundColour('pink')
1197 self._PRW_end.Refresh()
1198 self._PRW_end.SetFocus()
1199 return False
1200 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1201 self._PRW_end.Refresh()
1202
1203 return True
1204
1206 if not self.__is_valid_for_save():
1207 return False
1208
1209 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1210 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1211 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1212 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1213 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1214 self.__encounter.save_payload()
1215
1216 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1217 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1218
1219 return True
1220
1221
1223
1225 encounter = kwargs['encounter']
1226 del kwargs['encounter']
1227
1228 try:
1229 button_defs = kwargs['button_defs']
1230 del kwargs['button_defs']
1231 except KeyError:
1232 button_defs = None
1233
1234 try:
1235 msg = kwargs['msg']
1236 del kwargs['msg']
1237 except KeyError:
1238 msg = None
1239
1240 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1241 self.SetSize((450, 280))
1242 self.SetMinSize((450, 280))
1243
1244 if button_defs is not None:
1245 self._BTN_save.SetLabel(button_defs[0][0])
1246 self._BTN_save.SetToolTipString(button_defs[0][1])
1247 self._BTN_close.SetLabel(button_defs[1][0])
1248 self._BTN_close.SetToolTipString(button_defs[1][1])
1249 self.Refresh()
1250
1251 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1252
1253 self.Fit()
1254
1261
1263 start = self._PRW_encounter_start.GetData()
1264 if start is None:
1265 return
1266 start = start.get_pydt()
1267
1268 end = self._PRW_encounter_end.GetData()
1269 if end is None:
1270 fts = gmDateTime.cFuzzyTimestamp (
1271 timestamp = start,
1272 accuracy = gmDateTime.acc_minutes
1273 )
1274 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1275 return
1276 end = end.get_pydt()
1277
1278 if start > end:
1279 end = end.replace (
1280 year = start.year,
1281 month = start.month,
1282 day = start.day
1283 )
1284 fts = gmDateTime.cFuzzyTimestamp (
1285 timestamp = end,
1286 accuracy = gmDateTime.acc_minutes
1287 )
1288 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1289 return
1290
1291 emr = self.__pat.get_emr()
1292 if start != emr.active_encounter['started']:
1293 end = end.replace (
1294 year = start.year,
1295 month = start.month,
1296 day = start.day
1297 )
1298 fts = gmDateTime.cFuzzyTimestamp (
1299 timestamp = end,
1300 accuracy = gmDateTime.acc_minutes
1301 )
1302 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1303 return
1304
1305 return
1306
1307
1308 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1309
1311
1316
1318 self._TCTRL_encounter.SetValue(u'')
1319 self._TCTRL_encounter.SetToolTipString(u'')
1320 self._BTN_new.Enable(False)
1321 self._BTN_list.Enable(False)
1322
1324 pat = gmPerson.gmCurrentPatient()
1325 if not pat.connected:
1326 self.clear()
1327 return
1328
1329 enc = pat.get_emr().active_encounter
1330 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1331 self._TCTRL_encounter.SetToolTipString (
1332 _('The active encounter of the current patient:\n\n%s') %
1333 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1334 )
1335 self._BTN_new.Enable(True)
1336 self._BTN_list.Enable(True)
1337
1339 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1340
1341 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1342
1343
1344 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1345 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1346 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1347
1348
1349
1351 wx.CallAfter(self.clear)
1352
1354 wx.CallAfter(self.refresh)
1355 return True
1356
1362
1368
1373
1374
1375
1385
1455
1457 """Prepare changing health issue for an episode.
1458
1459 Checks for two-open-episodes conflict. When this
1460 function succeeds, the pk_health_issue has been set
1461 on the episode instance and the episode should - for
1462 all practical purposes - be ready for save_payload().
1463 """
1464
1465 if not episode['episode_open']:
1466 episode['pk_health_issue'] = target_issue['pk_health_issue']
1467 if save_to_backend:
1468 episode.save_payload()
1469 return True
1470
1471
1472 if target_issue is None:
1473 episode['pk_health_issue'] = None
1474 if save_to_backend:
1475 episode.save_payload()
1476 return True
1477
1478
1479 db_cfg = gmCfg.cCfgSQL()
1480 epi_ttl = int(db_cfg.get2 (
1481 option = u'episode.ttl',
1482 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1483 bias = 'user',
1484 default = 60
1485 ))
1486 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1487 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1488 existing_epi = target_issue.get_open_episode()
1489
1490
1491 if existing_epi is None:
1492 episode['pk_health_issue'] = target_issue['pk_health_issue']
1493 if save_to_backend:
1494 episode.save_payload()
1495 return True
1496
1497
1498 if existing_epi['pk_episode'] == episode['pk_episode']:
1499 episode['pk_health_issue'] = target_issue['pk_health_issue']
1500 if save_to_backend:
1501 episode.save_payload()
1502 return True
1503
1504
1505 move_range = episode.get_access_range()
1506 exist_range = existing_epi.get_access_range()
1507 question = _(
1508 'You want to associate the running episode:\n\n'
1509 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1510 'with the health issue:\n\n'
1511 ' "%(issue_name)s"\n\n'
1512 'There already is another episode running\n'
1513 'for this health issue:\n\n'
1514 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1515 'However, there can only be one running\n'
1516 'episode per health issue.\n\n'
1517 'Which episode do you want to close ?'
1518 ) % {
1519 'new_epi_name': episode['description'],
1520 'new_epi_start': move_range[0].strftime('%m/%y'),
1521 'new_epi_end': move_range[1].strftime('%m/%y'),
1522 'issue_name': target_issue['description'],
1523 'old_epi_name': existing_epi['description'],
1524 'old_epi_start': exist_range[0].strftime('%m/%y'),
1525 'old_epi_end': exist_range[1].strftime('%m/%y')
1526 }
1527 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1528 parent = None,
1529 id = -1,
1530 caption = _('Resolving two-running-episodes conflict'),
1531 question = question,
1532 button_defs = [
1533 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1534 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1535 ]
1536 )
1537 decision = dlg.ShowModal()
1538
1539 if decision == wx.ID_CANCEL:
1540
1541 return False
1542
1543 elif decision == wx.ID_YES:
1544
1545 existing_epi['episode_open'] = False
1546 existing_epi.save_payload()
1547
1548 elif decision == wx.ID_NO:
1549
1550 episode['episode_open'] = False
1551
1552 else:
1553 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1554
1555 episode['pk_health_issue'] = target_issue['pk_health_issue']
1556 if save_to_backend:
1557 episode.save_payload()
1558 return True
1559
1583
1585 """Let user select an episode *description*.
1586
1587 The user can select an episode description from the previously
1588 used descriptions across all episodes across all patients.
1589
1590 Selection is done with a phrasewheel so the user can
1591 type the episode name and matches will be shown. Typing
1592 "*" will show the entire list of episodes.
1593
1594 If the user types a description not existing yet a
1595 new episode description will be returned.
1596 """
1598
1599 mp = gmMatchProvider.cMatchProvider_SQL2 (
1600 queries = [
1601 u"""
1602 SELECT DISTINCT ON (description)
1603 description
1604 AS data,
1605 description
1606 AS field_label,
1607 description || ' ('
1608 || CASE
1609 WHEN is_open IS TRUE THEN _('ongoing')
1610 ELSE _('closed')
1611 END
1612 || ')'
1613 AS list_label
1614 FROM
1615 clin.episode
1616 WHERE
1617 description %(fragment_condition)s
1618 ORDER BY description
1619 LIMIT 30
1620 """
1621 ]
1622 )
1623 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1624 self.matcher = mp
1625
1627 """Let user select an episode.
1628
1629 The user can select an episode from the existing episodes of a
1630 patient. Selection is done with a phrasewheel so the user
1631 can type the episode name and matches will be shown. Typing
1632 "*" will show the entire list of episodes. Closed episodes
1633 will be marked as such. If the user types an episode name not
1634 in the list of existing episodes a new episode can be created
1635 from it if the programmer activated that feature.
1636
1637 If keyword <patient_id> is set to None or left out the control
1638 will listen to patient change signals and therefore act on
1639 gmPerson.gmCurrentPatient() changes.
1640 """
1642
1643 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1644
1645 mp = gmMatchProvider.cMatchProvider_SQL2 (
1646 queries = [
1647 u"""(
1648
1649 select
1650 pk_episode
1651 as data,
1652 description
1653 as field_label,
1654 coalesce (
1655 description || ' - ' || health_issue,
1656 description
1657 ) as list_label,
1658 1 as rank
1659 from
1660 clin.v_pat_episodes
1661 where
1662 episode_open is true and
1663 description %(fragment_condition)s
1664 %(ctxt_pat)s
1665
1666 ) union all (
1667
1668 select
1669 pk_episode
1670 as data,
1671 description
1672 as field_label,
1673 coalesce (
1674 description || _(' (closed)') || ' - ' || health_issue,
1675 description || _(' (closed)')
1676 ) as list_label,
1677 2 as rank
1678 from
1679 clin.v_pat_episodes
1680 where
1681 description %(fragment_condition)s and
1682 episode_open is false
1683 %(ctxt_pat)s
1684
1685 )
1686
1687 order by rank, list_label
1688 limit 30"""
1689 ],
1690 context = ctxt
1691 )
1692
1693 try:
1694 kwargs['patient_id']
1695 except KeyError:
1696 kwargs['patient_id'] = None
1697
1698 if kwargs['patient_id'] is None:
1699 self.use_current_patient = True
1700 self.__register_patient_change_signals()
1701 pat = gmPerson.gmCurrentPatient()
1702 if pat.connected:
1703 mp.set_context('pat', pat.ID)
1704 else:
1705 self.use_current_patient = False
1706 self.__patient_id = int(kwargs['patient_id'])
1707 mp.set_context('pat', self.__patient_id)
1708
1709 del kwargs['patient_id']
1710
1711 gmPhraseWheel.cPhraseWheel.__init__ (
1712 self,
1713 *args,
1714 **kwargs
1715 )
1716 self.matcher = mp
1717
1718
1719
1721 if self.use_current_patient:
1722 return False
1723 self.__patient_id = int(patient_id)
1724 self.set_context('pat', self.__patient_id)
1725 return True
1726
1727 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1730
1732
1733 epi_name = self.GetValue().strip()
1734 if epi_name == u'':
1735 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1736 _log.debug('cannot create episode without name')
1737 return
1738
1739 if self.use_current_patient:
1740 pat = gmPerson.gmCurrentPatient()
1741 else:
1742 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1743
1744 emr = pat.get_emr()
1745 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1746 if epi is None:
1747 self.data = {}
1748 else:
1749 self.SetText (
1750 value = epi_name,
1751 data = epi['pk_episode']
1752 )
1753
1756
1757
1758
1762
1765
1767 if self.use_current_patient:
1768 patient = gmPerson.gmCurrentPatient()
1769 self.set_context('pat', patient.ID)
1770 return True
1771
1772 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1773
1774 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1775
1788
1789
1790
1792
1793 errors = False
1794
1795 if len(self._PRW_description.GetValue().strip()) == 0:
1796 errors = True
1797 self._PRW_description.display_as_valid(False)
1798 self._PRW_description.SetFocus()
1799 else:
1800 self._PRW_description.display_as_valid(True)
1801 self._PRW_description.Refresh()
1802
1803 return not errors
1804
1806
1807 pat = gmPerson.gmCurrentPatient()
1808 emr = pat.get_emr()
1809
1810 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1811 epi['summary'] = self._TCTRL_status.GetValue().strip()
1812 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1813 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1814
1815 issue_name = self._PRW_issue.GetValue().strip()
1816 if len(issue_name) != 0:
1817 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1818 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1819
1820 if not move_episode_to_issue(episode = epi, 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 epi['description'],
1825 issue['description']
1826 )
1827 )
1828 gmEMRStructItems.delete_episode(episode = epi)
1829 return False
1830
1831 epi.save()
1832
1833 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1834
1835 self.data = epi
1836 return True
1837
1839
1840 self.data['description'] = self._PRW_description.GetValue().strip()
1841 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1842 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1843 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1844
1845 issue_name = self._PRW_issue.GetValue().strip()
1846 if len(issue_name) == 0:
1847 self.data['pk_health_issue'] = None
1848 else:
1849 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1850 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1851
1852 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1853 gmDispatcher.send (
1854 signal = 'statustext',
1855 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1856 self.data['description'],
1857 issue['description']
1858 )
1859 )
1860 return False
1861
1862 self.data.save()
1863 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1864
1865 return True
1866
1879
1898
1900 self._refresh_as_new()
1901
1902
1903
1913
1915
1916 if parent is None:
1917 parent = wx.GetApp().GetTopWindow()
1918
1919 def refresh(lctrl):
1920 issues = emr.get_health_issues()
1921 items = [
1922 [
1923 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1924 i['description'],
1925 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1926 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1927 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1928 ] for i in issues
1929 ]
1930 lctrl.set_string_items(items = items)
1931 lctrl.set_data(data = issues)
1932
1933 return gmListWidgets.get_choices_from_list (
1934 parent = parent,
1935 msg = _('\nSelect the health issues !\n'),
1936 caption = _('Showing health issues ...'),
1937 columns = [u'', _('Health issue'), u'', u'', u''],
1938 single_selection = False,
1939
1940
1941
1942 refresh_callback = refresh
1943 )
1944
1946
1947
1948
1950
1951 issues = kwargs['issues']
1952 del kwargs['issues']
1953
1954 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1955
1956 self.SetTitle(_('Select the health issues you are interested in ...'))
1957 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1958
1959 for issue in issues:
1960 if issue['is_confidential']:
1961 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1962 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1963 else:
1964 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1965
1966 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1967 if issue['clinically_relevant']:
1968 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1969 if issue['is_active']:
1970 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1971 if issue['is_cause_of_death']:
1972 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1973
1974 self._LCTRL_items.set_column_widths()
1975 self._LCTRL_items.set_data(data = issues)
1976
1978 """Let the user select a health issue.
1979
1980 The user can select a health issue from the existing issues
1981 of a patient. Selection is done with a phrasewheel so the user
1982 can type the issue name and matches will be shown. Typing
1983 "*" will show the entire list of issues. Inactive issues
1984 will be marked as such. If the user types an issue name not
1985 in the list of existing issues a new issue can be created
1986 from it if the programmer activated that feature.
1987
1988 If keyword <patient_id> is set to None or left out the control
1989 will listen to patient change signals and therefore act on
1990 gmPerson.gmCurrentPatient() changes.
1991 """
1993
1994 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1995
1996 mp = gmMatchProvider.cMatchProvider_SQL2 (
1997
1998 queries = [
1999 u"""
2000 SELECT
2001 data,
2002 field_label,
2003 list_label
2004 FROM ((
2005 SELECT
2006 pk_health_issue AS data,
2007 description AS field_label,
2008 description AS list_label
2009 FROM clin.v_health_issues
2010 WHERE
2011 is_active IS true
2012 AND
2013 description %(fragment_condition)s
2014 AND
2015 %(ctxt_pat)s
2016
2017 ) UNION (
2018
2019 SELECT
2020 pk_health_issue AS data,
2021 description AS field_label,
2022 description || _(' (inactive)') AS list_label
2023 FROM clin.v_health_issues
2024 WHERE
2025 is_active IS false
2026 AND
2027 description %(fragment_condition)s
2028 AND
2029 %(ctxt_pat)s
2030 )) AS union_query
2031 ORDER BY
2032 list_label"""],
2033 context = ctxt
2034 )
2035
2036 try: kwargs['patient_id']
2037 except KeyError: kwargs['patient_id'] = None
2038
2039 if kwargs['patient_id'] is None:
2040 self.use_current_patient = True
2041 self.__register_patient_change_signals()
2042 pat = gmPerson.gmCurrentPatient()
2043 if pat.connected:
2044 mp.set_context('pat', pat.ID)
2045 else:
2046 self.use_current_patient = False
2047 self.__patient_id = int(kwargs['patient_id'])
2048 mp.set_context('pat', self.__patient_id)
2049
2050 del kwargs['patient_id']
2051
2052 gmPhraseWheel.cPhraseWheel.__init__ (
2053 self,
2054 *args,
2055 **kwargs
2056 )
2057 self.matcher = mp
2058
2059
2060
2062 if self.use_current_patient:
2063 return False
2064 self.__patient_id = int(patient_id)
2065 self.set_context('pat', self.__patient_id)
2066 return True
2067
2069 issue_name = self.GetValue().strip()
2070 if issue_name == u'':
2071 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2072 _log.debug('cannot create health issue without name')
2073 return
2074
2075 if self.use_current_patient:
2076 pat = gmPerson.gmCurrentPatient()
2077 else:
2078 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2079
2080 emr = pat.get_emr()
2081 issue = emr.add_health_issue(issue_name = issue_name)
2082
2083 if issue is None:
2084 self.data = {}
2085 else:
2086 self.SetText (
2087 value = issue_name,
2088 data = issue['pk_health_issue']
2089 )
2090
2093
2094
2095
2099
2102
2104 if self.use_current_patient:
2105 patient = gmPerson.gmCurrentPatient()
2106 self.set_context('pat', patient.ID)
2107 return True
2108
2109 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2110
2133
2134 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2135
2136 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2137 """Panel encapsulating health issue edit area functionality."""
2138
2140
2141 try:
2142 issue = kwargs['issue']
2143 except KeyError:
2144 issue = None
2145
2146 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2147
2148 gmEditArea.cGenericEditAreaMixin.__init__(self)
2149
2150
2151 mp = gmMatchProvider.cMatchProvider_SQL2 (
2152 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2153 )
2154 mp.setThresholds(1, 3, 5)
2155 self._PRW_condition.matcher = mp
2156
2157 mp = gmMatchProvider.cMatchProvider_SQL2 (
2158 queries = [u"""
2159 select distinct on (grouping) grouping, grouping from (
2160
2161 select rank, grouping from ((
2162
2163 select
2164 grouping,
2165 1 as rank
2166 from
2167 clin.health_issue
2168 where
2169 grouping %%(fragment_condition)s
2170 and
2171 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2172
2173 ) union (
2174
2175 select
2176 grouping,
2177 2 as rank
2178 from
2179 clin.health_issue
2180 where
2181 grouping %%(fragment_condition)s
2182
2183 )) as union_result
2184
2185 order by rank
2186
2187 ) as order_result
2188
2189 limit 50""" % gmPerson.gmCurrentPatient().ID
2190 ]
2191 )
2192 mp.setThresholds(1, 3, 5)
2193 self._PRW_grouping.matcher = mp
2194
2195 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2196 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2197
2198 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2199 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2200
2201 self._PRW_year_noted.Enable(True)
2202
2203 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2204
2205 self.data = issue
2206
2207
2208
2228
2230 pat = gmPerson.gmCurrentPatient()
2231 emr = pat.get_emr()
2232
2233 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2234
2235 side = u''
2236 if self._ChBOX_left.GetValue():
2237 side += u's'
2238 if self._ChBOX_right.GetValue():
2239 side += u'd'
2240 issue['laterality'] = side
2241
2242 issue['summary'] = self._TCTRL_status.GetValue().strip()
2243 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2244 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2245 issue['is_active'] = self._ChBOX_active.GetValue()
2246 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2247 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2248 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2249
2250 age_noted = self._PRW_age_noted.GetData()
2251 if age_noted is not None:
2252 issue['age_noted'] = age_noted
2253
2254 issue.save()
2255
2256 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2257
2258 self.data = issue
2259 return True
2260
2262
2263 self.data['description'] = self._PRW_condition.GetValue().strip()
2264
2265 side = u''
2266 if self._ChBOX_left.GetValue():
2267 side += u's'
2268 if self._ChBOX_right.GetValue():
2269 side += u'd'
2270 self.data['laterality'] = side
2271
2272 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2273 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2274 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2275 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2276 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2277 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2278 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2279
2280 age_noted = self._PRW_age_noted.GetData()
2281 if age_noted is not None:
2282 self.data['age_noted'] = age_noted
2283
2284 self.data.save()
2285 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2286
2287 return True
2288
2290 self._PRW_condition.SetText()
2291 self._ChBOX_left.SetValue(0)
2292 self._ChBOX_right.SetValue(0)
2293 self._PRW_codes.SetText()
2294 self._on_leave_codes()
2295 self._PRW_certainty.SetText()
2296 self._PRW_grouping.SetText()
2297 self._TCTRL_status.SetValue(u'')
2298 self._PRW_age_noted.SetText()
2299 self._PRW_year_noted.SetText()
2300 self._ChBOX_active.SetValue(1)
2301 self._ChBOX_relevant.SetValue(1)
2302 self._ChBOX_confidential.SetValue(0)
2303 self._ChBOX_caused_death.SetValue(0)
2304
2305 return True
2306
2347
2349 return self._refresh_as_new()
2350
2351
2352
2354 if not self._PRW_codes.IsModified():
2355 return True
2356
2357 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2358
2360
2361 if not self._PRW_age_noted.IsModified():
2362 return True
2363
2364 str_age = self._PRW_age_noted.GetValue().strip()
2365
2366 if str_age == u'':
2367 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2368 return True
2369
2370 age = gmDateTime.str2interval(str_interval = str_age)
2371
2372 if age is None:
2373 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2374 self._PRW_age_noted.SetBackgroundColour('pink')
2375 self._PRW_age_noted.Refresh()
2376 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2377 return True
2378
2379 pat = gmPerson.gmCurrentPatient()
2380 if pat['dob'] is not None:
2381 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2382
2383 if age >= max_age:
2384 gmDispatcher.send (
2385 signal = 'statustext',
2386 msg = _(
2387 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2388 ) % (age, pat.get_medical_age())
2389 )
2390 self._PRW_age_noted.SetBackgroundColour('pink')
2391 self._PRW_age_noted.Refresh()
2392 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2393 return True
2394
2395 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2396 self._PRW_age_noted.Refresh()
2397 self._PRW_age_noted.SetData(data=age)
2398
2399 if pat['dob'] is not None:
2400 fts = gmDateTime.cFuzzyTimestamp (
2401 timestamp = pat['dob'] + age,
2402 accuracy = gmDateTime.acc_months
2403 )
2404 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2405
2406
2407
2408
2409
2410 return True
2411
2413
2414 if not self._PRW_year_noted.IsModified():
2415 return True
2416
2417 year_noted = self._PRW_year_noted.GetData()
2418
2419 if year_noted is None:
2420 if self._PRW_year_noted.GetValue().strip() == u'':
2421 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2422 return True
2423 self._PRW_year_noted.SetBackgroundColour('pink')
2424 self._PRW_year_noted.Refresh()
2425 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2426 return True
2427
2428 year_noted = year_noted.get_pydt()
2429
2430 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2431 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2432 self._PRW_year_noted.SetBackgroundColour('pink')
2433 self._PRW_year_noted.Refresh()
2434 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2435 return True
2436
2437 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2438 self._PRW_year_noted.Refresh()
2439
2440 pat = gmPerson.gmCurrentPatient()
2441 if pat['dob'] is not None:
2442 issue_age = year_noted - pat['dob']
2443 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2444 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2445
2446 return True
2447
2449 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2450 return True
2451
2453 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2454 return True
2455
2456
2457
2459
2461
2462 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2463
2464 self.selection_only = False
2465
2466 mp = gmMatchProvider.cMatchProvider_FixedList (
2467 aSeq = [
2468 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2469 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2470 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2471 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2472 ]
2473 )
2474 mp.setThresholds(1, 2, 4)
2475 self.matcher = mp
2476
2477 self.SetToolTipString(_(
2478 "The diagnostic classification or grading of this assessment.\n"
2479 "\n"
2480 "This documents how certain one is about this being a true diagnosis."
2481 ))
2482
2483
2484
2485 if __name__ == '__main__':
2486
2487 from Gnumed.business import gmPersonSearch
2488 from Gnumed.wxpython import gmPatSearchWidgets
2489
2490
2492 """
2493 Test application for testing EMR struct widgets
2494 """
2495
2497 """
2498 Create test application UI
2499 """
2500 frame = wx.Frame (
2501 None,
2502 -4,
2503 'Testing EMR struct widgets',
2504 size=wx.Size(600, 400),
2505 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2506 )
2507 filemenu= wx.Menu()
2508 filemenu.AppendSeparator()
2509 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2510
2511
2512 menuBar = wx.MenuBar()
2513 menuBar.Append(filemenu,"&File")
2514
2515 frame.SetMenuBar(menuBar)
2516
2517 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2518 wx.DefaultPosition, wx.DefaultSize, 0 )
2519
2520
2521 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2522
2523
2524 self.__pat = gmPerson.gmCurrentPatient()
2525
2526 frame.Show(1)
2527 return 1
2528
2530 """
2531 Close test aplication
2532 """
2533 self.ExitMainLoop ()
2534
2544
2553
2554
2555
2556
2557
2565
2571
2573 app = wx.PyWidgetTester(size = (400, 40))
2574 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2575 app.MainLoop()
2576
2578 app = wx.PyWidgetTester(size = (400, 40))
2579 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2580
2581 app.MainLoop()
2582
2584 app = wx.PyWidgetTester(size = (200, 300))
2585 edit_health_issue(parent=app.frame, issue=None)
2586
2588 app = wx.PyWidgetTester(size = (200, 300))
2589 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2590 app.MainLoop()
2591
2593 app = wx.PyWidgetTester(size = (200, 300))
2594 edit_procedure(parent=app.frame)
2595
2596
2597 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2598
2599 gmI18N.activate_locale()
2600 gmI18N.install_domain()
2601 gmDateTime.init()
2602
2603
2604 pat = gmPersonSearch.ask_for_patient()
2605 if pat is None:
2606 print "No patient. Exiting gracefully..."
2607 sys.exit(0)
2608 gmPatSearchWidgets.set_active_patient(patient=pat)
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626 test_edit_procedure()
2627
2628
2629