1
2 """GNUmed health related business object.
3
4 license: GPL v2 or later
5 """
6
7 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>"
8
9 import sys
10 import datetime
11 import logging
12 import io
13 import os
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmPG2
19 from Gnumed.pycommon import gmI18N
20 from Gnumed.pycommon import gmTools
21 from Gnumed.pycommon import gmDateTime
22 from Gnumed.pycommon import gmBusinessDBObject
23 from Gnumed.pycommon import gmNull
24 from Gnumed.pycommon import gmExceptions
25
26 from Gnumed.business import gmClinNarrative
27 from Gnumed.business import gmSoapDefs
28 from Gnumed.business import gmCoding
29 from Gnumed.business import gmPraxis
30 from Gnumed.business import gmOrganization
31 from Gnumed.business import gmExternalCare
32 from Gnumed.business import gmDocuments
33
34
35 _log = logging.getLogger('gm.emr')
36
37
38 if __name__ == '__main__':
39 gmI18N.activate_locale()
40 gmI18N.install_domain('gnumed')
41
42
43
44
45 __diagnostic_certainty_classification_map = None
46
64
65
66
67
68 laterality2str = {
69 None: '?',
70 'na': '',
71 'sd': _('bilateral'),
72 'ds': _('bilateral'),
73 's': _('left'),
74 'd': _('right')
75 }
76
77
79 """Represents one health issue."""
80
81
82 _cmd_fetch_payload = "select * from clin.v_health_issues where pk_health_issue = %s"
83 _cmds_store_payload = [
84 """update clin.health_issue set
85 description = %(description)s,
86 summary = gm.nullify_empty_string(%(summary)s),
87 age_noted = %(age_noted)s,
88 laterality = gm.nullify_empty_string(%(laterality)s),
89 grouping = gm.nullify_empty_string(%(grouping)s),
90 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
91 is_active = %(is_active)s,
92 clinically_relevant = %(clinically_relevant)s,
93 is_confidential = %(is_confidential)s,
94 is_cause_of_death = %(is_cause_of_death)s
95 WHERE
96 pk = %(pk_health_issue)s
97 AND
98 xmin = %(xmin_health_issue)s""",
99 "select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
100 ]
101 _updatable_fields = [
102 'description',
103 'summary',
104 'grouping',
105 'age_noted',
106 'laterality',
107 'is_active',
108 'clinically_relevant',
109 'is_confidential',
110 'is_cause_of_death',
111 'diagnostic_certainty_classification'
112 ]
113
114
115 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
116 pk = aPK_obj
117
118 if (pk is not None) or (row is not None):
119 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
120 return
121
122 if patient is None:
123 cmd = """select *, xmin_health_issue from clin.v_health_issues
124 where
125 description = %(desc)s
126 and
127 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
128 else:
129 cmd = """select *, xmin_health_issue from clin.v_health_issues
130 where
131 description = %(desc)s
132 and
133 pk_patient = %(pat)s"""
134
135 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
136 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
137
138 if len(rows) == 0:
139 raise gmExceptions.NoSuchBusinessObjectError('no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient))
140
141 pk = rows[0][0]
142 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
143
144 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
145
146
147
148
149 - def rename(self, description=None):
150 """Method for issue renaming.
151
152 @param description
153 - the new descriptive name for the issue
154 @type description
155 - a string instance
156 """
157
158 if not type(description) in [str, str] or description.strip() == '':
159 _log.error('<description> must be a non-empty string')
160 return False
161
162 old_description = self._payload[self._idx['description']]
163 self._payload[self._idx['description']] = description.strip()
164 self._is_modified = True
165 successful, data = self.save_payload()
166 if not successful:
167 _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
168 self._payload[self._idx['description']] = old_description
169 return False
170 return True
171
172
174 cmd = "SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
175 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
176 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
177
178
195
196
204
205
207 return self._payload[self._idx['has_open_episode']]
208
209
211 cmd = "select pk from clin.episode where fk_health_issue = %s and is_open IS True LIMIT 1"
212 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
213 if len(rows) == 0:
214 return None
215 return cEpisode(aPK_obj=rows[0][0])
216
217
219 if self._payload[self._idx['age_noted']] is None:
220 return '<???>'
221
222
223
224
225 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
226
227
229 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
230 cmd = "INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
231 args = {
232 'item': self._payload[self._idx['pk_health_issue']],
233 'code': pk_code
234 }
235 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
236 return True
237
238
240 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
241 cmd = "DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
242 args = {
243 'item': self._payload[self._idx['pk_health_issue']],
244 'code': pk_code
245 }
246 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
247 return True
248
249
302
303
547
548
549
552
553 external_care = property(_get_external_care, lambda x:x)
554
555
556 episodes = property(get_episodes, lambda x:x)
557
558 open_episode = property(get_open_episode, lambda x:x)
559
560 has_open_episode = property(has_open_episode, lambda x:x)
561
562
564
565 args = {'pk_issue': self.pk_obj}
566
567 cmd = """SELECT
568 earliest, pk_episode
569 FROM (
570 -- .modified_when of all episodes of this issue,
571 -- earliest-possible thereof = when created,
572 -- should actually go all the way back into audit.log_episode
573 (SELECT
574 c_epi.modified_when AS earliest,
575 c_epi.pk AS pk_episode
576 FROM clin.episode c_epi
577 WHERE c_epi.fk_health_issue = %(pk_issue)s
578 )
579 UNION ALL
580
581 -- last modification of encounter in which episodes of this issue were created,
582 -- earliest-possible thereof = initial creation of that encounter
583 (SELECT
584 c_enc.modified_when AS earliest,
585 c_epi.pk AS pk_episode
586 FROM
587 clin.episode c_epi
588 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
589 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
590 WHERE c_hi.pk = %(pk_issue)s
591 )
592 UNION ALL
593
594 -- start of encounter in which episodes of this issue were created,
595 -- earliest-possible thereof = set by user
596 (SELECT
597 c_enc.started AS earliest,
598 c_epi.pk AS pk_episode
599 FROM
600 clin.episode c_epi
601 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
602 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
603 WHERE c_hi.pk = %(pk_issue)s
604 )
605 UNION ALL
606
607 -- start of encounters of clinical items linked to episodes of this issue,
608 -- earliest-possible thereof = explicitely set by user
609 (SELECT
610 c_enc.started AS earliest,
611 c_epi.pk AS pk_episode
612 FROM
613 clin.clin_root_item c_cri
614 INNER JOIN clin.encounter c_enc ON (c_cri.fk_encounter = c_enc.pk)
615 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
616 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
617 WHERE c_hi.pk = %(pk_issue)s
618 )
619 UNION ALL
620
621 -- .clin_when of clinical items linked to episodes of this issue,
622 -- earliest-possible thereof = explicitely set by user
623 (SELECT
624 c_cri.clin_when AS earliest,
625 c_epi.pk AS pk_episode
626 FROM
627 clin.clin_root_item c_cri
628 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
629 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
630 WHERE c_hi.pk = %(pk_issue)s
631 )
632 UNION ALL
633
634 -- earliest modification time of clinical items linked to episodes of this issue
635 -- this CAN be used since if an item is linked to an episode it can be
636 -- assumed the episode (should have) existed at the time of creation
637 (SELECT
638 c_cri.modified_when AS earliest,
639 c_epi.pk AS pk_episode
640 FROM
641 clin.clin_root_item c_cri
642 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
643 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
644 WHERE c_hi.pk = %(pk_issue)s
645 )
646 UNION ALL
647
648 -- there may not be items, but there may still be documents ...
649 (SELECT
650 b_dm.clin_when AS earliest,
651 c_epi.pk AS pk_episode
652 FROM
653 blobs.doc_med b_dm
654 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
655 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
656 WHERE c_hi.pk = %(pk_issue)s
657 )
658 ) AS candidates
659 ORDER BY earliest NULLS LAST
660 LIMIT 1"""
661 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
662 if len(rows) == 0:
663 return None
664 return cEpisode(aPK_obj = rows[0]['pk_episode'])
665
666 first_episode = property(_get_first_episode, lambda x:x)
667
668
670
671
672 if self._payload[self._idx['has_open_episode']]:
673 return self.open_episode
674
675 args = {'pk_issue': self.pk_obj}
676
677
678 cmd = "SELECT 1 FROM clin.episode WHERE fk_health_issue = %(pk_issue)s"
679 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
680 if len(rows) == 0:
681 return None
682
683 cmd = """SELECT
684 latest, pk_episode
685 FROM (
686 -- .clin_when of clinical items linked to episodes of this issue,
687 -- latest-possible thereof = explicitely set by user
688 (SELECT
689 c_cri.clin_when AS latest,
690 c_epi.pk AS pk_episode,
691 1 AS rank
692 FROM
693 clin.clin_root_item c_cri
694 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
695 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
696 WHERE c_hi.pk = %(pk_issue)s
697 )
698 UNION ALL
699
700 -- .clin_when of documents linked to episodes of this issue
701 (SELECT
702 b_dm.clin_when AS latest,
703 c_epi.pk AS pk_episode,
704 1 AS rank
705 FROM
706 blobs.doc_med b_dm
707 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
708 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
709 WHERE c_hi.pk = %(pk_issue)s
710 )
711 UNION ALL
712
713 -- last_affirmed of encounter in which episodes of this issue were created,
714 -- earliest-possible thereof = set by user
715 (SELECT
716 c_enc.last_affirmed AS latest,
717 c_epi.pk AS pk_episode,
718 2 AS rank
719 FROM
720 clin.episode c_epi
721 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
722 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
723 WHERE c_hi.pk = %(pk_issue)s
724 )
725
726 ) AS candidates
727 WHERE
728 -- weed out NULL rows due to episodes w/o clinical items and w/o documents
729 latest IS NOT NULL
730 ORDER BY
731 rank,
732 latest DESC
733 LIMIT 1
734 """
735 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
736 if len(rows) == 0:
737
738 return None
739 return cEpisode(aPK_obj = rows[0]['pk_episode'])
740
741 latest_episode = property(_get_latest_episode, lambda x:x)
742
743
744
746 """This returns the date when we can assume to safely KNOW
747 the health issue existed (because the provider said so)."""
748
749 args = {
750 'enc': self._payload[self._idx['pk_encounter']],
751 'pk': self._payload[self._idx['pk_health_issue']]
752 }
753 cmd = """SELECT COALESCE (
754 -- this one must override all:
755 -- .age_noted if not null and DOB is known
756 (CASE
757 WHEN c_hi.age_noted IS NULL
758 THEN NULL::timestamp with time zone
759 WHEN
760 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
761 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
762 )) IS NULL
763 THEN NULL::timestamp with time zone
764 ELSE
765 c_hi.age_noted + (
766 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
767 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
768 )
769 )
770 END),
771
772 -- look at best_guess_clinical_start_date of all linked episodes
773
774 -- start of encounter in which created, earliest = explicitely set
775 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = c_hi.fk_encounter)
776 )
777 FROM clin.health_issue c_hi
778 WHERE c_hi.pk = %(pk)s"""
779 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
780 return rows[0][0]
781
782 safe_start_date = property(_get_safe_start_date, lambda x:x)
783
784
786 args = {'pk': self._payload[self._idx['pk_health_issue']]}
787 cmd = """
788 SELECT MIN(earliest) FROM (
789 -- last modification, earliest = when created in/changed to the current state
790 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
791
792 UNION ALL
793 -- last modification of encounter in which created, earliest = initial creation of that encounter
794 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
795 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
796 ))
797
798 UNION ALL
799 -- earliest explicit .clin_when of clinical items linked to this health_issue
800 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
801
802 UNION ALL
803 -- earliest modification time of clinical items linked to this health issue
804 -- this CAN be used since if an item is linked to a health issue it can be
805 -- assumed the health issue (should have) existed at the time of creation
806 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
807
808 UNION ALL
809 -- earliest start of encounters of clinical items linked to this episode
810 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
811 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
812 ))
813
814 -- here we should be looking at
815 -- .best_guess_clinical_start_date of all episodes linked to this encounter
816
817 ) AS candidates"""
818
819 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
820 return rows[0][0]
821
822 possible_start_date = property(_get_possible_start_date)
823
824
837
838 clinical_end_date = property(_get_clinical_end_date)
839
840
842 args = {
843 'enc': self._payload[self._idx['pk_encounter']],
844 'pk': self._payload[self._idx['pk_health_issue']]
845 }
846 cmd = """
847 SELECT
848 MAX(latest)
849 FROM (
850 -- last modification, latest = when last changed to the current state
851 -- DO NOT USE: database upgrades may change this field
852 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
853
854 --UNION ALL
855 -- last modification of encounter in which created, latest = initial creation of that encounter
856 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
857 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
858 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
859 -- )
860 --)
861
862 --UNION ALL
863 -- end of encounter in which created, latest = explicitely set
864 -- DO NOT USE: we can retrospectively create issues which
865 -- DO NOT USE: are long since finished
866 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
867 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
868 -- )
869 --)
870
871 UNION ALL
872 -- latest end of encounters of clinical items linked to this issue
873 (SELECT
874 MAX(last_affirmed) AS latest
875 FROM clin.encounter
876 WHERE pk IN (
877 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
878 )
879 )
880
881 UNION ALL
882 -- latest explicit .clin_when of clinical items linked to this issue
883 (SELECT
884 MAX(clin_when) AS latest
885 FROM clin.v_pat_items
886 WHERE pk_health_issue = %(pk)s
887 )
888
889 -- latest modification time of clinical items linked to this issue
890 -- this CAN be used since if an item is linked to an issue it can be
891 -- assumed the issue (should have) existed at the time of modification
892 -- DO NOT USE, because typo fixes should not extend the issue
893 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
894
895 ) AS candidates"""
896 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
897 return rows[0][0]
898
899 latest_access_date = property(_get_latest_access_date)
900
901
903 try:
904 return laterality2str[self._payload[self._idx['laterality']]]
905 except KeyError:
906 return '<?>'
907
908 laterality_description = property(_get_laterality_description, lambda x:x)
909
910
913
914 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
915
916
918 cmd = """SELECT
919 'NONE (live row)'::text as audit__action_applied,
920 NULL AS audit__action_when,
921 NULL AS audit__action_by,
922 pk_audit,
923 row_version,
924 modified_when,
925 modified_by,
926 pk,
927 description,
928 laterality,
929 age_noted,
930 is_active,
931 clinically_relevant,
932 is_confidential,
933 is_cause_of_death,
934 fk_encounter,
935 grouping,
936 diagnostic_certainty_classification,
937 summary
938 FROM clin.health_issue
939 WHERE pk = %(pk_health_issue)s
940 UNION ALL (
941 SELECT
942 audit_action as audit__action_applied,
943 audit_when as audit__action_when,
944 audit_by as audit__action_by,
945 pk_audit,
946 orig_version as row_version,
947 orig_when as modified_when,
948 orig_by as modified_by,
949 pk,
950 description,
951 laterality,
952 age_noted,
953 is_active,
954 clinically_relevant,
955 is_confidential,
956 is_cause_of_death,
957 fk_encounter,
958 grouping,
959 diagnostic_certainty_classification,
960 summary
961 FROM audit.log_health_issue
962 WHERE pk = %(pk_health_issue)s
963 )
964 ORDER BY row_version DESC
965 """
966 args = {'pk_health_issue': self.pk_obj}
967 title = _('Health issue: %s%s%s') % (
968 gmTools.u_left_double_angle_quote,
969 self._payload[self._idx['description']],
970 gmTools.u_right_double_angle_quote
971 )
972 return '\n'.join(self._get_revision_history(cmd, args, title))
973
974 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
975
977 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
978 return []
979
980 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
981 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
982 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
983 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
984
986 queries = []
987
988 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
989 queries.append ({
990 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
991 'args': {
992 'issue': self._payload[self._idx['pk_health_issue']],
993 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
994 }
995 })
996
997 for pk_code in pk_codes:
998 queries.append ({
999 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
1000 'args': {
1001 'issue': self._payload[self._idx['pk_health_issue']],
1002 'pk_code': pk_code
1003 }
1004 })
1005 if len(queries) == 0:
1006 return
1007
1008 rows, idx = gmPG2.run_rw_queries(queries = queries)
1009 return
1010
1011 generic_codes = property(_get_generic_codes, _set_generic_codes)
1012
1013
1015 """Creates a new health issue for a given patient.
1016
1017 description - health issue name
1018 """
1019 try:
1020 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
1021 return h_issue
1022 except gmExceptions.NoSuchBusinessObjectError:
1023 pass
1024
1025 queries = []
1026 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
1027 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
1028
1029 cmd = "select currval('clin.health_issue_pk_seq')"
1030 queries.append({'cmd': cmd})
1031
1032 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
1033 h_issue = cHealthIssue(aPK_obj = rows[0][0])
1034
1035 return h_issue
1036
1037
1039 if isinstance(health_issue, cHealthIssue):
1040 args = {'pk': health_issue['pk_health_issue']}
1041 else:
1042 args = {'pk': int(health_issue)}
1043 try:
1044 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}])
1045 except gmPG2.dbapi.IntegrityError:
1046
1047 _log.exception('cannot delete health issue')
1048 return False
1049
1050 return True
1051
1052
1053
1055 issue = {
1056 'pk_health_issue': None,
1057 'description': _('Unattributed episodes'),
1058 'age_noted': None,
1059 'laterality': 'na',
1060 'is_active': True,
1061 'clinically_relevant': True,
1062 'is_confidential': None,
1063 'is_cause_of_death': False,
1064 'is_dummy': True,
1065 'grouping': None
1066 }
1067 return issue
1068
1069
1071 return cProblem (
1072 aPK_obj = {
1073 'pk_patient': health_issue['pk_patient'],
1074 'pk_health_issue': health_issue['pk_health_issue'],
1075 'pk_episode': None
1076 },
1077 try_potential_problems = allow_irrelevant
1078 )
1079
1080
1081
1082
1083 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1084 """Represents one clinical episode.
1085 """
1086 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s"
1087 _cmds_store_payload = [
1088 """update clin.episode set
1089 fk_health_issue = %(pk_health_issue)s,
1090 is_open = %(episode_open)s::boolean,
1091 description = %(description)s,
1092 summary = gm.nullify_empty_string(%(summary)s),
1093 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
1094 where
1095 pk = %(pk_episode)s and
1096 xmin = %(xmin_episode)s""",
1097 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
1098 ]
1099 _updatable_fields = [
1100 'pk_health_issue',
1101 'episode_open',
1102 'description',
1103 'summary',
1104 'diagnostic_certainty_classification'
1105 ]
1106
1107 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1108 pk = aPK_obj
1109 if pk is None and row is None:
1110
1111 where_parts = ['description = %(desc)s']
1112
1113 if id_patient is not None:
1114 where_parts.append('pk_patient = %(pat)s')
1115
1116 if health_issue is not None:
1117 where_parts.append('pk_health_issue = %(issue)s')
1118
1119 if encounter is not None:
1120 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)')
1121
1122 args = {
1123 'pat': id_patient,
1124 'issue': health_issue,
1125 'enc': encounter,
1126 'desc': name
1127 }
1128
1129 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts)
1130
1131 rows, idx = gmPG2.run_ro_queries (
1132 link_obj = link_obj,
1133 queries = [{'cmd': cmd, 'args': args}],
1134 get_col_idx=True
1135 )
1136
1137 if len(rows) == 0:
1138 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter))
1139
1140 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
1141 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
1142
1143 else:
1144 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1145
1146
1147
1148
1150 return self._payload[self._idx['pk_patient']]
1151
1152
1153 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1160
1161
1162 - def rename(self, description=None):
1163 """Method for episode editing, that is, episode renaming.
1164
1165 @param description
1166 - the new descriptive name for the encounter
1167 @type description
1168 - a string instance
1169 """
1170
1171 if description.strip() == '':
1172 _log.error('<description> must be a non-empty string instance')
1173 return False
1174
1175 old_description = self._payload[self._idx['description']]
1176 self._payload[self._idx['description']] = description.strip()
1177 self._is_modified = True
1178 successful, data = self.save_payload()
1179 if not successful:
1180 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
1181 self._payload[self._idx['description']] = old_description
1182 return False
1183 return True
1184
1185
1187 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1188
1189 if pk_code in self._payload[self._idx['pk_generic_codes']]:
1190 return
1191
1192 cmd = """
1193 INSERT INTO clin.lnk_code2episode
1194 (fk_item, fk_generic_code)
1195 SELECT
1196 %(item)s,
1197 %(code)s
1198 WHERE NOT EXISTS (
1199 SELECT 1 FROM clin.lnk_code2episode
1200 WHERE
1201 fk_item = %(item)s
1202 AND
1203 fk_generic_code = %(code)s
1204 )"""
1205 args = {
1206 'item': self._payload[self._idx['pk_episode']],
1207 'code': pk_code
1208 }
1209 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1210 return
1211
1212
1214 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1215 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1216 args = {
1217 'item': self._payload[self._idx['pk_episode']],
1218 'code': pk_code
1219 }
1220 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1221 return True
1222
1223
1278
1279
1302
1303
1556
1557
1558
1559
1561 cmd = """SELECT MIN(earliest) FROM
1562 (
1563 -- last modification of episode,
1564 -- earliest-possible thereof = when created,
1565 -- should actually go all the way back into audit.log_episode
1566 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1567
1568 UNION ALL
1569
1570 -- last modification of encounter in which created,
1571 -- earliest-possible thereof = initial creation of that encounter
1572 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1573 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1574 ))
1575 UNION ALL
1576
1577 -- start of encounter in which created,
1578 -- earliest-possible thereof = explicitely set by user
1579 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1580 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1581 ))
1582 UNION ALL
1583
1584 -- start of encounters of clinical items linked to this episode,
1585 -- earliest-possible thereof = explicitely set by user
1586 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1587 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1588 ))
1589 UNION ALL
1590
1591 -- .clin_when of clinical items linked to this episode,
1592 -- earliest-possible thereof = explicitely set by user
1593 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1594
1595 UNION ALL
1596
1597 -- earliest modification time of clinical items linked to this episode
1598 -- this CAN be used since if an item is linked to an episode it can be
1599 -- assumed the episode (should have) existed at the time of creation
1600 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1601
1602 UNION ALL
1603
1604 -- there may not be items, but there may still be documents ...
1605 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s)
1606 ) AS candidates"""
1607 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1608 return rows[0][0]
1609
1610 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date)
1611
1612
1614 if self._payload[self._idx['episode_open']]:
1615 return None
1616
1617 cmd = """SELECT COALESCE (
1618 (SELECT
1619 latest --, source_type
1620 FROM (
1621 -- latest explicit .clin_when of clinical items linked to this episode
1622 (SELECT
1623 MAX(clin_when) AS latest,
1624 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type
1625 FROM clin.clin_root_item
1626 WHERE fk_episode = %(pk)s
1627 )
1628 UNION ALL
1629 -- latest explicit .clin_when of documents linked to this episode
1630 (SELECT
1631 MAX(clin_when) AS latest,
1632 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type
1633 FROM blobs.doc_med
1634 WHERE fk_episode = %(pk)s
1635 )
1636 ) AS candidates
1637 ORDER BY latest DESC NULLS LAST
1638 LIMIT 1
1639 ),
1640 -- last ditch, always exists, only use when no clinical items or documents linked:
1641 -- last modification, latest = when last changed to the current state
1642 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type
1643 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s
1644 )
1645 )"""
1646 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False)
1647 return rows[0][0]
1648
1649 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date)
1650
1651
1695
1696 formatted_clinical_duration = property(_get_formatted_clinical_duration)
1697
1698
1700 cmd = """SELECT MAX(latest) FROM (
1701 -- last modification, latest = when last changed to the current state
1702 (SELECT c_epi.modified_when AS latest, 'clin.episode.modified_when'::text AS candidate FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1703
1704 UNION ALL
1705
1706 -- last modification of encounter in which created, latest = initial creation of that encounter
1707 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1708 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1709 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1710 --))
1711
1712 -- end of encounter in which created, latest = explicitely set
1713 -- DO NOT USE: we can retrospectively create episodes which
1714 -- DO NOT USE: are long since finished
1715 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1716 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1717 --))
1718
1719 -- latest end of encounters of clinical items linked to this episode
1720 (SELECT
1721 MAX(last_affirmed) AS latest,
1722 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1723 FROM clin.encounter
1724 WHERE pk IN (
1725 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1726 ))
1727 UNION ALL
1728
1729 -- latest explicit .clin_when of clinical items linked to this episode
1730 (SELECT
1731 MAX(clin_when) AS latest,
1732 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1733 FROM clin.clin_root_item
1734 WHERE fk_episode = %(pk)s
1735 )
1736
1737 -- latest modification time of clinical items linked to this episode
1738 -- this CAN be used since if an item is linked to an episode it can be
1739 -- assumed the episode (should have) existed at the time of creation
1740 -- DO NOT USE, because typo fixes should not extend the episode
1741 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1742
1743 -- not sure about this one:
1744 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1745
1746 ) AS candidates"""
1747 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1748 return rows[0][0]
1749
1750 latest_access_date = property(_get_latest_access_date)
1751
1752
1755
1756 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1757
1758
1760 cmd = """SELECT
1761 'NONE (live row)'::text as audit__action_applied,
1762 NULL AS audit__action_when,
1763 NULL AS audit__action_by,
1764 pk_audit,
1765 row_version,
1766 modified_when,
1767 modified_by,
1768 pk, fk_health_issue, description, is_open, fk_encounter,
1769 diagnostic_certainty_classification,
1770 summary
1771 FROM clin.episode
1772 WHERE pk = %(pk_episode)s
1773 UNION ALL (
1774 SELECT
1775 audit_action as audit__action_applied,
1776 audit_when as audit__action_when,
1777 audit_by as audit__action_by,
1778 pk_audit,
1779 orig_version as row_version,
1780 orig_when as modified_when,
1781 orig_by as modified_by,
1782 pk, fk_health_issue, description, is_open, fk_encounter,
1783 diagnostic_certainty_classification,
1784 summary
1785 FROM audit.log_episode
1786 WHERE pk = %(pk_episode)s
1787 )
1788 ORDER BY row_version DESC
1789 """
1790 args = {'pk_episode': self.pk_obj}
1791 title = _('Episode: %s%s%s') % (
1792 gmTools.u_left_double_angle_quote,
1793 self._payload[self._idx['description']],
1794 gmTools.u_right_double_angle_quote
1795 )
1796 return '\n'.join(self._get_revision_history(cmd, args, title))
1797
1798 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
1799
1800
1802 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1803 return []
1804
1805 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
1806 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1807 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1808 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1809
1811 queries = []
1812
1813 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1814 queries.append ({
1815 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1816 'args': {
1817 'epi': self._payload[self._idx['pk_episode']],
1818 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1819 }
1820 })
1821
1822 for pk_code in pk_codes:
1823 queries.append ({
1824 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1825 'args': {
1826 'epi': self._payload[self._idx['pk_episode']],
1827 'pk_code': pk_code
1828 }
1829 })
1830 if len(queries) == 0:
1831 return
1832
1833 rows, idx = gmPG2.run_rw_queries(queries = queries)
1834 return
1835
1836 generic_codes = property(_get_generic_codes, _set_generic_codes)
1837
1838
1840 cmd = """SELECT EXISTS (
1841 SELECT 1 FROM clin.clin_narrative
1842 WHERE
1843 fk_episode = %(epi)s
1844 AND
1845 fk_encounter IN (
1846 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1847 )
1848 )"""
1849 args = {
1850 'pat': self._payload[self._idx['pk_patient']],
1851 'epi': self._payload[self._idx['pk_episode']]
1852 }
1853 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1854 return rows[0][0]
1855
1856 has_narrative = property(_get_has_narrative, lambda x:x)
1857
1858
1860 if self._payload[self._idx['pk_health_issue']] is None:
1861 return None
1862 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1863
1864 health_issue = property(_get_health_issue)
1865
1866
1867 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1868 """Creates a new episode for a given patient's health issue.
1869
1870 pk_health_issue - given health issue PK
1871 episode_name - name of episode
1872 """
1873 if not allow_dupes:
1874 try:
1875 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj)
1876 if episode['episode_open'] != is_open:
1877 episode['episode_open'] = is_open
1878 episode.save_payload()
1879 return episode
1880 except gmExceptions.ConstructorError:
1881 pass
1882
1883 queries = []
1884 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)"
1885 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1886 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"})
1887 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True)
1888
1889 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1890 return episode
1891
1892
1894 if isinstance(episode, cEpisode):
1895 pk = episode['pk_episode']
1896 else:
1897 pk = int(episode)
1898
1899 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s'
1900
1901 try:
1902 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1903 except gmPG2.dbapi.IntegrityError:
1904
1905 _log.exception('cannot delete episode, it is in use')
1906 return False
1907
1908 return True
1909
1911 return cProblem (
1912 aPK_obj = {
1913 'pk_patient': episode['pk_patient'],
1914 'pk_episode': episode['pk_episode'],
1915 'pk_health_issue': episode['pk_health_issue']
1916 },
1917 try_potential_problems = allow_closed
1918 )
1919
1920
1921
1922
1923 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s"
1924
1925 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1926 """Represents one encounter."""
1927
1928 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s'
1929 _cmds_store_payload = [
1930 """UPDATE clin.encounter SET
1931 started = %(started)s,
1932 last_affirmed = %(last_affirmed)s,
1933 fk_location = %(pk_org_unit)s,
1934 fk_type = %(pk_type)s,
1935 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
1936 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
1937 WHERE
1938 pk = %(pk_encounter)s AND
1939 xmin = %(xmin_encounter)s
1940 """,
1941
1942 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
1943 ]
1944 _updatable_fields = [
1945 'started',
1946 'last_affirmed',
1947 'pk_org_unit',
1948 'pk_type',
1949 'reason_for_encounter',
1950 'assessment_of_encounter'
1951 ]
1952
1954 """Set the encounter as the active one.
1955
1956 "Setting active" means making sure the encounter
1957 row has the youngest "last_affirmed" timestamp of
1958 all encounter rows for this patient.
1959 """
1960 self['last_affirmed'] = gmDateTime.pydt_now_here()
1961 self.save()
1962
1963 - def lock(self, exclusive=False, link_obj=None):
1964 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1965
1966 - def unlock(self, exclusive=False, link_obj=None):
1967 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1968
1970 """
1971 Moves every element currently linked to the current encounter
1972 and the source_episode onto target_episode.
1973
1974 @param source_episode The episode the elements are currently linked to.
1975 @type target_episode A cEpisode intance.
1976 @param target_episode The episode the elements will be relinked to.
1977 @type target_episode A cEpisode intance.
1978 """
1979 if source_episode['pk_episode'] == target_episode['pk_episode']:
1980 return True
1981
1982 queries = []
1983 cmd = """
1984 UPDATE clin.clin_root_item
1985 SET fk_episode = %(trg)s
1986 WHERE
1987 fk_encounter = %(enc)s AND
1988 fk_episode = %(src)s
1989 """
1990 rows, idx = gmPG2.run_rw_queries(queries = [{
1991 'cmd': cmd,
1992 'args': {
1993 'trg': target_episode['pk_episode'],
1994 'enc': self.pk_obj,
1995 'src': source_episode['pk_episode']
1996 }
1997 }])
1998 self.refetch_payload()
1999 return True
2000
2001
2003 if pk_target_encounter == self.pk_obj:
2004 return True
2005 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)"
2006 args = {
2007 'src': self.pk_obj,
2008 'trg': pk_target_encounter
2009 }
2010 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2011 return True
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2023
2024 relevant_fields = [
2025 'pk_org_unit',
2026 'pk_type',
2027 'pk_patient',
2028 'reason_for_encounter',
2029 'assessment_of_encounter'
2030 ]
2031 for field in relevant_fields:
2032 if self._payload[self._idx[field]] != another_object[field]:
2033 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
2034 return False
2035
2036 relevant_fields = [
2037 'started',
2038 'last_affirmed',
2039 ]
2040 for field in relevant_fields:
2041 if self._payload[self._idx[field]] is None:
2042 if another_object[field] is None:
2043 continue
2044 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2045 return False
2046
2047 if another_object[field] is None:
2048 return False
2049
2050
2051 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'):
2052 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2053 return False
2054
2055
2056
2057 if another_object['pk_generic_codes_rfe'] is None:
2058 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
2059 return False
2060 if another_object['pk_generic_codes_rfe'] is not None:
2061 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
2062 return False
2063 if (
2064 (another_object['pk_generic_codes_rfe'] is None)
2065 and
2066 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
2067 ) is False:
2068 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
2069 return False
2070
2071 if another_object['pk_generic_codes_aoe'] is None:
2072 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
2073 return False
2074 if another_object['pk_generic_codes_aoe'] is not None:
2075 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
2076 return False
2077 if (
2078 (another_object['pk_generic_codes_aoe'] is None)
2079 and
2080 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
2081 ) is False:
2082 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
2083 return False
2084
2085 return True
2086
2088 cmd = """
2089 select exists (
2090 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
2091 union all
2092 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2093 )"""
2094 args = {
2095 'pat': self._payload[self._idx['pk_patient']],
2096 'enc': self.pk_obj
2097 }
2098 rows, idx = gmPG2.run_ro_queries (
2099 queries = [{
2100 'cmd': cmd,
2101 'args': args
2102 }]
2103 )
2104 return rows[0][0]
2105
2106
2108 cmd = """
2109 select exists (
2110 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
2111 )"""
2112 args = {
2113 'pat': self._payload[self._idx['pk_patient']],
2114 'enc': self.pk_obj
2115 }
2116 rows, idx = gmPG2.run_ro_queries (
2117 queries = [{
2118 'cmd': cmd,
2119 'args': args
2120 }]
2121 )
2122 return rows[0][0]
2123
2125 """soap_cats: <space> = admin category"""
2126
2127 if soap_cats is None:
2128 soap_cats = 'soap '
2129 else:
2130 soap_cats = soap_cats.lower()
2131
2132 cats = []
2133 for cat in soap_cats:
2134 if cat in 'soapu':
2135 cats.append(cat)
2136 continue
2137 if cat == ' ':
2138 cats.append(None)
2139
2140 cmd = """
2141 SELECT EXISTS (
2142 SELECT 1 FROM clin.clin_narrative
2143 WHERE
2144 fk_encounter = %(enc)s
2145 AND
2146 soap_cat IN %(cats)s
2147 LIMIT 1
2148 )
2149 """
2150 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
2151 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
2152 return rows[0][0]
2153
2155 cmd = """
2156 select exists (
2157 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2158 )"""
2159 args = {
2160 'pat': self._payload[self._idx['pk_patient']],
2161 'enc': self.pk_obj
2162 }
2163 rows, idx = gmPG2.run_ro_queries (
2164 queries = [{
2165 'cmd': cmd,
2166 'args': args
2167 }]
2168 )
2169 return rows[0][0]
2170
2172
2173 if soap_cat is not None:
2174 soap_cat = soap_cat.lower()
2175
2176 if episode is None:
2177 epi_part = 'fk_episode is null'
2178 else:
2179 epi_part = 'fk_episode = %(epi)s'
2180
2181 cmd = """
2182 select narrative
2183 from clin.clin_narrative
2184 where
2185 fk_encounter = %%(enc)s
2186 and
2187 soap_cat = %%(cat)s
2188 and
2189 %s
2190 order by clin_when desc
2191 limit 1
2192 """ % epi_part
2193
2194 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
2195
2196 rows, idx = gmPG2.run_ro_queries (
2197 queries = [{
2198 'cmd': cmd,
2199 'args': args
2200 }]
2201 )
2202 if len(rows) == 0:
2203 return None
2204
2205 return rows[0][0]
2206
2208 cmd = """
2209 SELECT * FROM clin.v_pat_episodes
2210 WHERE pk_episode IN (
2211 SELECT DISTINCT fk_episode
2212 FROM clin.clin_root_item
2213 WHERE fk_encounter = %%(enc)s
2214
2215 UNION
2216
2217 SELECT DISTINCT fk_episode
2218 FROM blobs.doc_med
2219 WHERE fk_encounter = %%(enc)s
2220 ) %s"""
2221 args = {'enc': self.pk_obj}
2222 if exclude is not None:
2223 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s'
2224 args['excluded'] = tuple(exclude)
2225 else:
2226 cmd = cmd % ''
2227
2228 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2229
2230 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2231
2232 episodes = property(get_episodes, lambda x:x)
2233
2234 - def add_code(self, pk_code=None, field=None):
2235 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2236 if field == 'rfe':
2237 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2238 elif field == 'aoe':
2239 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2240 else:
2241 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2242 args = {
2243 'item': self._payload[self._idx['pk_encounter']],
2244 'code': pk_code
2245 }
2246 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2247 return True
2248
2250 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2251 if field == 'rfe':
2252 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2253 elif field == 'aoe':
2254 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2255 else:
2256 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2257 args = {
2258 'item': self._payload[self._idx['pk_encounter']],
2259 'code': pk_code
2260 }
2261 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2262 return True
2263
2264
2265
2266
2312
2313
2409
2410
2470
2471
2524
2525
2624
2625
2647
2648
2783
2784
2785
2786
2788 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2789 return []
2790
2791 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2792 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2793 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2794 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2795
2797 queries = []
2798
2799 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2800 queries.append ({
2801 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2802 'args': {
2803 'enc': self._payload[self._idx['pk_encounter']],
2804 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2805 }
2806 })
2807
2808 for pk_code in pk_codes:
2809 queries.append ({
2810 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2811 'args': {
2812 'enc': self._payload[self._idx['pk_encounter']],
2813 'pk_code': pk_code
2814 }
2815 })
2816 if len(queries) == 0:
2817 return
2818
2819 rows, idx = gmPG2.run_rw_queries(queries = queries)
2820 self.refetch_payload()
2821 return
2822
2823 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2824
2826 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2827 return []
2828
2829 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2830 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2831 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2832 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2833
2835 queries = []
2836
2837 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2838 queries.append ({
2839 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2840 'args': {
2841 'enc': self._payload[self._idx['pk_encounter']],
2842 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2843 }
2844 })
2845
2846 for pk_code in pk_codes:
2847 queries.append ({
2848 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2849 'args': {
2850 'enc': self._payload[self._idx['pk_encounter']],
2851 'pk_code': pk_code
2852 }
2853 })
2854 if len(queries) == 0:
2855 return
2856
2857 rows, idx = gmPG2.run_rw_queries(queries = queries)
2858 self.refetch_payload()
2859 return
2860
2861 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2862
2867
2868 praxis_branch = property(_get_praxis_branch, lambda x:x)
2869
2871 if self._payload[self._idx['pk_org_unit']] is None:
2872 return None
2873 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
2874
2875 org_unit = property(_get_org_unit, lambda x:x)
2876
2878 cmd = """SELECT
2879 'NONE (live row)'::text as audit__action_applied,
2880 NULL AS audit__action_when,
2881 NULL AS audit__action_by,
2882 pk_audit,
2883 row_version,
2884 modified_when,
2885 modified_by,
2886 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
2887 FROM clin.encounter
2888 WHERE pk = %(pk_encounter)s
2889 UNION ALL (
2890 SELECT
2891 audit_action as audit__action_applied,
2892 audit_when as audit__action_when,
2893 audit_by as audit__action_by,
2894 pk_audit,
2895 orig_version as row_version,
2896 orig_when as modified_when,
2897 orig_by as modified_by,
2898 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
2899 FROM audit.log_encounter
2900 WHERE pk = %(pk_encounter)s
2901 )
2902 ORDER BY row_version DESC
2903 """
2904 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]}
2905 title = _('Encounter: %s%s%s') % (
2906 gmTools.u_left_double_angle_quote,
2907 self._payload[self._idx['l10n_type']],
2908 gmTools.u_right_double_angle_quote
2909 )
2910 return '\n'.join(self._get_revision_history(cmd, args, title))
2911
2912 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
2913
2914
2916 """Creates a new encounter for a patient.
2917
2918 fk_patient - patient PK
2919 enc_type - type of encounter
2920 """
2921 if enc_type is None:
2922 enc_type = 'in surgery'
2923
2924 queries = []
2925 try:
2926 enc_type = int(enc_type)
2927 cmd = """
2928 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location)
2929 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk"""
2930 except ValueError:
2931 enc_type = enc_type
2932 cmd = """
2933 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type)
2934 VALUES (
2935 %(pat)s,
2936 %(prax)s,
2937 coalesce (
2938 (select pk from clin.encounter_type where description = %(typ)s),
2939 -- pick the first available
2940 (select pk from clin.encounter_type limit 1)
2941 )
2942 ) RETURNING pk"""
2943 praxis = gmPraxis.gmCurrentPraxisBranch()
2944 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']}
2945 queries.append({'cmd': cmd, 'args': args})
2946 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
2947 encounter = cEncounter(aPK_obj = rows[0]['pk'])
2948
2949 return encounter
2950
2951
2953 """Used to protect against deletion of active encounter from another client."""
2954 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2955
2956
2959
2960
2962 """Deletes an encounter by PK.
2963
2964 - attempts to obtain an exclusive lock which should
2965 fail if the encounter is the active encounter in
2966 this or any other client
2967 - catches DB exceptions which should mostly be related
2968 to clinical data already having been attached to
2969 the encounter thus making deletion fail
2970 """
2971 conn = gmPG2.get_connection(readonly = False)
2972 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn):
2973 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter)
2974 return False
2975 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s"""
2976 args = {'enc': pk_encounter}
2977 try:
2978 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2979 except gmPG2.dbapi.Error:
2980 _log.exception('cannot delete encounter [%s]', pk_encounter)
2981 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
2982 return False
2983 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
2984 return True
2985
2986
2987
2988
2990
2991 rows, idx = gmPG2.run_rw_queries(
2992 queries = [{
2993 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
2994 'args': {'desc': description, 'l10n_desc': l10n_description}
2995 }],
2996 return_data = True
2997 )
2998
2999 success = rows[0][0]
3000 if not success:
3001 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
3002
3003 return {'description': description, 'l10n_description': l10n_description}
3004
3006 """This will attempt to create a NEW encounter type."""
3007
3008
3009 if description is None:
3010 description = l10n_description
3011
3012 args = {
3013 'desc': description,
3014 'l10n_desc': l10n_description
3015 }
3016
3017 _log.debug('creating encounter type: %s, %s', description, l10n_description)
3018
3019
3020 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s"
3021 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3022
3023
3024 if len(rows) > 0:
3025
3026 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
3027 _log.info('encounter type [%s] already exists with the proper translation')
3028 return {'description': description, 'l10n_description': l10n_description}
3029
3030
3031
3032 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
3033 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3034
3035
3036 if rows[0][0]:
3037 _log.error('encounter type [%s] already exists but with another translation')
3038 return None
3039
3040
3041 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
3042 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3043 return {'description': description, 'l10n_description': l10n_description}
3044
3045
3046 queries = [
3047 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
3048 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
3049 ]
3050 rows, idx = gmPG2.run_rw_queries(queries = queries)
3051
3052 return {'description': description, 'l10n_description': l10n_description}
3053
3054
3056 cmd = """
3057 SELECT
3058 COUNT(1) AS type_count,
3059 fk_type
3060 FROM clin.encounter
3061 GROUP BY fk_type
3062 ORDER BY type_count DESC
3063 LIMIT 1
3064 """
3065 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3066 if len(rows) == 0:
3067 return None
3068 return rows[0]['fk_type']
3069
3070
3072 cmd = """
3073 SELECT
3074 _(description) AS l10n_description,
3075 description
3076 FROM
3077 clin.encounter_type
3078 ORDER BY
3079 l10n_description
3080 """
3081 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3082 return rows
3083
3084
3089
3090
3092 cmd = "delete from clin.encounter_type where description = %(desc)s"
3093 args = {'desc': description}
3094 try:
3095 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3096 except gmPG2.dbapi.IntegrityError as e:
3097 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
3098 return False
3099 raise
3100
3101 return True
3102
3103
3104 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3105 """Represents one problem.
3106
3107 problems are the aggregation of
3108 .clinically_relevant=True issues and
3109 .is_open=True episodes
3110 """
3111 _cmd_fetch_payload = ''
3112 _cmds_store_payload = ["select 1"]
3113 _updatable_fields = []
3114
3115
3116 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3117 """Initialize.
3118
3119 aPK_obj must contain the keys
3120 pk_patient
3121 pk_episode
3122 pk_health_issue
3123 """
3124 if aPK_obj is None:
3125 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj))
3126
3127
3128
3129
3130
3131 where_parts = []
3132 pk = {}
3133 for col_name in aPK_obj.keys():
3134 val = aPK_obj[col_name]
3135 if val is None:
3136 where_parts.append('%s IS NULL' % col_name)
3137 else:
3138 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
3139 pk[col_name] = val
3140
3141
3142 cProblem._cmd_fetch_payload = """
3143 SELECT *, False as is_potential_problem
3144 FROM clin.v_problem_list
3145 WHERE %s""" % ' AND '.join(where_parts)
3146
3147 try:
3148 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3149 return
3150 except gmExceptions.ConstructorError:
3151 _log.exception('actual problem not found, trying "potential" problems')
3152 if try_potential_problems is False:
3153 raise
3154
3155
3156 cProblem._cmd_fetch_payload = """
3157 SELECT *, True as is_potential_problem
3158 FROM clin.v_potential_problem_list
3159 WHERE %s""" % ' AND '.join(where_parts)
3160 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3161
3163 """
3164 Retrieve the cEpisode instance equivalent to this problem.
3165 The problem's type attribute must be 'episode'
3166 """
3167 if self._payload[self._idx['type']] != 'episode':
3168 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3169 return None
3170 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3171
3173 """
3174 Retrieve the cHealthIssue instance equivalent to this problem.
3175 The problem's type attribute must be 'issue'
3176 """
3177 if self._payload[self._idx['type']] != 'issue':
3178 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3179 return None
3180 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3181
3197
3198
3199
3200
3201
3204
3205 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
3206
3208 if self._payload[self._idx['type']] == 'issue':
3209 cmd = """
3210 SELECT * FROM clin.v_linked_codes WHERE
3211 item_table = 'clin.lnk_code2h_issue'::regclass
3212 AND
3213 pk_item = %(item)s
3214 """
3215 args = {'item': self._payload[self._idx['pk_health_issue']]}
3216 else:
3217 cmd = """
3218 SELECT * FROM clin.v_linked_codes WHERE
3219 item_table = 'clin.lnk_code2episode'::regclass
3220 AND
3221 pk_item = %(item)s
3222 """
3223 args = {'item': self._payload[self._idx['pk_episode']]}
3224
3225 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3226 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3227
3228 generic_codes = property(_get_generic_codes, lambda x:x)
3229
3231 """Retrieve the cEpisode instance equivalent to the given problem.
3232
3233 The problem's type attribute must be 'episode'
3234
3235 @param problem: The problem to retrieve its related episode for
3236 @type problem: A gmEMRStructItems.cProblem instance
3237 """
3238 if isinstance(problem, cEpisode):
3239 return problem
3240
3241 exc = TypeError('cannot convert [%s] to episode' % problem)
3242
3243 if not isinstance(problem, cProblem):
3244 raise exc
3245
3246 if problem['type'] != 'episode':
3247 raise exc
3248
3249 return cEpisode(aPK_obj = problem['pk_episode'])
3250
3252 """Retrieve the cIssue instance equivalent to the given problem.
3253
3254 The problem's type attribute must be 'issue'.
3255
3256 @param problem: The problem to retrieve the corresponding issue for
3257 @type problem: A gmEMRStructItems.cProblem instance
3258 """
3259 if isinstance(problem, cHealthIssue):
3260 return problem
3261
3262 exc = TypeError('cannot convert [%s] to health issue' % problem)
3263
3264 if not isinstance(problem, cProblem):
3265 raise exc
3266
3267 if problem['type'] != 'issue':
3268 raise exc
3269
3270 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3271
3273 """Transform given problem into either episode or health issue instance.
3274 """
3275 if isinstance(problem, (cEpisode, cHealthIssue)):
3276 return problem
3277
3278 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
3279
3280 if not isinstance(problem, cProblem):
3281 _log.debug('%s' % problem)
3282 raise exc
3283
3284 if problem['type'] == 'episode':
3285 return cEpisode(aPK_obj = problem['pk_episode'])
3286
3287 if problem['type'] == 'issue':
3288 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3289
3290 raise exc
3291
3292
3293 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s"
3294
3296
3297 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s"
3298 _cmds_store_payload = [
3299 """UPDATE clin.hospital_stay SET
3300 clin_when = %(admission)s,
3301 discharge = %(discharge)s,
3302 fk_org_unit = %(pk_org_unit)s,
3303 narrative = gm.nullify_empty_string(%(comment)s),
3304 fk_episode = %(pk_episode)s,
3305 fk_encounter = %(pk_encounter)s
3306 WHERE
3307 pk = %(pk_hospital_stay)s
3308 AND
3309 xmin = %(xmin_hospital_stay)s
3310 RETURNING
3311 xmin AS xmin_hospital_stay
3312 """
3313 ]
3314 _updatable_fields = [
3315 'admission',
3316 'discharge',
3317 'pk_org_unit',
3318 'pk_episode',
3319 'pk_encounter',
3320 'comment'
3321 ]
3322
3323
3329
3330
3364
3365
3367 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3368
3369 documents = property(_get_documents, lambda x:x)
3370
3371
3373 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1"
3374 queries = [{
3375
3376
3377 'cmd': cmd,
3378 'args': {'pat': patient}
3379 }]
3380 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3381 if len(rows) == 0:
3382 return None
3383 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3384
3385
3387 args = {'pat': patient}
3388 if ongoing_only:
3389 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission"
3390 else:
3391 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission"
3392
3393 queries = [{'cmd': cmd, 'args': args}]
3394 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3395
3396 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3397
3398
3400
3401 queries = [{
3402 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk',
3403 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit}
3404 }]
3405 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3406
3407 return cHospitalStay(aPK_obj = rows[0][0])
3408
3409
3411 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
3412 args = {'pk': stay}
3413 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3414 return True
3415
3416
3417 _SQL_get_procedures = "select * from clin.v_procedures where %s"
3418
3618
3619
3628
3629
3635
3636
3646
3647
3674
3675
3681
3682
3728
3729
3730
3731
3733
3734 aggregate_result = 0
3735
3736 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk')
3737 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ])
3738
3739 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk')
3740 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ]
3741
3742 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi)
3743
3744 tables_linking2enc = {}
3745 for fk in fks_linking2enc:
3746 table = fk['referencing_table']
3747 tables_linking2enc[table] = fk
3748
3749 tables_linking2epi = {}
3750 for fk in fks_linking2epi:
3751 table = fk['referencing_table']
3752 tables_linking2epi[table] = fk
3753
3754 for t in tables_linking2both:
3755
3756 table_file_name = 'x-check_enc_epi_xref-%s.log' % t
3757 table_file = io.open(table_file_name, 'w+', encoding = 'utf8')
3758
3759
3760 args = {'table': t}
3761 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}])
3762 pk_col = rows[0][0]
3763 print("checking table:", t, '- pk col:', pk_col)
3764 print(' =>', table_file_name)
3765 table_file.write('table: %s\n' % t)
3766 table_file.write('PK col: %s\n' % pk_col)
3767
3768
3769 cmd = 'select %s from %s' % (pk_col, t)
3770 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
3771 pks = [ r[0] for r in rows ]
3772 for pk in pks:
3773 args = {'pk': pk, 'tbl': t}
3774 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col)
3775 epi_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from clin.episode where pk = (select fk_episode from %s where %s = %%(pk)s))" % (t, pk_col)
3776 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}])
3777 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}])
3778 enc_pat = enc_rows[0][0]
3779 epi_pat = epi_rows[0][0]
3780 args['pat_enc'] = enc_pat
3781 args['pat_epi'] = epi_pat
3782 if epi_pat != enc_pat:
3783 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat))
3784 aggregate_result = -2
3785
3786 table_file.write('--------------------------------------------------------------------------------\n')
3787 table_file.write('mismatch on row with pk: %s\n' % pk)
3788 table_file.write('\n')
3789
3790 table_file.write('journal entry:\n')
3791 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s'
3792 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3793 if len(rows) > 0:
3794 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3795 table_file.write('\n\n')
3796
3797 table_file.write('row data:\n')
3798 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col)
3799 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3800 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3801 table_file.write('\n\n')
3802
3803 table_file.write('episode:\n')
3804 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col)
3805 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3806 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3807 table_file.write('\n\n')
3808
3809 table_file.write('patient of episode:\n')
3810 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s'
3811 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3812 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3813 table_file.write('\n\n')
3814
3815 table_file.write('encounter:\n')
3816 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col)
3817 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3818 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3819 table_file.write('\n\n')
3820
3821 table_file.write('patient of encounter:\n')
3822 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s'
3823 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3824 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3825 table_file.write('\n')
3826
3827 table_file.write('done\n')
3828 table_file.close()
3829
3830 return aggregate_result
3831
3832
3844
3845
3846
3847
3848 if __name__ == '__main__':
3849
3850 if len(sys.argv) < 2:
3851 sys.exit()
3852
3853 if sys.argv[1] != 'test':
3854 sys.exit()
3855
3856
3857
3858
3860 print("\nProblem test")
3861 print("------------")
3862 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
3863 print(prob)
3864 fields = prob.get_fields()
3865 for field in fields:
3866 print(field, ':', prob[field])
3867 print('\nupdatable:', prob.get_updatable_fields())
3868 epi = prob.get_as_episode()
3869 print('\nas episode:')
3870 if epi is not None:
3871 for field in epi.get_fields():
3872 print(' .%s : %s' % (field, epi[field]))
3873
3874
3893
3894
3896 print("episode test")
3897 print("------------")
3898 episode = cEpisode(aPK_obj = 1322)
3899
3900 print(episode['description'])
3901 print('start:', episode.best_guess_clinical_start_date)
3902 print('end :', episode.best_guess_clinical_end_date)
3903 return
3904
3905 print(episode)
3906 fields = episode.get_fields()
3907 for field in fields:
3908 print(field, ':', episode[field])
3909 print("updatable:", episode.get_updatable_fields())
3910 input('ENTER to continue')
3911
3912 old_description = episode['description']
3913 old_enc = cEncounter(aPK_obj = 1)
3914
3915 desc = '1-%s' % episode['description']
3916 print("==> renaming to", desc)
3917 successful = episode.rename (
3918 description = desc
3919 )
3920 if not successful:
3921 print("error")
3922 else:
3923 print("success")
3924 for field in fields:
3925 print(field, ':', episode[field])
3926
3927 print(episode.formatted_revision_history)
3928
3929 input('ENTER to continue')
3930
3931
3943
3944
3946 encounter = cEncounter(aPK_obj=1)
3947 print(encounter)
3948 print("")
3949 print(encounter.format_latex())
3950
3955
3965
3972
3977
3981
3982
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005 test_export_emr_structure()
4006
4007
4008