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