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
909
912
913 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
914
915
917 cmd = """SELECT
918 'NONE (live row)'::text as audit__action_applied,
919 NULL AS audit__action_when,
920 NULL AS audit__action_by,
921 pk_audit,
922 row_version,
923 modified_when,
924 modified_by,
925 pk,
926 description,
927 laterality,
928 age_noted,
929 is_active,
930 clinically_relevant,
931 is_confidential,
932 is_cause_of_death,
933 fk_encounter,
934 grouping,
935 diagnostic_certainty_classification,
936 summary
937 FROM clin.health_issue
938 WHERE pk = %(pk_health_issue)s
939 UNION ALL (
940 SELECT
941 audit_action as audit__action_applied,
942 audit_when as audit__action_when,
943 audit_by as audit__action_by,
944 pk_audit,
945 orig_version as row_version,
946 orig_when as modified_when,
947 orig_by as modified_by,
948 pk,
949 description,
950 laterality,
951 age_noted,
952 is_active,
953 clinically_relevant,
954 is_confidential,
955 is_cause_of_death,
956 fk_encounter,
957 grouping,
958 diagnostic_certainty_classification,
959 summary
960 FROM audit.log_health_issue
961 WHERE pk = %(pk_health_issue)s
962 )
963 ORDER BY row_version DESC
964 """
965 args = {'pk_health_issue': self.pk_obj}
966 title = _('Health issue: %s%s%s') % (
967 gmTools.u_left_double_angle_quote,
968 self._payload[self._idx['description']],
969 gmTools.u_right_double_angle_quote
970 )
971 return '\n'.join(self._get_revision_history(cmd, args, title))
972
973 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
974
976 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
977 return []
978
979 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
980 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
981 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
982 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
983
985 queries = []
986
987 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
988 queries.append ({
989 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
990 'args': {
991 'issue': self._payload[self._idx['pk_health_issue']],
992 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
993 }
994 })
995
996 for pk_code in pk_codes:
997 queries.append ({
998 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
999 'args': {
1000 'issue': self._payload[self._idx['pk_health_issue']],
1001 'pk_code': pk_code
1002 }
1003 })
1004 if len(queries) == 0:
1005 return
1006
1007 rows, idx = gmPG2.run_rw_queries(queries = queries)
1008 return
1009
1010 generic_codes = property(_get_generic_codes, _set_generic_codes)
1011
1012
1014 """Creates a new health issue for a given patient.
1015
1016 description - health issue name
1017 """
1018 try:
1019 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
1020 return h_issue
1021 except gmExceptions.NoSuchBusinessObjectError:
1022 pass
1023
1024 queries = []
1025 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
1026 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
1027
1028 cmd = "select currval('clin.health_issue_pk_seq')"
1029 queries.append({'cmd': cmd})
1030
1031 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
1032 h_issue = cHealthIssue(aPK_obj = rows[0][0])
1033
1034 return h_issue
1035
1036
1038 if isinstance(health_issue, cHealthIssue):
1039 args = {'pk': health_issue['pk_health_issue']}
1040 else:
1041 args = {'pk': int(health_issue)}
1042 try:
1043 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}])
1044 except gmPG2.dbapi.IntegrityError:
1045
1046 _log.exception('cannot delete health issue')
1047 return False
1048
1049 return True
1050
1051
1052
1054 issue = {
1055 'pk_health_issue': None,
1056 'description': _('Unattributed episodes'),
1057 'age_noted': None,
1058 'laterality': 'na',
1059 'is_active': True,
1060 'clinically_relevant': True,
1061 'is_confidential': None,
1062 'is_cause_of_death': False,
1063 'is_dummy': True,
1064 'grouping': None
1065 }
1066 return issue
1067
1068
1070 return cProblem (
1071 aPK_obj = {
1072 'pk_patient': health_issue['pk_patient'],
1073 'pk_health_issue': health_issue['pk_health_issue'],
1074 'pk_episode': None
1075 },
1076 try_potential_problems = allow_irrelevant
1077 )
1078
1079
1080
1081
1082 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
1083 """Represents one clinical episode.
1084 """
1085 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s"
1086 _cmds_store_payload = [
1087 """update clin.episode set
1088 fk_health_issue = %(pk_health_issue)s,
1089 is_open = %(episode_open)s::boolean,
1090 description = %(description)s,
1091 summary = gm.nullify_empty_string(%(summary)s),
1092 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
1093 where
1094 pk = %(pk_episode)s and
1095 xmin = %(xmin_episode)s""",
1096 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
1097 ]
1098 _updatable_fields = [
1099 'pk_health_issue',
1100 'episode_open',
1101 'description',
1102 'summary',
1103 'diagnostic_certainty_classification'
1104 ]
1105
1106 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1107 pk = aPK_obj
1108 if pk is None and row is None:
1109
1110 where_parts = ['description = %(desc)s']
1111
1112 if id_patient is not None:
1113 where_parts.append('pk_patient = %(pat)s')
1114
1115 if health_issue is not None:
1116 where_parts.append('pk_health_issue = %(issue)s')
1117
1118 if encounter is not None:
1119 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)')
1120
1121 args = {
1122 'pat': id_patient,
1123 'issue': health_issue,
1124 'enc': encounter,
1125 'desc': name
1126 }
1127
1128 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts)
1129
1130 rows, idx = gmPG2.run_ro_queries (
1131 link_obj = link_obj,
1132 queries = [{'cmd': cmd, 'args': args}],
1133 get_col_idx=True
1134 )
1135
1136 if len(rows) == 0:
1137 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter))
1138
1139 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
1140 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
1141
1142 else:
1143 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1144
1145
1146
1147
1149 return self._payload[self._idx['pk_patient']]
1150
1151
1152 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
1159
1160
1161 - def rename(self, description=None):
1162 """Method for episode editing, that is, episode renaming.
1163
1164 @param description
1165 - the new descriptive name for the encounter
1166 @type description
1167 - a string instance
1168 """
1169
1170 if description.strip() == '':
1171 _log.error('<description> must be a non-empty string instance')
1172 return False
1173
1174 old_description = self._payload[self._idx['description']]
1175 self._payload[self._idx['description']] = description.strip()
1176 self._is_modified = True
1177 successful, data = self.save_payload()
1178 if not successful:
1179 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
1180 self._payload[self._idx['description']] = old_description
1181 return False
1182 return True
1183
1184
1186 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1187
1188 if pk_code in self._payload[self._idx['pk_generic_codes']]:
1189 return
1190
1191 cmd = """
1192 INSERT INTO clin.lnk_code2episode
1193 (fk_item, fk_generic_code)
1194 SELECT
1195 %(item)s,
1196 %(code)s
1197 WHERE NOT EXISTS (
1198 SELECT 1 FROM clin.lnk_code2episode
1199 WHERE
1200 fk_item = %(item)s
1201 AND
1202 fk_generic_code = %(code)s
1203 )"""
1204 args = {
1205 'item': self._payload[self._idx['pk_episode']],
1206 'code': pk_code
1207 }
1208 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1209 return
1210
1211
1213 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1214 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1215 args = {
1216 'item': self._payload[self._idx['pk_episode']],
1217 'code': pk_code
1218 }
1219 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1220 return True
1221
1222
1277
1278
1301
1302
1555
1556
1557
1558
1560 cmd = """SELECT MIN(earliest) FROM
1561 (
1562 -- last modification of episode,
1563 -- earliest-possible thereof = when created,
1564 -- should actually go all the way back into audit.log_episode
1565 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1566
1567 UNION ALL
1568
1569 -- last modification of encounter in which created,
1570 -- earliest-possible thereof = initial creation of that encounter
1571 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1572 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1573 ))
1574 UNION ALL
1575
1576 -- start of encounter in which created,
1577 -- earliest-possible thereof = explicitely set by user
1578 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1579 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1580 ))
1581 UNION ALL
1582
1583 -- start of encounters of clinical items linked to this episode,
1584 -- earliest-possible thereof = explicitely set by user
1585 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1586 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1587 ))
1588 UNION ALL
1589
1590 -- .clin_when of clinical items linked to this episode,
1591 -- earliest-possible thereof = explicitely set by user
1592 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1593
1594 UNION ALL
1595
1596 -- earliest modification time of clinical items linked to this episode
1597 -- this CAN be used since if an item is linked to an episode it can be
1598 -- assumed the episode (should have) existed at the time of creation
1599 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1600
1601 UNION ALL
1602
1603 -- there may not be items, but there may still be documents ...
1604 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s)
1605 ) AS candidates"""
1606 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1607 return rows[0][0]
1608
1609 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date)
1610
1611
1613 if self._payload[self._idx['episode_open']]:
1614 return None
1615
1616 cmd = """SELECT COALESCE (
1617 (SELECT
1618 latest --, source_type
1619 FROM (
1620 -- latest explicit .clin_when of clinical items linked to this episode
1621 (SELECT
1622 MAX(clin_when) AS latest,
1623 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type
1624 FROM clin.clin_root_item
1625 WHERE fk_episode = %(pk)s
1626 )
1627 UNION ALL
1628 -- latest explicit .clin_when of documents linked to this episode
1629 (SELECT
1630 MAX(clin_when) AS latest,
1631 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type
1632 FROM blobs.doc_med
1633 WHERE fk_episode = %(pk)s
1634 )
1635 ) AS candidates
1636 ORDER BY latest DESC NULLS LAST
1637 LIMIT 1
1638 ),
1639 -- last ditch, always exists, only use when no clinical items or documents linked:
1640 -- last modification, latest = when last changed to the current state
1641 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type
1642 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s
1643 )
1644 )"""
1645 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False)
1646 return rows[0][0]
1647
1648 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date)
1649
1650
1694
1695 formatted_clinical_duration = property(_get_formatted_clinical_duration)
1696
1697
1699 cmd = """SELECT MAX(latest) FROM (
1700 -- last modification, latest = when last changed to the current state
1701 (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)
1702
1703 UNION ALL
1704
1705 -- last modification of encounter in which created, latest = initial creation of that encounter
1706 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1707 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1708 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1709 --))
1710
1711 -- end of encounter in which created, latest = explicitely set
1712 -- DO NOT USE: we can retrospectively create episodes which
1713 -- DO NOT USE: are long since finished
1714 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1715 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1716 --))
1717
1718 -- latest end of encounters of clinical items linked to this episode
1719 (SELECT
1720 MAX(last_affirmed) AS latest,
1721 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1722 FROM clin.encounter
1723 WHERE pk IN (
1724 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1725 ))
1726 UNION ALL
1727
1728 -- latest explicit .clin_when of clinical items linked to this episode
1729 (SELECT
1730 MAX(clin_when) AS latest,
1731 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1732 FROM clin.clin_root_item
1733 WHERE fk_episode = %(pk)s
1734 )
1735
1736 -- latest modification time of clinical items linked to this episode
1737 -- this CAN be used since if an item is linked to an episode it can be
1738 -- assumed the episode (should have) existed at the time of creation
1739 -- DO NOT USE, because typo fixes should not extend the episode
1740 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1741
1742 -- not sure about this one:
1743 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1744
1745 ) AS candidates"""
1746 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1747 return rows[0][0]
1748
1749 latest_access_date = property(_get_latest_access_date)
1750
1751
1754
1755 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1756
1757
1759 cmd = """SELECT
1760 'NONE (live row)'::text as audit__action_applied,
1761 NULL AS audit__action_when,
1762 NULL AS audit__action_by,
1763 pk_audit,
1764 row_version,
1765 modified_when,
1766 modified_by,
1767 pk, fk_health_issue, description, is_open, fk_encounter,
1768 diagnostic_certainty_classification,
1769 summary
1770 FROM clin.episode
1771 WHERE pk = %(pk_episode)s
1772 UNION ALL (
1773 SELECT
1774 audit_action as audit__action_applied,
1775 audit_when as audit__action_when,
1776 audit_by as audit__action_by,
1777 pk_audit,
1778 orig_version as row_version,
1779 orig_when as modified_when,
1780 orig_by as modified_by,
1781 pk, fk_health_issue, description, is_open, fk_encounter,
1782 diagnostic_certainty_classification,
1783 summary
1784 FROM audit.log_episode
1785 WHERE pk = %(pk_episode)s
1786 )
1787 ORDER BY row_version DESC
1788 """
1789 args = {'pk_episode': self.pk_obj}
1790 title = _('Episode: %s%s%s') % (
1791 gmTools.u_left_double_angle_quote,
1792 self._payload[self._idx['description']],
1793 gmTools.u_right_double_angle_quote
1794 )
1795 return '\n'.join(self._get_revision_history(cmd, args, title))
1796
1797 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
1798
1799
1801 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1802 return []
1803
1804 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
1805 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1806 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1807 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1808
1810 queries = []
1811
1812 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1813 queries.append ({
1814 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1815 'args': {
1816 'epi': self._payload[self._idx['pk_episode']],
1817 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1818 }
1819 })
1820
1821 for pk_code in pk_codes:
1822 queries.append ({
1823 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1824 'args': {
1825 'epi': self._payload[self._idx['pk_episode']],
1826 'pk_code': pk_code
1827 }
1828 })
1829 if len(queries) == 0:
1830 return
1831
1832 rows, idx = gmPG2.run_rw_queries(queries = queries)
1833 return
1834
1835 generic_codes = property(_get_generic_codes, _set_generic_codes)
1836
1837
1839 cmd = """SELECT EXISTS (
1840 SELECT 1 FROM clin.clin_narrative
1841 WHERE
1842 fk_episode = %(epi)s
1843 AND
1844 fk_encounter IN (
1845 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1846 )
1847 )"""
1848 args = {
1849 'pat': self._payload[self._idx['pk_patient']],
1850 'epi': self._payload[self._idx['pk_episode']]
1851 }
1852 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1853 return rows[0][0]
1854
1855 has_narrative = property(_get_has_narrative, lambda x:x)
1856
1857
1859 if self._payload[self._idx['pk_health_issue']] is None:
1860 return None
1861 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1862
1863 health_issue = property(_get_health_issue)
1864
1865
1866 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1867 """Creates a new episode for a given patient's health issue.
1868
1869 pk_health_issue - given health issue PK
1870 episode_name - name of episode
1871 """
1872 if not allow_dupes:
1873 try:
1874 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj)
1875 if episode['episode_open'] != is_open:
1876 episode['episode_open'] = is_open
1877 episode.save_payload()
1878 return episode
1879 except gmExceptions.ConstructorError:
1880 pass
1881
1882 queries = []
1883 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)"
1884 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1885 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"})
1886 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True)
1887
1888 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1889 return episode
1890
1891
1893 if isinstance(episode, cEpisode):
1894 pk = episode['pk_episode']
1895 else:
1896 pk = int(episode)
1897
1898 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s'
1899
1900 try:
1901 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1902 except gmPG2.dbapi.IntegrityError:
1903
1904 _log.exception('cannot delete episode, it is in use')
1905 return False
1906
1907 return True
1908
1910 return cProblem (
1911 aPK_obj = {
1912 'pk_patient': episode['pk_patient'],
1913 'pk_episode': episode['pk_episode'],
1914 'pk_health_issue': episode['pk_health_issue']
1915 },
1916 try_potential_problems = allow_closed
1917 )
1918
1919
1920
1921
1922 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s"
1923
1924 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1925 """Represents one encounter."""
1926
1927 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s'
1928 _cmds_store_payload = [
1929 """UPDATE clin.encounter SET
1930 started = %(started)s,
1931 last_affirmed = %(last_affirmed)s,
1932 fk_location = %(pk_org_unit)s,
1933 fk_type = %(pk_type)s,
1934 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
1935 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
1936 WHERE
1937 pk = %(pk_encounter)s AND
1938 xmin = %(xmin_encounter)s
1939 """,
1940
1941 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
1942 ]
1943 _updatable_fields = [
1944 'started',
1945 'last_affirmed',
1946 'pk_org_unit',
1947 'pk_type',
1948 'reason_for_encounter',
1949 'assessment_of_encounter'
1950 ]
1951
1953 """Set the encounter as the active one.
1954
1955 "Setting active" means making sure the encounter
1956 row has the youngest "last_affirmed" timestamp of
1957 all encounter rows for this patient.
1958 """
1959 self['last_affirmed'] = gmDateTime.pydt_now_here()
1960 self.save()
1961
1962 - def lock(self, exclusive=False, link_obj=None):
1963 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1964
1965 - def unlock(self, exclusive=False, link_obj=None):
1966 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
1967
1969 """
1970 Moves every element currently linked to the current encounter
1971 and the source_episode onto target_episode.
1972
1973 @param source_episode The episode the elements are currently linked to.
1974 @type target_episode A cEpisode intance.
1975 @param target_episode The episode the elements will be relinked to.
1976 @type target_episode A cEpisode intance.
1977 """
1978 if source_episode['pk_episode'] == target_episode['pk_episode']:
1979 return True
1980
1981 queries = []
1982 cmd = """
1983 UPDATE clin.clin_root_item
1984 SET fk_episode = %(trg)s
1985 WHERE
1986 fk_encounter = %(enc)s AND
1987 fk_episode = %(src)s
1988 """
1989 rows, idx = gmPG2.run_rw_queries(queries = [{
1990 'cmd': cmd,
1991 'args': {
1992 'trg': target_episode['pk_episode'],
1993 'enc': self.pk_obj,
1994 'src': source_episode['pk_episode']
1995 }
1996 }])
1997 self.refetch_payload()
1998 return True
1999
2000
2002 if pk_target_encounter == self.pk_obj:
2003 return True
2004 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)"
2005 args = {
2006 'src': self.pk_obj,
2007 'trg': pk_target_encounter
2008 }
2009 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2010 return True
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2022
2023 relevant_fields = [
2024 'pk_org_unit',
2025 'pk_type',
2026 'pk_patient',
2027 'reason_for_encounter',
2028 'assessment_of_encounter'
2029 ]
2030 for field in relevant_fields:
2031 if self._payload[self._idx[field]] != another_object[field]:
2032 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
2033 return False
2034
2035 relevant_fields = [
2036 'started',
2037 'last_affirmed',
2038 ]
2039 for field in relevant_fields:
2040 if self._payload[self._idx[field]] is None:
2041 if another_object[field] is None:
2042 continue
2043 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2044 return False
2045
2046 if another_object[field] is None:
2047 return False
2048
2049
2050 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'):
2051 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2052 return False
2053
2054
2055
2056 if another_object['pk_generic_codes_rfe'] is None:
2057 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
2058 return False
2059 if another_object['pk_generic_codes_rfe'] is not None:
2060 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
2061 return False
2062 if (
2063 (another_object['pk_generic_codes_rfe'] is None)
2064 and
2065 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
2066 ) is False:
2067 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
2068 return False
2069
2070 if another_object['pk_generic_codes_aoe'] is None:
2071 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
2072 return False
2073 if another_object['pk_generic_codes_aoe'] is not None:
2074 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
2075 return False
2076 if (
2077 (another_object['pk_generic_codes_aoe'] is None)
2078 and
2079 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
2080 ) is False:
2081 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
2082 return False
2083
2084 return True
2085
2087 cmd = """
2088 select exists (
2089 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
2090 union all
2091 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2092 )"""
2093 args = {
2094 'pat': self._payload[self._idx['pk_patient']],
2095 'enc': self.pk_obj
2096 }
2097 rows, idx = gmPG2.run_ro_queries (
2098 queries = [{
2099 'cmd': cmd,
2100 'args': args
2101 }]
2102 )
2103 return rows[0][0]
2104
2105
2107 cmd = """
2108 select exists (
2109 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
2110 )"""
2111 args = {
2112 'pat': self._payload[self._idx['pk_patient']],
2113 'enc': self.pk_obj
2114 }
2115 rows, idx = gmPG2.run_ro_queries (
2116 queries = [{
2117 'cmd': cmd,
2118 'args': args
2119 }]
2120 )
2121 return rows[0][0]
2122
2124 """soap_cats: <space> = admin category"""
2125
2126 if soap_cats is None:
2127 soap_cats = 'soap '
2128 else:
2129 soap_cats = soap_cats.lower()
2130
2131 cats = []
2132 for cat in soap_cats:
2133 if cat in 'soapu':
2134 cats.append(cat)
2135 continue
2136 if cat == ' ':
2137 cats.append(None)
2138
2139 cmd = """
2140 SELECT EXISTS (
2141 SELECT 1 FROM clin.clin_narrative
2142 WHERE
2143 fk_encounter = %(enc)s
2144 AND
2145 soap_cat IN %(cats)s
2146 LIMIT 1
2147 )
2148 """
2149 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
2150 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
2151 return rows[0][0]
2152
2154 cmd = """
2155 select exists (
2156 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2157 )"""
2158 args = {
2159 'pat': self._payload[self._idx['pk_patient']],
2160 'enc': self.pk_obj
2161 }
2162 rows, idx = gmPG2.run_ro_queries (
2163 queries = [{
2164 'cmd': cmd,
2165 'args': args
2166 }]
2167 )
2168 return rows[0][0]
2169
2171
2172 if soap_cat is not None:
2173 soap_cat = soap_cat.lower()
2174
2175 if episode is None:
2176 epi_part = 'fk_episode is null'
2177 else:
2178 epi_part = 'fk_episode = %(epi)s'
2179
2180 cmd = """
2181 select narrative
2182 from clin.clin_narrative
2183 where
2184 fk_encounter = %%(enc)s
2185 and
2186 soap_cat = %%(cat)s
2187 and
2188 %s
2189 order by clin_when desc
2190 limit 1
2191 """ % epi_part
2192
2193 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
2194
2195 rows, idx = gmPG2.run_ro_queries (
2196 queries = [{
2197 'cmd': cmd,
2198 'args': args
2199 }]
2200 )
2201 if len(rows) == 0:
2202 return None
2203
2204 return rows[0][0]
2205
2207 cmd = """
2208 SELECT * FROM clin.v_pat_episodes
2209 WHERE pk_episode IN (
2210 SELECT DISTINCT fk_episode
2211 FROM clin.clin_root_item
2212 WHERE fk_encounter = %%(enc)s
2213
2214 UNION
2215
2216 SELECT DISTINCT fk_episode
2217 FROM blobs.doc_med
2218 WHERE fk_encounter = %%(enc)s
2219 ) %s"""
2220 args = {'enc': self.pk_obj}
2221 if exclude is not None:
2222 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s'
2223 args['excluded'] = tuple(exclude)
2224 else:
2225 cmd = cmd % ''
2226
2227 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2228
2229 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2230
2231 episodes = property(get_episodes, lambda x:x)
2232
2233 - def add_code(self, pk_code=None, field=None):
2234 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2235 if field == 'rfe':
2236 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2237 elif field == 'aoe':
2238 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2239 else:
2240 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2241 args = {
2242 'item': self._payload[self._idx['pk_encounter']],
2243 'code': pk_code
2244 }
2245 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2246 return True
2247
2249 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2250 if field == 'rfe':
2251 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2252 elif field == 'aoe':
2253 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2254 else:
2255 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2256 args = {
2257 'item': self._payload[self._idx['pk_encounter']],
2258 'code': pk_code
2259 }
2260 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2261 return True
2262
2263
2264
2265
2311
2312
2408
2409
2469
2470
2523
2524
2623
2624
2646
2647
2782
2783
2784
2785
2787 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2788 return []
2789
2790 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2791 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2792 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2793 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2794
2796 queries = []
2797
2798 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2799 queries.append ({
2800 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2801 'args': {
2802 'enc': self._payload[self._idx['pk_encounter']],
2803 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2804 }
2805 })
2806
2807 for pk_code in pk_codes:
2808 queries.append ({
2809 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2810 'args': {
2811 'enc': self._payload[self._idx['pk_encounter']],
2812 'pk_code': pk_code
2813 }
2814 })
2815 if len(queries) == 0:
2816 return
2817
2818 rows, idx = gmPG2.run_rw_queries(queries = queries)
2819 self.refetch_payload()
2820 return
2821
2822 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2823
2825 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2826 return []
2827
2828 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2829 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2830 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2831 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2832
2834 queries = []
2835
2836 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2837 queries.append ({
2838 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2839 'args': {
2840 'enc': self._payload[self._idx['pk_encounter']],
2841 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2842 }
2843 })
2844
2845 for pk_code in pk_codes:
2846 queries.append ({
2847 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2848 'args': {
2849 'enc': self._payload[self._idx['pk_encounter']],
2850 'pk_code': pk_code
2851 }
2852 })
2853 if len(queries) == 0:
2854 return
2855
2856 rows, idx = gmPG2.run_rw_queries(queries = queries)
2857 self.refetch_payload()
2858 return
2859
2860 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2861
2866
2867 praxis_branch = property(_get_praxis_branch, lambda x:x)
2868
2870 if self._payload[self._idx['pk_org_unit']] is None:
2871 return None
2872 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
2873
2874 org_unit = property(_get_org_unit, lambda x:x)
2875
2877 cmd = """SELECT
2878 'NONE (live row)'::text as audit__action_applied,
2879 NULL AS audit__action_when,
2880 NULL AS audit__action_by,
2881 pk_audit,
2882 row_version,
2883 modified_when,
2884 modified_by,
2885 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
2886 FROM clin.encounter
2887 WHERE pk = %(pk_encounter)s
2888 UNION ALL (
2889 SELECT
2890 audit_action as audit__action_applied,
2891 audit_when as audit__action_when,
2892 audit_by as audit__action_by,
2893 pk_audit,
2894 orig_version as row_version,
2895 orig_when as modified_when,
2896 orig_by as modified_by,
2897 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
2898 FROM audit.log_encounter
2899 WHERE pk = %(pk_encounter)s
2900 )
2901 ORDER BY row_version DESC
2902 """
2903 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]}
2904 title = _('Encounter: %s%s%s') % (
2905 gmTools.u_left_double_angle_quote,
2906 self._payload[self._idx['l10n_type']],
2907 gmTools.u_right_double_angle_quote
2908 )
2909 return '\n'.join(self._get_revision_history(cmd, args, title))
2910
2911 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
2912
2913
2915 """Creates a new encounter for a patient.
2916
2917 fk_patient - patient PK
2918 enc_type - type of encounter
2919 """
2920 if enc_type is None:
2921 enc_type = 'in surgery'
2922
2923 queries = []
2924 try:
2925 enc_type = int(enc_type)
2926 cmd = """
2927 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location)
2928 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk"""
2929 except ValueError:
2930 enc_type = enc_type
2931 cmd = """
2932 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type)
2933 VALUES (
2934 %(pat)s,
2935 %(prax)s,
2936 coalesce (
2937 (select pk from clin.encounter_type where description = %(typ)s),
2938 -- pick the first available
2939 (select pk from clin.encounter_type limit 1)
2940 )
2941 ) RETURNING pk"""
2942 praxis = gmPraxis.gmCurrentPraxisBranch()
2943 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']}
2944 queries.append({'cmd': cmd, 'args': args})
2945 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
2946 encounter = cEncounter(aPK_obj = rows[0]['pk'])
2947
2948 return encounter
2949
2950
2952 """Used to protect against deletion of active encounter from another client."""
2953 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
2954
2955
2958
2959
2961 """Deletes an encounter by PK.
2962
2963 - attempts to obtain an exclusive lock which should
2964 fail if the encounter is the active encounter in
2965 this or any other client
2966 - catches DB exceptions which should mostly be related
2967 to clinical data already having been attached to
2968 the encounter thus making deletion fail
2969 """
2970 conn = gmPG2.get_connection(readonly = False)
2971 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn):
2972 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter)
2973 return False
2974 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s"""
2975 args = {'enc': pk_encounter}
2976 try:
2977 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2978 except gmPG2.dbapi.Error:
2979 _log.exception('cannot delete encounter [%s]', pk_encounter)
2980 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
2981 return False
2982 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
2983 return True
2984
2985
2986
2987
2989
2990 rows, idx = gmPG2.run_rw_queries(
2991 queries = [{
2992 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
2993 'args': {'desc': description, 'l10n_desc': l10n_description}
2994 }],
2995 return_data = True
2996 )
2997
2998 success = rows[0][0]
2999 if not success:
3000 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
3001
3002 return {'description': description, 'l10n_description': l10n_description}
3003
3005 """This will attempt to create a NEW encounter type."""
3006
3007
3008 if description is None:
3009 description = l10n_description
3010
3011 args = {
3012 'desc': description,
3013 'l10n_desc': l10n_description
3014 }
3015
3016 _log.debug('creating encounter type: %s, %s', description, l10n_description)
3017
3018
3019 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s"
3020 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3021
3022
3023 if len(rows) > 0:
3024
3025 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
3026 _log.info('encounter type [%s] already exists with the proper translation')
3027 return {'description': description, 'l10n_description': l10n_description}
3028
3029
3030
3031 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
3032 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3033
3034
3035 if rows[0][0]:
3036 _log.error('encounter type [%s] already exists but with another translation')
3037 return None
3038
3039
3040 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
3041 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3042 return {'description': description, 'l10n_description': l10n_description}
3043
3044
3045 queries = [
3046 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
3047 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
3048 ]
3049 rows, idx = gmPG2.run_rw_queries(queries = queries)
3050
3051 return {'description': description, 'l10n_description': l10n_description}
3052
3053
3055 cmd = """
3056 SELECT
3057 COUNT(1) AS type_count,
3058 fk_type
3059 FROM clin.encounter
3060 GROUP BY fk_type
3061 ORDER BY type_count DESC
3062 LIMIT 1
3063 """
3064 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3065 if len(rows) == 0:
3066 return None
3067 return rows[0]['fk_type']
3068
3069
3071 cmd = """
3072 SELECT
3073 _(description) AS l10n_description,
3074 description
3075 FROM
3076 clin.encounter_type
3077 ORDER BY
3078 l10n_description
3079 """
3080 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3081 return rows
3082
3083
3088
3089
3091 cmd = "delete from clin.encounter_type where description = %(desc)s"
3092 args = {'desc': description}
3093 try:
3094 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3095 except gmPG2.dbapi.IntegrityError as e:
3096 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
3097 return False
3098 raise
3099
3100 return True
3101
3102
3103 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
3104 """Represents one problem.
3105
3106 problems are the aggregation of
3107 .clinically_relevant=True issues and
3108 .is_open=True episodes
3109 """
3110 _cmd_fetch_payload = ''
3111 _cmds_store_payload = ["select 1"]
3112 _updatable_fields = []
3113
3114
3115 - def __init__(self, aPK_obj=None, try_potential_problems=False):
3116 """Initialize.
3117
3118 aPK_obj must contain the keys
3119 pk_patient
3120 pk_episode
3121 pk_health_issue
3122 """
3123 if aPK_obj is None:
3124 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj))
3125
3126
3127
3128
3129
3130 where_parts = []
3131 pk = {}
3132 for col_name in aPK_obj.keys():
3133 val = aPK_obj[col_name]
3134 if val is None:
3135 where_parts.append('%s IS NULL' % col_name)
3136 else:
3137 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
3138 pk[col_name] = val
3139
3140
3141 cProblem._cmd_fetch_payload = """
3142 SELECT *, False as is_potential_problem
3143 FROM clin.v_problem_list
3144 WHERE %s""" % ' AND '.join(where_parts)
3145
3146 try:
3147 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3148 return
3149 except gmExceptions.ConstructorError:
3150 _log.exception('actual problem not found, trying "potential" problems')
3151 if try_potential_problems is False:
3152 raise
3153
3154
3155 cProblem._cmd_fetch_payload = """
3156 SELECT *, True as is_potential_problem
3157 FROM clin.v_potential_problem_list
3158 WHERE %s""" % ' AND '.join(where_parts)
3159 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3160
3162 """
3163 Retrieve the cEpisode instance equivalent to this problem.
3164 The problem's type attribute must be 'episode'
3165 """
3166 if self._payload[self._idx['type']] != 'episode':
3167 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3168 return None
3169 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3170
3172 """
3173 Retrieve the cHealthIssue instance equivalent to this problem.
3174 The problem's type attribute must be 'issue'
3175 """
3176 if self._payload[self._idx['type']] != 'issue':
3177 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3178 return None
3179 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3180
3196
3197
3198
3199
3200
3203
3204 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
3205
3207 if self._payload[self._idx['type']] == 'issue':
3208 cmd = """
3209 SELECT * FROM clin.v_linked_codes WHERE
3210 item_table = 'clin.lnk_code2h_issue'::regclass
3211 AND
3212 pk_item = %(item)s
3213 """
3214 args = {'item': self._payload[self._idx['pk_health_issue']]}
3215 else:
3216 cmd = """
3217 SELECT * FROM clin.v_linked_codes WHERE
3218 item_table = 'clin.lnk_code2episode'::regclass
3219 AND
3220 pk_item = %(item)s
3221 """
3222 args = {'item': self._payload[self._idx['pk_episode']]}
3223
3224 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3225 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3226
3227 generic_codes = property(_get_generic_codes, lambda x:x)
3228
3230 """Retrieve the cEpisode instance equivalent to the given problem.
3231
3232 The problem's type attribute must be 'episode'
3233
3234 @param problem: The problem to retrieve its related episode for
3235 @type problem: A gmEMRStructItems.cProblem instance
3236 """
3237 if isinstance(problem, cEpisode):
3238 return problem
3239
3240 exc = TypeError('cannot convert [%s] to episode' % problem)
3241
3242 if not isinstance(problem, cProblem):
3243 raise exc
3244
3245 if problem['type'] != 'episode':
3246 raise exc
3247
3248 return cEpisode(aPK_obj = problem['pk_episode'])
3249
3251 """Retrieve the cIssue instance equivalent to the given problem.
3252
3253 The problem's type attribute must be 'issue'.
3254
3255 @param problem: The problem to retrieve the corresponding issue for
3256 @type problem: A gmEMRStructItems.cProblem instance
3257 """
3258 if isinstance(problem, cHealthIssue):
3259 return problem
3260
3261 exc = TypeError('cannot convert [%s] to health issue' % problem)
3262
3263 if not isinstance(problem, cProblem):
3264 raise exc
3265
3266 if problem['type'] != 'issue':
3267 raise exc
3268
3269 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3270
3272 """Transform given problem into either episode or health issue instance.
3273 """
3274 if isinstance(problem, (cEpisode, cHealthIssue)):
3275 return problem
3276
3277 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
3278
3279 if not isinstance(problem, cProblem):
3280 _log.debug('%s' % problem)
3281 raise exc
3282
3283 if problem['type'] == 'episode':
3284 return cEpisode(aPK_obj = problem['pk_episode'])
3285
3286 if problem['type'] == 'issue':
3287 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3288
3289 raise exc
3290
3291
3292 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s"
3293
3295
3296 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s"
3297 _cmds_store_payload = [
3298 """UPDATE clin.hospital_stay SET
3299 clin_when = %(admission)s,
3300 discharge = %(discharge)s,
3301 fk_org_unit = %(pk_org_unit)s,
3302 narrative = gm.nullify_empty_string(%(comment)s),
3303 fk_episode = %(pk_episode)s,
3304 fk_encounter = %(pk_encounter)s
3305 WHERE
3306 pk = %(pk_hospital_stay)s
3307 AND
3308 xmin = %(xmin_hospital_stay)s
3309 RETURNING
3310 xmin AS xmin_hospital_stay
3311 """
3312 ]
3313 _updatable_fields = [
3314 'admission',
3315 'discharge',
3316 'pk_org_unit',
3317 'pk_episode',
3318 'pk_encounter',
3319 'comment'
3320 ]
3321
3322
3328
3329
3363
3364
3366 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3367
3368 documents = property(_get_documents, lambda x:x)
3369
3370
3372 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1"
3373 queries = [{
3374
3375
3376 'cmd': cmd,
3377 'args': {'pat': patient}
3378 }]
3379 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3380 if len(rows) == 0:
3381 return None
3382 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3383
3384
3386 args = {'pat': patient}
3387 if ongoing_only:
3388 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission"
3389 else:
3390 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission"
3391
3392 queries = [{'cmd': cmd, 'args': args}]
3393 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3394
3395 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3396
3397
3399
3400 queries = [{
3401 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk',
3402 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit}
3403 }]
3404 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3405
3406 return cHospitalStay(aPK_obj = rows[0][0])
3407
3408
3410 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
3411 args = {'pk': stay}
3412 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3413 return True
3414
3415
3416 _SQL_get_procedures = "select * from clin.v_procedures where %s"
3417
3617
3618
3627
3628
3634
3635
3645
3646
3673
3674
3680
3681
3727
3728
3729
3730
3732
3733 aggregate_result = 0
3734
3735 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk')
3736 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ])
3737
3738 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk')
3739 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ]
3740
3741 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi)
3742
3743 tables_linking2enc = {}
3744 for fk in fks_linking2enc:
3745 table = fk['referencing_table']
3746 tables_linking2enc[table] = fk
3747
3748 tables_linking2epi = {}
3749 for fk in fks_linking2epi:
3750 table = fk['referencing_table']
3751 tables_linking2epi[table] = fk
3752
3753 for t in tables_linking2both:
3754
3755 table_file_name = 'x-check_enc_epi_xref-%s.log' % t
3756 table_file = io.open(table_file_name, 'w+', encoding = 'utf8')
3757
3758
3759 args = {'table': t}
3760 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}])
3761 pk_col = rows[0][0]
3762 print("checking table:", t, '- pk col:', pk_col)
3763 print(' =>', table_file_name)
3764 table_file.write('table: %s\n' % t)
3765 table_file.write('PK col: %s\n' % pk_col)
3766
3767
3768 cmd = 'select %s from %s' % (pk_col, t)
3769 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
3770 pks = [ r[0] for r in rows ]
3771 for pk in pks:
3772 args = {'pk': pk, 'tbl': t}
3773 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col)
3774 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)
3775 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}])
3776 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}])
3777 enc_pat = enc_rows[0][0]
3778 epi_pat = epi_rows[0][0]
3779 args['pat_enc'] = enc_pat
3780 args['pat_epi'] = epi_pat
3781 if epi_pat != enc_pat:
3782 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat))
3783 aggregate_result = -2
3784
3785 table_file.write('--------------------------------------------------------------------------------\n')
3786 table_file.write('mismatch on row with pk: %s\n' % pk)
3787 table_file.write('\n')
3788
3789 table_file.write('journal entry:\n')
3790 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s'
3791 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3792 if len(rows) > 0:
3793 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3794 table_file.write('\n\n')
3795
3796 table_file.write('row data:\n')
3797 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col)
3798 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3799 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3800 table_file.write('\n\n')
3801
3802 table_file.write('episode:\n')
3803 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col)
3804 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3805 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3806 table_file.write('\n\n')
3807
3808 table_file.write('patient of episode:\n')
3809 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s'
3810 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3811 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3812 table_file.write('\n\n')
3813
3814 table_file.write('encounter:\n')
3815 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col)
3816 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3817 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3818 table_file.write('\n\n')
3819
3820 table_file.write('patient of encounter:\n')
3821 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s'
3822 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3823 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3824 table_file.write('\n')
3825
3826 table_file.write('done\n')
3827 table_file.close()
3828
3829 return aggregate_result
3830
3831
3843
3844
3845
3846
3847 if __name__ == '__main__':
3848
3849 if len(sys.argv) < 2:
3850 sys.exit()
3851
3852 if sys.argv[1] != 'test':
3853 sys.exit()
3854
3855
3856
3857
3859 print("\nProblem test")
3860 print("------------")
3861 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
3862 print(prob)
3863 fields = prob.get_fields()
3864 for field in fields:
3865 print(field, ':', prob[field])
3866 print('\nupdatable:', prob.get_updatable_fields())
3867 epi = prob.get_as_episode()
3868 print('\nas episode:')
3869 if epi is not None:
3870 for field in epi.get_fields():
3871 print(' .%s : %s' % (field, epi[field]))
3872
3873
3892
3893
3895 print("episode test")
3896 print("------------")
3897 episode = cEpisode(aPK_obj = 1322)
3898
3899 print(episode['description'])
3900 print('start:', episode.best_guess_clinical_start_date)
3901 print('end :', episode.best_guess_clinical_end_date)
3902 return
3903
3904 print(episode)
3905 fields = episode.get_fields()
3906 for field in fields:
3907 print(field, ':', episode[field])
3908 print("updatable:", episode.get_updatable_fields())
3909 input('ENTER to continue')
3910
3911 old_description = episode['description']
3912 old_enc = cEncounter(aPK_obj = 1)
3913
3914 desc = '1-%s' % episode['description']
3915 print("==> renaming to", desc)
3916 successful = episode.rename (
3917 description = desc
3918 )
3919 if not successful:
3920 print("error")
3921 else:
3922 print("success")
3923 for field in fields:
3924 print(field, ':', episode[field])
3925
3926 print(episode.formatted_revision_history)
3927
3928 input('ENTER to continue')
3929
3930
3942
3943
3945 encounter = cEncounter(aPK_obj=1)
3946 print(encounter)
3947 print("")
3948 print(encounter.format_latex())
3949
3954
3964
3971
3976
3980
3981
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004 test_export_emr_structure()
4005
4006
4007