1
2 """GNUmed health related business object.
3
4 license: GPL v2 or later
5 """
6
7 __version__ = "$Revision: 1.157 $"
8 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>"
9
10 import types, sys, string, datetime, logging, time
11
12
13 if __name__ == '__main__':
14 sys.path.insert(0, '../../')
15 from Gnumed.pycommon import gmPG2
16 from Gnumed.pycommon import gmI18N
17 from Gnumed.pycommon import gmTools
18 from Gnumed.pycommon import gmDateTime
19 from Gnumed.pycommon import gmBusinessDBObject
20 from Gnumed.pycommon import gmNull
21 from Gnumed.pycommon import gmExceptions
22
23 from Gnumed.business import gmClinNarrative
24 from Gnumed.business import gmCoding
25
26
27 _log = logging.getLogger('gm.emr')
28 _log.info(__version__)
29
30 try: _
31 except NameError: _ = lambda x:x
32
33
34
35 __diagnostic_certainty_classification_map = None
36
54
55
56
57 laterality2str = {
58 None: u'?',
59 u'na': u'',
60 u'sd': _('bilateral'),
61 u'ds': _('bilateral'),
62 u's': _('left'),
63 u'd': _('right')
64 }
65
66
68 """Represents one health issue."""
69
70 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s"
71 _cmds_store_payload = [
72 u"""update clin.health_issue set
73 description = %(description)s,
74 summary = gm.nullify_empty_string(%(summary)s),
75 age_noted = %(age_noted)s,
76 laterality = gm.nullify_empty_string(%(laterality)s),
77 grouping = gm.nullify_empty_string(%(grouping)s),
78 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
79 is_active = %(is_active)s,
80 clinically_relevant = %(clinically_relevant)s,
81 is_confidential = %(is_confidential)s,
82 is_cause_of_death = %(is_cause_of_death)s
83 where
84 pk = %(pk_health_issue)s and
85 xmin = %(xmin_health_issue)s""",
86 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
87 ]
88 _updatable_fields = [
89 'description',
90 'summary',
91 'grouping',
92 'age_noted',
93 'laterality',
94 'is_active',
95 'clinically_relevant',
96 'is_confidential',
97 'is_cause_of_death',
98 'diagnostic_certainty_classification'
99 ]
100
101 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
102 pk = aPK_obj
103
104 if (pk is not None) or (row is not None):
105 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
106 return
107
108 if patient is None:
109 cmd = u"""select *, xmin_health_issue from clin.v_health_issues
110 where
111 description = %(desc)s
112 and
113 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
114 else:
115 cmd = u"""select *, xmin_health_issue from clin.v_health_issues
116 where
117 description = %(desc)s
118 and
119 pk_patient = %(pat)s"""
120
121 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
122 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
123
124 if len(rows) == 0:
125 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient)
126
127 pk = rows[0][0]
128 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
129
130 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
131
132
133
134 - def rename(self, description=None):
135 """Method for issue renaming.
136
137 @param description
138 - the new descriptive name for the issue
139 @type description
140 - a string instance
141 """
142
143 if not type(description) in [str, unicode] or description.strip() == '':
144 _log.error('<description> must be a non-empty string')
145 return False
146
147 old_description = self._payload[self._idx['description']]
148 self._payload[self._idx['description']] = description.strip()
149 self._is_modified = True
150 successful, data = self.save_payload()
151 if not successful:
152 _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
153 self._payload[self._idx['description']] = old_description
154 return False
155 return True
156
158 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
160 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
161
177
185
187 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)"
188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
189 return rows[0][0]
190
192 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True"
193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
194 if len(rows) == 0:
195 return None
196 return cEpisode(aPK_obj=rows[0][0])
197
199 if self._payload[self._idx['age_noted']] is None:
200 return u'<???>'
201
202
203
204
205 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
206
208 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
209 cmd = u"INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
210 args = {
211 'item': self._payload[self._idx['pk_health_issue']],
212 'code': pk_code
213 }
214 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
215 return True
216
218 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
219 cmd = u"DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
220 args = {
221 'item': self._payload[self._idx['pk_health_issue']],
222 'code': pk_code
223 }
224 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
225 return True
226
279
508
509
510
511 episodes = property(get_episodes, lambda x:x)
512
513 open_episode = property(get_open_episode, lambda x:x)
514
515 has_open_episode = property(has_open_episode, lambda x:x)
516
518 cmd = u"""SELECT pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY started_first limit 1"""
519 args = {'issue': self.pk_obj}
520 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
521 if len(rows) == 0:
522 return None
523 return cEpisode(aPK_obj = rows[0][0])
524
525 first_episode = property(_get_first_episode, lambda x:x)
526
528 cmd = u"""SELECT
529 coalesce (
530 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE),
531 (SELECT pk_episode AS pk FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1)
532 )"""
533 args = {'issue': self.pk_obj}
534 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
535 if len(rows) == 0:
536 return None
537 if rows[0][0] is None:
538 return None
539 return cEpisode(aPK_obj = rows[0][0])
540
541 latest_episode = property(_get_latest_episode, lambda x:x)
542
543
545 """This returns the date when we can assume to safely
546 KNOW the health issue existed (because
547 the provider said so)."""
548 args = {
549 'enc': self._payload[self._idx['pk_encounter']],
550 'pk': self._payload[self._idx['pk_health_issue']]
551 }
552 cmd = u"""
553 SELECT COALESCE (
554 -- this one must override all:
555 -- .age_noted if not null and DOB is known
556 (CASE
557 WHEN c_hi.age_noted IS NULL
558 THEN NULL::timestamp with time zone
559 WHEN
560 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
561 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
562 )) IS NULL
563 THEN NULL::timestamp with time zone
564 ELSE
565 c_hi.age_noted + (
566 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
567 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
568 )
569 )
570 END),
571
572 -- start of encounter in which created, earliest = explicitely set
573 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
574 c_hi.fk_encounter
575 --SELECT fk_encounter FROM clin.health_issue WHERE clin.health_issue.pk = %(pk)s
576 ))
577 )
578 FROM clin.health_issue c_hi
579 WHERE c_hi.pk = %(pk)s"""
580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
581 return rows[0][0]
582
583 safe_start_date = property(_get_safe_start_date, lambda x:x)
584
586 args = {'pk': self._payload[self._idx['pk_health_issue']]}
587 cmd = u"""
588 SELECT MIN(earliest) FROM (
589 -- last modification, earliest = when created in/changed to the current state
590 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
591
592 UNION ALL
593 -- last modification of encounter in which created, earliest = initial creation of that encounter
594 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
595 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
596 ))
597
598 UNION ALL
599 -- earliest explicit .clin_when of clinical items linked to this health_issue
600 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
601
602 UNION ALL
603 -- earliest modification time of clinical items linked to this health issue
604 -- this CAN be used since if an item is linked to a health issue it can be
605 -- assumed the health issue (should have) existed at the time of creation
606 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
607
608 UNION ALL
609 -- earliest start of encounters of clinical items linked to this episode
610 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
611 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
612 ))
613
614 -- here we should be looking at
615 -- .best_guess_start_date of all episodes linked to this encounter
616
617 ) AS candidates"""
618
619 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
620 return rows[0][0]
621
622 possible_start_date = property(_get_possible_start_date)
623
630
631 end_date = property(_get_end_date)
632
634 args = {
635 'enc': self._payload[self._idx['pk_encounter']],
636 'pk': self._payload[self._idx['pk_health_issue']]
637 }
638 cmd = u"""
639 SELECT
640 MAX(latest)
641 FROM (
642 -- last modification, latest = when last changed to the current state
643 -- DO NOT USE: database upgrades may change this field
644 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
645
646 --UNION ALL
647 -- last modification of encounter in which created, latest = initial creation of that encounter
648 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
649 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
650 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
651 -- )
652 --)
653
654 --UNION ALL
655 -- end of encounter in which created, latest = explicitely set
656 -- DO NOT USE: we can retrospectively create issues which
657 -- DO NOT USE: are long since finished
658 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
659 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
660 -- )
661 --)
662
663 UNION ALL
664 -- latest end of encounters of clinical items linked to this issue
665 (SELECT
666 MAX(last_affirmed) AS latest
667 FROM clin.encounter
668 WHERE pk IN (
669 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
670 )
671 )
672
673 UNION ALL
674 -- latest explicit .clin_when of clinical items linked to this issue
675 (SELECT
676 MAX(clin_when) AS latest
677 FROM clin.v_pat_items
678 WHERE pk_health_issue = %(pk)s
679 )
680
681 -- latest modification time of clinical items linked to this issue
682 -- this CAN be used since if an item is linked to an issue it can be
683 -- assumed the issue (should have) existed at the time of modification
684 -- DO NOT USE, because typo fixes should not extend the issue
685 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
686
687 ) AS candidates"""
688 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
689 return rows[0][0]
690
691 latest_access_date = property(_get_latest_access_date)
692
694 try:
695 return laterality2str[self._payload[self._idx['laterality']]]
696 except KeyError:
697 return u'<???>'
698
699 laterality_description = property(_get_laterality_description, lambda x:x)
700
703
704 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
705
707 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
708 return []
709
710 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
711 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
712 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
713 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
714
716 queries = []
717
718 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
719 queries.append ({
720 'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
721 'args': {
722 'issue': self._payload[self._idx['pk_health_issue']],
723 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
724 }
725 })
726
727 for pk_code in pk_codes:
728 queries.append ({
729 'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
730 'args': {
731 'issue': self._payload[self._idx['pk_health_issue']],
732 'pk_code': pk_code
733 }
734 })
735 if len(queries) == 0:
736 return
737
738 rows, idx = gmPG2.run_rw_queries(queries = queries)
739 return
740
741 generic_codes = property(_get_generic_codes, _set_generic_codes)
742
744 """Creates a new health issue for a given patient.
745
746 description - health issue name
747 """
748 try:
749 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
750 return h_issue
751 except gmExceptions.NoSuchBusinessObjectError:
752 pass
753
754 queries = []
755 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
756 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
757
758 cmd = u"select currval('clin.health_issue_pk_seq')"
759 queries.append({'cmd': cmd})
760
761 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
762 h_issue = cHealthIssue(aPK_obj = rows[0][0])
763
764 return h_issue
765
767 if isinstance(health_issue, cHealthIssue):
768 pk = health_issue['pk_health_issue']
769 else:
770 pk = int(health_issue)
771
772 try:
773 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}])
774 except gmPG2.dbapi.IntegrityError:
775
776 _log.exception('cannot delete health issue')
777 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
778
779
781 issue = {
782 'pk_health_issue': None,
783 'description': _('Unattributed episodes'),
784 'age_noted': None,
785 'laterality': u'na',
786 'is_active': True,
787 'clinically_relevant': True,
788 'is_confidential': None,
789 'is_cause_of_death': False,
790 'is_dummy': True,
791 'grouping': None
792 }
793 return issue
794
796 return cProblem (
797 aPK_obj = {
798 'pk_patient': health_issue['pk_patient'],
799 'pk_health_issue': health_issue['pk_health_issue'],
800 'pk_episode': None
801 },
802 try_potential_problems = allow_irrelevant
803 )
804
805
806
807 -class cEpisode(gmBusinessDBObject.cBusinessDBObject):
808 """Represents one clinical episode.
809 """
810 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s"
811 _cmds_store_payload = [
812 u"""update clin.episode set
813 fk_health_issue = %(pk_health_issue)s,
814 is_open = %(episode_open)s::boolean,
815 description = %(description)s,
816 summary = gm.nullify_empty_string(%(summary)s),
817 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
818 where
819 pk = %(pk_episode)s and
820 xmin = %(xmin_episode)s""",
821 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
822 ]
823 _updatable_fields = [
824 'pk_health_issue',
825 'episode_open',
826 'description',
827 'summary',
828 'diagnostic_certainty_classification'
829 ]
830
831 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
832 pk = aPK_obj
833 if pk is None and row is None:
834
835 where_parts = [u'description = %(desc)s']
836
837 if id_patient is not None:
838 where_parts.append(u'pk_patient = %(pat)s')
839
840 if health_issue is not None:
841 where_parts.append(u'pk_health_issue = %(issue)s')
842
843 if encounter is not None:
844 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)')
845
846 args = {
847 'pat': id_patient,
848 'issue': health_issue,
849 'enc': encounter,
850 'desc': name
851 }
852
853 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts)
854
855 rows, idx = gmPG2.run_ro_queries(
856 queries = [{'cmd': cmd, 'args': args}],
857 get_col_idx=True
858 )
859
860 if len(rows) == 0:
861 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter)
862
863 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
864 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
865
866 else:
867 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
868
869
870
877
879 return self._payload[self._idx['pk_patient']]
880
881 - def get_narrative(self, soap_cats=None, encounters=None, order_by = None):
888
889 - def rename(self, description=None):
890 """Method for episode editing, that is, episode renaming.
891
892 @param description
893 - the new descriptive name for the encounter
894 @type description
895 - a string instance
896 """
897
898 if description.strip() == '':
899 _log.error('<description> must be a non-empty string instance')
900 return False
901
902 old_description = self._payload[self._idx['description']]
903 self._payload[self._idx['description']] = description.strip()
904 self._is_modified = True
905 successful, data = self.save_payload()
906 if not successful:
907 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
908 self._payload[self._idx['description']] = old_description
909 return False
910 return True
911
913 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
914
915 if pk_code in self._payload[self._idx['pk_generic_codes']]:
916 return
917
918 cmd = u"""
919 INSERT INTO clin.lnk_code2episode
920 (fk_item, fk_generic_code)
921 SELECT
922 %(item)s,
923 %(code)s
924 WHERE NOT EXISTS (
925 SELECT 1 FROM clin.lnk_code2episode
926 WHERE
927 fk_item = %(item)s
928 AND
929 fk_generic_code = %(code)s
930 )"""
931 args = {
932 'item': self._payload[self._idx['pk_episode']],
933 'code': pk_code
934 }
935 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
936 return
937
939 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
940 cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
941 args = {
942 'item': self._payload[self._idx['pk_episode']],
943 'code': pk_code
944 }
945 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
946 return True
947
1002
1264
1265
1266
1268 cmd = u"""
1269 SELECT
1270 MIN(earliest)
1271 FROM (
1272 -- last modification, earliest = when created in/changed to the current state
1273 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1274
1275 UNION ALL
1276
1277 -- last modification of encounter in which created, earliest = initial creation of that encounter
1278 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1279 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1280 )
1281 )
1282 UNION ALL
1283
1284 -- start of encounter in which created, earliest = explicitely set
1285 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1286 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1287 )
1288 )
1289 UNION ALL
1290
1291 -- earliest start of encounters of clinical items linked to this episode
1292 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1293 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1294 )
1295 )
1296 UNION ALL
1297
1298 -- earliest explicit .clin_when of clinical items linked to this episode
1299 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1300
1301 UNION ALL
1302
1303 -- earliest modification time of clinical items linked to this episode
1304 -- this CAN be used since if an item is linked to an episode it can be
1305 -- assumed the episode (should have) existed at the time of creation
1306 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1307
1308 -- not sure about this one:
1309 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1310
1311 ) AS candidates"""
1312 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1313 return rows[0][0]
1314
1315 best_guess_start_date = property(_get_best_guess_start_date)
1316
1318 cmd = u"""
1319 SELECT
1320 MAX(latest)
1321 FROM (
1322 -- last modification, latest = when last changed to the current state
1323 (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)
1324
1325 UNION ALL
1326
1327 -- last modification of encounter in which created, latest = initial creation of that encounter
1328 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1329 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1330 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1331 -- )
1332 --)
1333
1334 -- end of encounter in which created, latest = explicitely set
1335 -- DO NOT USE: we can retrospectively create episodes which
1336 -- DO NOT USE: are long since finished
1337 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1338 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1339 -- )
1340 --)
1341
1342 -- latest end of encounters of clinical items linked to this episode
1343 (SELECT
1344 MAX(last_affirmed) AS latest,
1345 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1346 FROM clin.encounter
1347 WHERE pk IN (
1348 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1349 )
1350 )
1351 UNION ALL
1352
1353 -- latest explicit .clin_when of clinical items linked to this episode
1354 (SELECT
1355 MAX(clin_when) AS latest,
1356 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1357 FROM clin.clin_root_item
1358 WHERE fk_episode = %(pk)s
1359 )
1360
1361 -- latest modification time of clinical items linked to this episode
1362 -- this CAN be used since if an item is linked to an episode it can be
1363 -- assumed the episode (should have) existed at the time of creation
1364 -- DO NOT USE, because typo fixes should not extend the episode
1365 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1366
1367 -- not sure about this one:
1368 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1369
1370 ) AS candidates"""
1371 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1372
1373 return rows[0][0]
1374
1375 latest_access_date = property(_get_latest_access_date)
1376
1379
1380 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1381
1383 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1384 return []
1385
1386 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
1387 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1389 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1390
1392 queries = []
1393
1394 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1395 queries.append ({
1396 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1397 'args': {
1398 'epi': self._payload[self._idx['pk_episode']],
1399 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1400 }
1401 })
1402
1403 for pk_code in pk_codes:
1404 queries.append ({
1405 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1406 'args': {
1407 'epi': self._payload[self._idx['pk_episode']],
1408 'pk_code': pk_code
1409 }
1410 })
1411 if len(queries) == 0:
1412 return
1413
1414 rows, idx = gmPG2.run_rw_queries(queries = queries)
1415 return
1416
1417 generic_codes = property(_get_generic_codes, _set_generic_codes)
1418
1420 cmd = u"""SELECT EXISTS (
1421 SELECT 1 FROM clin.clin_narrative
1422 WHERE
1423 fk_episode = %(epi)s
1424 AND
1425 fk_encounter IN (
1426 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1427 )
1428 )"""
1429 args = {
1430 u'pat': self._payload[self._idx['pk_patient']],
1431 u'epi': self._payload[self._idx['pk_episode']]
1432 }
1433 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1434 return rows[0][0]
1435
1436 has_narrative = property(_get_has_narrative, lambda x:x)
1437
1438 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
1439 """Creates a new episode for a given patient's health issue.
1440
1441 pk_health_issue - given health issue PK
1442 episode_name - name of episode
1443 """
1444 if not allow_dupes:
1445 try:
1446 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter)
1447 if episode['episode_open'] != is_open:
1448 episode['episode_open'] = is_open
1449 episode.save_payload()
1450 return episode
1451 except gmExceptions.ConstructorError:
1452 pass
1453
1454 queries = []
1455 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)"
1456 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1457 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"})
1458 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True)
1459
1460 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1461 return episode
1462
1464 if isinstance(episode, cEpisode):
1465 pk = episode['pk_episode']
1466 else:
1467 pk = int(episode)
1468
1469 cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s'
1470
1471 try:
1472 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1473 except gmPG2.dbapi.IntegrityError:
1474
1475 _log.exception('cannot delete episode, it is in use')
1476 return False
1477
1478 return True
1479
1481 return cProblem (
1482 aPK_obj = {
1483 'pk_patient': episode['pk_patient'],
1484 'pk_episode': episode['pk_episode'],
1485 'pk_health_issue': episode['pk_health_issue']
1486 },
1487 try_potential_problems = allow_closed
1488 )
1489
1490
1491
1492 -class cEncounter(gmBusinessDBObject.cBusinessDBObject):
1493 """Represents one encounter."""
1494
1495 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s"
1496 _cmds_store_payload = [
1497 u"""UPDATE clin.encounter SET
1498 started = %(started)s,
1499 last_affirmed = %(last_affirmed)s,
1500 fk_location = %(pk_location)s,
1501 fk_type = %(pk_type)s,
1502 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
1503 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
1504 WHERE
1505 pk = %(pk_encounter)s AND
1506 xmin = %(xmin_encounter)s
1507 """,
1508
1509 u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s"""
1510 ]
1511 _updatable_fields = [
1512 'started',
1513 'last_affirmed',
1514 'pk_location',
1515 'pk_type',
1516 'reason_for_encounter',
1517 'assessment_of_encounter'
1518 ]
1519
1521 """Set the encounter as the active one.
1522
1523 "Setting active" means making sure the encounter
1524 row has the youngest "last_affirmed" timestamp of
1525 all encounter rows for this patient.
1526 """
1527 self['last_affirmed'] = gmDateTime.pydt_now_here()
1528 self.save()
1529
1531 """
1532 Moves every element currently linked to the current encounter
1533 and the source_episode onto target_episode.
1534
1535 @param source_episode The episode the elements are currently linked to.
1536 @type target_episode A cEpisode intance.
1537 @param target_episode The episode the elements will be relinked to.
1538 @type target_episode A cEpisode intance.
1539 """
1540 if source_episode['pk_episode'] == target_episode['pk_episode']:
1541 return True
1542
1543 queries = []
1544 cmd = u"""
1545 UPDATE clin.clin_root_item
1546 SET fk_episode = %(trg)s
1547 WHERE
1548 fk_encounter = %(enc)s AND
1549 fk_episode = %(src)s
1550 """
1551 rows, idx = gmPG2.run_rw_queries(queries = [{
1552 'cmd': cmd,
1553 'args': {
1554 'trg': target_episode['pk_episode'],
1555 'enc': self.pk_obj,
1556 'src': source_episode['pk_episode']
1557 }
1558 }])
1559 self.refetch_payload()
1560 return True
1561
1563
1564 relevant_fields = [
1565 'pk_location',
1566 'pk_type',
1567 'pk_patient',
1568 'reason_for_encounter',
1569 'assessment_of_encounter'
1570 ]
1571 for field in relevant_fields:
1572 if self._payload[self._idx[field]] != another_object[field]:
1573 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1574 return False
1575
1576 relevant_fields = [
1577 'started',
1578 'last_affirmed',
1579 ]
1580 for field in relevant_fields:
1581 if self._payload[self._idx[field]] is None:
1582 if another_object[field] is None:
1583 continue
1584 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1585 return False
1586
1587 if another_object[field] is None:
1588 return False
1589
1590
1591 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'):
1592 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1593 return False
1594
1595
1596
1597 if another_object['pk_generic_codes_rfe'] is None:
1598 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
1599 return False
1600 if another_object['pk_generic_codes_rfe'] is not None:
1601 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
1602 return False
1603 if (
1604 (another_object['pk_generic_codes_rfe'] is None)
1605 and
1606 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
1607 ) is False:
1608 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
1609 return False
1610
1611 if another_object['pk_generic_codes_aoe'] is None:
1612 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
1613 return False
1614 if another_object['pk_generic_codes_aoe'] is not None:
1615 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
1616 return False
1617 if (
1618 (another_object['pk_generic_codes_aoe'] is None)
1619 and
1620 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
1621 ) is False:
1622 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
1623 return False
1624
1625 return True
1626
1628 cmd = u"""
1629 select exists (
1630 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
1631 union all
1632 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1633 )"""
1634 args = {
1635 'pat': self._payload[self._idx['pk_patient']],
1636 'enc': self.pk_obj
1637 }
1638 rows, idx = gmPG2.run_ro_queries (
1639 queries = [{
1640 'cmd': cmd,
1641 'args': args
1642 }]
1643 )
1644 return rows[0][0]
1645
1647 cmd = u"""
1648 select exists (
1649 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
1650 )"""
1651 args = {
1652 'pat': self._payload[self._idx['pk_patient']],
1653 'enc': self.pk_obj
1654 }
1655 rows, idx = gmPG2.run_ro_queries (
1656 queries = [{
1657 'cmd': cmd,
1658 'args': args
1659 }]
1660 )
1661 return rows[0][0]
1662
1664 """soap_cats: <space> = admin category"""
1665
1666 if soap_cats is None:
1667 soap_cats = u'soap '
1668 else:
1669 soap_cats = soap_cats.lower()
1670
1671 cats = []
1672 for cat in soap_cats:
1673 if cat in u'soapu':
1674 cats.append(cat)
1675 continue
1676 if cat == u' ':
1677 cats.append(None)
1678
1679 cmd = u"""
1680 SELECT EXISTS (
1681 SELECT 1 FROM clin.clin_narrative
1682 WHERE
1683 fk_encounter = %(enc)s
1684 AND
1685 soap_cat IN %(cats)s
1686 LIMIT 1
1687 )
1688 """
1689 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
1690 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
1691 return rows[0][0]
1692
1694 cmd = u"""
1695 select exists (
1696 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1697 )"""
1698 args = {
1699 'pat': self._payload[self._idx['pk_patient']],
1700 'enc': self.pk_obj
1701 }
1702 rows, idx = gmPG2.run_ro_queries (
1703 queries = [{
1704 'cmd': cmd,
1705 'args': args
1706 }]
1707 )
1708 return rows[0][0]
1709
1711
1712 if soap_cat is not None:
1713 soap_cat = soap_cat.lower()
1714
1715 if episode is None:
1716 epi_part = u'fk_episode is null'
1717 else:
1718 epi_part = u'fk_episode = %(epi)s'
1719
1720 cmd = u"""
1721 select narrative
1722 from clin.clin_narrative
1723 where
1724 fk_encounter = %%(enc)s
1725 and
1726 soap_cat = %%(cat)s
1727 and
1728 %s
1729 order by clin_when desc
1730 limit 1
1731 """ % epi_part
1732
1733 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
1734
1735 rows, idx = gmPG2.run_ro_queries (
1736 queries = [{
1737 'cmd': cmd,
1738 'args': args
1739 }]
1740 )
1741 if len(rows) == 0:
1742 return None
1743
1744 return rows[0][0]
1745
1747 cmd = u"""
1748 SELECT * FROM clin.v_pat_episodes
1749 WHERE
1750 pk_episode IN (
1751
1752 SELECT DISTINCT fk_episode
1753 FROM clin.clin_root_item
1754 WHERE fk_encounter = %%(enc)s
1755
1756 UNION
1757
1758 SELECT DISTINCT fk_episode
1759 FROM blobs.doc_med
1760 WHERE fk_encounter = %%(enc)s
1761 )
1762 %s"""
1763 args = {'enc': self.pk_obj}
1764 if exclude is not None:
1765 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s'
1766 args['excluded'] = tuple(exclude)
1767 else:
1768 cmd = cmd % u''
1769
1770 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1771
1772 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1773
1774 - def add_code(self, pk_code=None, field=None):
1775 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1776 if field == u'rfe':
1777 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1778 elif field == u'aoe':
1779 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1780 else:
1781 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1782 args = {
1783 'item': self._payload[self._idx['pk_encounter']],
1784 'code': pk_code
1785 }
1786 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1787 return True
1788
1790 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1791 if field == u'rfe':
1792 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1793 elif field == u'aoe':
1794 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1795 else:
1796 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1797 args = {
1798 'item': self._payload[self._idx['pk_encounter']],
1799 'code': pk_code
1800 }
1801 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1802 return True
1803
1849
1920
2138
2139
2140
2142 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2143 return []
2144
2145 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2146 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2147 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2148 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2149
2151 queries = []
2152
2153 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2154 queries.append ({
2155 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2156 'args': {
2157 'enc': self._payload[self._idx['pk_encounter']],
2158 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2159 }
2160 })
2161
2162 for pk_code in pk_codes:
2163 queries.append ({
2164 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2165 'args': {
2166 'enc': self._payload[self._idx['pk_encounter']],
2167 'pk_code': pk_code
2168 }
2169 })
2170 if len(queries) == 0:
2171 return
2172
2173 rows, idx = gmPG2.run_rw_queries(queries = queries)
2174 self.refetch_payload()
2175 return
2176
2177 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2178
2180 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2181 return []
2182
2183 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2184 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2185 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2186 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2187
2189 queries = []
2190
2191 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2192 queries.append ({
2193 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2194 'args': {
2195 'enc': self._payload[self._idx['pk_encounter']],
2196 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2197 }
2198 })
2199
2200 for pk_code in pk_codes:
2201 queries.append ({
2202 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2203 'args': {
2204 'enc': self._payload[self._idx['pk_encounter']],
2205 'pk_code': pk_code
2206 }
2207 })
2208 if len(queries) == 0:
2209 return
2210
2211 rows, idx = gmPG2.run_rw_queries(queries = queries)
2212 self.refetch_payload()
2213 return
2214
2215 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2216
2218 """Creates a new encounter for a patient.
2219
2220 fk_patient - patient PK
2221 fk_location - encounter location
2222 enc_type - type of encounter
2223
2224 FIXME: we don't deal with location yet
2225 """
2226 if enc_type is None:
2227 enc_type = u'in surgery'
2228
2229 queries = []
2230 try:
2231 enc_type = int(enc_type)
2232 cmd = u"""
2233 INSERT INTO clin.encounter (
2234 fk_patient, fk_location, fk_type
2235 ) VALUES (
2236 %(pat)s,
2237 -1,
2238 %(typ)s
2239 ) RETURNING pk"""
2240 except ValueError:
2241 enc_type = enc_type
2242 cmd = u"""
2243 insert into clin.encounter (
2244 fk_patient, fk_location, fk_type
2245 ) values (
2246 %(pat)s,
2247 -1,
2248 coalesce (
2249 (select pk from clin.encounter_type where description = %(typ)s),
2250 -- pick the first available
2251 (select pk from clin.encounter_type limit 1)
2252 )
2253 ) RETURNING pk"""
2254 args = {'pat': fk_patient, 'typ': enc_type}
2255 queries.append({'cmd': cmd, 'args': args})
2256 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
2257 encounter = cEncounter(aPK_obj = rows[0]['pk'])
2258
2259 return encounter
2260
2262
2263 rows, idx = gmPG2.run_rw_queries(
2264 queries = [{
2265 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
2266 'args': {'desc': description, 'l10n_desc': l10n_description}
2267 }],
2268 return_data = True
2269 )
2270
2271 success = rows[0][0]
2272 if not success:
2273 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
2274
2275 return {'description': description, 'l10n_description': l10n_description}
2276
2278 """This will attempt to create a NEW encounter type."""
2279
2280
2281 if description is None:
2282 description = l10n_description
2283
2284 args = {
2285 'desc': description,
2286 'l10n_desc': l10n_description
2287 }
2288
2289 _log.debug('creating encounter type: %s, %s', description, l10n_description)
2290
2291
2292 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s"
2293 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2294
2295
2296 if len(rows) > 0:
2297
2298 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
2299 _log.info('encounter type [%s] already exists with the proper translation')
2300 return {'description': description, 'l10n_description': l10n_description}
2301
2302
2303
2304 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
2305 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2306
2307
2308 if rows[0][0]:
2309 _log.error('encounter type [%s] already exists but with another translation')
2310 return None
2311
2312
2313 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
2314 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2315 return {'description': description, 'l10n_description': l10n_description}
2316
2317
2318 queries = [
2319 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
2320 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
2321 ]
2322 rows, idx = gmPG2.run_rw_queries(queries = queries)
2323
2324 return {'description': description, 'l10n_description': l10n_description}
2325
2327 cmd = u"""
2328 SELECT
2329 _(description) AS l10n_description,
2330 description
2331 FROM
2332 clin.encounter_type
2333 ORDER BY
2334 l10n_description
2335 """
2336 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
2337 return rows
2338
2343
2345 cmd = u"delete from clin.encounter_type where description = %(desc)s"
2346 args = {'desc': description}
2347 try:
2348 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2349 except gmPG2.dbapi.IntegrityError, e:
2350 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
2351 return False
2352 raise
2353
2354 return True
2355
2356 -class cProblem(gmBusinessDBObject.cBusinessDBObject):
2357 """Represents one problem.
2358
2359 problems are the aggregation of
2360 .clinically_relevant=True issues and
2361 .is_open=True episodes
2362 """
2363 _cmd_fetch_payload = u''
2364 _cmds_store_payload = [u"select 1"]
2365 _updatable_fields = []
2366
2367
2368 - def __init__(self, aPK_obj=None, try_potential_problems=False):
2369 """Initialize.
2370
2371 aPK_obj must contain the keys
2372 pk_patient
2373 pk_episode
2374 pk_health_issue
2375 """
2376 if aPK_obj is None:
2377 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj)
2378
2379
2380
2381
2382
2383 where_parts = []
2384 pk = {}
2385 for col_name in aPK_obj.keys():
2386 val = aPK_obj[col_name]
2387 if val is None:
2388 where_parts.append('%s IS NULL' % col_name)
2389 else:
2390 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
2391 pk[col_name] = val
2392
2393
2394 cProblem._cmd_fetch_payload = u"""
2395 SELECT *, False as is_potential_problem
2396 FROM clin.v_problem_list
2397 WHERE %s""" % u' AND '.join(where_parts)
2398
2399 try:
2400 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2401 return
2402 except gmExceptions.ConstructorError:
2403 _log.exception('actual problem not found, trying "potential" problems')
2404 if try_potential_problems is False:
2405 raise
2406
2407
2408 cProblem._cmd_fetch_payload = u"""
2409 SELECT *, True as is_potential_problem
2410 FROM clin.v_potential_problem_list
2411 WHERE %s""" % u' AND '.join(where_parts)
2412 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2413
2415 """
2416 Retrieve the cEpisode instance equivalent to this problem.
2417 The problem's type attribute must be 'episode'
2418 """
2419 if self._payload[self._idx['type']] != 'episode':
2420 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2421 return None
2422 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
2423
2425 """
2426 Retrieve the cHealthIssue instance equivalent to this problem.
2427 The problem's type attribute must be 'issue'
2428 """
2429 if self._payload[self._idx['type']] != 'issue':
2430 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2431 return None
2432 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
2433
2447
2448
2449
2450
2453
2454 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
2455
2457 if self._payload[self._idx['type']] == u'issue':
2458 cmd = u"""
2459 SELECT * FROM clin.v_linked_codes WHERE
2460 item_table = 'clin.lnk_code2h_issue'::regclass
2461 AND
2462 pk_item = %(item)s
2463 """
2464 args = {'item': self._payload[self._idx['pk_health_issue']]}
2465 else:
2466 cmd = u"""
2467 SELECT * FROM clin.v_linked_codes WHERE
2468 item_table = 'clin.lnk_code2episode'::regclass
2469 AND
2470 pk_item = %(item)s
2471 """
2472 args = {'item': self._payload[self._idx['pk_episode']]}
2473
2474 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2475 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2476
2477 generic_codes = property(_get_generic_codes, lambda x:x)
2478
2480 """Retrieve the cEpisode instance equivalent to the given problem.
2481
2482 The problem's type attribute must be 'episode'
2483
2484 @param problem: The problem to retrieve its related episode for
2485 @type problem: A gmEMRStructItems.cProblem instance
2486 """
2487 if isinstance(problem, cEpisode):
2488 return problem
2489
2490 exc = TypeError('cannot convert [%s] to episode' % problem)
2491
2492 if not isinstance(problem, cProblem):
2493 raise exc
2494
2495 if problem['type'] != 'episode':
2496 raise exc
2497
2498 return cEpisode(aPK_obj = problem['pk_episode'])
2499
2501 """Retrieve the cIssue instance equivalent to the given problem.
2502
2503 The problem's type attribute must be 'issue'.
2504
2505 @param problem: The problem to retrieve the corresponding issue for
2506 @type problem: A gmEMRStructItems.cProblem instance
2507 """
2508 if isinstance(problem, cHealthIssue):
2509 return problem
2510
2511 exc = TypeError('cannot convert [%s] to health issue' % problem)
2512
2513 if not isinstance(problem, cProblem):
2514 raise exc
2515
2516 if problem['type'] != 'issue':
2517 raise exc
2518
2519 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2520
2522 """Transform given problem into either episode or health issue instance.
2523 """
2524 if isinstance(problem, (cEpisode, cHealthIssue)):
2525 return problem
2526
2527 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
2528
2529 if not isinstance(problem, cProblem):
2530 _log.debug(u'%s' % problem)
2531 raise exc
2532
2533 if problem['type'] == 'episode':
2534 return cEpisode(aPK_obj = problem['pk_episode'])
2535
2536 if problem['type'] == 'issue':
2537 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2538
2539 raise exc
2540
2542
2543 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s"
2544 _cmds_store_payload = [
2545 u"""update clin.hospital_stay set
2546 clin_when = %(admission)s,
2547 discharge = %(discharge)s,
2548 narrative = gm.nullify_empty_string(%(hospital)s),
2549 fk_episode = %(pk_episode)s,
2550 fk_encounter = %(pk_encounter)s
2551 where
2552 pk = %(pk_hospital_stay)s and
2553 xmin = %(xmin_hospital_stay)s""",
2554 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s"""
2555 ]
2556 _updatable_fields = [
2557 'admission',
2558 'discharge',
2559 'hospital',
2560 'pk_episode',
2561 'pk_encounter'
2562 ]
2563
2582
2584 queries = [{
2585
2586 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1',
2587 'args': {'pat': patient}
2588 }]
2589 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2590 if len(rows) == 0:
2591 return None
2592 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
2593
2595 args = {'pat': patient}
2596 if ongoing_only:
2597 cmd = u"""
2598 SELECT *
2599 FROM clin.v_pat_hospital_stays
2600 WHERE
2601 pk_patient = %(pat)s
2602 AND
2603 discharge is NULL
2604 ORDER BY admission"""
2605 else:
2606 cmd = u"""
2607 SELECT *
2608 FROM clin.v_pat_hospital_stays
2609 WHERE pk_patient = %(pat)s
2610 ORDER BY admission"""
2611
2612 queries = [{'cmd': cmd, 'args': args}]
2613 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2614
2615 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
2616
2618
2619 queries = [{
2620 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk',
2621 'args': {'enc': encounter, 'epi': episode}
2622 }]
2623 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
2624
2625 return cHospitalStay(aPK_obj = rows[0][0])
2626
2628 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
2629 args = {'pk': stay}
2630 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2631 return True
2632
2774
2787
2799
2826
2832
2833
2834
2835 if __name__ == '__main__':
2836
2837 if len(sys.argv) < 2:
2838 sys.exit()
2839
2840 if sys.argv[1] != 'test':
2841 sys.exit()
2842
2843
2844
2845
2847 print "\nProblem test"
2848 print "------------"
2849 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
2850 print prob
2851 fields = prob.get_fields()
2852 for field in fields:
2853 print field, ':', prob[field]
2854 print '\nupdatable:', prob.get_updatable_fields()
2855 epi = prob.get_as_episode()
2856 print '\nas episode:'
2857 if epi is not None:
2858 for field in epi.get_fields():
2859 print ' .%s : %s' % (field, epi[field])
2860
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2880 print "\nepisode test"
2881 print "------------"
2882 episode = cEpisode(aPK_obj=1)
2883 print episode
2884 fields = episode.get_fields()
2885 for field in fields:
2886 print field, ':', episode[field]
2887 print "updatable:", episode.get_updatable_fields()
2888 raw_input('ENTER to continue')
2889
2890 old_description = episode['description']
2891 old_enc = cEncounter(aPK_obj = 1)
2892
2893 desc = '1-%s' % episode['description']
2894 print "==> renaming to", desc
2895 successful = episode.rename (
2896 description = desc
2897 )
2898 if not successful:
2899 print "error"
2900 else:
2901 print "success"
2902 for field in fields:
2903 print field, ':', episode[field]
2904
2905 print "episode range:", episode.get_access_range()
2906
2907 raw_input('ENTER to continue')
2908
2909
2911 print "\nencounter test"
2912 print "--------------"
2913 encounter = cEncounter(aPK_obj=1)
2914 print encounter
2915 fields = encounter.get_fields()
2916 for field in fields:
2917 print field, ':', encounter[field]
2918 print "updatable:", encounter.get_updatable_fields()
2919
2925
2930
2940
2947
2952
2953
2954
2955
2956
2957 test_health_issue()
2958
2959
2960
2961
2962
2963
2964