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 from Gnumed.pycommon import gmMatchProvider
26
27 from Gnumed.business import gmClinNarrative
28 from Gnumed.business import gmSoapDefs
29 from Gnumed.business import gmCoding
30 from Gnumed.business import gmPraxis
31 from Gnumed.business import gmOrganization
32 from Gnumed.business import gmExternalCare
33 from Gnumed.business import gmDocuments
34
35
36 _log = logging.getLogger('gm.emr')
37
38
39 if __name__ == '__main__':
40 gmI18N.activate_locale()
41 gmI18N.install_domain('gnumed')
42
43
44
45
46 __diagnostic_certainty_classification_map = None
47
65
66
67
68
69 laterality2str = {
70 None: '?',
71 'na': '',
72 'sd': _('bilateral'),
73 'ds': _('bilateral'),
74 's': _('left'),
75 'd': _('right')
76 }
77
78
80 """Represents one health issue."""
81
82
83 _cmd_fetch_payload = "select * from clin.v_health_issues where pk_health_issue = %s"
84 _cmds_store_payload = [
85 """update clin.health_issue set
86 description = %(description)s,
87 summary = gm.nullify_empty_string(%(summary)s),
88 age_noted = %(age_noted)s,
89 laterality = gm.nullify_empty_string(%(laterality)s),
90 grouping = gm.nullify_empty_string(%(grouping)s),
91 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
92 is_active = %(is_active)s,
93 clinically_relevant = %(clinically_relevant)s,
94 is_confidential = %(is_confidential)s,
95 is_cause_of_death = %(is_cause_of_death)s
96 WHERE
97 pk = %(pk_health_issue)s
98 AND
99 xmin = %(xmin_health_issue)s""",
100 "select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
101 ]
102 _updatable_fields = [
103 'description',
104 'summary',
105 'grouping',
106 'age_noted',
107 'laterality',
108 'is_active',
109 'clinically_relevant',
110 'is_confidential',
111 'is_cause_of_death',
112 'diagnostic_certainty_classification'
113 ]
114
115
116 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
117 pk = aPK_obj
118
119 if (pk is not None) or (row is not None):
120 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
121 return
122
123 if patient is None:
124 cmd = """select *, xmin_health_issue from clin.v_health_issues
125 where
126 description = %(desc)s
127 and
128 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
129 else:
130 cmd = """select *, xmin_health_issue from clin.v_health_issues
131 where
132 description = %(desc)s
133 and
134 pk_patient = %(pat)s"""
135
136 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
137 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
138
139 if len(rows) == 0:
140 raise gmExceptions.NoSuchBusinessObjectError('no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient))
141
142 pk = rows[0][0]
143 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
144
145 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
146
147
148
149
150 - def rename(self, description=None):
151 """Method for issue renaming.
152
153 @param description
154 - the new descriptive name for the issue
155 @type description
156 - a string instance
157 """
158
159 if not type(description) in [str, str] or description.strip() == '':
160 _log.error('<description> must be a non-empty string')
161 return False
162
163 old_description = self._payload[self._idx['description']]
164 self._payload[self._idx['description']] = description.strip()
165 self._is_modified = True
166 successful, data = self.save_payload()
167 if not successful:
168 _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
169 self._payload[self._idx['description']] = old_description
170 return False
171 return True
172
173
175 cmd = "SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
176 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
177 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
178
179
196
197
205
206
208 return self._payload[self._idx['has_open_episode']]
209
210
212 cmd = "select pk from clin.episode where fk_health_issue = %s and is_open IS True LIMIT 1"
213 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
214 if len(rows) == 0:
215 return None
216 return cEpisode(aPK_obj=rows[0][0])
217
218
220 if self._payload[self._idx['age_noted']] is None:
221 return '<???>'
222
223
224
225
226 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
227
228
230 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
231 cmd = "INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
232 args = {
233 'item': self._payload[self._idx['pk_health_issue']],
234 'code': pk_code
235 }
236 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
237 return True
238
239
241 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
242 cmd = "DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
243 args = {
244 'item': self._payload[self._idx['pk_health_issue']],
245 'code': pk_code
246 }
247 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
248 return True
249
250
303
304
548
549
550
553
554 external_care = property(_get_external_care, lambda x:x)
555
556
557 episodes = property(get_episodes, lambda x:x)
558
559 open_episode = property(get_open_episode, lambda x:x)
560
561 has_open_episode = property(has_open_episode, lambda x:x)
562
563
565
566 args = {'pk_issue': self.pk_obj}
567
568 cmd = """SELECT
569 earliest, pk_episode
570 FROM (
571 -- .modified_when of all episodes of this issue,
572 -- earliest-possible thereof = when created,
573 -- should actually go all the way back into audit.log_episode
574 (SELECT
575 c_epi.modified_when AS earliest,
576 c_epi.pk AS pk_episode
577 FROM clin.episode c_epi
578 WHERE c_epi.fk_health_issue = %(pk_issue)s
579 )
580 UNION ALL
581
582 -- last modification of encounter in which episodes of this issue were created,
583 -- earliest-possible thereof = initial creation of that encounter
584 (SELECT
585 c_enc.modified_when AS earliest,
586 c_epi.pk AS pk_episode
587 FROM
588 clin.episode c_epi
589 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
590 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
591 WHERE c_hi.pk = %(pk_issue)s
592 )
593 UNION ALL
594
595 -- start of encounter in which episodes of this issue were created,
596 -- earliest-possible thereof = set by user
597 (SELECT
598 c_enc.started AS earliest,
599 c_epi.pk AS pk_episode
600 FROM
601 clin.episode c_epi
602 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
603 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
604 WHERE c_hi.pk = %(pk_issue)s
605 )
606 UNION ALL
607
608 -- start of encounters of clinical items linked to episodes of this issue,
609 -- earliest-possible thereof = explicitely set by user
610 (SELECT
611 c_enc.started AS earliest,
612 c_epi.pk AS pk_episode
613 FROM
614 clin.clin_root_item c_cri
615 INNER JOIN clin.encounter c_enc ON (c_cri.fk_encounter = c_enc.pk)
616 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
617 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
618 WHERE c_hi.pk = %(pk_issue)s
619 )
620 UNION ALL
621
622 -- .clin_when of clinical items linked to episodes of this issue,
623 -- earliest-possible thereof = explicitely set by user
624 (SELECT
625 c_cri.clin_when AS earliest,
626 c_epi.pk AS pk_episode
627 FROM
628 clin.clin_root_item c_cri
629 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
630 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
631 WHERE c_hi.pk = %(pk_issue)s
632 )
633 UNION ALL
634
635 -- earliest modification time of clinical items linked to episodes of this issue
636 -- this CAN be used since if an item is linked to an episode it can be
637 -- assumed the episode (should have) existed at the time of creation
638 (SELECT
639 c_cri.modified_when AS earliest,
640 c_epi.pk AS pk_episode
641 FROM
642 clin.clin_root_item c_cri
643 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
644 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
645 WHERE c_hi.pk = %(pk_issue)s
646 )
647 UNION ALL
648
649 -- there may not be items, but there may still be documents ...
650 (SELECT
651 b_dm.clin_when AS earliest,
652 c_epi.pk AS pk_episode
653 FROM
654 blobs.doc_med b_dm
655 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
656 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
657 WHERE c_hi.pk = %(pk_issue)s
658 )
659 ) AS candidates
660 ORDER BY earliest NULLS LAST
661 LIMIT 1"""
662 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
663 if len(rows) == 0:
664 return None
665 return cEpisode(aPK_obj = rows[0]['pk_episode'])
666
667 first_episode = property(_get_first_episode, lambda x:x)
668
669
671
672
673 if self._payload[self._idx['has_open_episode']]:
674 return self.open_episode
675
676 args = {'pk_issue': self.pk_obj}
677
678
679 cmd = "SELECT 1 FROM clin.episode WHERE fk_health_issue = %(pk_issue)s"
680 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
681 if len(rows) == 0:
682 return None
683
684 cmd = """SELECT
685 latest, pk_episode
686 FROM (
687 -- .clin_when of clinical items linked to episodes of this issue,
688 -- latest-possible thereof = explicitely set by user
689 (SELECT
690 c_cri.clin_when AS latest,
691 c_epi.pk AS pk_episode,
692 1 AS rank
693 FROM
694 clin.clin_root_item c_cri
695 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
696 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
697 WHERE c_hi.pk = %(pk_issue)s
698 )
699 UNION ALL
700
701 -- .clin_when of documents linked to episodes of this issue
702 (SELECT
703 b_dm.clin_when AS latest,
704 c_epi.pk AS pk_episode,
705 1 AS rank
706 FROM
707 blobs.doc_med b_dm
708 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
709 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
710 WHERE c_hi.pk = %(pk_issue)s
711 )
712 UNION ALL
713
714 -- last_affirmed of encounter in which episodes of this issue were created,
715 -- earliest-possible thereof = set by user
716 (SELECT
717 c_enc.last_affirmed AS latest,
718 c_epi.pk AS pk_episode,
719 2 AS rank
720 FROM
721 clin.episode c_epi
722 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
723 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
724 WHERE c_hi.pk = %(pk_issue)s
725 )
726
727 ) AS candidates
728 WHERE
729 -- weed out NULL rows due to episodes w/o clinical items and w/o documents
730 latest IS NOT NULL
731 ORDER BY
732 rank,
733 latest DESC
734 LIMIT 1
735 """
736 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
737 if len(rows) == 0:
738
739 return None
740 return cEpisode(aPK_obj = rows[0]['pk_episode'])
741
742 latest_episode = property(_get_latest_episode, lambda x:x)
743
744
745
747 """This returns the date when we can assume to safely KNOW
748 the health issue existed (because the provider said so)."""
749
750 args = {
751 'enc': self._payload[self._idx['pk_encounter']],
752 'pk': self._payload[self._idx['pk_health_issue']]
753 }
754 cmd = """SELECT COALESCE (
755 -- this one must override all:
756 -- .age_noted if not null and DOB is known
757 (CASE
758 WHEN c_hi.age_noted IS NULL
759 THEN NULL::timestamp with time zone
760 WHEN
761 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
762 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
763 )) IS NULL
764 THEN NULL::timestamp with time zone
765 ELSE
766 c_hi.age_noted + (
767 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
768 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
769 )
770 )
771 END),
772
773 -- look at best_guess_clinical_start_date of all linked episodes
774
775 -- start of encounter in which created, earliest = explicitely set
776 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = c_hi.fk_encounter)
777 )
778 FROM clin.health_issue c_hi
779 WHERE c_hi.pk = %(pk)s"""
780 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
781 start = rows[0][0]
782
783
784
785
786 return start
787
788 safe_start_date = property(_get_safe_start_date, lambda x:x)
789
790
792 args = {'pk': self._payload[self._idx['pk_health_issue']]}
793 cmd = """
794 SELECT MIN(earliest) FROM (
795 -- last modification, earliest = when created in/changed to the current state
796 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
797
798 UNION ALL
799 -- last modification of encounter in which created, earliest = initial creation of that encounter
800 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
801 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
802 ))
803
804 UNION ALL
805 -- earliest explicit .clin_when of clinical items linked to this health_issue
806 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
807
808 UNION ALL
809 -- earliest modification time of clinical items linked to this health issue
810 -- this CAN be used since if an item is linked to a health issue it can be
811 -- assumed the health issue (should have) existed at the time of creation
812 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
813
814 UNION ALL
815 -- earliest start of encounters of clinical items linked to this episode
816 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
817 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
818 ))
819
820 -- here we should be looking at
821 -- .best_guess_clinical_start_date of all episodes linked to this encounter
822
823 ) AS candidates"""
824
825 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
826 return rows[0][0]
827
828 possible_start_date = property(_get_possible_start_date)
829
830
843
844 clinical_end_date = property(_get_clinical_end_date)
845
846
848 args = {
849 'enc': self._payload[self._idx['pk_encounter']],
850 'pk': self._payload[self._idx['pk_health_issue']]
851 }
852 cmd = """
853 SELECT
854 MAX(latest)
855 FROM (
856 -- last modification, latest = when last changed to the current state
857 -- DO NOT USE: database upgrades may change this field
858 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
859
860 --UNION ALL
861 -- last modification of encounter in which created, latest = initial creation of that encounter
862 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
863 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
864 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
865 -- )
866 --)
867
868 --UNION ALL
869 -- end of encounter in which created, latest = explicitely set
870 -- DO NOT USE: we can retrospectively create issues which
871 -- DO NOT USE: are long since finished
872 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
873 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
874 -- )
875 --)
876
877 UNION ALL
878 -- latest end of encounters of clinical items linked to this issue
879 (SELECT
880 MAX(last_affirmed) AS latest
881 FROM clin.encounter
882 WHERE pk IN (
883 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
884 )
885 )
886
887 UNION ALL
888 -- latest explicit .clin_when of clinical items linked to this issue
889 (SELECT
890 MAX(clin_when) AS latest
891 FROM clin.v_pat_items
892 WHERE pk_health_issue = %(pk)s
893 )
894
895 -- latest modification time of clinical items linked to this issue
896 -- this CAN be used since if an item is linked to an issue it can be
897 -- assumed the issue (should have) existed at the time of modification
898 -- DO NOT USE, because typo fixes should not extend the issue
899 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
900
901 ) AS candidates"""
902 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
903 return rows[0][0]
904
905 latest_access_date = property(_get_latest_access_date)
906
907
909 try:
910 return laterality2str[self._payload[self._idx['laterality']]]
911 except KeyError:
912 return '<?>'
913
914 laterality_description = property(_get_laterality_description, lambda x:x)
915
916
919
920 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
921
922
924 cmd = """SELECT
925 'NONE (live row)'::text as audit__action_applied,
926 NULL AS audit__action_when,
927 NULL AS audit__action_by,
928 pk_audit,
929 row_version,
930 modified_when,
931 modified_by,
932 pk,
933 description,
934 laterality,
935 age_noted,
936 is_active,
937 clinically_relevant,
938 is_confidential,
939 is_cause_of_death,
940 fk_encounter,
941 grouping,
942 diagnostic_certainty_classification,
943 summary
944 FROM clin.health_issue
945 WHERE pk = %(pk_health_issue)s
946 UNION ALL (
947 SELECT
948 audit_action as audit__action_applied,
949 audit_when as audit__action_when,
950 audit_by as audit__action_by,
951 pk_audit,
952 orig_version as row_version,
953 orig_when as modified_when,
954 orig_by as modified_by,
955 pk,
956 description,
957 laterality,
958 age_noted,
959 is_active,
960 clinically_relevant,
961 is_confidential,
962 is_cause_of_death,
963 fk_encounter,
964 grouping,
965 diagnostic_certainty_classification,
966 summary
967 FROM audit.log_health_issue
968 WHERE pk = %(pk_health_issue)s
969 )
970 ORDER BY row_version DESC
971 """
972 args = {'pk_health_issue': self.pk_obj}
973 title = _('Health issue: %s%s%s') % (
974 gmTools.u_left_double_angle_quote,
975 self._payload[self._idx['description']],
976 gmTools.u_right_double_angle_quote
977 )
978 return '\n'.join(self._get_revision_history(cmd, args, title))
979
980 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
981
983 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
984 return []
985
986 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
987 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
988 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
989 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
990
992 queries = []
993
994 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
995 queries.append ({
996 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
997 'args': {
998 'issue': self._payload[self._idx['pk_health_issue']],
999 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1000 }
1001 })
1002
1003 for pk_code in pk_codes:
1004 queries.append ({
1005 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
1006 'args': {
1007 'issue': self._payload[self._idx['pk_health_issue']],
1008 'pk_code': pk_code
1009 }
1010 })
1011 if len(queries) == 0:
1012 return
1013
1014 rows, idx = gmPG2.run_rw_queries(queries = queries)
1015 return
1016
1017 generic_codes = property(_get_generic_codes, _set_generic_codes)
1018
1019
1021 """Creates a new health issue for a given patient.
1022
1023 description - health issue name
1024 """
1025 try:
1026 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
1027 return h_issue
1028 except gmExceptions.NoSuchBusinessObjectError:
1029 pass
1030
1031 queries = []
1032 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
1033 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
1034
1035 cmd = "select currval('clin.health_issue_pk_seq')"
1036 queries.append({'cmd': cmd})
1037
1038 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
1039 h_issue = cHealthIssue(aPK_obj = rows[0][0])
1040
1041 return h_issue
1042
1043
1045 if isinstance(health_issue, cHealthIssue):
1046 args = {'pk': health_issue['pk_health_issue']}
1047 else:
1048 args = {'pk': int(health_issue)}
1049 try:
1050 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}])
1051 except gmPG2.dbapi.IntegrityError:
1052
1053 _log.exception('cannot delete health issue')
1054 return False
1055
1056 return True
1057
1058
1059
1061 issue = {
1062 'pk_health_issue': None,
1063 'description': _('Unattributed episodes'),
1064 'age_noted': None,
1065 'laterality': 'na',
1066 'is_active': True,
1067 'clinically_relevant': True,
1068 'is_confidential': None,
1069 'is_cause_of_death': False,
1070 'is_dummy': True,
1071 'grouping': None
1072 }
1073 return issue
1074
1075
1077 return cProblem (
1078 aPK_obj = {
1079 'pk_patient': health_issue['pk_patient'],
1080 'pk_health_issue': health_issue['pk_health_issue'],
1081 'pk_episode': None
1082 },
1083 try_potential_problems = allow_irrelevant
1084 )
1085
1086
1087
1088
1089 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1090 """Represents one clinical episode.
1091 """
1092 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s"
1093 _cmds_store_payload = [
1094 """update clin.episode set
1095 fk_health_issue = %(pk_health_issue)s,
1096 is_open = %(episode_open)s::boolean,
1097 description = %(description)s,
1098 summary = gm.nullify_empty_string(%(summary)s),
1099 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
1100 where
1101 pk = %(pk_episode)s and
1102 xmin = %(xmin_episode)s""",
1103 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
1104 ]
1105 _updatable_fields = [
1106 'pk_health_issue',
1107 'episode_open',
1108 'description',
1109 'summary',
1110 'diagnostic_certainty_classification'
1111 ]
1112
1113 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1114 pk = aPK_obj
1115 if pk is None and row is None:
1116
1117 where_parts = ['description = %(desc)s']
1118
1119 if id_patient is not None:
1120 where_parts.append('pk_patient = %(pat)s')
1121
1122 if health_issue is not None:
1123 where_parts.append('pk_health_issue = %(issue)s')
1124
1125 if encounter is not None:
1126 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)')
1127
1128 args = {
1129 'pat': id_patient,
1130 'issue': health_issue,
1131 'enc': encounter,
1132 'desc': name
1133 }
1134
1135 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts)
1136
1137 rows, idx = gmPG2.run_ro_queries (
1138 link_obj = link_obj,
1139 queries = [{'cmd': cmd, 'args': args}],
1140 get_col_idx=True
1141 )
1142
1143 if len(rows) == 0:
1144 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter))
1145
1146 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
1147 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
1148
1149 else:
1150 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1151
1152
1153
1154
1156 return self._payload[self._idx['pk_patient']]
1157
1158
1159 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1166
1167
1168 - def rename(self, description=None):
1169 """Method for episode editing, that is, episode renaming.
1170
1171 @param description
1172 - the new descriptive name for the encounter
1173 @type description
1174 - a string instance
1175 """
1176
1177 if description.strip() == '':
1178 _log.error('<description> must be a non-empty string instance')
1179 return False
1180
1181 old_description = self._payload[self._idx['description']]
1182 self._payload[self._idx['description']] = description.strip()
1183 self._is_modified = True
1184 successful, data = self.save_payload()
1185 if not successful:
1186 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
1187 self._payload[self._idx['description']] = old_description
1188 return False
1189 return True
1190
1191
1193 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1194
1195 if pk_code in self._payload[self._idx['pk_generic_codes']]:
1196 return
1197
1198 cmd = """
1199 INSERT INTO clin.lnk_code2episode
1200 (fk_item, fk_generic_code)
1201 SELECT
1202 %(item)s,
1203 %(code)s
1204 WHERE NOT EXISTS (
1205 SELECT 1 FROM clin.lnk_code2episode
1206 WHERE
1207 fk_item = %(item)s
1208 AND
1209 fk_generic_code = %(code)s
1210 )"""
1211 args = {
1212 'item': self._payload[self._idx['pk_episode']],
1213 'code': pk_code
1214 }
1215 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1216 return
1217
1218
1220 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1221 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1222 args = {
1223 'item': self._payload[self._idx['pk_episode']],
1224 'code': pk_code
1225 }
1226 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1227 return True
1228
1229
1284
1285
1308
1309
1562
1563
1564
1565
1568
1569 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date)
1570
1571
1574
1575 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date)
1576
1577
1583
1584 formatted_clinical_duration = property(_get_formatted_clinical_duration)
1585
1586
1588 cmd = """SELECT MAX(latest) FROM (
1589 -- last modification, latest = when last changed to the current state
1590 (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)
1591
1592 UNION ALL
1593
1594 -- last modification of encounter in which created, latest = initial creation of that encounter
1595 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1596 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1597 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1598 --))
1599
1600 -- end of encounter in which created, latest = explicitely set
1601 -- DO NOT USE: we can retrospectively create episodes which
1602 -- DO NOT USE: are long since finished
1603 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1604 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1605 --))
1606
1607 -- latest end of encounters of clinical items linked to this episode
1608 (SELECT
1609 MAX(last_affirmed) AS latest,
1610 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1611 FROM clin.encounter
1612 WHERE pk IN (
1613 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1614 ))
1615 UNION ALL
1616
1617 -- latest explicit .clin_when of clinical items linked to this episode
1618 (SELECT
1619 MAX(clin_when) AS latest,
1620 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1621 FROM clin.clin_root_item
1622 WHERE fk_episode = %(pk)s
1623 )
1624
1625 -- latest modification time of clinical items linked to this episode
1626 -- this CAN be used since if an item is linked to an episode it can be
1627 -- assumed the episode (should have) existed at the time of creation
1628 -- DO NOT USE, because typo fixes should not extend the episode
1629 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1630
1631 -- not sure about this one:
1632 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1633
1634 ) AS candidates"""
1635 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1636 return rows[0][0]
1637
1638 latest_access_date = property(_get_latest_access_date)
1639
1640
1643
1644 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1645
1646
1648 cmd = """SELECT
1649 'NONE (live row)'::text as audit__action_applied,
1650 NULL AS audit__action_when,
1651 NULL AS audit__action_by,
1652 pk_audit,
1653 row_version,
1654 modified_when,
1655 modified_by,
1656 pk, fk_health_issue, description, is_open, fk_encounter,
1657 diagnostic_certainty_classification,
1658 summary
1659 FROM clin.episode
1660 WHERE pk = %(pk_episode)s
1661 UNION ALL (
1662 SELECT
1663 audit_action as audit__action_applied,
1664 audit_when as audit__action_when,
1665 audit_by as audit__action_by,
1666 pk_audit,
1667 orig_version as row_version,
1668 orig_when as modified_when,
1669 orig_by as modified_by,
1670 pk, fk_health_issue, description, is_open, fk_encounter,
1671 diagnostic_certainty_classification,
1672 summary
1673 FROM audit.log_episode
1674 WHERE pk = %(pk_episode)s
1675 )
1676 ORDER BY row_version DESC
1677 """
1678 args = {'pk_episode': self.pk_obj}
1679 title = _('Episode: %s%s%s') % (
1680 gmTools.u_left_double_angle_quote,
1681 self._payload[self._idx['description']],
1682 gmTools.u_right_double_angle_quote
1683 )
1684 return '\n'.join(self._get_revision_history(cmd, args, title))
1685
1686 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
1687
1688
1690 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1691 return []
1692
1693 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
1694 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1695 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1696 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1697
1699 queries = []
1700
1701 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1702 queries.append ({
1703 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1704 'args': {
1705 'epi': self._payload[self._idx['pk_episode']],
1706 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1707 }
1708 })
1709
1710 for pk_code in pk_codes:
1711 queries.append ({
1712 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1713 'args': {
1714 'epi': self._payload[self._idx['pk_episode']],
1715 'pk_code': pk_code
1716 }
1717 })
1718 if len(queries) == 0:
1719 return
1720
1721 rows, idx = gmPG2.run_rw_queries(queries = queries)
1722 return
1723
1724 generic_codes = property(_get_generic_codes, _set_generic_codes)
1725
1726
1728 cmd = """SELECT EXISTS (
1729 SELECT 1 FROM clin.clin_narrative
1730 WHERE
1731 fk_episode = %(epi)s
1732 AND
1733 fk_encounter IN (
1734 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1735 )
1736 )"""
1737 args = {
1738 'pat': self._payload[self._idx['pk_patient']],
1739 'epi': self._payload[self._idx['pk_episode']]
1740 }
1741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1742 return rows[0][0]
1743
1744 has_narrative = property(_get_has_narrative, lambda x:x)
1745
1746
1748 if self._payload[self._idx['pk_health_issue']] is None:
1749 return None
1750 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1751
1752 health_issue = property(_get_health_issue)
1753
1754
1755 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1756 """Creates a new episode for a given patient's health issue.
1757
1758 pk_health_issue - given health issue PK
1759 episode_name - name of episode
1760 """
1761 if not allow_dupes:
1762 try:
1763 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj)
1764 if episode['episode_open'] != is_open:
1765 episode['episode_open'] = is_open
1766 episode.save_payload()
1767 return episode
1768 except gmExceptions.ConstructorError:
1769 pass
1770
1771 queries = []
1772 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)"
1773 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1774 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"})
1775 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True)
1776
1777 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1778 return episode
1779
1780
1782 if isinstance(episode, cEpisode):
1783 pk = episode['pk_episode']
1784 else:
1785 pk = int(episode)
1786
1787 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s'
1788
1789 try:
1790 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1791 except gmPG2.dbapi.IntegrityError:
1792
1793 _log.exception('cannot delete episode, it is in use')
1794 return False
1795
1796 return True
1797
1798
1800 return cProblem (
1801 aPK_obj = {
1802 'pk_patient': episode['pk_patient'],
1803 'pk_episode': episode['pk_episode'],
1804 'pk_health_issue': episode['pk_health_issue']
1805 },
1806 try_potential_problems = allow_closed
1807 )
1808
1809
1810 _SQL_best_guess_clinical_start_date_for_episode = """
1811 SELECT MIN(earliest) FROM (
1812 -- modified_when of episode,
1813 -- earliest possible thereof = when created,
1814 -- should actually go all the way back into audit.log_episode
1815 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1816
1817 UNION ALL
1818
1819 -- last modification of encounter in which created,
1820 -- earliest-possible thereof = initial creation of that encounter
1821 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1822 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1823 ))
1824 UNION ALL
1825
1826 -- start of encounter in which created,
1827 -- earliest-possible thereof = explicitely set by user
1828 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1829 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1830 ))
1831 UNION ALL
1832
1833 -- start of encounters of clinical items linked to this episode,
1834 -- earliest-possible thereof = explicitely set by user
1835 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1836 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1837 ))
1838 UNION ALL
1839
1840 -- .clin_when of clinical items linked to this episode,
1841 -- earliest-possible thereof = explicitely set by user
1842 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1843
1844 UNION ALL
1845
1846 -- earliest modification time of clinical items linked to this episode
1847 -- this CAN be used since if an item is linked to an episode it can be
1848 -- assumed the episode (should have) existed at the time of creation
1849 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1850
1851 UNION ALL
1852
1853 -- there may not be items, but there may still be documents ...
1854 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s)
1855 ) AS candidates
1856 """
1857
1866
1867
1868 _SQL_best_guess_clinical_end_date_for_episode = """
1869 SELECT
1870 CASE WHEN
1871 -- if open episode ...
1872 (SELECT is_open FROM clin.episode WHERE pk = %(pk)s)
1873 THEN
1874 -- ... no end date
1875 NULL::timestamp with time zone
1876 ELSE (
1877 SELECT COALESCE (
1878 (SELECT
1879 latest --, source_type
1880 FROM (
1881 -- latest explicit .clin_when of clinical items linked to this episode
1882 (SELECT
1883 MAX(clin_when) AS latest,
1884 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type
1885 FROM clin.clin_root_item
1886 WHERE fk_episode = %(pk)s
1887 )
1888 UNION ALL
1889 -- latest explicit .clin_when of documents linked to this episode
1890 (SELECT
1891 MAX(clin_when) AS latest,
1892 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type
1893 FROM blobs.doc_med
1894 WHERE fk_episode = %(pk)s
1895 )
1896 ) AS candidates
1897 ORDER BY latest DESC NULLS LAST
1898 LIMIT 1
1899 ),
1900 -- last ditch, always exists, only use when no clinical items or documents linked:
1901 -- last modification, latest = when last changed to the current state
1902 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type
1903 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s
1904 )
1905 )
1906 )
1907 END
1908 """
1909
1918
1919
1963
1964
1966
1967 _SQL_episode_start = _SQL_best_guess_clinical_start_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1968 _SQL_episode_end = _SQL_best_guess_clinical_end_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1969
1970 _SQL_open_episodes = """
1971 SELECT
1972 c_vpe.pk_episode,
1973 c_vpe.description
1974 AS episode,
1975 c_vpe.health_issue,
1976 1 AS rank,
1977 (%s) AS episode_start,
1978 NULL::timestamp with time zone AS episode_end
1979 FROM
1980 clin.v_pat_episodes c_vpe
1981 WHERE
1982 c_vpe.episode_open IS TRUE
1983 AND
1984 c_vpe.description %%(fragment_condition)s
1985 %%(ctxt_pat)s
1986 """ % _SQL_episode_start
1987
1988 _SQL_closed_episodes = """
1989 SELECT
1990 c_vpe.pk_episode,
1991 c_vpe.description
1992 AS episode,
1993 c_vpe.health_issue,
1994 2 AS rank,
1995 (%s) AS episode_start,
1996 (%s) AS episode_end
1997 FROM
1998 clin.v_pat_episodes c_vpe
1999 WHERE
2000 c_vpe.episode_open IS FALSE
2001 AND
2002 c_vpe.description %%(fragment_condition)s
2003 %%(ctxt_pat)s
2004 """ % (
2005 _SQL_episode_start,
2006 _SQL_episode_end
2007 )
2008
2009
2024
2025
2027 matches = []
2028 for row in rows:
2029 match = {
2030 'weight': 0,
2031 'data': row['pk_episode']
2032 }
2033 label = '%s (%s)%s' % (
2034 row['episode'],
2035 format_clinical_duration_of_episode (
2036 start = row['episode_start'],
2037 end = row['episode_end']
2038 )[0],
2039 gmTools.coalesce (
2040 row['health_issue'],
2041 '',
2042 ' - %s'
2043 )
2044 )
2045 match['list_label'] = label
2046 match['field_label'] = label
2047 matches.append(match)
2048
2049 return matches
2050
2051
2052
2053
2054 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s"
2055
2056 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
2057 """Represents one encounter."""
2058
2059 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s'
2060 _cmds_store_payload = [
2061 """UPDATE clin.encounter SET
2062 started = %(started)s,
2063 last_affirmed = %(last_affirmed)s,
2064 fk_location = %(pk_org_unit)s,
2065 fk_type = %(pk_type)s,
2066 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
2067 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
2068 WHERE
2069 pk = %(pk_encounter)s AND
2070 xmin = %(xmin_encounter)s
2071 """,
2072
2073 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
2074 ]
2075 _updatable_fields = [
2076 'started',
2077 'last_affirmed',
2078 'pk_org_unit',
2079 'pk_type',
2080 'reason_for_encounter',
2081 'assessment_of_encounter'
2082 ]
2083
2085 """Set the encounter as the active one.
2086
2087 "Setting active" means making sure the encounter
2088 row has the youngest "last_affirmed" timestamp of
2089 all encounter rows for this patient.
2090 """
2091 self['last_affirmed'] = gmDateTime.pydt_now_here()
2092 self.save()
2093
2094 - def lock(self, exclusive=False, link_obj=None):
2095 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2096
2097 - def unlock(self, exclusive=False, link_obj=None):
2098 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2099
2101 """
2102 Moves every element currently linked to the current encounter
2103 and the source_episode onto target_episode.
2104
2105 @param source_episode The episode the elements are currently linked to.
2106 @type target_episode A cEpisode intance.
2107 @param target_episode The episode the elements will be relinked to.
2108 @type target_episode A cEpisode intance.
2109 """
2110 if source_episode['pk_episode'] == target_episode['pk_episode']:
2111 return True
2112
2113 queries = []
2114 cmd = """
2115 UPDATE clin.clin_root_item
2116 SET fk_episode = %(trg)s
2117 WHERE
2118 fk_encounter = %(enc)s AND
2119 fk_episode = %(src)s
2120 """
2121 rows, idx = gmPG2.run_rw_queries(queries = [{
2122 'cmd': cmd,
2123 'args': {
2124 'trg': target_episode['pk_episode'],
2125 'enc': self.pk_obj,
2126 'src': source_episode['pk_episode']
2127 }
2128 }])
2129 self.refetch_payload()
2130 return True
2131
2132
2134 if pk_target_encounter == self.pk_obj:
2135 return True
2136 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)"
2137 args = {
2138 'src': self.pk_obj,
2139 'trg': pk_target_encounter
2140 }
2141 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2142 return True
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2154
2155 relevant_fields = [
2156 'pk_org_unit',
2157 'pk_type',
2158 'pk_patient',
2159 'reason_for_encounter',
2160 'assessment_of_encounter'
2161 ]
2162 for field in relevant_fields:
2163 if self._payload[self._idx[field]] != another_object[field]:
2164 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
2165 return False
2166
2167 relevant_fields = [
2168 'started',
2169 'last_affirmed',
2170 ]
2171 for field in relevant_fields:
2172 if self._payload[self._idx[field]] is None:
2173 if another_object[field] is None:
2174 continue
2175 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2176 return False
2177
2178 if another_object[field] is None:
2179 return False
2180
2181
2182 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'):
2183 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2184 return False
2185
2186
2187
2188 if another_object['pk_generic_codes_rfe'] is None:
2189 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
2190 return False
2191 if another_object['pk_generic_codes_rfe'] is not None:
2192 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
2193 return False
2194 if (
2195 (another_object['pk_generic_codes_rfe'] is None)
2196 and
2197 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
2198 ) is False:
2199 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
2200 return False
2201
2202 if another_object['pk_generic_codes_aoe'] is None:
2203 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
2204 return False
2205 if another_object['pk_generic_codes_aoe'] is not None:
2206 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
2207 return False
2208 if (
2209 (another_object['pk_generic_codes_aoe'] is None)
2210 and
2211 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
2212 ) is False:
2213 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
2214 return False
2215
2216 return True
2217
2219 cmd = """
2220 select exists (
2221 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
2222 union all
2223 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2224 )"""
2225 args = {
2226 'pat': self._payload[self._idx['pk_patient']],
2227 'enc': self.pk_obj
2228 }
2229 rows, idx = gmPG2.run_ro_queries (
2230 queries = [{
2231 'cmd': cmd,
2232 'args': args
2233 }]
2234 )
2235 return rows[0][0]
2236
2237
2239 cmd = """
2240 select exists (
2241 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
2242 )"""
2243 args = {
2244 'pat': self._payload[self._idx['pk_patient']],
2245 'enc': self.pk_obj
2246 }
2247 rows, idx = gmPG2.run_ro_queries (
2248 queries = [{
2249 'cmd': cmd,
2250 'args': args
2251 }]
2252 )
2253 return rows[0][0]
2254
2256 """soap_cats: <space> = admin category"""
2257
2258 if soap_cats is None:
2259 soap_cats = 'soap '
2260 else:
2261 soap_cats = soap_cats.lower()
2262
2263 cats = []
2264 for cat in soap_cats:
2265 if cat in 'soapu':
2266 cats.append(cat)
2267 continue
2268 if cat == ' ':
2269 cats.append(None)
2270
2271 cmd = """
2272 SELECT EXISTS (
2273 SELECT 1 FROM clin.clin_narrative
2274 WHERE
2275 fk_encounter = %(enc)s
2276 AND
2277 soap_cat IN %(cats)s
2278 LIMIT 1
2279 )
2280 """
2281 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
2282 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
2283 return rows[0][0]
2284
2286 cmd = """
2287 select exists (
2288 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2289 )"""
2290 args = {
2291 'pat': self._payload[self._idx['pk_patient']],
2292 'enc': self.pk_obj
2293 }
2294 rows, idx = gmPG2.run_ro_queries (
2295 queries = [{
2296 'cmd': cmd,
2297 'args': args
2298 }]
2299 )
2300 return rows[0][0]
2301
2303
2304 if soap_cat is not None:
2305 soap_cat = soap_cat.lower()
2306
2307 if episode is None:
2308 epi_part = 'fk_episode is null'
2309 else:
2310 epi_part = 'fk_episode = %(epi)s'
2311
2312 cmd = """
2313 select narrative
2314 from clin.clin_narrative
2315 where
2316 fk_encounter = %%(enc)s
2317 and
2318 soap_cat = %%(cat)s
2319 and
2320 %s
2321 order by clin_when desc
2322 limit 1
2323 """ % epi_part
2324
2325 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
2326
2327 rows, idx = gmPG2.run_ro_queries (
2328 queries = [{
2329 'cmd': cmd,
2330 'args': args
2331 }]
2332 )
2333 if len(rows) == 0:
2334 return None
2335
2336 return rows[0][0]
2337
2339 cmd = """
2340 SELECT * FROM clin.v_pat_episodes
2341 WHERE pk_episode IN (
2342 SELECT DISTINCT fk_episode
2343 FROM clin.clin_root_item
2344 WHERE fk_encounter = %%(enc)s
2345
2346 UNION
2347
2348 SELECT DISTINCT fk_episode
2349 FROM blobs.doc_med
2350 WHERE fk_encounter = %%(enc)s
2351 ) %s"""
2352 args = {'enc': self.pk_obj}
2353 if exclude is not None:
2354 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s'
2355 args['excluded'] = tuple(exclude)
2356 else:
2357 cmd = cmd % ''
2358
2359 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2360
2361 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2362
2363 episodes = property(get_episodes, lambda x:x)
2364
2365 - def add_code(self, pk_code=None, field=None):
2366 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2367 if field == 'rfe':
2368 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2369 elif field == 'aoe':
2370 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2371 else:
2372 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2373 args = {
2374 'item': self._payload[self._idx['pk_encounter']],
2375 'code': pk_code
2376 }
2377 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2378 return True
2379
2381 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2382 if field == 'rfe':
2383 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2384 elif field == 'aoe':
2385 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2386 else:
2387 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2388 args = {
2389 'item': self._payload[self._idx['pk_encounter']],
2390 'code': pk_code
2391 }
2392 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2393 return True
2394
2395
2396
2397
2443
2444
2540
2541
2601
2602
2655
2656
2755
2756
2778
2779
2914
2915
2916
2917
2919 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2920 return []
2921
2922 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2923 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2924 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2925 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2926
2928 queries = []
2929
2930 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2931 queries.append ({
2932 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2933 'args': {
2934 'enc': self._payload[self._idx['pk_encounter']],
2935 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2936 }
2937 })
2938
2939 for pk_code in pk_codes:
2940 queries.append ({
2941 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2942 'args': {
2943 'enc': self._payload[self._idx['pk_encounter']],
2944 'pk_code': pk_code
2945 }
2946 })
2947 if len(queries) == 0:
2948 return
2949
2950 rows, idx = gmPG2.run_rw_queries(queries = queries)
2951 self.refetch_payload()
2952 return
2953
2954 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2955
2957 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2958 return []
2959
2960 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2961 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2962 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2963 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2964
2966 queries = []
2967
2968 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2969 queries.append ({
2970 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2971 'args': {
2972 'enc': self._payload[self._idx['pk_encounter']],
2973 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2974 }
2975 })
2976
2977 for pk_code in pk_codes:
2978 queries.append ({
2979 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2980 'args': {
2981 'enc': self._payload[self._idx['pk_encounter']],
2982 'pk_code': pk_code
2983 }
2984 })
2985 if len(queries) == 0:
2986 return
2987
2988 rows, idx = gmPG2.run_rw_queries(queries = queries)
2989 self.refetch_payload()
2990 return
2991
2992 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2993
2998
2999 praxis_branch = property(_get_praxis_branch, lambda x:x)
3000
3002 if self._payload[self._idx['pk_org_unit']] is None:
3003 return None
3004 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
3005
3006 org_unit = property(_get_org_unit, lambda x:x)
3007
3009 cmd = """SELECT
3010 'NONE (live row)'::text as audit__action_applied,
3011 NULL AS audit__action_when,
3012 NULL AS audit__action_by,
3013 pk_audit,
3014 row_version,
3015 modified_when,
3016 modified_by,
3017 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3018 FROM clin.encounter
3019 WHERE pk = %(pk_encounter)s
3020 UNION ALL (
3021 SELECT
3022 audit_action as audit__action_applied,
3023 audit_when as audit__action_when,
3024 audit_by as audit__action_by,
3025 pk_audit,
3026 orig_version as row_version,
3027 orig_when as modified_when,
3028 orig_by as modified_by,
3029 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3030 FROM audit.log_encounter
3031 WHERE pk = %(pk_encounter)s
3032 )
3033 ORDER BY row_version DESC
3034 """
3035 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]}
3036 title = _('Encounter: %s%s%s') % (
3037 gmTools.u_left_double_angle_quote,
3038 self._payload[self._idx['l10n_type']],
3039 gmTools.u_right_double_angle_quote
3040 )
3041 return '\n'.join(self._get_revision_history(cmd, args, title))
3042
3043 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
3044
3045
3047 """Creates a new encounter for a patient.
3048
3049 fk_patient - patient PK
3050 enc_type - type of encounter
3051 """
3052 if enc_type is None:
3053 enc_type = 'in surgery'
3054
3055 queries = []
3056 try:
3057 enc_type = int(enc_type)
3058 cmd = """
3059 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location)
3060 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk"""
3061 except ValueError:
3062 enc_type = enc_type
3063 cmd = """
3064 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type)
3065 VALUES (
3066 %(pat)s,
3067 %(prax)s,
3068 coalesce (
3069 (select pk from clin.encounter_type where description = %(typ)s),
3070 -- pick the first available
3071 (select pk from clin.encounter_type limit 1)
3072 )
3073 ) RETURNING pk"""
3074 praxis = gmPraxis.gmCurrentPraxisBranch()
3075 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']}
3076 queries.append({'cmd': cmd, 'args': args})
3077 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
3078 encounter = cEncounter(aPK_obj = rows[0]['pk'])
3079
3080 return encounter
3081
3082
3084 """Used to protect against deletion of active encounter from another client."""
3085 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
3086
3087
3090
3091
3093 """Deletes an encounter by PK.
3094
3095 - attempts to obtain an exclusive lock which should
3096 fail if the encounter is the active encounter in
3097 this or any other client
3098 - catches DB exceptions which should mostly be related
3099 to clinical data already having been attached to
3100 the encounter thus making deletion fail
3101 """
3102 conn = gmPG2.get_connection(readonly = False)
3103 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn):
3104 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter)
3105 return False
3106 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s"""
3107 args = {'enc': pk_encounter}
3108 try:
3109 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3110 except gmPG2.PG_ERROR_EXCEPTION:
3111 _log.exception('cannot delete encounter [%s]', pk_encounter)
3112 gmPG2.log_pg_exception_details(exc)
3113 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3114 return False
3115 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3116 return True
3117
3118
3119
3120
3122
3123 rows, idx = gmPG2.run_rw_queries(
3124 queries = [{
3125 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
3126 'args': {'desc': description, 'l10n_desc': l10n_description}
3127 }],
3128 return_data = True
3129 )
3130
3131 success = rows[0][0]
3132 if not success:
3133 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
3134
3135 return {'description': description, 'l10n_description': l10n_description}
3136
3138 """This will attempt to create a NEW encounter type."""
3139
3140
3141 if description is None:
3142 description = l10n_description
3143
3144 args = {
3145 'desc': description,
3146 'l10n_desc': l10n_description
3147 }
3148
3149 _log.debug('creating encounter type: %s, %s', description, l10n_description)
3150
3151
3152 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s"
3153 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3154
3155
3156 if len(rows) > 0:
3157
3158 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
3159 _log.info('encounter type [%s] already exists with the proper translation')
3160 return {'description': description, 'l10n_description': l10n_description}
3161
3162
3163
3164 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
3165 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3166
3167
3168 if rows[0][0]:
3169 _log.error('encounter type [%s] already exists but with another translation')
3170 return None
3171
3172
3173 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
3174 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3175 return {'description': description, 'l10n_description': l10n_description}
3176
3177
3178 queries = [
3179 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
3180 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
3181 ]
3182 rows, idx = gmPG2.run_rw_queries(queries = queries)
3183
3184 return {'description': description, 'l10n_description': l10n_description}
3185
3186
3188 cmd = """
3189 SELECT
3190 COUNT(1) AS type_count,
3191 fk_type
3192 FROM clin.encounter
3193 GROUP BY fk_type
3194 ORDER BY type_count DESC
3195 LIMIT 1
3196 """
3197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3198 if len(rows) == 0:
3199 return None
3200 return rows[0]['fk_type']
3201
3202
3204 cmd = """
3205 SELECT
3206 _(description) AS l10n_description,
3207 description
3208 FROM
3209 clin.encounter_type
3210 ORDER BY
3211 l10n_description
3212 """
3213 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3214 return rows
3215
3216
3221
3222
3224 deleted = False
3225 cmd = "delete from clin.encounter_type where description = %(desc)s"
3226 args = {'desc': description}
3227 try:
3228 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3229 deleted = True
3230 except gmPG2.dbapi.IntegrityError as e:
3231 if e.pgcode != gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
3232 raise
3233
3234 return deleted
3235
3236
3237 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3238 """Represents one problem.
3239
3240 problems are the aggregation of
3241 .clinically_relevant=True issues and
3242 .is_open=True episodes
3243 """
3244 _cmd_fetch_payload = ''
3245 _cmds_store_payload = ["select 1"]
3246 _updatable_fields = []
3247
3248
3249 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3250 """Initialize.
3251
3252 aPK_obj must contain the keys
3253 pk_patient
3254 pk_episode
3255 pk_health_issue
3256 """
3257 if aPK_obj is None:
3258 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj))
3259
3260
3261
3262
3263
3264 where_parts = []
3265 pk = {}
3266 for col_name in aPK_obj.keys():
3267 val = aPK_obj[col_name]
3268 if val is None:
3269 where_parts.append('%s IS NULL' % col_name)
3270 else:
3271 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
3272 pk[col_name] = val
3273
3274
3275 cProblem._cmd_fetch_payload = """
3276 SELECT *, False as is_potential_problem
3277 FROM clin.v_problem_list
3278 WHERE %s""" % ' AND '.join(where_parts)
3279
3280 try:
3281 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj = pk)
3282 return
3283
3284 except gmExceptions.ConstructorError:
3285 _log.exception('actual problem not found, trying "potential" problems')
3286 if try_potential_problems is False:
3287 raise
3288
3289
3290 cProblem._cmd_fetch_payload = """
3291 SELECT *, True as is_potential_problem
3292 FROM clin.v_potential_problem_list
3293 WHERE %s""" % ' AND '.join(where_parts)
3294 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3295
3297 """
3298 Retrieve the cEpisode instance equivalent to this problem.
3299 The problem's type attribute must be 'episode'
3300 """
3301 if self._payload[self._idx['type']] != 'episode':
3302 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3303 return None
3304 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3305
3307 """
3308 Retrieve the cHealthIssue instance equivalent to this problem.
3309 The problem's type attribute must be 'issue'
3310 """
3311 if self._payload[self._idx['type']] != 'issue':
3312 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3313 return None
3314 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3315
3331
3332
3333
3334
3335
3338
3339 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
3340
3342 if self._payload[self._idx['type']] == 'issue':
3343 cmd = """
3344 SELECT * FROM clin.v_linked_codes WHERE
3345 item_table = 'clin.lnk_code2h_issue'::regclass
3346 AND
3347 pk_item = %(item)s
3348 """
3349 args = {'item': self._payload[self._idx['pk_health_issue']]}
3350 else:
3351 cmd = """
3352 SELECT * FROM clin.v_linked_codes WHERE
3353 item_table = 'clin.lnk_code2episode'::regclass
3354 AND
3355 pk_item = %(item)s
3356 """
3357 args = {'item': self._payload[self._idx['pk_episode']]}
3358
3359 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3360 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3361
3362 generic_codes = property(_get_generic_codes, lambda x:x)
3363
3365 """Retrieve the cEpisode instance equivalent to the given problem.
3366
3367 The problem's type attribute must be 'episode'
3368
3369 @param problem: The problem to retrieve its related episode for
3370 @type problem: A gmEMRStructItems.cProblem instance
3371 """
3372 if isinstance(problem, cEpisode):
3373 return problem
3374
3375 exc = TypeError('cannot convert [%s] to episode' % problem)
3376
3377 if not isinstance(problem, cProblem):
3378 raise exc
3379
3380 if problem['type'] != 'episode':
3381 raise exc
3382
3383 return cEpisode(aPK_obj = problem['pk_episode'])
3384
3386 """Retrieve the cIssue instance equivalent to the given problem.
3387
3388 The problem's type attribute must be 'issue'.
3389
3390 @param problem: The problem to retrieve the corresponding issue for
3391 @type problem: A gmEMRStructItems.cProblem instance
3392 """
3393 if isinstance(problem, cHealthIssue):
3394 return problem
3395
3396 exc = TypeError('cannot convert [%s] to health issue' % problem)
3397
3398 if not isinstance(problem, cProblem):
3399 raise exc
3400
3401 if problem['type'] != 'issue':
3402 raise exc
3403
3404 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3405
3407 """Transform given problem into either episode or health issue instance.
3408 """
3409 if isinstance(problem, (cEpisode, cHealthIssue)):
3410 return problem
3411
3412 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
3413
3414 if not isinstance(problem, cProblem):
3415 _log.debug('%s' % problem)
3416 raise exc
3417
3418 if problem['type'] == 'episode':
3419 return cEpisode(aPK_obj = problem['pk_episode'])
3420
3421 if problem['type'] == 'issue':
3422 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3423
3424 raise exc
3425
3426
3427 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s"
3428
3430
3431 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s"
3432 _cmds_store_payload = [
3433 """UPDATE clin.hospital_stay SET
3434 clin_when = %(admission)s,
3435 discharge = %(discharge)s,
3436 fk_org_unit = %(pk_org_unit)s,
3437 narrative = gm.nullify_empty_string(%(comment)s),
3438 fk_episode = %(pk_episode)s,
3439 fk_encounter = %(pk_encounter)s
3440 WHERE
3441 pk = %(pk_hospital_stay)s
3442 AND
3443 xmin = %(xmin_hospital_stay)s
3444 RETURNING
3445 xmin AS xmin_hospital_stay
3446 """
3447 ]
3448 _updatable_fields = [
3449 'admission',
3450 'discharge',
3451 'pk_org_unit',
3452 'pk_episode',
3453 'pk_encounter',
3454 'comment'
3455 ]
3456
3457
3463
3464
3498
3499
3501 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3502
3503 documents = property(_get_documents, lambda x:x)
3504
3505
3507 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1"
3508 queries = [{
3509
3510
3511 'cmd': cmd,
3512 'args': {'pat': patient}
3513 }]
3514 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3515 if len(rows) == 0:
3516 return None
3517 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3518
3519
3521 args = {'pat': patient}
3522 if ongoing_only:
3523 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission"
3524 else:
3525 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission"
3526
3527 queries = [{'cmd': cmd, 'args': args}]
3528 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3529 if return_pks:
3530 return [ r['pk_hospital_stay'] for r in rows ]
3531 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3532
3533
3535
3536 queries = [{
3537 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk',
3538 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit}
3539 }]
3540 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3541
3542 return cHospitalStay(aPK_obj = rows[0][0])
3543
3544
3546 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
3547 args = {'pk': stay}
3548 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3549 return True
3550
3551
3552 _SQL_get_procedures = "select * from clin.v_procedures where %s"
3553
3753
3754
3764
3765
3767 args = {'pk_doc': pk_document}
3768 cmd = _SQL_get_procedures % 'pk_doc = %(pk_doc)s'
3769 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3770 if return_pks:
3771 return [ r['pk_procedure'] for r in rows ]
3772 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3773
3774
3784
3785
3812
3813
3819
3820
3866
3867
3868
3869
3871
3872 aggregate_result = 0
3873
3874 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk')
3875 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ])
3876
3877 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk')
3878 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ]
3879
3880 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi)
3881
3882 tables_linking2enc = {}
3883 for fk in fks_linking2enc:
3884 table = fk['referencing_table']
3885 tables_linking2enc[table] = fk
3886
3887 tables_linking2epi = {}
3888 for fk in fks_linking2epi:
3889 table = fk['referencing_table']
3890 tables_linking2epi[table] = fk
3891
3892 for t in tables_linking2both:
3893
3894 table_file_name = 'x-check_enc_epi_xref-%s.log' % t
3895 table_file = io.open(table_file_name, 'w+', encoding = 'utf8')
3896
3897
3898 args = {'table': t}
3899 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}])
3900 pk_col = rows[0][0]
3901 print("checking table:", t, '- pk col:', pk_col)
3902 print(' =>', table_file_name)
3903 table_file.write('table: %s\n' % t)
3904 table_file.write('PK col: %s\n' % pk_col)
3905
3906
3907 cmd = 'select %s from %s' % (pk_col, t)
3908 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
3909 pks = [ r[0] for r in rows ]
3910 for pk in pks:
3911 args = {'pk': pk, 'tbl': t}
3912 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col)
3913 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)
3914 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}])
3915 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}])
3916 enc_pat = enc_rows[0][0]
3917 epi_pat = epi_rows[0][0]
3918 args['pat_enc'] = enc_pat
3919 args['pat_epi'] = epi_pat
3920 if epi_pat != enc_pat:
3921 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat))
3922 aggregate_result = -2
3923
3924 table_file.write('--------------------------------------------------------------------------------\n')
3925 table_file.write('mismatch on row with pk: %s\n' % pk)
3926 table_file.write('\n')
3927
3928 table_file.write('journal entry:\n')
3929 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s'
3930 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3931 if len(rows) > 0:
3932 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3933 table_file.write('\n\n')
3934
3935 table_file.write('row data:\n')
3936 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col)
3937 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3938 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3939 table_file.write('\n\n')
3940
3941 table_file.write('episode:\n')
3942 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col)
3943 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3944 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3945 table_file.write('\n\n')
3946
3947 table_file.write('patient of episode:\n')
3948 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s'
3949 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3950 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3951 table_file.write('\n\n')
3952
3953 table_file.write('encounter:\n')
3954 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col)
3955 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3956 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3957 table_file.write('\n\n')
3958
3959 table_file.write('patient of encounter:\n')
3960 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s'
3961 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3962 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3963 table_file.write('\n')
3964
3965 table_file.write('done\n')
3966 table_file.close()
3967
3968 return aggregate_result
3969
3970
3982
3983
3984
3985
3986 if __name__ == '__main__':
3987
3988 if len(sys.argv) < 2:
3989 sys.exit()
3990
3991 if sys.argv[1] != 'test':
3992 sys.exit()
3993
3994
3995
3996
3998 print("\nProblem test")
3999 print("------------")
4000 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
4001 print(prob)
4002 fields = prob.get_fields()
4003 for field in fields:
4004 print(field, ':', prob[field])
4005 print('\nupdatable:', prob.get_updatable_fields())
4006 epi = prob.get_as_episode()
4007 print('\nas episode:')
4008 if epi is not None:
4009 for field in epi.get_fields():
4010 print(' .%s : %s' % (field, epi[field]))
4011
4012
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4037 print("episode test")
4038 for i in range(1,15):
4039 print("------------")
4040 episode = cEpisode(aPK_obj = i)
4041
4042 print(episode['description'])
4043 print(' start:', episode.best_guess_clinical_start_date)
4044 print(' end :', episode.best_guess_clinical_end_date)
4045 print(' dura :', get_formatted_clinical_duration(pk_episode = i))
4046 return
4047
4048 print(episode)
4049 fields = episode.get_fields()
4050 for field in fields:
4051 print(field, ':', episode[field])
4052 print("updatable:", episode.get_updatable_fields())
4053 input('ENTER to continue')
4054
4055 old_description = episode['description']
4056 old_enc = cEncounter(aPK_obj = 1)
4057
4058 desc = '1-%s' % episode['description']
4059 print("==> renaming to", desc)
4060 successful = episode.rename (
4061 description = desc
4062 )
4063 if not successful:
4064 print("error")
4065 else:
4066 print("success")
4067 for field in fields:
4068 print(field, ':', episode[field])
4069
4070 print(episode.formatted_revision_history)
4071
4072 input('ENTER to continue')
4073
4074
4086
4087
4089 encounter = cEncounter(aPK_obj=1)
4090 print(encounter)
4091 print("")
4092 print(encounter.format_latex())
4093
4098
4108
4115
4120
4124
4125
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141 test_health_issue()
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151