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 u'%s @ %s' % (s['ward'], s['hospital'])
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
532
534 """Phrasewheel to allow selection of a hospitalization."""
536
537 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
538
539 query = u"""
540 SELECT data, list_label, field_label FROM (
541 SELECT DISTINCT ON (data) * FROM ((
542
543 -- already-used org_units
544 SELECT
545 pk_org_unit
546 AS data,
547 ward || ' @ ' || hospital
548 AS list_label,
549 ward || ' @ ' || hospital
550 AS field_label,
551 1
552 AS rank
553 FROM
554 clin.v_hospital_stays
555 WHERE
556 ward %(fragment_condition)s
557 OR
558 hospital %(fragment_condition)s
559
560 ) UNION ALL (
561 -- wards
562 SELECT
563 pk_org_unit
564 AS data,
565 unit || ' (' || l10n_unit_category || ') @ ' || organization
566 AS list_label,
567 unit || ' @ ' || organization
568 AS field_label,
569 2
570 AS rank
571 FROM
572 dem.v_org_units
573 WHERE
574 unit_category = 'Ward'
575 AND
576 unit %(fragment_condition)s
577 AND
578 NOT EXISTS (
579 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
580 )
581
582 ) UNION ALL (
583 -- hospital units
584 SELECT
585 pk_org_unit
586 AS data,
587 unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
588 AS list_label,
589 unit || ' @ ' || organization
590 AS field_label,
591 3
592 AS rank
593 FROM
594 dem.v_org_units
595 WHERE
596 unit_category <> 'Ward'
597 AND
598 organization_category = 'Hospital'
599 AND
600 unit %(fragment_condition)s
601 AND
602 NOT EXISTS (
603 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
604 )
605
606 ) UNION ALL (
607 -- any other units
608 SELECT
609 pk_org_unit
610 AS data,
611 unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
612 AS list_label,
613 unit || ' @ ' || organization
614 AS field_label,
615 3
616 AS rank
617 FROM
618 dem.v_org_units
619 WHERE
620 unit_category <> 'Ward'
621 AND
622 organization_category <> 'Hospital'
623 AND
624 unit %(fragment_condition)s
625 AND
626 NOT EXISTS (
627 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
628 )
629 )) AS all_matches
630 ORDER BY data, rank
631 ) AS distinct_matches
632 ORDER BY rank, list_label
633 LIMIT 50
634 """
635
636 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
637 mp.setThresholds(2, 4, 6)
638 self.matcher = mp
639 self.selection_only = True
640
641
643 """Phrasewheel to allow selection of a hospital-type org_unit."""
645
646 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
647
648 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
649
650 mp = gmMatchProvider.cMatchProvider_SQL2 (
651 queries = [
652 u"""
653 SELECT
654 pk_hospital_stay,
655 descr
656 FROM (
657 SELECT DISTINCT ON (pk_hospital_stay)
658 pk_hospital_stay,
659 descr
660 FROM
661 (SELECT
662 pk_hospital_stay,
663 (
664 to_char(admission, 'YYYY-Mon-DD')
665 || ' (' || ward || ' @ ' || hospital || '):'
666 || episode
667 || coalesce((' (' || health_issue || ')'), '')
668 ) AS descr
669 FROM
670 clin.v_hospital_stays
671 WHERE
672 %(ctxt_pat)s
673
674 hospital %(fragment_condition)s
675 OR
676 ward %(fragment_condition)s
677 OR
678 episode %(fragment_condition)s
679 OR
680 health_issue %(fragment_condition)s
681 ) AS the_stays
682 ) AS distinct_stays
683 ORDER BY descr
684 LIMIT 25
685 """ ],
686 context = ctxt
687 )
688 mp.setThresholds(3, 4, 6)
689 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
690
691 self.matcher = mp
692 self.selection_only = True
693
694
695 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
696
697 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
698
702
703
704
706
707 valid = True
708
709 if self._PRW_episode.GetValue().strip() == u'':
710 valid = False
711 self._PRW_episode.display_as_valid(False)
712 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
713 self._PRW_episode.SetFocus()
714
715 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
716 valid = False
717 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
718 self._PRW_admission.SetFocus()
719
720 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
721 if self._PRW_discharge.date is not None:
722 adm = self._PRW_admission.date
723 discharge = self._PRW_discharge.date
724
725 discharge = discharge.replace (
726 hour = adm.hour,
727 minute = adm.minute,
728 second = adm.second,
729 microsecond = adm.microsecond
730 )
731 if adm is not None:
732 if discharge == adm:
733 self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
734 elif not self._PRW_discharge.date > self._PRW_admission.date:
735 valid = False
736 self._PRW_discharge.display_as_valid(False)
737 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
738 self._PRW_discharge.SetFocus()
739
740 if self._PRW_hospital.GetData() is None:
741 self._PRW_hospital.display_as_valid(False)
742 self.status_message = _('Must select a hospital. Cannot save hospitalization.')
743 self._PRW_hospital.SetFocus()
744 else:
745 self._PRW_hospital.display_as_valid(True)
746
747 return (valid is True)
748
762
764
765 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
766 self.data['pk_org_unit'] = self._PRW_hospital.GetData()
767 self.data['admission'] = self._PRW_admission.GetData()
768 self.data['discharge'] = self._PRW_discharge.GetData()
769 self.data['comment'] = self._TCTRL_comment.GetValue()
770 self.data.save_payload()
771
772 return True
773
781
793
795 print "this was not expected to be used in this edit area"
796
797
798
799
808
809 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
810
812 if parent is None:
813 parent = wx.GetApp().GetTopWindow()
814
815
816 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
817 if dlg.ShowModal() == wx.ID_OK:
818 dlg.Destroy()
819 return True
820 dlg.Destroy()
821 return False
822
825
826 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
854
855 def edit(enc=None):
856 return edit_encounter(parent = parent, encounter = enc)
857
858 def edit_active(enc=None):
859 return edit_encounter(parent = parent, encounter = emr.active_encounter)
860
861 def start_new(enc=None):
862 start_new_encounter(emr = emr)
863 return True
864
865 def get_tooltip(data):
866 if data is None:
867 return None
868 return data.format (
869 patient = patient,
870 with_soap = False,
871 with_docs = False,
872 with_tests = False,
873 with_vaccinations = False,
874 with_rfe_aoe = True,
875 with_family_history = False,
876 by_episode=False,
877 fancy_header = True,
878 )
879
880 def refresh(lctrl):
881 if encounters is None:
882 encs = emr.get_encounters()
883 else:
884 encs = encounters
885
886 items = [
887 [
888 u'%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'), e['last_affirmed'].strftime('%H:%M')),
889 e['l10n_type'],
890 gmTools.coalesce(e['praxis_branch'], u''),
891 gmTools.coalesce(e['reason_for_encounter'], u''),
892 gmTools.coalesce(e['assessment_of_encounter'], u''),
893 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
894 e['pk_encounter']
895 ] for e in encs
896 ]
897 lctrl.set_string_items(items = items)
898 lctrl.set_data(data = encs)
899 active_pk = emr.active_encounter['pk_encounter']
900 for idx in range(len(encs)):
901 e = encs[idx]
902 if e['pk_encounter'] == active_pk:
903 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
904
905 return gmListWidgets.get_choices_from_list (
906 parent = parent,
907 msg = _("The patient's encounters.\n"),
908 caption = _('Encounters ...'),
909 columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
910 can_return_empty = False,
911 single_selection = single_selection,
912 refresh_callback = refresh,
913 edit_callback = edit,
914 new_callback = new,
915 list_tooltip_callback = get_tooltip,
916 ignore_OK_button = ignore_OK_button,
917 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
918 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
919 )
920
921
923 """This is used as the callback when the EMR detects that the
924 patient was here rather recently and wants to ask the
925 provider whether to continue the recent encounter.
926 """
927 if parent is None:
928 parent = wx.GetApp().GetTopWindow()
929
930 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
931 parent = None,
932 id = -1,
933 caption = caption,
934 question = msg,
935 button_defs = [
936 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
937 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
938 ],
939 show_checkbox = False
940 )
941
942 result = dlg.ShowModal()
943 dlg.Destroy()
944
945 if result == wx.ID_YES:
946 return True
947
948 return False
949
951
952 if parent is None:
953 parent = wx.GetApp().GetTopWindow()
954
955
956 def edit(enc_type=None):
957 return edit_encounter_type(parent = parent, encounter_type = enc_type)
958
959 def delete(enc_type=None):
960 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
961 return True
962 gmDispatcher.send (
963 signal = u'statustext',
964 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
965 beep = True
966 )
967 return False
968
969 def refresh(lctrl):
970 enc_types = gmEMRStructItems.get_encounter_types()
971 lctrl.set_string_items(items = enc_types)
972
973 gmListWidgets.get_choices_from_list (
974 parent = parent,
975 msg = _('\nSelect the encounter type you want to edit !\n'),
976 caption = _('Managing encounter types ...'),
977 columns = [_('Local name'), _('Encounter type')],
978 single_selection = True,
979 edit_callback = edit,
980 new_callback = edit,
981 delete_callback = delete,
982 refresh_callback = refresh
983 )
984
994
996
998 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
999
1000 cmd = u"""
1001 SELECT DISTINCT ON (list_label)
1002 pk_encounter
1003 AS data,
1004 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
1005 AS list_label,
1006 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
1007 AS field_label
1008 FROM
1009 clin.v_pat_encounters
1010 WHERE
1011 (
1012 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
1013 OR
1014 l10n_type %(fragment_condition)s
1015 OR
1016 type %(fragment_condition)s
1017 ) %(ctxt_patient)s
1018 ORDER BY
1019 list_label
1020 LIMIT
1021 30
1022 """
1023 context = {'ctxt_patient': {
1024 'where_part': u'AND pk_patient = %(patient)s',
1025 'placeholder': u'patient'
1026 }}
1027
1028 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
1029 self.matcher._SQL_data2match = u"""
1030 SELECT
1031 pk_encounter
1032 AS data,
1033 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
1034 AS list_label,
1035 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
1036 AS field_label
1037 FROM
1038 clin.v_pat_encounters
1039 WHERE
1040 pk_encounter = %(pk)s
1041 """
1042 self.matcher.setThresholds(1, 3, 5)
1043
1044 self.selection_only = True
1045
1046 self.set_context(context = 'patient', val = None)
1047
1054
1065
1067 """Phrasewheel to allow selection of encounter type.
1068
1069 - user input interpreted as encounter type in English or local language
1070 - data returned is pk of corresponding encounter type or None
1071 """
1073
1074 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
1075
1076 mp = gmMatchProvider.cMatchProvider_SQL2 (
1077 queries = [
1078 u"""
1079 SELECT
1080 data,
1081 field_label,
1082 list_label
1083 FROM (
1084 SELECT DISTINCT ON (data) *
1085 FROM (
1086 SELECT
1087 pk AS data,
1088 _(description) AS field_label,
1089 case
1090 when _(description) = description then _(description)
1091 else _(description) || ' (' || description || ')'
1092 end AS list_label
1093 FROM
1094 clin.encounter_type
1095 WHERE
1096 _(description) %(fragment_condition)s
1097 OR
1098 description %(fragment_condition)s
1099 ) AS q_distinct_pk
1100 ) AS q_ordered
1101 ORDER BY
1102 list_label
1103 """ ]
1104 )
1105 mp.setThresholds(2, 4, 6)
1106
1107 self.matcher = mp
1108 self.selection_only = True
1109 self.picklist_delay = 50
1110
1111 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
1112
1114
1119
1120
1121
1122
1123
1125 if self.mode == 'edit':
1126 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1127 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1128 return False
1129 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1130 return True
1131
1132 no_errors = True
1133
1134 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1135 if self._TCTRL_name.GetValue().strip() == u'':
1136 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1137 no_errors = False
1138 else:
1139 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1140 else:
1141 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1142
1143 if self._TCTRL_name.GetValue().strip() == u'':
1144 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1145 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
1146 no_errors = False
1147 else:
1148 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1149 else:
1150 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1151
1152 return no_errors
1153
1166
1176
1178 self._TCTRL_l10n_name.SetValue(u'')
1179 self._TCTRL_name.SetValue(u'')
1180 self._TCTRL_name.Enable(True)
1181
1183 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1184 self._TCTRL_name.SetValue(self.data['description'])
1185
1186 self._TCTRL_name.Enable(False)
1187
1189 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1190 self._TCTRL_name.SetValue(self.data['description'])
1191 self._TCTRL_name.Enable(True)
1192
1193
1194
1195
1196
1197
1198 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1199
1201
1203 try:
1204 self.__encounter = kwargs['encounter']
1205 del kwargs['encounter']
1206 except KeyError:
1207 self.__encounter = None
1208
1209 try:
1210 msg = kwargs['msg']
1211 del kwargs['msg']
1212 except KeyError:
1213 msg = None
1214
1215 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1216
1217 self.refresh(msg = msg)
1218
1219
1220
1221 - def refresh(self, encounter=None, msg=None):
1222
1223 if msg is not None:
1224 self._LBL_instructions.SetLabel(msg)
1225
1226 if encounter is not None:
1227 self.__encounter = encounter
1228
1229 if self.__encounter is None:
1230 return True
1231
1232
1233
1234 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1235 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
1236 curr_pat = gmPerson.gmCurrentPatient()
1237 if curr_pat.connected:
1238 if curr_pat.ID == self.__encounter['pk_patient']:
1239 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
1240 else:
1241 self._LBL_patient.SetForegroundColour('red')
1242
1243 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
1244 self._PRW_location.Enable(True)
1245 self._PRW_location.display_as_disabled(False)
1246 branch = self.__encounter.praxis_branch
1247 if branch is None:
1248 unit = self.__encounter.org_unit
1249 if unit is None:
1250 self._PRW_location.SetText(u'', data = None)
1251 else:
1252 self._PRW_location.Enable(False)
1253 self._PRW_location.display_as_disabled(True)
1254 self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
1255 else:
1256 self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
1257
1258 fts = gmDateTime.cFuzzyTimestamp (
1259 timestamp = self.__encounter['started'],
1260 accuracy = gmDateTime.acc_minutes
1261 )
1262 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1263
1264 fts = gmDateTime.cFuzzyTimestamp (
1265 timestamp = self.__encounter['last_affirmed'],
1266 accuracy = gmDateTime.acc_minutes
1267 )
1268 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1269
1270
1271 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1272 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1273 self._PRW_rfe_codes.SetText(val, data)
1274
1275
1276 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1277 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1278 self._PRW_aoe_codes.SetText(val, data)
1279
1280
1281 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1282 self._PRW_end.SetFocus()
1283 else:
1284 self._TCTRL_aoe.SetFocus()
1285
1286 return True
1287
1289
1290 if self._PRW_encounter_type.GetData() is None:
1291 self._PRW_encounter_type.SetBackgroundColour('pink')
1292 self._PRW_encounter_type.Refresh()
1293 self._PRW_encounter_type.SetFocus()
1294 return False
1295 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1296 self._PRW_encounter_type.Refresh()
1297
1298
1299 if self._PRW_start.GetValue().strip() == u'':
1300 self._PRW_start.SetBackgroundColour('pink')
1301 self._PRW_start.Refresh()
1302 self._PRW_start.SetFocus()
1303 return False
1304 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1305 self._PRW_start.SetBackgroundColour('pink')
1306 self._PRW_start.Refresh()
1307 self._PRW_start.SetFocus()
1308 return False
1309 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1310 self._PRW_start.Refresh()
1311
1312
1313
1314
1315
1316
1317
1318 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1319 self._PRW_end.SetBackgroundColour('pink')
1320 self._PRW_end.Refresh()
1321 self._PRW_end.SetFocus()
1322 return False
1323 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1324 self._PRW_end.Refresh()
1325
1326 return True
1327
1329 if not self.__is_valid_for_save():
1330 return False
1331
1332 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1333 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1334 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1335 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1336 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1337 self.__encounter.save_payload()
1338
1339 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1340 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1341
1342 return True
1343
1344
1346
1348 encounter = kwargs['encounter']
1349 del kwargs['encounter']
1350
1351 try:
1352 button_defs = kwargs['button_defs']
1353 del kwargs['button_defs']
1354 except KeyError:
1355 button_defs = None
1356
1357 try:
1358 msg = kwargs['msg']
1359 del kwargs['msg']
1360 except KeyError:
1361 msg = None
1362
1363 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1364 self.SetSize((450, 280))
1365 self.SetMinSize((450, 280))
1366
1367 if button_defs is not None:
1368 self._BTN_save.SetLabel(button_defs[0][0])
1369 self._BTN_save.SetToolTipString(button_defs[0][1])
1370 self._BTN_close.SetLabel(button_defs[1][0])
1371 self._BTN_close.SetToolTipString(button_defs[1][1])
1372 self.Refresh()
1373
1374 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1375
1376 self.Fit()
1377
1384
1386 start = self._PRW_encounter_start.GetData()
1387 if start is None:
1388 return
1389 start = start.get_pydt()
1390
1391 end = self._PRW_encounter_end.GetData()
1392 if end is None:
1393 fts = gmDateTime.cFuzzyTimestamp (
1394 timestamp = start,
1395 accuracy = gmDateTime.acc_minutes
1396 )
1397 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1398 return
1399 end = end.get_pydt()
1400
1401 if start > end:
1402 end = end.replace (
1403 year = start.year,
1404 month = start.month,
1405 day = start.day
1406 )
1407 fts = gmDateTime.cFuzzyTimestamp (
1408 timestamp = end,
1409 accuracy = gmDateTime.acc_minutes
1410 )
1411 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1412 return
1413
1414 emr = self.__pat.get_emr()
1415 if start != emr.active_encounter['started']:
1416 end = end.replace (
1417 year = start.year,
1418 month = start.month,
1419 day = start.day
1420 )
1421 fts = gmDateTime.cFuzzyTimestamp (
1422 timestamp = end,
1423 accuracy = gmDateTime.acc_minutes
1424 )
1425 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1426 return
1427
1428 return
1429
1430
1431 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1432
1434
1439
1441 self._TCTRL_encounter.SetValue(u'')
1442 self._TCTRL_encounter.SetToolTipString(u'')
1443 self._BTN_new.Enable(False)
1444 self._BTN_list.Enable(False)
1445
1447 pat = gmPerson.gmCurrentPatient()
1448 if not pat.connected:
1449 self.clear()
1450 return
1451
1452 enc = pat.get_emr().active_encounter
1453 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1454 self._TCTRL_encounter.SetToolTipString (
1455 _('The active encounter of the current patient:\n\n%s') %
1456 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1457 )
1458 self._BTN_new.Enable(True)
1459 self._BTN_list.Enable(True)
1460
1462 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1463
1464 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1465
1466
1467 gmDispatcher.connect(signal = u'clin.episode_mod_db', receiver = self._schedule_refresh)
1468 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1469 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1470
1471
1472
1474 wx.CallAfter(self.clear)
1475
1477 wx.CallAfter(self.refresh)
1478 return True
1479
1485
1491
1496
1497
1498
1508
1578
1580 """Prepare changing health issue for an episode.
1581
1582 Checks for two-open-episodes conflict. When this
1583 function succeeds, the pk_health_issue has been set
1584 on the episode instance and the episode should - for
1585 all practical purposes - be ready for save_payload().
1586 """
1587
1588 if not episode['episode_open']:
1589 episode['pk_health_issue'] = target_issue['pk_health_issue']
1590 if save_to_backend:
1591 episode.save_payload()
1592 return True
1593
1594
1595 if target_issue is None:
1596 episode['pk_health_issue'] = None
1597 if save_to_backend:
1598 episode.save_payload()
1599 return True
1600
1601
1602 db_cfg = gmCfg.cCfgSQL()
1603 epi_ttl = int(db_cfg.get2 (
1604 option = u'episode.ttl',
1605 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1606 bias = 'user',
1607 default = 60
1608 ))
1609 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1610 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1611 existing_epi = target_issue.get_open_episode()
1612
1613
1614 if existing_epi is None:
1615 episode['pk_health_issue'] = target_issue['pk_health_issue']
1616 if save_to_backend:
1617 episode.save_payload()
1618 return True
1619
1620
1621 if existing_epi['pk_episode'] == episode['pk_episode']:
1622 episode['pk_health_issue'] = target_issue['pk_health_issue']
1623 if save_to_backend:
1624 episode.save_payload()
1625 return True
1626
1627
1628 move_range = episode.get_access_range()
1629 exist_range = existing_epi.get_access_range()
1630 question = _(
1631 'You want to associate the running episode:\n\n'
1632 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1633 'with the health issue:\n\n'
1634 ' "%(issue_name)s"\n\n'
1635 'There already is another episode running\n'
1636 'for this health issue:\n\n'
1637 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1638 'However, there can only be one running\n'
1639 'episode per health issue.\n\n'
1640 'Which episode do you want to close ?'
1641 ) % {
1642 'new_epi_name': episode['description'],
1643 'new_epi_start': move_range[0].strftime('%m/%y'),
1644 'new_epi_end': move_range[1].strftime('%m/%y'),
1645 'issue_name': target_issue['description'],
1646 'old_epi_name': existing_epi['description'],
1647 'old_epi_start': exist_range[0].strftime('%m/%y'),
1648 'old_epi_end': exist_range[1].strftime('%m/%y')
1649 }
1650 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1651 parent = None,
1652 id = -1,
1653 caption = _('Resolving two-running-episodes conflict'),
1654 question = question,
1655 button_defs = [
1656 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1657 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1658 ]
1659 )
1660 decision = dlg.ShowModal()
1661
1662 if decision == wx.ID_CANCEL:
1663
1664 return False
1665
1666 elif decision == wx.ID_YES:
1667
1668 existing_epi['episode_open'] = False
1669 existing_epi.save_payload()
1670
1671 elif decision == wx.ID_NO:
1672
1673 episode['episode_open'] = False
1674
1675 else:
1676 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1677
1678 episode['pk_health_issue'] = target_issue['pk_health_issue']
1679 if save_to_backend:
1680 episode.save_payload()
1681 return True
1682
1706
1708 """Let user select an episode *description*.
1709
1710 The user can select an episode description from the previously
1711 used descriptions across all episodes across all patients.
1712
1713 Selection is done with a phrasewheel so the user can
1714 type the episode name and matches will be shown. Typing
1715 "*" will show the entire list of episodes.
1716
1717 If the user types a description not existing yet a
1718 new episode description will be returned.
1719 """
1721
1722 mp = gmMatchProvider.cMatchProvider_SQL2 (
1723 queries = [
1724 u"""
1725 SELECT DISTINCT ON (description)
1726 description
1727 AS data,
1728 description
1729 AS field_label,
1730 description || ' ('
1731 || CASE
1732 WHEN is_open IS TRUE THEN _('ongoing')
1733 ELSE _('closed')
1734 END
1735 || ')'
1736 AS list_label
1737 FROM
1738 clin.episode
1739 WHERE
1740 description %(fragment_condition)s
1741 ORDER BY description
1742 LIMIT 30
1743 """
1744 ]
1745 )
1746 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1747 self.matcher = mp
1748
1750 """Let user select an episode.
1751
1752 The user can select an episode from the existing episodes of a
1753 patient. Selection is done with a phrasewheel so the user
1754 can type the episode name and matches will be shown. Typing
1755 "*" will show the entire list of episodes. Closed episodes
1756 will be marked as such. If the user types an episode name not
1757 in the list of existing episodes a new episode can be created
1758 from it if the programmer activated that feature.
1759
1760 If keyword <patient_id> is set to None or left out the control
1761 will listen to patient change signals and therefore act on
1762 gmPerson.gmCurrentPatient() changes.
1763 """
1765
1766 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1767
1768 mp = gmMatchProvider.cMatchProvider_SQL2 (
1769 queries = [
1770 u"""(
1771
1772 SELECT
1773 pk_episode
1774 as data,
1775 description
1776 as field_label,
1777 coalesce (
1778 description || ' - ' || health_issue,
1779 description
1780 ) as list_label,
1781 1 as rank
1782 from
1783 clin.v_pat_episodes
1784 where
1785 episode_open is true and
1786 description %(fragment_condition)s
1787 %(ctxt_pat)s
1788
1789 ) union all (
1790
1791 SELECT
1792 pk_episode
1793 as data,
1794 description
1795 as field_label,
1796 coalesce (
1797 description || _(' (closed)') || ' - ' || health_issue,
1798 description || _(' (closed)')
1799 ) as list_label,
1800 2 as rank
1801 from
1802 clin.v_pat_episodes
1803 where
1804 description %(fragment_condition)s and
1805 episode_open is false
1806 %(ctxt_pat)s
1807
1808 )
1809
1810 order by rank, list_label
1811 limit 30"""
1812 ],
1813 context = ctxt
1814 )
1815
1816 try:
1817 kwargs['patient_id']
1818 except KeyError:
1819 kwargs['patient_id'] = None
1820
1821 if kwargs['patient_id'] is None:
1822 self.use_current_patient = True
1823 self.__register_patient_change_signals()
1824 pat = gmPerson.gmCurrentPatient()
1825 if pat.connected:
1826 mp.set_context('pat', pat.ID)
1827 else:
1828 self.use_current_patient = False
1829 self.__patient_id = int(kwargs['patient_id'])
1830 mp.set_context('pat', self.__patient_id)
1831
1832 del kwargs['patient_id']
1833
1834 gmPhraseWheel.cPhraseWheel.__init__ (
1835 self,
1836 *args,
1837 **kwargs
1838 )
1839 self.matcher = mp
1840
1841
1842
1844 if self.use_current_patient:
1845 return False
1846 self.__patient_id = int(patient_id)
1847 self.set_context('pat', self.__patient_id)
1848 return True
1849
1850 - def GetData(self, can_create=False, as_instance=False, is_open=False):
1853
1855
1856 epi_name = self.GetValue().strip()
1857 if epi_name == u'':
1858 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1859 _log.debug('cannot create episode without name')
1860 return
1861
1862 if self.use_current_patient:
1863 pat = gmPerson.gmCurrentPatient()
1864 else:
1865 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1866
1867 emr = pat.get_emr()
1868 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1869 if epi is None:
1870 self.data = {}
1871 else:
1872 self.SetText (
1873 value = epi_name,
1874 data = epi['pk_episode']
1875 )
1876
1879
1880
1881
1885
1888
1890 if self.use_current_patient:
1891 patient = gmPerson.gmCurrentPatient()
1892 self.set_context('pat', patient.ID)
1893 return True
1894
1895 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1896
1897 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1898
1911
1912
1913
1915
1916 errors = False
1917
1918 if len(self._PRW_description.GetValue().strip()) == 0:
1919 errors = True
1920 self._PRW_description.display_as_valid(False)
1921 self._PRW_description.SetFocus()
1922 else:
1923 self._PRW_description.display_as_valid(True)
1924 self._PRW_description.Refresh()
1925
1926 return not errors
1927
1929
1930 pat = gmPerson.gmCurrentPatient()
1931 emr = pat.get_emr()
1932
1933 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1934 epi['summary'] = self._TCTRL_status.GetValue().strip()
1935 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1936 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1937
1938 issue_name = self._PRW_issue.GetValue().strip()
1939 if len(issue_name) != 0:
1940 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1941 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1942
1943 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1944 gmDispatcher.send (
1945 signal = 'statustext',
1946 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1947 epi['description'],
1948 issue['description']
1949 )
1950 )
1951 gmEMRStructItems.delete_episode(episode = epi)
1952 return False
1953
1954 epi.save()
1955
1956 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1957
1958 self.data = epi
1959 return True
1960
1962
1963 self.data['description'] = self._PRW_description.GetValue().strip()
1964 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1965 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1966 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1967
1968 issue_name = self._PRW_issue.GetValue().strip()
1969 if len(issue_name) == 0:
1970 self.data['pk_health_issue'] = None
1971 else:
1972 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1973 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1974
1975 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1976 gmDispatcher.send (
1977 signal = 'statustext',
1978 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1979 self.data['description'],
1980 issue['description']
1981 )
1982 )
1983 return False
1984
1985 self.data.save()
1986 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1987
1988 return True
1989
2002
2021
2023 self._refresh_as_new()
2024
2025
2026
2036
2038
2039 if parent is None:
2040 parent = wx.GetApp().GetTopWindow()
2041
2042 def refresh(lctrl):
2043 issues = emr.get_health_issues()
2044 items = [
2045 [
2046 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
2047 i['description'],
2048 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
2049 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
2050 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
2051 ] for i in issues
2052 ]
2053 lctrl.set_string_items(items = items)
2054 lctrl.set_data(data = issues)
2055
2056 return gmListWidgets.get_choices_from_list (
2057 parent = parent,
2058 msg = _('\nSelect the health issues !\n'),
2059 caption = _('Showing health issues ...'),
2060 columns = [u'', _('Health issue'), u'', u'', u''],
2061 single_selection = False,
2062
2063
2064
2065 refresh_callback = refresh
2066 )
2067
2069
2070
2071
2073
2074 issues = kwargs['issues']
2075 del kwargs['issues']
2076
2077 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
2078
2079 self.SetTitle(_('Select the health issues you are interested in ...'))
2080 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
2081
2082 for issue in issues:
2083 if issue['is_confidential']:
2084 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
2085 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
2086 else:
2087 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
2088
2089 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
2090 if issue['clinically_relevant']:
2091 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
2092 if issue['is_active']:
2093 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
2094 if issue['is_cause_of_death']:
2095 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
2096
2097 self._LCTRL_items.set_column_widths()
2098 self._LCTRL_items.set_data(data = issues)
2099
2101 """Let the user select a health issue.
2102
2103 The user can select a health issue from the existing issues
2104 of a patient. Selection is done with a phrasewheel so the user
2105 can type the issue name and matches will be shown. Typing
2106 "*" will show the entire list of issues. Inactive issues
2107 will be marked as such. If the user types an issue name not
2108 in the list of existing issues a new issue can be created
2109 from it if the programmer activated that feature.
2110
2111 If keyword <patient_id> is set to None or left out the control
2112 will listen to patient change signals and therefore act on
2113 gmPerson.gmCurrentPatient() changes.
2114 """
2116
2117 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
2118
2119 mp = gmMatchProvider.cMatchProvider_SQL2 (
2120
2121 queries = [
2122 u"""
2123 SELECT
2124 data,
2125 field_label,
2126 list_label
2127 FROM ((
2128 SELECT
2129 pk_health_issue AS data,
2130 description AS field_label,
2131 description AS list_label
2132 FROM clin.v_health_issues
2133 WHERE
2134 is_active IS true
2135 AND
2136 description %(fragment_condition)s
2137 AND
2138 %(ctxt_pat)s
2139
2140 ) UNION (
2141
2142 SELECT
2143 pk_health_issue AS data,
2144 description AS field_label,
2145 description || _(' (inactive)') AS list_label
2146 FROM clin.v_health_issues
2147 WHERE
2148 is_active IS false
2149 AND
2150 description %(fragment_condition)s
2151 AND
2152 %(ctxt_pat)s
2153 )) AS union_query
2154 ORDER BY
2155 list_label"""],
2156 context = ctxt
2157 )
2158
2159 try: kwargs['patient_id']
2160 except KeyError: kwargs['patient_id'] = None
2161
2162 if kwargs['patient_id'] is None:
2163 self.use_current_patient = True
2164 self.__register_patient_change_signals()
2165 pat = gmPerson.gmCurrentPatient()
2166 if pat.connected:
2167 mp.set_context('pat', pat.ID)
2168 else:
2169 self.use_current_patient = False
2170 self.__patient_id = int(kwargs['patient_id'])
2171 mp.set_context('pat', self.__patient_id)
2172
2173 del kwargs['patient_id']
2174
2175 gmPhraseWheel.cPhraseWheel.__init__ (
2176 self,
2177 *args,
2178 **kwargs
2179 )
2180 self.matcher = mp
2181
2182
2183
2185 if self.use_current_patient:
2186 return False
2187 self.__patient_id = int(patient_id)
2188 self.set_context('pat', self.__patient_id)
2189 return True
2190
2192 issue_name = self.GetValue().strip()
2193 if issue_name == u'':
2194 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2195 _log.debug('cannot create health issue without name')
2196 return
2197
2198 if self.use_current_patient:
2199 pat = gmPerson.gmCurrentPatient()
2200 else:
2201 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2202
2203 emr = pat.get_emr()
2204 issue = emr.add_health_issue(issue_name = issue_name)
2205
2206 if issue is None:
2207 self.data = {}
2208 else:
2209 self.SetText (
2210 value = issue_name,
2211 data = issue['pk_health_issue']
2212 )
2213
2216
2217
2218
2222
2225
2227 if self.use_current_patient:
2228 patient = gmPerson.gmCurrentPatient()
2229 self.set_context('pat', patient.ID)
2230 return True
2231
2232 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2233
2256
2257 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2258
2259 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2260 """Panel encapsulating health issue edit area functionality."""
2261
2263
2264 try:
2265 issue = kwargs['issue']
2266 except KeyError:
2267 issue = None
2268
2269 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2270
2271 gmEditArea.cGenericEditAreaMixin.__init__(self)
2272
2273
2274 mp = gmMatchProvider.cMatchProvider_SQL2 (
2275 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2276 )
2277 mp.setThresholds(1, 3, 5)
2278 self._PRW_condition.matcher = mp
2279
2280 mp = gmMatchProvider.cMatchProvider_SQL2 (
2281 queries = [u"""
2282 SELECT DISTINCT ON (grouping) grouping, grouping from (
2283
2284 SELECT rank, grouping from ((
2285
2286 SELECT
2287 grouping,
2288 1 as rank
2289 from
2290 clin.health_issue
2291 where
2292 grouping %%(fragment_condition)s
2293 and
2294 (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2295
2296 ) union (
2297
2298 SELECT
2299 grouping,
2300 2 as rank
2301 from
2302 clin.health_issue
2303 where
2304 grouping %%(fragment_condition)s
2305
2306 )) as union_result
2307
2308 order by rank
2309
2310 ) as order_result
2311
2312 limit 50""" % gmPerson.gmCurrentPatient().ID
2313 ]
2314 )
2315 mp.setThresholds(1, 3, 5)
2316 self._PRW_grouping.matcher = mp
2317
2318 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2319 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2320
2321 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2322 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2323
2324 self._PRW_year_noted.Enable(True)
2325
2326 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2327
2328 self.data = issue
2329
2330
2331
2351
2353 pat = gmPerson.gmCurrentPatient()
2354 emr = pat.get_emr()
2355
2356 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2357
2358 side = u''
2359 if self._ChBOX_left.GetValue():
2360 side += u's'
2361 if self._ChBOX_right.GetValue():
2362 side += u'd'
2363 issue['laterality'] = side
2364
2365 issue['summary'] = self._TCTRL_status.GetValue().strip()
2366 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2367 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2368 issue['is_active'] = self._ChBOX_active.GetValue()
2369 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2370 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2371 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2372
2373 age_noted = self._PRW_age_noted.GetData()
2374 if age_noted is not None:
2375 issue['age_noted'] = age_noted
2376
2377 issue.save()
2378
2379 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2380
2381 self.data = issue
2382 return True
2383
2385
2386 self.data['description'] = self._PRW_condition.GetValue().strip()
2387
2388 side = u''
2389 if self._ChBOX_left.GetValue():
2390 side += u's'
2391 if self._ChBOX_right.GetValue():
2392 side += u'd'
2393 self.data['laterality'] = side
2394
2395 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2396 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2397 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2398 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2399 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2400 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2401 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2402
2403 age_noted = self._PRW_age_noted.GetData()
2404 if age_noted is not None:
2405 self.data['age_noted'] = age_noted
2406
2407 self.data.save()
2408 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2409
2410 return True
2411
2413 self._PRW_condition.SetText()
2414 self._ChBOX_left.SetValue(0)
2415 self._ChBOX_right.SetValue(0)
2416 self._PRW_codes.SetText()
2417 self._on_leave_codes()
2418 self._PRW_certainty.SetText()
2419 self._PRW_grouping.SetText()
2420 self._TCTRL_status.SetValue(u'')
2421 self._PRW_age_noted.SetText()
2422 self._PRW_year_noted.SetText()
2423 self._ChBOX_active.SetValue(1)
2424 self._ChBOX_relevant.SetValue(1)
2425 self._ChBOX_confidential.SetValue(0)
2426 self._ChBOX_caused_death.SetValue(0)
2427
2428 return True
2429
2470
2472 return self._refresh_as_new()
2473
2474
2475
2477 if not self._PRW_codes.IsModified():
2478 return True
2479
2480 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2481
2483
2484 if not self._PRW_age_noted.IsModified():
2485 return True
2486
2487 str_age = self._PRW_age_noted.GetValue().strip()
2488
2489 if str_age == u'':
2490 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2491 return True
2492
2493 age = gmDateTime.str2interval(str_interval = str_age)
2494
2495 if age is None:
2496 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2497 self._PRW_age_noted.SetBackgroundColour('pink')
2498 self._PRW_age_noted.Refresh()
2499 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2500 return True
2501
2502 pat = gmPerson.gmCurrentPatient()
2503 if pat['dob'] is not None:
2504 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2505
2506 if age >= max_age:
2507 gmDispatcher.send (
2508 signal = 'statustext',
2509 msg = _(
2510 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2511 ) % (age, pat.get_medical_age())
2512 )
2513 self._PRW_age_noted.SetBackgroundColour('pink')
2514 self._PRW_age_noted.Refresh()
2515 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2516 return True
2517
2518 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2519 self._PRW_age_noted.Refresh()
2520 self._PRW_age_noted.SetData(data=age)
2521
2522 if pat['dob'] is not None:
2523 fts = gmDateTime.cFuzzyTimestamp (
2524 timestamp = pat['dob'] + age,
2525 accuracy = gmDateTime.acc_months
2526 )
2527 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2528
2529
2530
2531
2532
2533 return True
2534
2536
2537 if not self._PRW_year_noted.IsModified():
2538 return True
2539
2540 year_noted = self._PRW_year_noted.GetData()
2541
2542 if year_noted is None:
2543 if self._PRW_year_noted.GetValue().strip() == u'':
2544 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2545 return True
2546 self._PRW_year_noted.SetBackgroundColour('pink')
2547 self._PRW_year_noted.Refresh()
2548 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2549 return True
2550
2551 year_noted = year_noted.get_pydt()
2552
2553 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2554 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2555 self._PRW_year_noted.SetBackgroundColour('pink')
2556 self._PRW_year_noted.Refresh()
2557 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2558 return True
2559
2560 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2561 self._PRW_year_noted.Refresh()
2562
2563 pat = gmPerson.gmCurrentPatient()
2564 if pat['dob'] is not None:
2565 issue_age = year_noted - pat['dob']
2566 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2567 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2568
2569 return True
2570
2572 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2573 return True
2574
2576 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2577 return True
2578
2579
2580
2582
2584
2585 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2586
2587 self.selection_only = False
2588
2589 mp = gmMatchProvider.cMatchProvider_FixedList (
2590 aSeq = [
2591 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2592 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2593 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2594 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2595 ]
2596 )
2597 mp.setThresholds(1, 2, 4)
2598 self.matcher = mp
2599
2600 self.SetToolTipString(_(
2601 "The diagnostic classification or grading of this assessment.\n"
2602 "\n"
2603 "This documents how certain one is about this being a true diagnosis."
2604 ))
2605
2606
2607
2608 if __name__ == '__main__':
2609
2610 from Gnumed.business import gmPersonSearch
2611 from Gnumed.wxpython import gmPatSearchWidgets
2612
2613
2615 """
2616 Test application for testing EMR struct widgets
2617 """
2618
2620 """
2621 Create test application UI
2622 """
2623 frame = wx.Frame (
2624 None,
2625 -4,
2626 'Testing EMR struct widgets',
2627 size=wx.Size(600, 400),
2628 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2629 )
2630 filemenu= wx.Menu()
2631 filemenu.AppendSeparator()
2632 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2633
2634
2635 menuBar = wx.MenuBar()
2636 menuBar.Append(filemenu,"&File")
2637
2638 frame.SetMenuBar(menuBar)
2639
2640 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2641 wx.DefaultPosition, wx.DefaultSize, 0 )
2642
2643
2644 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2645
2646
2647 self.__pat = gmPerson.gmCurrentPatient()
2648
2649 frame.Show(1)
2650 return 1
2651
2653 """
2654 Close test aplication
2655 """
2656 self.ExitMainLoop ()
2657
2667
2676
2677
2678
2679
2680
2688
2694
2696 app = wx.PyWidgetTester(size = (400, 40))
2697 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2698 app.MainLoop()
2699
2701 app = wx.PyWidgetTester(size = (400, 40))
2702 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2703
2704 app.MainLoop()
2705
2707 app = wx.PyWidgetTester(size = (200, 300))
2708 edit_health_issue(parent=app.frame, issue=None)
2709
2711 app = wx.PyWidgetTester(size = (200, 300))
2712 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2713 app.MainLoop()
2714
2716 app = wx.PyWidgetTester(size = (200, 300))
2717 edit_procedure(parent=app.frame)
2718
2719
2720 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2721
2722 gmI18N.activate_locale()
2723 gmI18N.install_domain()
2724 gmDateTime.init()
2725
2726
2727 pat = gmPersonSearch.ask_for_patient()
2728 if pat is None:
2729 print "No patient. Exiting gracefully..."
2730 sys.exit(0)
2731 gmPatSearchWidgets.set_active_patient(patient=pat)
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749 test_edit_procedure()
2750
2751
2752