1 """GNUmed measurements related business objects."""
2
3
4
5 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL"
7
8
9 import sys
10 import logging
11 import io
12 import decimal
13 import re as regex
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18
19 from Gnumed.pycommon import gmDateTime
20 if __name__ == '__main__':
21 from Gnumed.pycommon import gmLog2
22 from Gnumed.pycommon import gmI18N
23 gmI18N.activate_locale()
24 gmI18N.install_domain('gnumed')
25 gmDateTime.init()
26 from Gnumed.pycommon import gmExceptions
27 from Gnumed.pycommon import gmBusinessDBObject
28 from Gnumed.pycommon import gmPG2
29 from Gnumed.pycommon import gmTools
30 from Gnumed.pycommon import gmDispatcher
31 from Gnumed.pycommon import gmHooks
32
33 from Gnumed.business import gmOrganization
34 from Gnumed.business import gmCoding
35
36 _log = logging.getLogger('gm.lab')
37
38
39 HL7_RESULT_STATI = {
40 None: _('unknown'),
41 '': _('empty status'),
42 'C': _('C (HL7: Correction, replaces previous final)'),
43 'D': _('D (HL7: Deletion)'),
44 'F': _('F (HL7: Final)'),
45 'I': _('I (HL7: pending, specimen In lab)'),
46 'P': _('P (HL7: Preliminary)'),
47 'R': _('R (HL7: result entered, not yet verified)'),
48 'S': _('S (HL7: partial)'),
49 'X': _('X (HL7: cannot obtain results for this observation)'),
50 'U': _('U (HL7: mark as final (I/P/R/S -> F, value Unchanged)'),
51 'W': _('W (HL7: original Wrong (say, wrong patient))')
52 }
53
54 URL_test_result_information = 'http://www.laborlexikon.de'
55 URL_test_result_information_search = "http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de"
56
57
61
62 gmDispatcher.connect(_on_test_result_modified, 'clin.test_result_mod_db')
63
64
65 _SQL_get_test_orgs = "SELECT * FROM clin.v_test_orgs WHERE %s"
66
67 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
68 """Represents one test org/lab."""
69 _cmd_fetch_payload = _SQL_get_test_orgs % 'pk_test_org = %s'
70 _cmds_store_payload = [
71 """UPDATE clin.test_org SET
72 fk_org_unit = %(pk_org_unit)s,
73 contact = gm.nullify_empty_string(%(test_org_contact)s),
74 comment = gm.nullify_empty_string(%(comment)s)
75 WHERE
76 pk = %(pk_test_org)s
77 AND
78 xmin = %(xmin_test_org)s
79 RETURNING
80 xmin AS xmin_test_org
81 """
82 ]
83 _updatable_fields = [
84 'pk_org_unit',
85 'test_org_contact',
86 'comment'
87 ]
88
89 -def create_test_org(name=None, comment=None, pk_org_unit=None, link_obj=None):
90
91 _log.debug('creating test org [%s:%s:%s]', name, comment, pk_org_unit)
92
93 if name is None:
94 name = 'unassigned lab'
95
96
97 if pk_org_unit is None:
98 org = gmOrganization.create_org (
99 link_obj = link_obj,
100 organization = name,
101 category = 'Laboratory'
102 )
103 org_unit = gmOrganization.create_org_unit (
104 link_obj = link_obj,
105 pk_organization = org['pk_org'],
106 unit = name
107 )
108 pk_org_unit = org_unit['pk_org_unit']
109
110
111 args = {'pk_unit': pk_org_unit}
112 cmd = 'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
113 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
114
115 if len(rows) == 0:
116 cmd = 'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
117 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
118
119 test_org = cTestOrg(link_obj = link_obj, aPK_obj = rows[0][0])
120 if comment is not None:
121 comment = comment.strip()
122 test_org['comment'] = comment
123 test_org.save(conn = link_obj)
124
125 return test_org
126
128 args = {'pk': test_org}
129 cmd = """
130 DELETE FROM clin.test_org
131 WHERE
132 pk = %(pk)s
133 AND
134 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
135 AND
136 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
137 """
138 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
139
141 cmd = 'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
142 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
143 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
144
145
146
147
148 _SQL_get_test_panels = "SELECT * FROM clin.v_test_panels WHERE %s"
149
150 -class cTestPanel(gmBusinessDBObject.cBusinessDBObject):
151 """Represents a grouping/listing of tests into a panel."""
152
153 _cmd_fetch_payload = _SQL_get_test_panels % "pk_test_panel = %s"
154 _cmds_store_payload = [
155 """
156 UPDATE clin.test_panel SET
157 description = gm.nullify_empty_string(%(description)s),
158 comment = gm.nullify_empty_string(%(comment)s)
159 WHERE
160 pk = %(pk_test_panel)s
161 AND
162 xmin = %(xmin_test_panel)s
163 RETURNING
164 xmin AS xmin_test_panel
165 """
166 ]
167 _updatable_fields = [
168 'description',
169 'comment'
170 ]
171
218
219
221 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
222 cmd = "INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)"
223 args = {
224 'tp': self._payload[self._idx['pk_test_panel']],
225 'code': pk_code
226 }
227 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
228 return True
229
230
232 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
233 cmd = "DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s"
234 args = {
235 'tp': self._payload[self._idx['pk_test_panel']],
236 'code': pk_code
237 }
238 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
239 return True
240
241
243 """Retrieve data about test types on this panel (for which this patient has results)."""
244
245 if order_by is None:
246 order_by = ''
247 else:
248 order_by = 'ORDER BY %s' % order_by
249
250 if unique_meta_types:
251 cmd = """
252 SELECT * FROM clin.v_test_types c_vtt
253 WHERE c_vtt.pk_test_type IN (
254 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
255 FROM clin.v_test_results c_vtr1
256 WHERE
257 c_vtr1.pk_test_type IN %%(pks)s
258 AND
259 c_vtr1.pk_patient = %%(pat)s
260 AND
261 c_vtr1.pk_meta_test_type IS NOT NULL
262 UNION ALL
263 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
264 FROM clin.v_test_results c_vtr2
265 WHERE
266 c_vtr2.pk_test_type IN %%(pks)s
267 AND
268 c_vtr2.pk_patient = %%(pat)s
269 AND
270 c_vtr2.pk_meta_test_type IS NULL
271 )
272 %s""" % order_by
273 else:
274 cmd = """
275 SELECT * FROM clin.v_test_types c_vtt
276 WHERE c_vtt.pk_test_type IN (
277 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
278 FROM clin.v_test_results c_vtr
279 WHERE
280 c_vtr.pk_test_type IN %%(pks)s
281 AND
282 c_vtr.pk_patient = %%(pat)s
283 )
284 %s""" % order_by
285
286 args = {
287 'pat': pk_patient,
288 'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ])
289 }
290 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
291 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
292
293
295 return self._payload[self._idx['loincs']]
296
298 queries = []
299
300 if len(loincs) == 0:
301 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s'
302 else:
303 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s AND loinc NOT IN %(loincs)s'
304 queries.append({'cmd': cmd, 'args': {'loincs': tuple(loincs), 'pk_pnl': self._payload[self._idx['pk_test_panel']]}})
305
306 if len(loincs) > 0:
307 for loinc in loincs:
308 cmd = """INSERT INTO clin.lnk_loinc2test_panel (fk_test_panel, loinc)
309 SELECT %(pk_pnl)s, %(loinc)s WHERE NOT EXISTS (
310 SELECT 1 FROM clin.lnk_loinc2test_panel WHERE
311 fk_test_panel = %(pk_pnl)s
312 AND
313 loinc = %(loinc)s
314 )"""
315 queries.append({'cmd': cmd, 'args': {'loinc': loinc, 'pk_pnl': self._payload[self._idx['pk_test_panel']]}})
316 return gmPG2.run_rw_queries(queries = queries)
317
318 included_loincs = property(_get_included_loincs, _set_included_loincs)
319
320
321
322
324 if len(self._payload[self._idx['test_types']]) == 0:
325 return []
326
327 rows, idx = gmPG2.run_ro_queries (
328 queries = [{
329 'cmd': _SQL_get_test_types % 'pk_test_type IN %(pks)s ORDER BY unified_abbrev',
330 'args': {'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ])}
331 }],
332 get_col_idx = True
333 )
334 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
335
336 test_types = property(_get_test_types, lambda x:x)
337
338
340 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
341 return []
342
343 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
344 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
345 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
346 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
347
349 queries = []
350
351 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
352 queries.append ({
353 'cmd': 'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s',
354 'args': {
355 'tp': self._payload[self._idx['pk_test_panel']],
356 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
357 }
358 })
359
360 for pk_code in pk_codes:
361 queries.append ({
362 'cmd': 'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)',
363 'args': {
364 'tp': self._payload[self._idx['pk_test_panel']],
365 'pk_code': pk_code
366 }
367 })
368 if len(queries) == 0:
369 return
370
371 rows, idx = gmPG2.run_rw_queries(queries = queries)
372 return
373
374 generic_codes = property(_get_generic_codes, _set_generic_codes)
375
376
378 return get_most_recent_results_for_panel (
379 pk_patient = pk_patient,
380 pk_panel = self._payload[self._idx['pk_test_panel']],
381 order_by = order_by,
382 group_by_meta_type = group_by_meta_type
383 )
384
385
387 if order_by is None:
388 order_by = 'true'
389 else:
390 order_by = 'true ORDER BY %s' % order_by
391
392 cmd = _SQL_get_test_panels % order_by
393 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
394 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
395
396
398
399 args = {'desc': description.strip()}
400 cmd = """
401 INSERT INTO clin.test_panel (description)
402 VALUES (gm.nullify_empty_string(%(desc)s))
403 RETURNING pk
404 """
405 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
406
407 return cTestPanel(aPK_obj = rows[0]['pk'])
408
409
411 args = {'pk': pk}
412 cmd = "DELETE FROM clin.test_panel WHERE pk = %(pk)s"
413 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
414 return True
415
416
582
583
611
612
625
626
631
632
633 _SQL_get_test_types = "SELECT * FROM clin.v_test_types WHERE %s"
634
636 """Represents one test result type."""
637
638 _cmd_fetch_payload = _SQL_get_test_types % "pk_test_type = %s"
639
640 _cmds_store_payload = [
641 """UPDATE clin.test_type SET
642 abbrev = gm.nullify_empty_string(%(abbrev)s),
643 name = gm.nullify_empty_string(%(name)s),
644 loinc = gm.nullify_empty_string(%(loinc)s),
645 comment = gm.nullify_empty_string(%(comment_type)s),
646 reference_unit = gm.nullify_empty_string(%(reference_unit)s),
647 fk_test_org = %(pk_test_org)s,
648 fk_meta_test_type = %(pk_meta_test_type)s
649 WHERE
650 pk = %(pk_test_type)s
651 AND
652 xmin = %(xmin_test_type)s
653 RETURNING
654 xmin AS xmin_test_type"""
655 ]
656
657 _updatable_fields = [
658 'abbrev',
659 'name',
660 'loinc',
661 'comment_type',
662 'reference_unit',
663 'pk_test_org',
664 'pk_meta_test_type'
665 ]
666
667
668
670 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
671 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
673 return rows[0][0]
674
675 in_use = property(_get_in_use, lambda x:x)
676
677
694
695
710
711
713 if self._payload[self._idx['pk_test_panels']] is None:
714 return None
715
716 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
717
718 test_panels = property(_get_test_panels, lambda x:x)
719
720
727
728 meta_test_type = property(get_meta_test_type, lambda x:x)
729
730
732 """Returns the closest test result which does have normal range information.
733
734 - needs <unit>
735 - if <timestamp> is None it will assume now() and thus return the most recent
736 """
737 if timestamp is None:
738 timestamp = gmDateTime.pydt_now_here()
739 cmd = """
740 SELECT * FROM clin.v_test_results
741 WHERE
742 pk_test_type = %(pk_type)s
743 AND
744 val_unit = %(unit)s
745 AND
746 (
747 (val_normal_min IS NOT NULL)
748 OR
749 (val_normal_max IS NOT NULL)
750 OR
751 (val_normal_range IS NOT NULL)
752 )
753 ORDER BY
754 CASE
755 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
756 ELSE %(clin_when)s - clin_when
757 END
758 LIMIT 1"""
759 args = {
760 'pk_type': self._payload[self._idx['pk_test_type']],
761 'unit': unit,
762 'clin_when': timestamp
763 }
764 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
765 if len(rows) == 0:
766 return None
767 r = rows[0]
768 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
769
770
772 """Returns the closest test result which does have target range information.
773
774 - needs <unit>
775 - needs <patient> (as target will be per-patient)
776 - if <timestamp> is None it will assume now() and thus return the most recent
777 """
778 if timestamp is None:
779 timestamp = gmDateTime.pydt_now_here()
780 cmd = """
781 SELECT * FROM clin.v_test_results
782 WHERE
783 pk_test_type = %(pk_type)s
784 AND
785 val_unit = %(unit)s
786 AND
787 pk_patient = %(pat)s
788 AND
789 (
790 (val_target_min IS NOT NULL)
791 OR
792 (val_target_max IS NOT NULL)
793 OR
794 (val_target_range IS NOT NULL)
795 )
796 ORDER BY
797 CASE
798 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
799 ELSE %(clin_when)s - clin_when
800 END
801 LIMIT 1"""
802 args = {
803 'pk_type': self._payload[self._idx['pk_test_type']],
804 'unit': unit,
805 'pat': patient,
806 'clin_when': timestamp
807 }
808 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
809 if len(rows) == 0:
810 return None
811 r = rows[0]
812 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
813
814
816 """Returns the unit of the closest test result.
817
818 - if <timestamp> is None it will assume now() and thus return the most recent
819 """
820 if timestamp is None:
821 timestamp = gmDateTime.pydt_now_here()
822 cmd = """
823 SELECT val_unit FROM clin.v_test_results
824 WHERE
825 pk_test_type = %(pk_type)s
826 AND
827 val_unit IS NOT NULL
828 ORDER BY
829 CASE
830 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
831 ELSE %(clin_when)s - clin_when
832 END
833 LIMIT 1"""
834 args = {
835 'pk_type': self._payload[self._idx['pk_test_type']],
836 'clin_when': timestamp
837 }
838 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
839 if len(rows) == 0:
840 return None
841 return rows[0]['val_unit']
842
843 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x)
844
845
906
907
909 args = {}
910 where_parts = []
911 if loincs is not None:
912 if len(loincs) > 0:
913 where_parts.append('loinc IN %(loincs)s')
914 args['loincs'] = tuple(loincs)
915 if len(where_parts) == 0:
916 where_parts.append('TRUE')
917 WHERE_clause = ' AND '.join(where_parts)
918 cmd = (_SQL_get_test_types % WHERE_clause) + gmTools.coalesce(order_by, '', ' ORDER BY %s')
919
920 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
921 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
922
923
925
926 if (abbrev is None) and (name is None):
927 raise ValueError('must have <abbrev> and/or <name> set')
928
929 where_snippets = []
930
931 if lab is None:
932 where_snippets.append('pk_test_org IS NULL')
933 else:
934 try:
935 int(lab)
936 where_snippets.append('pk_test_org = %(lab)s')
937 except (TypeError, ValueError):
938 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
939
940 if abbrev is not None:
941 where_snippets.append('abbrev = %(abbrev)s')
942
943 if name is not None:
944 where_snippets.append('name = %(name)s')
945
946 where_clause = ' and '.join(where_snippets)
947 cmd = "select * from clin.v_test_types where %s" % where_clause
948 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
949
950 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
951
952 if len(rows) == 0:
953 return None
954
955 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
956 return tt
957
958
960 cmd = 'delete from clin.test_type where pk = %(pk)s'
961 args = {'pk': measurement_type}
962 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
963
964
966 """Create or get test type."""
967
968 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name, link_obj = link_obj)
969
970 if ttype is not None:
971 return ttype
972
973 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
974
975
976
977
978
979
980
981 cols = []
982 val_snippets = []
983 vals = {}
984
985
986 if lab is None:
987 lab = create_test_org(link_obj = link_obj)['pk_test_org']
988
989 cols.append('fk_test_org')
990 try:
991 vals['lab'] = int(lab)
992 val_snippets.append('%(lab)s')
993 except:
994 vals['lab'] = lab
995 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
996
997
998 cols.append('abbrev')
999 val_snippets.append('%(abbrev)s')
1000 vals['abbrev'] = abbrev
1001
1002
1003 if unit is not None:
1004 cols.append('reference_unit')
1005 val_snippets.append('%(unit)s')
1006 vals['unit'] = unit
1007
1008
1009 if name is not None:
1010 cols.append('name')
1011 val_snippets.append('%(name)s')
1012 vals['name'] = name
1013
1014 col_clause = ', '.join(cols)
1015 val_clause = ', '.join(val_snippets)
1016 queries = [
1017 {'cmd': 'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
1018 {'cmd': "select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
1019 ]
1020 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, get_col_idx = True, return_data = True)
1021 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
1022
1023 return ttype
1024
1025
1026 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
1027 """Represents one test result."""
1028
1029 _cmd_fetch_payload = "select * from clin.v_test_results where pk_test_result = %s"
1030
1031 _cmds_store_payload = [
1032 """UPDATE clin.test_result SET
1033 clin_when = %(clin_when)s,
1034 narrative = nullif(trim(%(comment)s), ''),
1035 val_num = %(val_num)s,
1036 val_alpha = nullif(trim(%(val_alpha)s), ''),
1037 val_unit = nullif(trim(%(val_unit)s), ''),
1038 val_normal_min = %(val_normal_min)s,
1039 val_normal_max = %(val_normal_max)s,
1040 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
1041 val_target_min = %(val_target_min)s,
1042 val_target_max = %(val_target_max)s,
1043 val_target_range = nullif(trim(%(val_target_range)s), ''),
1044 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
1045 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
1046 note_test_org = nullif(trim(%(note_test_org)s), ''),
1047 material = nullif(trim(%(material)s), ''),
1048 material_detail = nullif(trim(%(material_detail)s), ''),
1049 status = gm.nullify_empty_string(%(status)s),
1050 val_grouping = gm.nullify_empty_string(%(val_grouping)s),
1051 source_data = gm.nullify_empty_string(%(source_data)s),
1052 fk_intended_reviewer = %(pk_intended_reviewer)s,
1053 fk_encounter = %(pk_encounter)s,
1054 fk_episode = %(pk_episode)s,
1055 fk_type = %(pk_test_type)s,
1056 fk_request = %(pk_request)s
1057 WHERE
1058 pk = %(pk_test_result)s AND
1059 xmin = %(xmin_test_result)s
1060 RETURNING
1061 xmin AS xmin_test_result
1062 """
1063
1064 ]
1065
1066 _updatable_fields = [
1067 'clin_when',
1068 'comment',
1069 'val_num',
1070 'val_alpha',
1071 'val_unit',
1072 'val_normal_min',
1073 'val_normal_max',
1074 'val_normal_range',
1075 'val_target_min',
1076 'val_target_max',
1077 'val_target_range',
1078 'abnormality_indicator',
1079 'norm_ref_group',
1080 'note_test_org',
1081 'material',
1082 'material_detail',
1083 'status',
1084 'val_grouping',
1085 'source_data',
1086 'pk_intended_reviewer',
1087 'pk_encounter',
1088 'pk_episode',
1089 'pk_test_type',
1090 'pk_request'
1091 ]
1092
1093
1126
1127
1375
1376
1378 return (
1379 self._payload[self._idx['val_normal_min']] is not None
1380 ) or (
1381 self._payload[self._idx['val_normal_max']] is not None
1382 )
1383
1384 has_normal_min_or_max = property(_get_has_normal_min_or_max, lambda x:x)
1385
1386
1388 has_range_info = (
1389 self._payload[self._idx['val_normal_min']] is not None
1390 ) or (
1391 self._payload[self._idx['val_normal_max']] is not None
1392 )
1393 if has_range_info is False:
1394 return None
1395
1396 return '%s - %s' % (
1397 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'),
1398 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?')
1399 )
1400
1401 normal_min_max = property(_get_normal_min_max, lambda x:x)
1402
1403
1430
1431 formatted_normal_range = property(_get_formatted_normal_range, lambda x:x)
1432
1433
1435 return (
1436 self._payload[self._idx['val_target_min']] is not None
1437 ) or (
1438 self._payload[self._idx['val_target_max']] is not None
1439 )
1440
1441 has_clinical_min_or_max = property(_get_has_clinical_min_or_max, lambda x:x)
1442
1443
1445 has_range_info = (
1446 self._payload[self._idx['val_target_min']] is not None
1447 ) or (
1448 self._payload[self._idx['val_target_max']] is not None
1449 )
1450 if has_range_info is False:
1451 return None
1452
1453 return '%s - %s' % (
1454 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'),
1455 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?')
1456 )
1457
1458 clinical_min_max = property(_get_clinical_min_max, lambda x:x)
1459
1460
1487
1488 formatted_clinical_range = property(_get_formatted_clinical_range, lambda x:x)
1489
1490
1492 """Returns the closest test result which does have normal range information."""
1493 if self._payload[self._idx['val_normal_min']] is not None:
1494 return self
1495 if self._payload[self._idx['val_normal_max']] is not None:
1496 return self
1497 if self._payload[self._idx['val_normal_range']] is not None:
1498 return self
1499 cmd = """
1500 SELECT * from clin.v_test_results
1501 WHERE
1502 pk_type = %(pk_type)s
1503 AND
1504 val_unit = %(unit)s
1505 AND
1506 (
1507 (val_normal_min IS NOT NULL)
1508 OR
1509 (val_normal_max IS NOT NULL)
1510 OR
1511 (val_normal_range IS NOT NULL)
1512 )
1513 ORDER BY
1514 CASE
1515 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
1516 ELSE %(clin_when)s - clin_when
1517 END
1518 LIMIT 1"""
1519 args = {
1520 'pk_type': self._payload[self._idx['pk_test_type']],
1521 'unit': self._payload[self._idx['val_unit']],
1522 'clin_when': self._payload[self._idx['clin_when']]
1523 }
1524 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1525 if len(rows) == 0:
1526 return None
1527 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1528
1529 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x)
1530
1531
1591
1592 formatted_range = property(_get_formatted_range, lambda x:x)
1593
1594
1596 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1597
1598 test_type = property(_get_test_type, lambda x:x)
1599
1600
1602
1603 if self._payload[self._idx['is_technically_abnormal']] is False:
1604 return False
1605
1606 indicator = self._payload[self._idx['abnormality_indicator']]
1607 if indicator is not None:
1608 indicator = indicator.strip()
1609 if indicator != '':
1610 if indicator.strip('+') == '':
1611 return True
1612 if indicator.strip('-') == '':
1613 return False
1614
1615 if self._payload[self._idx['val_num']] is None:
1616 return None
1617
1618 target_max = self._payload[self._idx['val_target_max']]
1619 if target_max is not None:
1620 if target_max < self._payload[self._idx['val_num']]:
1621 return True
1622
1623 normal_max = self._payload[self._idx['val_normal_max']]
1624 if normal_max is not None:
1625 if normal_max < self._payload[self._idx['val_num']]:
1626 return True
1627 return None
1628
1629 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x)
1630
1631
1633
1634 if self._payload[self._idx['is_technically_abnormal']] is False:
1635 return False
1636
1637 indicator = self._payload[self._idx['abnormality_indicator']]
1638 if indicator is not None:
1639 indicator = indicator.strip()
1640 if indicator != '':
1641 if indicator.strip('+') == '':
1642 return False
1643 if indicator.strip('-') == '':
1644 return True
1645
1646 if self._payload[self._idx['val_num']] is None:
1647 return None
1648
1649 target_min = self._payload[self._idx['val_target_min']]
1650 if target_min is not None:
1651 if target_min > self._payload[self._idx['val_num']]:
1652 return True
1653
1654 normal_min = self._payload[self._idx['val_normal_min']]
1655 if normal_min is not None:
1656 if normal_min > self._payload[self._idx['val_num']]:
1657 return True
1658 return None
1659
1660 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x)
1661
1662
1671
1672 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x)
1673
1674
1676 """Parse reference range from string.
1677
1678 Note: does NOT save the result.
1679 """
1680 ref_range = ref_range.strip().replace(' ', '')
1681
1682 is_range = regex.match('-{0,1}\d+[.,]{0,1}\d*--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1683 if is_range is not None:
1684 min_val = regex.match('-{0,1}\d+[.,]{0,1}\d*-', ref_range, regex.UNICODE).group(0).rstrip('-')
1685 success, min_val = gmTools.input2decimal(min_val)
1686 max_val = (regex.search('--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE).group(0))[1:]
1687 success, max_val = gmTools.input2decimal(max_val)
1688 self['val_normal_min'] = min_val
1689 self['val_normal_max'] = max_val
1690 return
1691
1692 if ref_range.startswith('<'):
1693 is_range = regex.match('<\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1694 if is_range is not None:
1695 max_val = ref_range[1:]
1696 success, max_val = gmTools.input2decimal(max_val)
1697 self['val_normal_min'] = 0
1698 self['val_normal_max'] = max_val
1699 return
1700
1701 if ref_range.startswith('<-'):
1702 is_range = regex.match('<-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1703 if is_range is not None:
1704 max_val = ref_range[1:]
1705 success, max_val = gmTools.input2decimal(max_val)
1706 self['val_normal_min'] = None
1707 self['val_normal_max'] = max_val
1708 return
1709
1710 if ref_range.startswith('>'):
1711 is_range = regex.match('>\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1712 if is_range is not None:
1713 min_val = ref_range[1:]
1714 success, min_val = gmTools.input2decimal(min_val)
1715 self['val_normal_min'] = min_val
1716 self['val_normal_max'] = None
1717 return
1718
1719 if ref_range.startswith('>-'):
1720 is_range = regex.match('>-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1721 if is_range is not None:
1722 min_val = ref_range[1:]
1723 success, min_val = gmTools.input2decimal(min_val)
1724 self['val_normal_min'] = min_val
1725 self['val_normal_max'] = 0
1726 return
1727
1728 self['val_normal_range'] = ref_range
1729 return
1730
1731 reference_range = property(lambda x:x, _set_reference_range)
1732
1733
1770
1771 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x)
1772
1773
1775 if self._payload[self._idx['val_alpha']] is None:
1776 return False
1777 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = '\n', return_list = True)
1778 if len(lines) > 4:
1779 return True
1780 return False
1781
1782 is_long_text = property(_get_is_long_text, lambda x:x)
1783
1784
1786 if self._payload[self._idx['val_alpha']] is None:
1787 return None
1788 val = self._payload[self._idx['val_alpha']].lstrip()
1789 if val[0] == '<':
1790 factor = decimal.Decimal(0.5)
1791 val = val[1:]
1792 elif val[0] == '>':
1793 factor = 2
1794 val = val[1:]
1795 else:
1796 return None
1797 success, val = gmTools.input2decimal(initial = val)
1798 if not success:
1799 return None
1800 return val * factor
1801
1802 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x)
1803
1804
1805 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1806
1807
1808 if self._payload[self._idx['reviewed']]:
1809 self.__change_existing_review (
1810 technically_abnormal = technically_abnormal,
1811 clinically_relevant = clinically_relevant,
1812 comment = comment
1813 )
1814 else:
1815
1816
1817 if technically_abnormal is None:
1818 if clinically_relevant is None:
1819 comment = gmTools.none_if(comment, '', strip_string = True)
1820 if comment is None:
1821 if make_me_responsible is False:
1822 return True
1823 self.__set_new_review (
1824 technically_abnormal = technically_abnormal,
1825 clinically_relevant = clinically_relevant,
1826 comment = comment
1827 )
1828
1829 if make_me_responsible is True:
1830 cmd = "SELECT pk FROM dem.staff WHERE db_user = current_user"
1831 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
1832 self['pk_intended_reviewer'] = rows[0][0]
1833 self.save_payload()
1834 return
1835
1836 self.refetch_payload()
1837
1838
1839 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1840
1841 if desired_earlier_results < 1:
1842 raise ValueError('<desired_earlier_results> must be > 0')
1843
1844 if desired_later_results < 1:
1845 raise ValueError('<desired_later_results> must be > 0')
1846
1847 args = {
1848 'pat': self._payload[self._idx['pk_patient']],
1849 'ttyp': self._payload[self._idx['pk_test_type']],
1850 'tloinc': self._payload[self._idx['loinc_tt']],
1851 'mtyp': self._payload[self._idx['pk_meta_test_type']],
1852 'mloinc': self._payload[self._idx['loinc_meta']],
1853 'when': self._payload[self._idx['clin_when']],
1854 'offset': max_offset
1855 }
1856 WHERE = '((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
1857 WHERE_meta = '((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
1858 if max_offset is not None:
1859 WHERE = WHERE + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1860 WHERE_meta = WHERE_meta + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1861
1862 SQL = """
1863 SELECT * FROM clin.v_test_results
1864 WHERE
1865 pk_patient = %%(pat)s
1866 AND
1867 clin_when %s %%(when)s
1868 AND
1869 %s
1870 ORDER BY clin_when
1871 LIMIT %s"""
1872
1873
1874 earlier_results = []
1875
1876 cmd = SQL % ('<', WHERE, desired_earlier_results)
1877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1878 if len(rows) > 0:
1879 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1880
1881 missing_results = desired_earlier_results - len(earlier_results)
1882 if missing_results > 0:
1883 cmd = SQL % ('<', WHERE_meta, missing_results)
1884 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1885 if len(rows) > 0:
1886 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1887
1888
1889 later_results = []
1890
1891 cmd = SQL % ('>', WHERE, desired_later_results)
1892 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1893 if len(rows) > 0:
1894 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1895
1896 missing_results = desired_later_results - len(later_results)
1897 if missing_results > 0:
1898 cmd = SQL % ('>', WHERE_meta, missing_results)
1899 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1900 if len(rows) > 0:
1901 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1902
1903 return earlier_results, later_results
1904
1905
1906
1907
1908 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1909 """Add a review to a row.
1910
1911 - if technically abnormal is not provided/None it will be set
1912 to True if the lab's indicator has a meaningful value
1913 - if clinically relevant is not provided/None it is set to
1914 whatever technically abnormal is
1915 """
1916 if technically_abnormal is None:
1917 technically_abnormal = False
1918 if self._payload[self._idx['abnormality_indicator']] is not None:
1919 if self._payload[self._idx['abnormality_indicator']].strip() != '':
1920 technically_abnormal = True
1921
1922 if clinically_relevant is None:
1923 clinically_relevant = technically_abnormal
1924
1925 cmd = """
1926 INSERT INTO clin.reviewed_test_results (
1927 fk_reviewed_row,
1928 is_technically_abnormal,
1929 clinically_relevant,
1930 comment
1931 ) VALUES (
1932 %(pk)s,
1933 %(abnormal)s,
1934 %(relevant)s,
1935 gm.nullify_empty_string(%(cmt)s)
1936 )"""
1937 args = {
1938 'pk': self._payload[self._idx['pk_test_result']],
1939 'abnormal': technically_abnormal,
1940 'relevant': clinically_relevant,
1941 'cmt': comment
1942 }
1943
1944 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1945
1946
1948 """Change a review on a row.
1949
1950 - if technically abnormal/clinically relevant are
1951 None they are not set
1952 """
1953 args = {
1954 'pk_row': self._payload[self._idx['pk_test_result']],
1955 'abnormal': technically_abnormal,
1956 'relevant': clinically_relevant,
1957 'cmt': comment
1958 }
1959
1960 set_parts = [
1961 'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
1962 'comment = gm.nullify_empty_string(%(cmt)s)'
1963 ]
1964
1965 if technically_abnormal is not None:
1966 set_parts.append('is_technically_abnormal = %(abnormal)s')
1967
1968 if clinically_relevant is not None:
1969 set_parts.append('clinically_relevant = %(relevant)s')
1970
1971 cmd = """
1972 UPDATE clin.reviewed_test_results SET
1973 %s
1974 WHERE
1975 fk_reviewed_row = %%(pk_row)s
1976 """ % ',\n '.join(set_parts)
1977
1978 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1979
1980
1981 -def get_test_results(pk_patient=None, encounters=None, episodes=None, order_by=None):
1982
1983 where_parts = []
1984
1985 if pk_patient is not None:
1986 where_parts.append('pk_patient = %(pat)s')
1987 args = {'pat': pk_patient}
1988
1989
1990
1991
1992
1993 if encounters is not None:
1994 where_parts.append('pk_encounter IN %(encs)s')
1995 args['encs'] = tuple(encounters)
1996
1997 if episodes is not None:
1998 where_parts.append('pk_episode IN %(epis)s')
1999 args['epis'] = tuple(episodes)
2000
2001 if order_by is None:
2002 order_by = ''
2003 else:
2004 order_by = 'ORDER BY %s' % order_by
2005
2006 cmd = """
2007 SELECT * FROM clin.v_test_results
2008 WHERE %s
2009 %s
2010 """ % (
2011 ' AND '.join(where_parts),
2012 order_by
2013 )
2014 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2015
2016 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2017 return tests
2018
2019
2021
2022 if order_by is None:
2023 order_by = ''
2024 else:
2025 order_by = 'ORDER BY %s' % order_by
2026
2027 args = {
2028 'pat': pk_patient,
2029 'pk_pnl': pk_panel
2030 }
2031
2032 if group_by_meta_type:
2033
2034
2035
2036 cmd = """
2037 SELECT c_vtr.*
2038 FROM (
2039 -- max(clin_when) per test_type-in-panel for patient
2040 SELECT
2041 pk_meta_test_type,
2042 MAX(clin_when) AS max_clin_when
2043 FROM clin.v_test_results
2044 WHERE
2045 pk_patient = %(pat)s
2046 AND
2047 pk_meta_test_type IS DISTINCT FROM NULL
2048 AND
2049 pk_test_type IN (
2050 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2051 )
2052 GROUP BY pk_meta_test_type
2053 ) AS latest_results
2054 INNER JOIN clin.v_test_results c_vtr ON
2055 c_vtr.pk_meta_test_type = latest_results.pk_meta_test_type
2056 AND
2057 c_vtr.clin_when = latest_results.max_clin_when
2058
2059 UNION ALL
2060
2061 SELECT c_vtr.*
2062 FROM (
2063 -- max(clin_when) per test_type-in-panel for patient
2064 SELECT
2065 pk_test_type,
2066 MAX(clin_when) AS max_clin_when
2067 FROM clin.v_test_results
2068 WHERE
2069 pk_patient = %(pat)s
2070 AND
2071 pk_meta_test_type IS NULL
2072 AND
2073 pk_test_type IN (
2074 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2075 )
2076 GROUP BY pk_test_type
2077 ) AS latest_results
2078 INNER JOIN clin.v_test_results c_vtr ON
2079 c_vtr.pk_test_type = latest_results.pk_test_type
2080 AND
2081 c_vtr.clin_when = latest_results.max_clin_when
2082 """
2083 else:
2084
2085
2086
2087 cmd = """
2088 SELECT c_vtr.*
2089 FROM (
2090 -- max(clin_when) per test_type-in-panel for patient
2091 SELECT
2092 pk_test_type,
2093 MAX(clin_when) AS max_clin_when
2094 FROM clin.v_test_results
2095 WHERE
2096 pk_patient = %(pat)s
2097 AND
2098 pk_test_type IN (
2099 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2100 )
2101 GROUP BY pk_test_type
2102 ) AS latest_results
2103 -- this INNER join makes certain we do not expand
2104 -- the row selection beyond the patient's rows
2105 -- which we constrained to inside the SELECT
2106 -- producing "latest_results"
2107 INNER JOIN clin.v_test_results c_vtr ON
2108 c_vtr.pk_test_type = latest_results.pk_test_type
2109 AND
2110 c_vtr.clin_when = latest_results.max_clin_when
2111 """
2112 cmd += order_by
2113 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2114 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2115
2116
2118
2119 if None not in [test_type, loinc]:
2120 raise ValueError('either <test_type> or <loinc> must be None')
2121
2122 args = {
2123 'pat': patient,
2124 'ttyp': test_type,
2125 'loinc': loinc,
2126 'ts': timestamp,
2127 'intv': tolerance_interval
2128 }
2129
2130 where_parts = ['pk_patient = %(pat)s']
2131 if test_type is not None:
2132 where_parts.append('pk_test_type = %(ttyp)s')
2133 elif loinc is not None:
2134 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2135 args['loinc'] = tuple(loinc)
2136
2137 if tolerance_interval is None:
2138 where_parts.append('clin_when = %(ts)s')
2139 else:
2140 where_parts.append('clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)')
2141
2142 cmd = """
2143 SELECT * FROM clin.v_test_results
2144 WHERE
2145 %s
2146 ORDER BY
2147 abs(extract(epoch from age(clin_when, %%(ts)s)))
2148 LIMIT 1""" % ' AND '.join(where_parts)
2149
2150 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2151 if len(rows) == 0:
2152 return None
2153
2154 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2155
2156
2158
2159 args = {
2160 'pat': patient,
2161 'ts': timestamp
2162 }
2163
2164 where_parts = [
2165 'pk_patient = %(pat)s',
2166 "date_trunc('day'::text, clin_when) = date_trunc('day'::text, %(ts)s)"
2167 ]
2168
2169 cmd = """
2170 SELECT * FROM clin.v_test_results
2171 WHERE
2172 %s
2173 ORDER BY
2174 val_grouping,
2175 abbrev_tt,
2176 clin_when DESC
2177 """ % ' AND '.join(where_parts)
2178 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2179 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2180
2181
2183 args = {'pk_issue': pk_health_issue}
2184 where_parts = ['pk_health_issue = %(pk_issue)s']
2185 cmd = """
2186 SELECT * FROM clin.v_test_results
2187 WHERE %s
2188 ORDER BY
2189 val_grouping,
2190 abbrev_tt,
2191 clin_when DESC
2192 """ % ' AND '.join(where_parts)
2193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2194 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2195
2196
2198 args = {'pk_epi': pk_episode}
2199 where_parts = ['pk_episode = %(pk_epi)s']
2200 cmd = """
2201 SELECT * FROM clin.v_test_results
2202 WHERE %s
2203 ORDER BY
2204 val_grouping,
2205 abbrev_tt,
2206 clin_when DESC
2207 """ % ' AND '.join(where_parts)
2208 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2209 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2210
2211
2213
2214
2215
2216 if no_of_results < 1:
2217 raise ValueError('<no_of_results> must be > 0')
2218
2219 if not consider_meta_type:
2220 return get_most_recent_results (
2221 loinc = loinc,
2222 no_of_results = no_of_results,
2223 patient = patient
2224 )
2225
2226 args = {'pat': patient, 'loinc': tuple(loinc)}
2227 if max_age is None:
2228 max_age_cond = ''
2229 else:
2230 max_age_cond = 'AND clin_when > (now() - %(max_age)s::interval)'
2231 args['max_age'] = max_age
2232
2233 if consider_meta_type:
2234 rank_order = '_rank ASC'
2235 else:
2236 rank_order = '_rank DESC'
2237
2238 cmd = """
2239 SELECT DISTINCT ON (pk_test_type) * FROM (
2240 ( -- get results for meta type loinc
2241 SELECT *, 1 AS _rank
2242 FROM clin.v_test_results
2243 WHERE
2244 pk_patient = %%(pat)s
2245 AND
2246 loinc_meta IN %%(loinc)s
2247 %s
2248 -- no use weeding out duplicates by UNION-only, because _rank will make them unique anyway
2249 ) UNION ALL (
2250 -- get results for direct loinc
2251 SELECT *, 2 AS _rank
2252 FROM clin.v_test_results
2253 WHERE
2254 pk_patient = %%(pat)s
2255 AND
2256 loinc_tt IN %%(loinc)s
2257 %s
2258 )
2259 ORDER BY
2260 -- all of them by most-recent
2261 clin_when DESC,
2262 -- then by rank-of meta vs direct
2263 %s
2264 ) AS ordered_results
2265 -- then return only what's needed
2266 LIMIT %s""" % (
2267 max_age_cond,
2268 max_age_cond,
2269 rank_order,
2270 no_of_results
2271 )
2272 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2273 if no_of_results == 1:
2274 if len(rows) == 0:
2275 return None
2276 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2277 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2278
2279
2281
2282
2283 if None not in [test_type, loinc]:
2284 raise ValueError('either <test_type> or <loinc> must be None')
2285
2286 if no_of_results < 1:
2287 raise ValueError('<no_of_results> must be > 0')
2288
2289 args = {
2290 'pat': patient,
2291 'ttyp': test_type,
2292 'loinc': loinc
2293 }
2294 where_parts = ['pk_patient = %(pat)s']
2295 if test_type is not None:
2296 where_parts.append('pk_test_type = %(ttyp)s')
2297 elif loinc is not None:
2298 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2299 args['loinc'] = tuple(loinc)
2300 cmd = """
2301 SELECT * FROM clin.v_test_results
2302 WHERE
2303 %s
2304 ORDER BY clin_when DESC
2305 LIMIT %s""" % (
2306 ' AND '.join(where_parts),
2307 no_of_results
2308 )
2309 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2310 if no_of_results == 1:
2311 if len(rows) == 0:
2312 return None
2313 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2314 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2315
2316
2318
2319 if None not in [test_type, loinc]:
2320 raise ValueError('either <test_type> or <loinc> must be None')
2321
2322 args = {
2323 'pat': patient,
2324 'ttyp': test_type,
2325 'loinc': loinc
2326 }
2327
2328 where_parts = ['pk_patient = %(pat)s']
2329 if test_type is not None:
2330 where_parts.append('pk_test_type = %(ttyp)s')
2331 elif loinc is not None:
2332 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2333 args['loinc'] = tuple(loinc)
2334
2335 cmd = """
2336 SELECT * FROM clin.v_test_results
2337 WHERE
2338 %s
2339 ORDER BY clin_when
2340 LIMIT 1""" % ' AND '.join(where_parts)
2341 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2342 if len(rows) == 0:
2343 return None
2344
2345 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2346
2347
2349 try:
2350 pk = int(result)
2351 except (TypeError, AttributeError):
2352 pk = result['pk_test_result']
2353
2354 cmd = 'DELETE FROM clin.test_result WHERE pk = %(pk)s'
2355 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
2356
2357
2358 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
2359
2360 cmd1 = """
2361 INSERT INTO clin.test_result (
2362 fk_encounter,
2363 fk_episode,
2364 fk_type,
2365 fk_intended_reviewer,
2366 val_num,
2367 val_alpha,
2368 val_unit
2369 ) VALUES (
2370 %(enc)s,
2371 %(epi)s,
2372 %(type)s,
2373 %(rev)s,
2374 %(v_num)s,
2375 %(v_alpha)s,
2376 %(unit)s
2377 )
2378 """
2379 cmd2 = "SELECT * from clin.v_test_results WHERE pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"
2380 args = {
2381 'enc': encounter,
2382 'epi': episode,
2383 'type': type,
2384 'rev': intended_reviewer,
2385 'v_num': val_num,
2386 'v_alpha': val_alpha,
2387 'unit': unit
2388 }
2389 rows, idx = gmPG2.run_rw_queries (
2390 link_obj = link_obj,
2391 queries = [
2392 {'cmd': cmd1, 'args': args},
2393 {'cmd': cmd2}
2394 ],
2395 return_data = True,
2396 get_col_idx = True
2397 )
2398 tr = cTestResult(row = {
2399 'pk_field': 'pk_test_result',
2400 'idx': idx,
2401 'data': rows[0]
2402 })
2403 return tr
2404
2405
2416
2417
2418 -def __tests2latex_minipage(results=None, width='1.5cm', show_time=False, show_range=True):
2419
2420 if len(results) == 0:
2421 return '\\begin{minipage}{%s} \\end{minipage}' % width
2422
2423 lines = []
2424 for t in results:
2425
2426 tmp = ''
2427
2428 if show_time:
2429 tmp += '{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
2430
2431 tmp += '%.8s' % t['unified_val']
2432
2433 lines.append(tmp)
2434 tmp = ''
2435
2436 if show_range:
2437 has_range = (
2438 t['unified_target_range'] is not None
2439 or
2440 t['unified_target_min'] is not None
2441 or
2442 t['unified_target_max'] is not None
2443 )
2444 if has_range:
2445 if t['unified_target_range'] is not None:
2446 tmp += '{\\tiny %s}' % t['unified_target_range']
2447 else:
2448 tmp += '{\\tiny %s}' % (
2449 gmTools.coalesce(t['unified_target_min'], '- ', '%s - '),
2450 gmTools.coalesce(t['unified_target_max'], '', '%s')
2451 )
2452 lines.append(tmp)
2453
2454 return '\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, ' \\\\ '.join(lines))
2455
2456
2458
2459 if len(results) == 0:
2460 return ''
2461
2462 lines = []
2463 for t in results:
2464
2465 tmp = ''
2466
2467 if show_time:
2468 tmp += '\\tiny %s ' % t['clin_when'].strftime('%H:%M')
2469
2470 tmp += '\\normalsize %.8s' % t['unified_val']
2471
2472 lines.append(tmp)
2473 tmp = '\\tiny %s' % gmTools.coalesce(t['val_unit'], '', '%s ')
2474
2475 if not show_range:
2476 lines.append(tmp)
2477 continue
2478
2479 has_range = (
2480 t['unified_target_range'] is not None
2481 or
2482 t['unified_target_min'] is not None
2483 or
2484 t['unified_target_max'] is not None
2485 )
2486
2487 if not has_range:
2488 lines.append(tmp)
2489 continue
2490
2491 if t['unified_target_range'] is not None:
2492 tmp += '[%s]' % t['unified_target_range']
2493 else:
2494 tmp += '[%s%s]' % (
2495 gmTools.coalesce(t['unified_target_min'], '--', '%s--'),
2496 gmTools.coalesce(t['unified_target_max'], '', '%s')
2497 )
2498 lines.append(tmp)
2499
2500 return ' \\\\ '.join(lines)
2501
2502
2578
2579
2581
2582 if filename is None:
2583 filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.dat')
2584
2585
2586 series = {}
2587 for r in results:
2588 try:
2589 series[r['unified_name']].append(r)
2590 except KeyError:
2591 series[r['unified_name']] = [r]
2592
2593 gp_data = io.open(filename, mode = 'wt', encoding = 'utf8')
2594
2595 gp_data.write('# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
2596 gp_data.write('# -------------------------------------------------------------\n')
2597 gp_data.write('# first line of index: test type abbreviation & name\n')
2598 gp_data.write('#\n')
2599 gp_data.write('# clin_when at full precision\n')
2600 gp_data.write('# value\n')
2601 gp_data.write('# unit\n')
2602 gp_data.write('# unified (target or normal) range: lower bound\n')
2603 gp_data.write('# unified (target or normal) range: upper bound\n')
2604 gp_data.write('# normal range: lower bound\n')
2605 gp_data.write('# normal range: upper bound\n')
2606 gp_data.write('# target range: lower bound\n')
2607 gp_data.write('# target range: upper bound\n')
2608 gp_data.write('# clin_when formatted into string as x-axis tic label\n')
2609 gp_data.write('# -------------------------------------------------------------\n')
2610
2611 for test_type in series.keys():
2612 if len(series[test_type]) == 0:
2613 continue
2614
2615 r = series[test_type][0]
2616 title = '%s (%s)' % (
2617 r['unified_abbrev'],
2618 r['unified_name']
2619 )
2620 gp_data.write('\n\n"%s" "%s"\n' % (title, title))
2621
2622 prev_date = None
2623 prev_year = None
2624 for r in series[test_type]:
2625 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days)
2626 if curr_date == prev_date:
2627 gp_data.write('\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values'))
2628 if show_year:
2629 if r['clin_when'].year == prev_year:
2630 when_template = '%b %d %H:%M'
2631 else:
2632 when_template = '%b %d %H:%M (%Y)'
2633 prev_year = r['clin_when'].year
2634 else:
2635 when_template = '%b %d'
2636 val = r['val_num']
2637 if val is None:
2638 val = r.estimate_numeric_value_from_alpha
2639 if val is None:
2640 continue
2641 gp_data.write ('%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
2642
2643 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes),
2644 val,
2645 gmTools.coalesce(r['val_unit'], '"<?>"'),
2646 gmTools.coalesce(r['unified_target_min'], '"<?>"'),
2647 gmTools.coalesce(r['unified_target_max'], '"<?>"'),
2648 gmTools.coalesce(r['val_normal_min'], '"<?>"'),
2649 gmTools.coalesce(r['val_normal_max'], '"<?>"'),
2650 gmTools.coalesce(r['val_target_min'], '"<?>"'),
2651 gmTools.coalesce(r['val_target_max'], '"<?>"'),
2652 gmDateTime.pydt_strftime (
2653 r['clin_when'],
2654 format = when_template,
2655 accuracy = gmDateTime.acc_minutes
2656 )
2657 ))
2658 prev_date = curr_date
2659
2660 gp_data.close()
2661
2662 return filename
2663
2664
2665 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
2666 """Represents one lab result."""
2667
2668 _cmd_fetch_payload = """
2669 select *, xmin_test_result from v_results4lab_req
2670 where pk_result=%s"""
2671 _cmds_lock_rows_for_update = [
2672 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
2673 ]
2674 _cmds_store_payload = [
2675 """update test_result set
2676 clin_when = %(val_when)s,
2677 narrative = %(progress_note_result)s,
2678 fk_type = %(pk_test_type)s,
2679 val_num = %(val_num)s::numeric,
2680 val_alpha = %(val_alpha)s,
2681 val_unit = %(val_unit)s,
2682 val_normal_min = %(val_normal_min)s,
2683 val_normal_max = %(val_normal_max)s,
2684 val_normal_range = %(val_normal_range)s,
2685 val_target_min = %(val_target_min)s,
2686 val_target_max = %(val_target_max)s,
2687 val_target_range = %(val_target_range)s,
2688 abnormality_indicator = %(abnormal)s,
2689 norm_ref_group = %(ref_group)s,
2690 note_provider = %(note_provider)s,
2691 material = %(material)s,
2692 material_detail = %(material_detail)s
2693 where pk = %(pk_result)s""",
2694 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
2695 ]
2696
2697 _updatable_fields = [
2698 'val_when',
2699 'progress_note_result',
2700 'val_num',
2701 'val_alpha',
2702 'val_unit',
2703 'val_normal_min',
2704 'val_normal_max',
2705 'val_normal_range',
2706 'val_target_min',
2707 'val_target_max',
2708 'val_target_range',
2709 'abnormal',
2710 'ref_group',
2711 'note_provider',
2712 'material',
2713 'material_detail'
2714 ]
2715
2716 - def __init__(self, aPK_obj=None, row=None):
2717 """Instantiate.
2718
2719 aPK_obj as dict:
2720 - patient_id
2721 - when_field (see view definition)
2722 - when
2723 - test_type
2724 - val_num
2725 - val_alpha
2726 - unit
2727 """
2728
2729 if aPK_obj is None:
2730 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2731 return
2732 pk = aPK_obj
2733
2734 if type(aPK_obj) == dict:
2735
2736 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
2737 raise gmExceptions.ConstructorError('parameter error: %s' % aPK_obj)
2738 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
2739 raise gmExceptions.ConstructorError('parameter error: val_num and val_alpha cannot both be None')
2740
2741 where_snippets = [
2742 'pk_patient=%(patient_id)s',
2743 'pk_test_type=%(test_type)s',
2744 '%s=%%(when)s' % aPK_obj['when_field'],
2745 'val_unit=%(unit)s'
2746 ]
2747 if aPK_obj['val_num'] is not None:
2748 where_snippets.append('val_num=%(val_num)s::numeric')
2749 if aPK_obj['val_alpha'] is not None:
2750 where_snippets.append('val_alpha=%(val_alpha)s')
2751
2752 where_clause = ' and '.join(where_snippets)
2753 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
2754 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2755 if data is None:
2756 raise gmExceptions.ConstructorError('error getting lab result for: %s' % aPK_obj)
2757 if len(data) == 0:
2758 raise gmExceptions.NoSuchClinItemError('no lab result for: %s' % aPK_obj)
2759 pk = data[0][0]
2760
2761 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2762
2764 cmd = """
2765 select
2766 %s,
2767 vbp.title,
2768 vbp.firstnames,
2769 vbp.lastnames,
2770 vbp.dob
2771 from v_active_persons vbp
2772 where vbp.pk_identity = %%s""" % self._payload[self._idx['pk_patient']]
2773 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
2774 return pat[0]
2775
2776
2777 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
2778 """Represents one lab request."""
2779
2780 _cmd_fetch_payload = """
2781 select *, xmin_lab_request from v_lab_requests
2782 where pk_request=%s"""
2783 _cmds_lock_rows_for_update = [
2784 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
2785 ]
2786 _cmds_store_payload = [
2787 """update lab_request set
2788 request_id=%(request_id)s,
2789 lab_request_id=%(lab_request_id)s,
2790 clin_when=%(sampled_when)s,
2791 lab_rxd_when=%(lab_rxd_when)s,
2792 results_reported_when=%(results_reported_when)s,
2793 request_status=%(request_status)s,
2794 is_pending=%(is_pending)s::bool,
2795 narrative=%(progress_note)s
2796 where pk=%(pk_request)s""",
2797 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
2798 ]
2799 _updatable_fields = [
2800 'request_id',
2801 'lab_request_id',
2802 'sampled_when',
2803 'lab_rxd_when',
2804 'results_reported_when',
2805 'request_status',
2806 'is_pending',
2807 'progress_note'
2808 ]
2809
2810 - def __init__(self, aPK_obj=None, row=None):
2811 """Instantiate lab request.
2812
2813 The aPK_obj can be either a dict with the keys "req_id"
2814 and "lab" or a simple primary key.
2815 """
2816
2817 if aPK_obj is None:
2818 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2819 return
2820 pk = aPK_obj
2821
2822 if type(aPK_obj) == dict:
2823
2824 try:
2825 aPK_obj['req_id']
2826 aPK_obj['lab']
2827 except:
2828 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
2829 raise gmExceptions.ConstructorError('[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj))
2830
2831 where_snippets = []
2832 vals = {}
2833 where_snippets.append('request_id=%(req_id)s')
2834 if type(aPK_obj['lab']) == int:
2835 where_snippets.append('pk_test_org=%(lab)s')
2836 else:
2837 where_snippets.append('lab_name=%(lab)s')
2838 where_clause = ' and '.join(where_snippets)
2839 cmd = "select pk_request from v_lab_requests where %s" % where_clause
2840
2841 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2842 if data is None:
2843 raise gmExceptions.ConstructorError('[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj))
2844 if len(data) == 0:
2845 raise gmExceptions.NoSuchClinItemError('[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj))
2846 pk = data[0][0]
2847
2848 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2849
2851 cmd = """
2852 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
2853 from v_pat_items vpi, v_active_persons vbp
2854 where
2855 vpi.pk_item=%s
2856 and
2857 vbp.pk_identity=vpi.pk_patient"""
2858 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
2859 if pat is None:
2860 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
2861 return None
2862 if len(pat) == 0:
2863 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
2864 return None
2865 return pat[0]
2866
2867
2868
2869
2870 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2871 """Create or get lab request.
2872
2873 returns tuple (status, value):
2874 (True, lab request instance)
2875 (False, error message)
2876 (None, housekeeping_todo primary key)
2877 """
2878 req = None
2879 aPK_obj = {
2880 'lab': lab,
2881 'req_id': req_id
2882 }
2883 try:
2884 req = cLabRequest (aPK_obj)
2885 except gmExceptions.NoSuchClinItemError as msg:
2886 _log.info('%s: will try to create lab request' % str(msg))
2887 except gmExceptions.ConstructorError as msg:
2888 _log.exception(str(msg), sys.exc_info(), verbose=0)
2889 return (False, msg)
2890
2891 if req is not None:
2892 db_pat = req.get_patient()
2893 if db_pat is None:
2894 _log.error('cannot cross-check patient on lab request')
2895 return (None, '')
2896
2897 if pat_id != db_pat[0]:
2898 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
2899 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
2900 to = 'user'
2901 prob = _('The lab request already exists but belongs to a different patient.')
2902 sol = _('Verify which patient this lab request really belongs to.')
2903 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
2904 cat = 'lab'
2905 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
2906 return (None, data)
2907 return (True, req)
2908
2909 queries = []
2910 if type(lab) is int:
2911 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
2912 else:
2913 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, (select pk from test_org where internal_OBSOLETE_name=%s), %s)"
2914 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
2915 cmd = "select currval('lab_request_pk_seq')"
2916 queries.append((cmd, []))
2917
2918 result, err = gmPG.run_commit('historica', queries, True)
2919 if result is None:
2920 return (False, err)
2921 try:
2922 req = cLabRequest(aPK_obj=result[0][0])
2923 except gmExceptions.ConstructorError as msg:
2924 _log.exception(str(msg), sys.exc_info(), verbose=0)
2925 return (False, msg)
2926 return (True, req)
2927
2928 -def create_lab_result(patient_id=None, when_field=None, when=None, test_type=None, val_num=None, val_alpha=None, unit=None, encounter_id=None, request=None):
2929 tres = None
2930 data = {
2931 'patient_id': patient_id,
2932 'when_field': when_field,
2933 'when': when,
2934 'test_type': test_type,
2935 'val_num': val_num,
2936 'val_alpha': val_alpha,
2937 'unit': unit
2938 }
2939 try:
2940 tres = cLabResult(aPK_obj=data)
2941
2942 _log.error('will not overwrite existing test result')
2943 _log.debug(str(tres))
2944 return (None, tres)
2945 except gmExceptions.NoSuchClinItemError:
2946 _log.debug('test result not found - as expected, will create it')
2947 except gmExceptions.ConstructorError as msg:
2948 _log.exception(str(msg), sys.exc_info(), verbose=0)
2949 return (False, msg)
2950 if request is None:
2951 return (False, _('need lab request when inserting lab result'))
2952
2953 if encounter_id is None:
2954 encounter_id = request['pk_encounter']
2955 queries = []
2956 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
2957 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
2958 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
2959 queries.append((cmd, [request['pk_request']]))
2960 cmd = "select currval('test_result_pk_seq')"
2961 queries.append((cmd, []))
2962
2963 result, err = gmPG.run_commit('historica', queries, True)
2964 if result is None:
2965 return (False, err)
2966 try:
2967 tres = cLabResult(aPK_obj=result[0][0])
2968 except gmExceptions.ConstructorError as msg:
2969 _log.exception(str(msg), sys.exc_info(), verbose=0)
2970 return (False, msg)
2971 return (True, tres)
2972
2974
2975 if limit < 1:
2976 limit = 1
2977
2978 lim = limit + 1
2979 cmd = """
2980 select pk_result
2981 from v_results4lab_req
2982 where reviewed is false
2983 order by pk_patient
2984 limit %s""" % lim
2985 rows = gmPG.run_ro_query('historica', cmd)
2986 if rows is None:
2987 _log.error('error retrieving unreviewed lab results')
2988 return (None, _('error retrieving unreviewed lab results'))
2989 if len(rows) == 0:
2990 return (False, [])
2991
2992 if len(rows) == lim:
2993 more_avail = True
2994
2995 del rows[limit]
2996 else:
2997 more_avail = False
2998 results = []
2999 for row in rows:
3000 try:
3001 results.append(cLabResult(aPK_obj=row[0]))
3002 except gmExceptions.ConstructorError:
3003 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
3004 return (more_avail, results)
3005
3006
3008 lim = limit + 1
3009 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
3010 rows = gmPG.run_ro_query('historica', cmd)
3011 if rows is None:
3012 _log.error('error retrieving pending lab requests')
3013 return (None, None)
3014 if len(rows) == 0:
3015 return (False, [])
3016 results = []
3017
3018 if len(rows) == lim:
3019 too_many = True
3020
3021 del rows[limit]
3022 else:
3023 too_many = False
3024 requests = []
3025 for row in rows:
3026 try:
3027 requests.append(cLabRequest(aPK_obj=row[0]))
3028 except gmExceptions.ConstructorError:
3029 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
3030 return (too_many, requests)
3031
3032
3034 """Get logically next request ID for given lab.
3035
3036 - incrementor_func:
3037 - if not supplied the next ID is guessed
3038 - if supplied it is applied to the most recently used ID
3039 """
3040 if type(lab) == int:
3041 lab_snippet = 'vlr.fk_test_org=%s'
3042 else:
3043 lab_snippet = 'vlr.lab_name=%s'
3044 lab = str(lab)
3045 cmd = """
3046 select request_id
3047 from lab_request lr0
3048 where lr0.clin_when = (
3049 select max(vlr.sampled_when)
3050 from v_lab_requests vlr
3051 where %s
3052 )""" % lab_snippet
3053 rows = gmPG.run_ro_query('historica', cmd, None, lab)
3054 if rows is None:
3055 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
3056 return ''
3057 if len(rows) == 0:
3058 return ''
3059 most_recent = rows[0][0]
3060
3061 if incrementor_func is not None:
3062 try:
3063 next = incrementor_func(most_recent)
3064 except TypeError:
3065 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
3066 return most_recent
3067 return next
3068
3069 for pos in range(len(most_recent)):
3070 header = most_recent[:pos]
3071 trailer = most_recent[pos:]
3072 try:
3073 return '%s%s' % (header, str(int(trailer) + 1))
3074 except ValueError:
3075 header = most_recent[:-1]
3076 trailer = most_recent[-1:]
3077 return '%s%s' % (header, chr(ord(trailer) + 1))
3078
3079
3081 """Calculate BMI.
3082
3083 mass: kg
3084 height: cm
3085 age: not yet used
3086
3087 returns:
3088 (True/False, data)
3089 True: data = (bmi, lower_normal, upper_normal)
3090 False: data = error message
3091 """
3092 converted, mass = gmTools.input2decimal(mass)
3093 if not converted:
3094 return False, 'mass: cannot convert <%s> to Decimal' % mass
3095
3096 converted, height = gmTools.input2decimal(height)
3097 if not converted:
3098 return False, 'height: cannot convert <%s> to Decimal' % height
3099
3100 approx_surface = (height / decimal.Decimal(100))**2
3101 bmi = mass / approx_surface
3102
3103 print(mass, height, '->', approx_surface, '->', bmi)
3104
3105 lower_normal_mass = 20.0 * approx_surface
3106 upper_normal_mass = 25.0 * approx_surface
3107
3108 return True, (bmi, lower_normal_mass, upper_normal_mass)
3109
3110
3111
3112
3113 if __name__ == '__main__':
3114
3115 if len(sys.argv) < 2:
3116 sys.exit()
3117
3118 if sys.argv[1] != 'test':
3119 sys.exit()
3120
3121 import time
3122
3123 gmI18N.activate_locale()
3124 gmI18N.install_domain()
3125
3126
3128 tr = create_test_result (
3129 encounter = 1,
3130 episode = 1,
3131 type = 1,
3132 intended_reviewer = 1,
3133 val_num = '12',
3134 val_alpha=None,
3135 unit = 'mg/dl'
3136 )
3137 print(tr)
3138 return tr
3139
3143
3151
3153 print("test_result()")
3154
3155 data = {
3156 'patient_id': 12,
3157 'when_field': 'val_when',
3158 'when': '2000-09-17 18:23:00+02',
3159 'test_type': 9,
3160 'val_num': 17.3,
3161 'val_alpha': None,
3162 'unit': 'mg/l'
3163 }
3164 lab_result = cLabResult(aPK_obj=data)
3165 print(lab_result)
3166 fields = lab_result.get_fields()
3167 for field in fields:
3168 print(field, ':', lab_result[field])
3169 print("updatable:", lab_result.get_updatable_fields())
3170 print(time.time())
3171 print(lab_result.get_patient())
3172 print(time.time())
3173
3175 print("test_request()")
3176 try:
3177
3178
3179 data = {
3180 'req_id': 'EML#SC937-0176-CEC#11',
3181 'lab': 'Enterprise Main Lab'
3182 }
3183 lab_req = cLabRequest(aPK_obj=data)
3184 except gmExceptions.ConstructorError as msg:
3185 print("no such lab request:", msg)
3186 return
3187 print(lab_req)
3188 fields = lab_req.get_fields()
3189 for field in fields:
3190 print(field, ':', lab_req[field])
3191 print("updatable:", lab_req.get_updatable_fields())
3192 print(time.time())
3193 print(lab_req.get_patient())
3194 print(time.time())
3195
3200
3205
3213
3218
3223
3232
3234 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
3235 bmi, low, high = data
3236 print("BMI:", bmi)
3237 print("low:", low, "kg")
3238 print("hi :", high, "kg")
3239
3240
3246
3247
3249 tp = cTestPanel(aPK_obj = 1)
3250
3251
3252 print(tp.format())
3253
3254
3255
3256 most_recent = tp.get_most_recent_results(pk_patient = 12, group_by_meta_type = True)
3257
3258 print('found:', len(most_recent))
3259
3260 for t in most_recent:
3261 print('--------------')
3262 if t['pk_meta_test_type'] is None:
3263 print("standalone")
3264 else:
3265 print("meta")
3266 print(t.format())
3267
3268
3270 most_recent = get_most_recent_results_by_loinc (
3271
3272 loinc = ['8867-4'],
3273 no_of_results = 2,
3274 patient = 12,
3275 consider_meta_type = True
3276
3277 )
3278 for t in most_recent:
3279 if t['pk_meta_test_type'] is None:
3280 print("---- standalone ----")
3281 else:
3282 print("---- meta ----")
3283 print(t.format())
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300 test_test_panel()
3301
3302
3303
3304
3305