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 return rows[0][0]
782
783 safe_start_date = property(_get_safe_start_date, lambda x:x)
784
785
787 args = {'pk': self._payload[self._idx['pk_health_issue']]}
788 cmd = """
789 SELECT MIN(earliest) FROM (
790 -- last modification, earliest = when created in/changed to the current state
791 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
792
793 UNION ALL
794 -- last modification of encounter in which created, earliest = initial creation of that encounter
795 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
796 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
797 ))
798
799 UNION ALL
800 -- earliest explicit .clin_when of clinical items linked to this health_issue
801 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
802
803 UNION ALL
804 -- earliest modification time of clinical items linked to this health issue
805 -- this CAN be used since if an item is linked to a health issue it can be
806 -- assumed the health issue (should have) existed at the time of creation
807 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
808
809 UNION ALL
810 -- earliest start of encounters of clinical items linked to this episode
811 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
812 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
813 ))
814
815 -- here we should be looking at
816 -- .best_guess_clinical_start_date of all episodes linked to this encounter
817
818 ) AS candidates"""
819
820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
821 return rows[0][0]
822
823 possible_start_date = property(_get_possible_start_date)
824
825
838
839 clinical_end_date = property(_get_clinical_end_date)
840
841
843 args = {
844 'enc': self._payload[self._idx['pk_encounter']],
845 'pk': self._payload[self._idx['pk_health_issue']]
846 }
847 cmd = """
848 SELECT
849 MAX(latest)
850 FROM (
851 -- last modification, latest = when last changed to the current state
852 -- DO NOT USE: database upgrades may change this field
853 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
854
855 --UNION ALL
856 -- last modification of encounter in which created, latest = initial creation of that encounter
857 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
858 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
859 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
860 -- )
861 --)
862
863 --UNION ALL
864 -- end of encounter in which created, latest = explicitely set
865 -- DO NOT USE: we can retrospectively create issues which
866 -- DO NOT USE: are long since finished
867 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
868 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
869 -- )
870 --)
871
872 UNION ALL
873 -- latest end of encounters of clinical items linked to this issue
874 (SELECT
875 MAX(last_affirmed) AS latest
876 FROM clin.encounter
877 WHERE pk IN (
878 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
879 )
880 )
881
882 UNION ALL
883 -- latest explicit .clin_when of clinical items linked to this issue
884 (SELECT
885 MAX(clin_when) AS latest
886 FROM clin.v_pat_items
887 WHERE pk_health_issue = %(pk)s
888 )
889
890 -- latest modification time of clinical items linked to this issue
891 -- this CAN be used since if an item is linked to an issue it can be
892 -- assumed the issue (should have) existed at the time of modification
893 -- DO NOT USE, because typo fixes should not extend the issue
894 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
895
896 ) AS candidates"""
897 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
898 return rows[0][0]
899
900 latest_access_date = property(_get_latest_access_date)
901
902
904 try:
905 return laterality2str[self._payload[self._idx['laterality']]]
906 except KeyError:
907 return '<?>'
908
909 laterality_description = property(_get_laterality_description, lambda x:x)
910
911
914
915 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
916
917
919 cmd = """SELECT
920 'NONE (live row)'::text as audit__action_applied,
921 NULL AS audit__action_when,
922 NULL AS audit__action_by,
923 pk_audit,
924 row_version,
925 modified_when,
926 modified_by,
927 pk,
928 description,
929 laterality,
930 age_noted,
931 is_active,
932 clinically_relevant,
933 is_confidential,
934 is_cause_of_death,
935 fk_encounter,
936 grouping,
937 diagnostic_certainty_classification,
938 summary
939 FROM clin.health_issue
940 WHERE pk = %(pk_health_issue)s
941 UNION ALL (
942 SELECT
943 audit_action as audit__action_applied,
944 audit_when as audit__action_when,
945 audit_by as audit__action_by,
946 pk_audit,
947 orig_version as row_version,
948 orig_when as modified_when,
949 orig_by as modified_by,
950 pk,
951 description,
952 laterality,
953 age_noted,
954 is_active,
955 clinically_relevant,
956 is_confidential,
957 is_cause_of_death,
958 fk_encounter,
959 grouping,
960 diagnostic_certainty_classification,
961 summary
962 FROM audit.log_health_issue
963 WHERE pk = %(pk_health_issue)s
964 )
965 ORDER BY row_version DESC
966 """
967 args = {'pk_health_issue': self.pk_obj}
968 title = _('Health issue: %s%s%s') % (
969 gmTools.u_left_double_angle_quote,
970 self._payload[self._idx['description']],
971 gmTools.u_right_double_angle_quote
972 )
973 return '\n'.join(self._get_revision_history(cmd, args, title))
974
975 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
976
978 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
979 return []
980
981 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
982 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
983 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
984 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
985
987 queries = []
988
989 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
990 queries.append ({
991 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
992 'args': {
993 'issue': self._payload[self._idx['pk_health_issue']],
994 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
995 }
996 })
997
998 for pk_code in pk_codes:
999 queries.append ({
1000 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
1001 'args': {
1002 'issue': self._payload[self._idx['pk_health_issue']],
1003 'pk_code': pk_code
1004 }
1005 })
1006 if len(queries) == 0:
1007 return
1008
1009 rows, idx = gmPG2.run_rw_queries(queries = queries)
1010 return
1011
1012 generic_codes = property(_get_generic_codes, _set_generic_codes)
1013
1014
1016 """Creates a new health issue for a given patient.
1017
1018 description - health issue name
1019 """
1020 try:
1021 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
1022 return h_issue
1023 except gmExceptions.NoSuchBusinessObjectError:
1024 pass
1025
1026 queries = []
1027 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
1028 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
1029
1030 cmd = "select currval('clin.health_issue_pk_seq')"
1031 queries.append({'cmd': cmd})
1032
1033 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
1034 h_issue = cHealthIssue(aPK_obj = rows[0][0])
1035
1036 return h_issue
1037
1038
1040 if isinstance(health_issue, cHealthIssue):
1041 args = {'pk': health_issue['pk_health_issue']}
1042 else:
1043 args = {'pk': int(health_issue)}
1044 try:
1045 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}])
1046 except gmPG2.dbapi.IntegrityError:
1047
1048 _log.exception('cannot delete health issue')
1049 return False
1050
1051 return True
1052
1053
1054
1056 issue = {
1057 'pk_health_issue': None,
1058 'description': _('Unattributed episodes'),
1059 'age_noted': None,
1060 'laterality': 'na',
1061 'is_active': True,
1062 'clinically_relevant': True,
1063 'is_confidential': None,
1064 'is_cause_of_death': False,
1065 'is_dummy': True,
1066 'grouping': None
1067 }
1068 return issue
1069
1070
1072 return cProblem (
1073 aPK_obj = {
1074 'pk_patient': health_issue['pk_patient'],
1075 'pk_health_issue': health_issue['pk_health_issue'],
1076 'pk_episode': None
1077 },
1078 try_potential_problems = allow_irrelevant
1079 )
1080
1081
1082
1083
1084 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1085 """Represents one clinical episode.
1086 """
1087 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s"
1088 _cmds_store_payload = [
1089 """update clin.episode set
1090 fk_health_issue = %(pk_health_issue)s,
1091 is_open = %(episode_open)s::boolean,
1092 description = %(description)s,
1093 summary = gm.nullify_empty_string(%(summary)s),
1094 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
1095 where
1096 pk = %(pk_episode)s and
1097 xmin = %(xmin_episode)s""",
1098 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
1099 ]
1100 _updatable_fields = [
1101 'pk_health_issue',
1102 'episode_open',
1103 'description',
1104 'summary',
1105 'diagnostic_certainty_classification'
1106 ]
1107
1108 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1109 pk = aPK_obj
1110 if pk is None and row is None:
1111
1112 where_parts = ['description = %(desc)s']
1113
1114 if id_patient is not None:
1115 where_parts.append('pk_patient = %(pat)s')
1116
1117 if health_issue is not None:
1118 where_parts.append('pk_health_issue = %(issue)s')
1119
1120 if encounter is not None:
1121 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)')
1122
1123 args = {
1124 'pat': id_patient,
1125 'issue': health_issue,
1126 'enc': encounter,
1127 'desc': name
1128 }
1129
1130 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts)
1131
1132 rows, idx = gmPG2.run_ro_queries (
1133 link_obj = link_obj,
1134 queries = [{'cmd': cmd, 'args': args}],
1135 get_col_idx=True
1136 )
1137
1138 if len(rows) == 0:
1139 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter))
1140
1141 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
1142 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
1143
1144 else:
1145 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1146
1147
1148
1149
1151 return self._payload[self._idx['pk_patient']]
1152
1153
1154 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1161
1162
1163 - def rename(self, description=None):
1164 """Method for episode editing, that is, episode renaming.
1165
1166 @param description
1167 - the new descriptive name for the encounter
1168 @type description
1169 - a string instance
1170 """
1171
1172 if description.strip() == '':
1173 _log.error('<description> must be a non-empty string instance')
1174 return False
1175
1176 old_description = self._payload[self._idx['description']]
1177 self._payload[self._idx['description']] = description.strip()
1178 self._is_modified = True
1179 successful, data = self.save_payload()
1180 if not successful:
1181 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
1182 self._payload[self._idx['description']] = old_description
1183 return False
1184 return True
1185
1186
1188 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1189
1190 if pk_code in self._payload[self._idx['pk_generic_codes']]:
1191 return
1192
1193 cmd = """
1194 INSERT INTO clin.lnk_code2episode
1195 (fk_item, fk_generic_code)
1196 SELECT
1197 %(item)s,
1198 %(code)s
1199 WHERE NOT EXISTS (
1200 SELECT 1 FROM clin.lnk_code2episode
1201 WHERE
1202 fk_item = %(item)s
1203 AND
1204 fk_generic_code = %(code)s
1205 )"""
1206 args = {
1207 'item': self._payload[self._idx['pk_episode']],
1208 'code': pk_code
1209 }
1210 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1211 return
1212
1213
1215 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1216 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1217 args = {
1218 'item': self._payload[self._idx['pk_episode']],
1219 'code': pk_code
1220 }
1221 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1222 return True
1223
1224
1279
1280
1303
1304
1557
1558
1559
1560
1563
1564 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date)
1565
1566
1569
1570 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date)
1571
1572
1578
1579 formatted_clinical_duration = property(_get_formatted_clinical_duration)
1580
1581
1583 cmd = """SELECT MAX(latest) FROM (
1584 -- last modification, latest = when last changed to the current state
1585 (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)
1586
1587 UNION ALL
1588
1589 -- last modification of encounter in which created, latest = initial creation of that encounter
1590 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1591 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1592 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1593 --))
1594
1595 -- end of encounter in which created, latest = explicitely set
1596 -- DO NOT USE: we can retrospectively create episodes which
1597 -- DO NOT USE: are long since finished
1598 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1599 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1600 --))
1601
1602 -- latest end of encounters of clinical items linked to this episode
1603 (SELECT
1604 MAX(last_affirmed) AS latest,
1605 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1606 FROM clin.encounter
1607 WHERE pk IN (
1608 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1609 ))
1610 UNION ALL
1611
1612 -- latest explicit .clin_when of clinical items linked to this episode
1613 (SELECT
1614 MAX(clin_when) AS latest,
1615 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1616 FROM clin.clin_root_item
1617 WHERE fk_episode = %(pk)s
1618 )
1619
1620 -- latest modification time of clinical items linked to this episode
1621 -- this CAN be used since if an item is linked to an episode it can be
1622 -- assumed the episode (should have) existed at the time of creation
1623 -- DO NOT USE, because typo fixes should not extend the episode
1624 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1625
1626 -- not sure about this one:
1627 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1628
1629 ) AS candidates"""
1630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1631 return rows[0][0]
1632
1633 latest_access_date = property(_get_latest_access_date)
1634
1635
1638
1639 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1640
1641
1643 cmd = """SELECT
1644 'NONE (live row)'::text as audit__action_applied,
1645 NULL AS audit__action_when,
1646 NULL AS audit__action_by,
1647 pk_audit,
1648 row_version,
1649 modified_when,
1650 modified_by,
1651 pk, fk_health_issue, description, is_open, fk_encounter,
1652 diagnostic_certainty_classification,
1653 summary
1654 FROM clin.episode
1655 WHERE pk = %(pk_episode)s
1656 UNION ALL (
1657 SELECT
1658 audit_action as audit__action_applied,
1659 audit_when as audit__action_when,
1660 audit_by as audit__action_by,
1661 pk_audit,
1662 orig_version as row_version,
1663 orig_when as modified_when,
1664 orig_by as modified_by,
1665 pk, fk_health_issue, description, is_open, fk_encounter,
1666 diagnostic_certainty_classification,
1667 summary
1668 FROM audit.log_episode
1669 WHERE pk = %(pk_episode)s
1670 )
1671 ORDER BY row_version DESC
1672 """
1673 args = {'pk_episode': self.pk_obj}
1674 title = _('Episode: %s%s%s') % (
1675 gmTools.u_left_double_angle_quote,
1676 self._payload[self._idx['description']],
1677 gmTools.u_right_double_angle_quote
1678 )
1679 return '\n'.join(self._get_revision_history(cmd, args, title))
1680
1681 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
1682
1683
1685 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1686 return []
1687
1688 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
1689 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1690 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1691 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1692
1694 queries = []
1695
1696 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1697 queries.append ({
1698 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1699 'args': {
1700 'epi': self._payload[self._idx['pk_episode']],
1701 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1702 }
1703 })
1704
1705 for pk_code in pk_codes:
1706 queries.append ({
1707 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1708 'args': {
1709 'epi': self._payload[self._idx['pk_episode']],
1710 'pk_code': pk_code
1711 }
1712 })
1713 if len(queries) == 0:
1714 return
1715
1716 rows, idx = gmPG2.run_rw_queries(queries = queries)
1717 return
1718
1719 generic_codes = property(_get_generic_codes, _set_generic_codes)
1720
1721
1723 cmd = """SELECT EXISTS (
1724 SELECT 1 FROM clin.clin_narrative
1725 WHERE
1726 fk_episode = %(epi)s
1727 AND
1728 fk_encounter IN (
1729 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1730 )
1731 )"""
1732 args = {
1733 'pat': self._payload[self._idx['pk_patient']],
1734 'epi': self._payload[self._idx['pk_episode']]
1735 }
1736 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1737 return rows[0][0]
1738
1739 has_narrative = property(_get_has_narrative, lambda x:x)
1740
1741
1743 if self._payload[self._idx['pk_health_issue']] is None:
1744 return None
1745 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1746
1747 health_issue = property(_get_health_issue)
1748
1749
1750 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1751 """Creates a new episode for a given patient's health issue.
1752
1753 pk_health_issue - given health issue PK
1754 episode_name - name of episode
1755 """
1756 if not allow_dupes:
1757 try:
1758 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj)
1759 if episode['episode_open'] != is_open:
1760 episode['episode_open'] = is_open
1761 episode.save_payload()
1762 return episode
1763 except gmExceptions.ConstructorError:
1764 pass
1765
1766 queries = []
1767 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)"
1768 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1769 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"})
1770 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True)
1771
1772 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1773 return episode
1774
1775
1777 if isinstance(episode, cEpisode):
1778 pk = episode['pk_episode']
1779 else:
1780 pk = int(episode)
1781
1782 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s'
1783
1784 try:
1785 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1786 except gmPG2.dbapi.IntegrityError:
1787
1788 _log.exception('cannot delete episode, it is in use')
1789 return False
1790
1791 return True
1792
1793
1795 return cProblem (
1796 aPK_obj = {
1797 'pk_patient': episode['pk_patient'],
1798 'pk_episode': episode['pk_episode'],
1799 'pk_health_issue': episode['pk_health_issue']
1800 },
1801 try_potential_problems = allow_closed
1802 )
1803
1804
1805 _SQL_best_guess_clinical_start_date_for_episode = """
1806 SELECT MIN(earliest) FROM (
1807 -- modified_when of episode,
1808 -- earliest possible thereof = when created,
1809 -- should actually go all the way back into audit.log_episode
1810 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1811
1812 UNION ALL
1813
1814 -- last modification of encounter in which created,
1815 -- earliest-possible thereof = initial creation of that encounter
1816 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1817 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1818 ))
1819 UNION ALL
1820
1821 -- start of encounter in which created,
1822 -- earliest-possible thereof = explicitely set by user
1823 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1824 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1825 ))
1826 UNION ALL
1827
1828 -- start of encounters of clinical items linked to this episode,
1829 -- earliest-possible thereof = explicitely set by user
1830 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1831 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1832 ))
1833 UNION ALL
1834
1835 -- .clin_when of clinical items linked to this episode,
1836 -- earliest-possible thereof = explicitely set by user
1837 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1838
1839 UNION ALL
1840
1841 -- earliest modification time of clinical items linked to this episode
1842 -- this CAN be used since if an item is linked to an episode it can be
1843 -- assumed the episode (should have) existed at the time of creation
1844 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1845
1846 UNION ALL
1847
1848 -- there may not be items, but there may still be documents ...
1849 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s)
1850 ) AS candidates
1851 """
1852
1861
1862
1863 _SQL_best_guess_clinical_end_date_for_episode = """
1864 SELECT
1865 CASE WHEN
1866 -- if open episode ...
1867 (SELECT is_open FROM clin.episode WHERE pk = %(pk)s)
1868 THEN
1869 -- ... no end date
1870 NULL::timestamp with time zone
1871 ELSE (
1872 SELECT COALESCE (
1873 (SELECT
1874 latest --, source_type
1875 FROM (
1876 -- latest explicit .clin_when of clinical items linked to this episode
1877 (SELECT
1878 MAX(clin_when) AS latest,
1879 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type
1880 FROM clin.clin_root_item
1881 WHERE fk_episode = %(pk)s
1882 )
1883 UNION ALL
1884 -- latest explicit .clin_when of documents linked to this episode
1885 (SELECT
1886 MAX(clin_when) AS latest,
1887 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type
1888 FROM blobs.doc_med
1889 WHERE fk_episode = %(pk)s
1890 )
1891 ) AS candidates
1892 ORDER BY latest DESC NULLS LAST
1893 LIMIT 1
1894 ),
1895 -- last ditch, always exists, only use when no clinical items or documents linked:
1896 -- last modification, latest = when last changed to the current state
1897 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type
1898 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s
1899 )
1900 )
1901 )
1902 END
1903 """
1904
1913
1914
1958
1959
1961
1962 _SQL_episode_start = _SQL_best_guess_clinical_start_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1963 _SQL_episode_end = _SQL_best_guess_clinical_end_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1964
1965 _SQL_open_episodes = """
1966 SELECT
1967 c_vpe.pk_episode,
1968 c_vpe.description
1969 AS episode,
1970 c_vpe.health_issue,
1971 1 AS rank,
1972 (%s) AS episode_start,
1973 NULL::timestamp with time zone AS episode_end
1974 FROM
1975 clin.v_pat_episodes c_vpe
1976 WHERE
1977 c_vpe.episode_open IS TRUE
1978 AND
1979 c_vpe.description %%(fragment_condition)s
1980 %%(ctxt_pat)s
1981 """ % _SQL_episode_start
1982
1983 _SQL_closed_episodes = """
1984 SELECT
1985 c_vpe.pk_episode,
1986 c_vpe.description
1987 AS episode,
1988 c_vpe.health_issue,
1989 2 AS rank,
1990 (%s) AS episode_start,
1991 (%s) AS episode_end
1992 FROM
1993 clin.v_pat_episodes c_vpe
1994 WHERE
1995 c_vpe.episode_open IS FALSE
1996 AND
1997 c_vpe.description %%(fragment_condition)s
1998 %%(ctxt_pat)s
1999 """ % (
2000 _SQL_episode_start,
2001 _SQL_episode_end
2002 )
2003
2004
2019
2020
2022 matches = []
2023 for row in rows:
2024 match = {
2025 'weight': 0,
2026 'data': row['pk_episode']
2027 }
2028 label = '%s (%s)%s' % (
2029 row['episode'],
2030 format_clinical_duration_of_episode (
2031 start = row['episode_start'],
2032 end = row['episode_end']
2033 )[0],
2034 gmTools.coalesce (
2035 row['health_issue'],
2036 '',
2037 ' - %s'
2038 )
2039 )
2040 match['list_label'] = label
2041 match['field_label'] = label
2042 matches.append(match)
2043
2044 return matches
2045
2046
2047
2048
2049 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s"
2050
2051 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
2052 """Represents one encounter."""
2053
2054 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s'
2055 _cmds_store_payload = [
2056 """UPDATE clin.encounter SET
2057 started = %(started)s,
2058 last_affirmed = %(last_affirmed)s,
2059 fk_location = %(pk_org_unit)s,
2060 fk_type = %(pk_type)s,
2061 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
2062 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
2063 WHERE
2064 pk = %(pk_encounter)s AND
2065 xmin = %(xmin_encounter)s
2066 """,
2067
2068 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
2069 ]
2070 _updatable_fields = [
2071 'started',
2072 'last_affirmed',
2073 'pk_org_unit',
2074 'pk_type',
2075 'reason_for_encounter',
2076 'assessment_of_encounter'
2077 ]
2078
2080 """Set the encounter as the active one.
2081
2082 "Setting active" means making sure the encounter
2083 row has the youngest "last_affirmed" timestamp of
2084 all encounter rows for this patient.
2085 """
2086 self['last_affirmed'] = gmDateTime.pydt_now_here()
2087 self.save()
2088
2089 - def lock(self, exclusive=False, link_obj=None):
2090 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2091
2092 - def unlock(self, exclusive=False, link_obj=None):
2093 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2094
2096 """
2097 Moves every element currently linked to the current encounter
2098 and the source_episode onto target_episode.
2099
2100 @param source_episode The episode the elements are currently linked to.
2101 @type target_episode A cEpisode intance.
2102 @param target_episode The episode the elements will be relinked to.
2103 @type target_episode A cEpisode intance.
2104 """
2105 if source_episode['pk_episode'] == target_episode['pk_episode']:
2106 return True
2107
2108 queries = []
2109 cmd = """
2110 UPDATE clin.clin_root_item
2111 SET fk_episode = %(trg)s
2112 WHERE
2113 fk_encounter = %(enc)s AND
2114 fk_episode = %(src)s
2115 """
2116 rows, idx = gmPG2.run_rw_queries(queries = [{
2117 'cmd': cmd,
2118 'args': {
2119 'trg': target_episode['pk_episode'],
2120 'enc': self.pk_obj,
2121 'src': source_episode['pk_episode']
2122 }
2123 }])
2124 self.refetch_payload()
2125 return True
2126
2127
2129 if pk_target_encounter == self.pk_obj:
2130 return True
2131 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)"
2132 args = {
2133 'src': self.pk_obj,
2134 'trg': pk_target_encounter
2135 }
2136 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2137 return True
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2149
2150 relevant_fields = [
2151 'pk_org_unit',
2152 'pk_type',
2153 'pk_patient',
2154 'reason_for_encounter',
2155 'assessment_of_encounter'
2156 ]
2157 for field in relevant_fields:
2158 if self._payload[self._idx[field]] != another_object[field]:
2159 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
2160 return False
2161
2162 relevant_fields = [
2163 'started',
2164 'last_affirmed',
2165 ]
2166 for field in relevant_fields:
2167 if self._payload[self._idx[field]] is None:
2168 if another_object[field] is None:
2169 continue
2170 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2171 return False
2172
2173 if another_object[field] is None:
2174 return False
2175
2176
2177 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'):
2178 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2179 return False
2180
2181
2182
2183 if another_object['pk_generic_codes_rfe'] is None:
2184 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
2185 return False
2186 if another_object['pk_generic_codes_rfe'] is not None:
2187 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
2188 return False
2189 if (
2190 (another_object['pk_generic_codes_rfe'] is None)
2191 and
2192 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
2193 ) is False:
2194 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
2195 return False
2196
2197 if another_object['pk_generic_codes_aoe'] is None:
2198 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
2199 return False
2200 if another_object['pk_generic_codes_aoe'] is not None:
2201 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
2202 return False
2203 if (
2204 (another_object['pk_generic_codes_aoe'] is None)
2205 and
2206 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
2207 ) is False:
2208 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
2209 return False
2210
2211 return True
2212
2214 cmd = """
2215 select exists (
2216 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
2217 union all
2218 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2219 )"""
2220 args = {
2221 'pat': self._payload[self._idx['pk_patient']],
2222 'enc': self.pk_obj
2223 }
2224 rows, idx = gmPG2.run_ro_queries (
2225 queries = [{
2226 'cmd': cmd,
2227 'args': args
2228 }]
2229 )
2230 return rows[0][0]
2231
2232
2234 cmd = """
2235 select exists (
2236 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
2237 )"""
2238 args = {
2239 'pat': self._payload[self._idx['pk_patient']],
2240 'enc': self.pk_obj
2241 }
2242 rows, idx = gmPG2.run_ro_queries (
2243 queries = [{
2244 'cmd': cmd,
2245 'args': args
2246 }]
2247 )
2248 return rows[0][0]
2249
2251 """soap_cats: <space> = admin category"""
2252
2253 if soap_cats is None:
2254 soap_cats = 'soap '
2255 else:
2256 soap_cats = soap_cats.lower()
2257
2258 cats = []
2259 for cat in soap_cats:
2260 if cat in 'soapu':
2261 cats.append(cat)
2262 continue
2263 if cat == ' ':
2264 cats.append(None)
2265
2266 cmd = """
2267 SELECT EXISTS (
2268 SELECT 1 FROM clin.clin_narrative
2269 WHERE
2270 fk_encounter = %(enc)s
2271 AND
2272 soap_cat IN %(cats)s
2273 LIMIT 1
2274 )
2275 """
2276 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
2277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
2278 return rows[0][0]
2279
2281 cmd = """
2282 select exists (
2283 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2284 )"""
2285 args = {
2286 'pat': self._payload[self._idx['pk_patient']],
2287 'enc': self.pk_obj
2288 }
2289 rows, idx = gmPG2.run_ro_queries (
2290 queries = [{
2291 'cmd': cmd,
2292 'args': args
2293 }]
2294 )
2295 return rows[0][0]
2296
2298
2299 if soap_cat is not None:
2300 soap_cat = soap_cat.lower()
2301
2302 if episode is None:
2303 epi_part = 'fk_episode is null'
2304 else:
2305 epi_part = 'fk_episode = %(epi)s'
2306
2307 cmd = """
2308 select narrative
2309 from clin.clin_narrative
2310 where
2311 fk_encounter = %%(enc)s
2312 and
2313 soap_cat = %%(cat)s
2314 and
2315 %s
2316 order by clin_when desc
2317 limit 1
2318 """ % epi_part
2319
2320 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
2321
2322 rows, idx = gmPG2.run_ro_queries (
2323 queries = [{
2324 'cmd': cmd,
2325 'args': args
2326 }]
2327 )
2328 if len(rows) == 0:
2329 return None
2330
2331 return rows[0][0]
2332
2334 cmd = """
2335 SELECT * FROM clin.v_pat_episodes
2336 WHERE pk_episode IN (
2337 SELECT DISTINCT fk_episode
2338 FROM clin.clin_root_item
2339 WHERE fk_encounter = %%(enc)s
2340
2341 UNION
2342
2343 SELECT DISTINCT fk_episode
2344 FROM blobs.doc_med
2345 WHERE fk_encounter = %%(enc)s
2346 ) %s"""
2347 args = {'enc': self.pk_obj}
2348 if exclude is not None:
2349 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s'
2350 args['excluded'] = tuple(exclude)
2351 else:
2352 cmd = cmd % ''
2353
2354 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2355
2356 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2357
2358 episodes = property(get_episodes, lambda x:x)
2359
2360 - def add_code(self, pk_code=None, field=None):
2361 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2362 if field == 'rfe':
2363 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2364 elif field == 'aoe':
2365 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2366 else:
2367 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2368 args = {
2369 'item': self._payload[self._idx['pk_encounter']],
2370 'code': pk_code
2371 }
2372 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2373 return True
2374
2376 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2377 if field == 'rfe':
2378 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2379 elif field == 'aoe':
2380 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2381 else:
2382 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2383 args = {
2384 'item': self._payload[self._idx['pk_encounter']],
2385 'code': pk_code
2386 }
2387 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2388 return True
2389
2390
2391
2392
2438
2439
2535
2536
2596
2597
2650
2651
2750
2751
2773
2774
2909
2910
2911
2912
2914 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2915 return []
2916
2917 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2918 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2919 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2920 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2921
2923 queries = []
2924
2925 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2926 queries.append ({
2927 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2928 'args': {
2929 'enc': self._payload[self._idx['pk_encounter']],
2930 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2931 }
2932 })
2933
2934 for pk_code in pk_codes:
2935 queries.append ({
2936 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2937 'args': {
2938 'enc': self._payload[self._idx['pk_encounter']],
2939 'pk_code': pk_code
2940 }
2941 })
2942 if len(queries) == 0:
2943 return
2944
2945 rows, idx = gmPG2.run_rw_queries(queries = queries)
2946 self.refetch_payload()
2947 return
2948
2949 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2950
2952 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2953 return []
2954
2955 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2956 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2957 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2958 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2959
2961 queries = []
2962
2963 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2964 queries.append ({
2965 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2966 'args': {
2967 'enc': self._payload[self._idx['pk_encounter']],
2968 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2969 }
2970 })
2971
2972 for pk_code in pk_codes:
2973 queries.append ({
2974 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2975 'args': {
2976 'enc': self._payload[self._idx['pk_encounter']],
2977 'pk_code': pk_code
2978 }
2979 })
2980 if len(queries) == 0:
2981 return
2982
2983 rows, idx = gmPG2.run_rw_queries(queries = queries)
2984 self.refetch_payload()
2985 return
2986
2987 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2988
2993
2994 praxis_branch = property(_get_praxis_branch, lambda x:x)
2995
2997 if self._payload[self._idx['pk_org_unit']] is None:
2998 return None
2999 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
3000
3001 org_unit = property(_get_org_unit, lambda x:x)
3002
3004 cmd = """SELECT
3005 'NONE (live row)'::text as audit__action_applied,
3006 NULL AS audit__action_when,
3007 NULL AS audit__action_by,
3008 pk_audit,
3009 row_version,
3010 modified_when,
3011 modified_by,
3012 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3013 FROM clin.encounter
3014 WHERE pk = %(pk_encounter)s
3015 UNION ALL (
3016 SELECT
3017 audit_action as audit__action_applied,
3018 audit_when as audit__action_when,
3019 audit_by as audit__action_by,
3020 pk_audit,
3021 orig_version as row_version,
3022 orig_when as modified_when,
3023 orig_by as modified_by,
3024 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3025 FROM audit.log_encounter
3026 WHERE pk = %(pk_encounter)s
3027 )
3028 ORDER BY row_version DESC
3029 """
3030 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]}
3031 title = _('Encounter: %s%s%s') % (
3032 gmTools.u_left_double_angle_quote,
3033 self._payload[self._idx['l10n_type']],
3034 gmTools.u_right_double_angle_quote
3035 )
3036 return '\n'.join(self._get_revision_history(cmd, args, title))
3037
3038 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
3039
3040
3042 """Creates a new encounter for a patient.
3043
3044 fk_patient - patient PK
3045 enc_type - type of encounter
3046 """
3047 if enc_type is None:
3048 enc_type = 'in surgery'
3049
3050 queries = []
3051 try:
3052 enc_type = int(enc_type)
3053 cmd = """
3054 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location)
3055 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk"""
3056 except ValueError:
3057 enc_type = enc_type
3058 cmd = """
3059 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type)
3060 VALUES (
3061 %(pat)s,
3062 %(prax)s,
3063 coalesce (
3064 (select pk from clin.encounter_type where description = %(typ)s),
3065 -- pick the first available
3066 (select pk from clin.encounter_type limit 1)
3067 )
3068 ) RETURNING pk"""
3069 praxis = gmPraxis.gmCurrentPraxisBranch()
3070 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']}
3071 queries.append({'cmd': cmd, 'args': args})
3072 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
3073 encounter = cEncounter(aPK_obj = rows[0]['pk'])
3074
3075 return encounter
3076
3077
3079 """Used to protect against deletion of active encounter from another client."""
3080 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
3081
3082
3085
3086
3088 """Deletes an encounter by PK.
3089
3090 - attempts to obtain an exclusive lock which should
3091 fail if the encounter is the active encounter in
3092 this or any other client
3093 - catches DB exceptions which should mostly be related
3094 to clinical data already having been attached to
3095 the encounter thus making deletion fail
3096 """
3097 conn = gmPG2.get_connection(readonly = False)
3098 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn):
3099 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter)
3100 return False
3101 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s"""
3102 args = {'enc': pk_encounter}
3103 try:
3104 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3105 except gmPG2.PG_ERROR_EXCEPTION:
3106 _log.exception('cannot delete encounter [%s]', pk_encounter)
3107 gmPG2.log_pg_exception_details(exc)
3108 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3109 return False
3110 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3111 return True
3112
3113
3114
3115
3117
3118 rows, idx = gmPG2.run_rw_queries(
3119 queries = [{
3120 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
3121 'args': {'desc': description, 'l10n_desc': l10n_description}
3122 }],
3123 return_data = True
3124 )
3125
3126 success = rows[0][0]
3127 if not success:
3128 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
3129
3130 return {'description': description, 'l10n_description': l10n_description}
3131
3133 """This will attempt to create a NEW encounter type."""
3134
3135
3136 if description is None:
3137 description = l10n_description
3138
3139 args = {
3140 'desc': description,
3141 'l10n_desc': l10n_description
3142 }
3143
3144 _log.debug('creating encounter type: %s, %s', description, l10n_description)
3145
3146
3147 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s"
3148 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3149
3150
3151 if len(rows) > 0:
3152
3153 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
3154 _log.info('encounter type [%s] already exists with the proper translation')
3155 return {'description': description, 'l10n_description': l10n_description}
3156
3157
3158
3159 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
3160 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3161
3162
3163 if rows[0][0]:
3164 _log.error('encounter type [%s] already exists but with another translation')
3165 return None
3166
3167
3168 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
3169 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3170 return {'description': description, 'l10n_description': l10n_description}
3171
3172
3173 queries = [
3174 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
3175 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
3176 ]
3177 rows, idx = gmPG2.run_rw_queries(queries = queries)
3178
3179 return {'description': description, 'l10n_description': l10n_description}
3180
3181
3183 cmd = """
3184 SELECT
3185 COUNT(1) AS type_count,
3186 fk_type
3187 FROM clin.encounter
3188 GROUP BY fk_type
3189 ORDER BY type_count DESC
3190 LIMIT 1
3191 """
3192 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3193 if len(rows) == 0:
3194 return None
3195 return rows[0]['fk_type']
3196
3197
3199 cmd = """
3200 SELECT
3201 _(description) AS l10n_description,
3202 description
3203 FROM
3204 clin.encounter_type
3205 ORDER BY
3206 l10n_description
3207 """
3208 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3209 return rows
3210
3211
3216
3217
3219 cmd = "delete from clin.encounter_type where description = %(desc)s"
3220 args = {'desc': description}
3221 try:
3222 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3223 except gmPG2.dbapi.IntegrityError as e:
3224 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
3225 return False
3226 raise
3227
3228 return True
3229
3230
3231 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3232 """Represents one problem.
3233
3234 problems are the aggregation of
3235 .clinically_relevant=True issues and
3236 .is_open=True episodes
3237 """
3238 _cmd_fetch_payload = ''
3239 _cmds_store_payload = ["select 1"]
3240 _updatable_fields = []
3241
3242
3243 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3244 """Initialize.
3245
3246 aPK_obj must contain the keys
3247 pk_patient
3248 pk_episode
3249 pk_health_issue
3250 """
3251 if aPK_obj is None:
3252 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj))
3253
3254
3255
3256
3257
3258 where_parts = []
3259 pk = {}
3260 for col_name in aPK_obj.keys():
3261 val = aPK_obj[col_name]
3262 if val is None:
3263 where_parts.append('%s IS NULL' % col_name)
3264 else:
3265 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
3266 pk[col_name] = val
3267
3268
3269 cProblem._cmd_fetch_payload = """
3270 SELECT *, False as is_potential_problem
3271 FROM clin.v_problem_list
3272 WHERE %s""" % ' AND '.join(where_parts)
3273
3274 try:
3275 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3276 return
3277 except gmExceptions.ConstructorError:
3278 _log.exception('actual problem not found, trying "potential" problems')
3279 if try_potential_problems is False:
3280 raise
3281
3282
3283 cProblem._cmd_fetch_payload = """
3284 SELECT *, True as is_potential_problem
3285 FROM clin.v_potential_problem_list
3286 WHERE %s""" % ' AND '.join(where_parts)
3287 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3288
3290 """
3291 Retrieve the cEpisode instance equivalent to this problem.
3292 The problem's type attribute must be 'episode'
3293 """
3294 if self._payload[self._idx['type']] != 'episode':
3295 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3296 return None
3297 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3298
3300 """
3301 Retrieve the cHealthIssue instance equivalent to this problem.
3302 The problem's type attribute must be 'issue'
3303 """
3304 if self._payload[self._idx['type']] != 'issue':
3305 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3306 return None
3307 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3308
3324
3325
3326
3327
3328
3331
3332 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
3333
3335 if self._payload[self._idx['type']] == 'issue':
3336 cmd = """
3337 SELECT * FROM clin.v_linked_codes WHERE
3338 item_table = 'clin.lnk_code2h_issue'::regclass
3339 AND
3340 pk_item = %(item)s
3341 """
3342 args = {'item': self._payload[self._idx['pk_health_issue']]}
3343 else:
3344 cmd = """
3345 SELECT * FROM clin.v_linked_codes WHERE
3346 item_table = 'clin.lnk_code2episode'::regclass
3347 AND
3348 pk_item = %(item)s
3349 """
3350 args = {'item': self._payload[self._idx['pk_episode']]}
3351
3352 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3353 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3354
3355 generic_codes = property(_get_generic_codes, lambda x:x)
3356
3358 """Retrieve the cEpisode instance equivalent to the given problem.
3359
3360 The problem's type attribute must be 'episode'
3361
3362 @param problem: The problem to retrieve its related episode for
3363 @type problem: A gmEMRStructItems.cProblem instance
3364 """
3365 if isinstance(problem, cEpisode):
3366 return problem
3367
3368 exc = TypeError('cannot convert [%s] to episode' % problem)
3369
3370 if not isinstance(problem, cProblem):
3371 raise exc
3372
3373 if problem['type'] != 'episode':
3374 raise exc
3375
3376 return cEpisode(aPK_obj = problem['pk_episode'])
3377
3379 """Retrieve the cIssue instance equivalent to the given problem.
3380
3381 The problem's type attribute must be 'issue'.
3382
3383 @param problem: The problem to retrieve the corresponding issue for
3384 @type problem: A gmEMRStructItems.cProblem instance
3385 """
3386 if isinstance(problem, cHealthIssue):
3387 return problem
3388
3389 exc = TypeError('cannot convert [%s] to health issue' % problem)
3390
3391 if not isinstance(problem, cProblem):
3392 raise exc
3393
3394 if problem['type'] != 'issue':
3395 raise exc
3396
3397 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3398
3400 """Transform given problem into either episode or health issue instance.
3401 """
3402 if isinstance(problem, (cEpisode, cHealthIssue)):
3403 return problem
3404
3405 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
3406
3407 if not isinstance(problem, cProblem):
3408 _log.debug('%s' % problem)
3409 raise exc
3410
3411 if problem['type'] == 'episode':
3412 return cEpisode(aPK_obj = problem['pk_episode'])
3413
3414 if problem['type'] == 'issue':
3415 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3416
3417 raise exc
3418
3419
3420 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s"
3421
3423
3424 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s"
3425 _cmds_store_payload = [
3426 """UPDATE clin.hospital_stay SET
3427 clin_when = %(admission)s,
3428 discharge = %(discharge)s,
3429 fk_org_unit = %(pk_org_unit)s,
3430 narrative = gm.nullify_empty_string(%(comment)s),
3431 fk_episode = %(pk_episode)s,
3432 fk_encounter = %(pk_encounter)s
3433 WHERE
3434 pk = %(pk_hospital_stay)s
3435 AND
3436 xmin = %(xmin_hospital_stay)s
3437 RETURNING
3438 xmin AS xmin_hospital_stay
3439 """
3440 ]
3441 _updatable_fields = [
3442 'admission',
3443 'discharge',
3444 'pk_org_unit',
3445 'pk_episode',
3446 'pk_encounter',
3447 'comment'
3448 ]
3449
3450
3456
3457
3491
3492
3494 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3495
3496 documents = property(_get_documents, lambda x:x)
3497
3498
3500 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1"
3501 queries = [{
3502
3503
3504 'cmd': cmd,
3505 'args': {'pat': patient}
3506 }]
3507 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3508 if len(rows) == 0:
3509 return None
3510 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3511
3512
3514 args = {'pat': patient}
3515 if ongoing_only:
3516 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission"
3517 else:
3518 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission"
3519
3520 queries = [{'cmd': cmd, 'args': args}]
3521 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3522 if return_pks:
3523 return [ r['pk_hospital_stay'] for r in rows ]
3524 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3525
3526
3528
3529 queries = [{
3530 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk',
3531 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit}
3532 }]
3533 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3534
3535 return cHospitalStay(aPK_obj = rows[0][0])
3536
3537
3539 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
3540 args = {'pk': stay}
3541 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3542 return True
3543
3544
3545 _SQL_get_procedures = "select * from clin.v_procedures where %s"
3546
3746
3747
3757
3758
3760 args = {'pk_doc': pk_document}
3761 cmd = _SQL_get_procedures % 'pk_doc = %(pk_doc)s'
3762 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3763 if return_pks:
3764 return [ r['pk_procedure'] for r in rows ]
3765 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3766
3767
3777
3778
3805
3806
3812
3813
3859
3860
3861
3862
3864
3865 aggregate_result = 0
3866
3867 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk')
3868 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ])
3869
3870 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk')
3871 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ]
3872
3873 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi)
3874
3875 tables_linking2enc = {}
3876 for fk in fks_linking2enc:
3877 table = fk['referencing_table']
3878 tables_linking2enc[table] = fk
3879
3880 tables_linking2epi = {}
3881 for fk in fks_linking2epi:
3882 table = fk['referencing_table']
3883 tables_linking2epi[table] = fk
3884
3885 for t in tables_linking2both:
3886
3887 table_file_name = 'x-check_enc_epi_xref-%s.log' % t
3888 table_file = io.open(table_file_name, 'w+', encoding = 'utf8')
3889
3890
3891 args = {'table': t}
3892 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}])
3893 pk_col = rows[0][0]
3894 print("checking table:", t, '- pk col:', pk_col)
3895 print(' =>', table_file_name)
3896 table_file.write('table: %s\n' % t)
3897 table_file.write('PK col: %s\n' % pk_col)
3898
3899
3900 cmd = 'select %s from %s' % (pk_col, t)
3901 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
3902 pks = [ r[0] for r in rows ]
3903 for pk in pks:
3904 args = {'pk': pk, 'tbl': t}
3905 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col)
3906 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)
3907 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}])
3908 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}])
3909 enc_pat = enc_rows[0][0]
3910 epi_pat = epi_rows[0][0]
3911 args['pat_enc'] = enc_pat
3912 args['pat_epi'] = epi_pat
3913 if epi_pat != enc_pat:
3914 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat))
3915 aggregate_result = -2
3916
3917 table_file.write('--------------------------------------------------------------------------------\n')
3918 table_file.write('mismatch on row with pk: %s\n' % pk)
3919 table_file.write('\n')
3920
3921 table_file.write('journal entry:\n')
3922 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s'
3923 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3924 if len(rows) > 0:
3925 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3926 table_file.write('\n\n')
3927
3928 table_file.write('row data:\n')
3929 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col)
3930 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3931 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3932 table_file.write('\n\n')
3933
3934 table_file.write('episode:\n')
3935 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col)
3936 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3937 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3938 table_file.write('\n\n')
3939
3940 table_file.write('patient of episode:\n')
3941 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s'
3942 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3943 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3944 table_file.write('\n\n')
3945
3946 table_file.write('encounter:\n')
3947 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col)
3948 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3949 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3950 table_file.write('\n\n')
3951
3952 table_file.write('patient of encounter:\n')
3953 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s'
3954 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3955 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3956 table_file.write('\n')
3957
3958 table_file.write('done\n')
3959 table_file.close()
3960
3961 return aggregate_result
3962
3963
3975
3976
3977
3978
3979 if __name__ == '__main__':
3980
3981 if len(sys.argv) < 2:
3982 sys.exit()
3983
3984 if sys.argv[1] != 'test':
3985 sys.exit()
3986
3987
3988
3989
3991 print("\nProblem test")
3992 print("------------")
3993 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
3994 print(prob)
3995 fields = prob.get_fields()
3996 for field in fields:
3997 print(field, ':', prob[field])
3998 print('\nupdatable:', prob.get_updatable_fields())
3999 epi = prob.get_as_episode()
4000 print('\nas episode:')
4001 if epi is not None:
4002 for field in epi.get_fields():
4003 print(' .%s : %s' % (field, epi[field]))
4004
4005
4024
4025
4027 print("episode test")
4028 for i in range(1,15):
4029 print("------------")
4030 episode = cEpisode(aPK_obj = i)
4031
4032 print(episode['description'])
4033 print(' start:', episode.best_guess_clinical_start_date)
4034 print(' end :', episode.best_guess_clinical_end_date)
4035 print(' dura :', get_formatted_clinical_duration(pk_episode = i))
4036 return
4037
4038 print(episode)
4039 fields = episode.get_fields()
4040 for field in fields:
4041 print(field, ':', episode[field])
4042 print("updatable:", episode.get_updatable_fields())
4043 input('ENTER to continue')
4044
4045 old_description = episode['description']
4046 old_enc = cEncounter(aPK_obj = 1)
4047
4048 desc = '1-%s' % episode['description']
4049 print("==> renaming to", desc)
4050 successful = episode.rename (
4051 description = desc
4052 )
4053 if not successful:
4054 print("error")
4055 else:
4056 print("success")
4057 for field in fields:
4058 print(field, ':', episode[field])
4059
4060 print(episode.formatted_revision_history)
4061
4062 input('ENTER to continue')
4063
4064
4076
4077
4079 encounter = cEncounter(aPK_obj=1)
4080 print(encounter)
4081 print("")
4082 print(encounter.format_latex())
4083
4088
4098
4105
4110
4114
4115
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127 test_episode()
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141