1 """GNUmed measurements related business objects."""
2
3
4
5 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL"
7
8
9 import types
10 import sys
11 import logging
12 import codecs
13 import decimal
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 gmDateTime.init()
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmBusinessDBObject
26 from Gnumed.pycommon import gmPG2
27 from Gnumed.pycommon import gmTools
28 from Gnumed.pycommon import gmDispatcher
29 from Gnumed.pycommon import gmHooks
30 from Gnumed.business import gmOrganization
31 from Gnumed.business import gmCoding
32
33
34 _log = logging.getLogger('gm.lab')
35
36
40
41 gmDispatcher.connect(_on_test_result_modified, u'clin.test_result_mod_db')
42
43
44 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
45 """Represents one test org/lab."""
46 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
47 _cmds_store_payload = [
48 u"""UPDATE clin.test_org SET
49 fk_org_unit = %(pk_org_unit)s,
50 contact = gm.nullify_empty_string(%(test_org_contact)s),
51 comment = gm.nullify_empty_string(%(comment)s)
52 WHERE
53 pk = %(pk_test_org)s
54 AND
55 xmin = %(xmin_test_org)s
56 RETURNING
57 xmin AS xmin_test_org
58 """
59 ]
60 _updatable_fields = [
61 u'pk_org_unit',
62 u'test_org_contact',
63 u'comment'
64 ]
65
101
103 args = {'pk': test_org}
104 cmd = u"""
105 DELETE FROM clin.test_org
106 WHERE
107 pk = %(pk)s
108 AND
109 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
110 AND
111 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
112 """
113 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
114
116 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
118 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
119
120
121
122
123 _SQL_get_test_panels = u"SELECT * FROM clin.v_test_panels WHERE %s"
124
125 -class cTestPanel(gmBusinessDBObject.cBusinessDBObject):
126 """Represents a grouping/listing of tests into a panel."""
127
128 _cmd_fetch_payload = _SQL_get_test_panels % u"pk_test_panel = %s"
129 _cmds_store_payload = [
130 u"""
131 UPDATE clin.test_panel SET
132 description = gm.nullify_empty_string(%(description)s),
133 comment = gm.nullify_empty_string(%(comment)s),
134 fk_test_types = %(pk_test_types)s
135 WHERE
136 pk = %(pk_test_panel)s
137 AND
138 xmin = %(xmin_test_panel)s
139 RETURNING
140 xmin AS xmin_test_panel
141 """
142 ]
143 _updatable_fields = [
144 u'description',
145 u'comment',
146 u'pk_test_types'
147 ]
148
188
190 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
191 cmd = u"INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)"
192 args = {
193 'tp': self._payload[self._idx['pk_test_panel']],
194 'code': pk_code
195 }
196 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
197 return True
198
200 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
201 cmd = u"DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s"
202 args = {
203 'tp': self._payload[self._idx['pk_test_panel']],
204 'code': pk_code
205 }
206 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
207 return True
208
209
210
212 if self._payload[self._idx['pk_test_types']] is None:
213 return None
214
215 rows, idx = gmPG2.run_ro_queries (
216 queries = [{
217 'cmd': _SQL_get_test_types % u'pk_test_type IN %(pks)s ORDER BY unified_abbrev',
218 'args': {'pks': tuple(self._payload[self._idx['pk_test_types']])}
219 }],
220 get_col_idx = True
221 )
222 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
223
224 test_types = property(_get_test_types, lambda x:x)
225
227 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
228 return []
229
230 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
231 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
232 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
233 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
234
236 queries = []
237
238 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
239 queries.append ({
240 'cmd': u'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s',
241 'args': {
242 'tp': self._payload[self._idx['pk_test_panel']],
243 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
244 }
245 })
246
247 for pk_code in pk_codes:
248 queries.append ({
249 'cmd': u'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)',
250 'args': {
251 'tp': self._payload[self._idx['pk_test_panel']],
252 'pk_code': pk_code
253 }
254 })
255 if len(queries) == 0:
256 return
257
258 rows, idx = gmPG2.run_rw_queries(queries = queries)
259 return
260
261 generic_codes = property(_get_generic_codes, _set_generic_codes)
262
269
271 if order_by is None:
272 order_by = u'true'
273 else:
274 order_by = u'true ORDER BY %s' % order_by
275
276 cmd = _SQL_get_test_panels % order_by
277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
278 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
279
280
282
283 args = {u'desc': description.strip()}
284 cmd = u"""
285 INSERT INTO clin.test_panel (description)
286 VALUES (gm.nullify_empty_string(%(desc)s))
287 RETURNING pk
288 """
289 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
290
291 return cTestPanel(aPK_obj = rows[0]['pk'])
292
293
295 args = {'pk': pk}
296 cmd = u"DELETE FROM clin.test_panel WHERE pk = %(pk)s"
297 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
298 return True
299
300
418
419
447
448
461
462
467
468
469 _SQL_get_test_types = u"SELECT * FROM clin.v_test_types WHERE %s"
470
472 """Represents one test result type."""
473
474 _cmd_fetch_payload = _SQL_get_test_types % u"pk_test_type = %s"
475
476 _cmds_store_payload = [
477 u"""UPDATE clin.test_type SET
478 abbrev = gm.nullify_empty_string(%(abbrev)s),
479 name = gm.nullify_empty_string(%(name)s),
480 loinc = gm.nullify_empty_string(%(loinc)s),
481 comment = gm.nullify_empty_string(%(comment_type)s),
482 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
483 fk_test_org = %(pk_test_org)s,
484 fk_meta_test_type = %(pk_meta_test_type)s
485 WHERE
486 pk = %(pk_test_type)s
487 AND
488 xmin = %(xmin_test_type)s
489 RETURNING
490 xmin AS xmin_test_type"""
491 ]
492
493 _updatable_fields = [
494 'abbrev',
495 'name',
496 'loinc',
497 'comment_type',
498 'conversion_unit',
499 'pk_test_org',
500 'pk_meta_test_type'
501 ]
502
503
504
506 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
507 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
508 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
509 return rows[0][0]
510
511 in_use = property(_get_in_use, lambda x:x)
512
529
530
545
546
548 if self._payload[self._idx['pk_test_panels']] is None:
549 return None
550
551 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
552
553 test_panels = property(_get_test_panels, lambda x:x)
554
555
562
563 meta_test_type = property(get_meta_test_type, lambda x:x)
564
566 """Returns the closest test result which does have normal range information.
567
568 - needs <unit>
569 - if <timestamp> is None it will assume now() and thus return the most recent
570 """
571 if timestamp is None:
572 timestamp = gmDateTime.pydt_now_here()
573 cmd = u"""
574 SELECT * FROM clin.v_test_results
575 WHERE
576 pk_test_type = %(pk_type)s
577 AND
578 val_unit = %(unit)s
579 AND
580 (
581 (val_normal_min IS NOT NULL)
582 OR
583 (val_normal_max IS NOT NULL)
584 OR
585 (val_normal_range IS NOT NULL)
586 )
587 ORDER BY
588 CASE
589 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
590 ELSE %(clin_when)s - clin_when
591 END
592 LIMIT 1"""
593 args = {
594 u'pk_type': self._payload[self._idx['pk_test_type']],
595 u'unit': unit,
596 u'clin_when': timestamp
597 }
598 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
599 if len(rows) == 0:
600 return None
601 r = rows[0]
602 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
603
604
606 """Returns the closest test result which does have target range information.
607
608 - needs <unit>
609 - needs <patient> (as target will be per-patient)
610 - if <timestamp> is None it will assume now() and thus return the most recent
611 """
612 if timestamp is None:
613 timestamp = gmDateTime.pydt_now_here()
614 cmd = u"""
615 SELECT * FROM clin.v_test_results
616 WHERE
617 pk_test_type = %(pk_type)s
618 AND
619 val_unit = %(unit)s
620 AND
621 pk_patient = %(pat)s
622 AND
623 (
624 (val_target_min IS NOT NULL)
625 OR
626 (val_target_max IS NOT NULL)
627 OR
628 (val_target_range IS NOT NULL)
629 )
630 ORDER BY
631 CASE
632 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
633 ELSE %(clin_when)s - clin_when
634 END
635 LIMIT 1"""
636 args = {
637 u'pk_type': self._payload[self._idx['pk_test_type']],
638 u'unit': unit,
639 u'pat': patient,
640 u'clin_when': timestamp
641 }
642 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
643 if len(rows) == 0:
644 return None
645 r = rows[0]
646 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
647
648
650 """Returns the unit of the closest test result.
651
652 - if <timestamp> is None it will assume now() and thus return the most recent
653 """
654 if timestamp is None:
655 timestamp = gmDateTime.pydt_now_here()
656 cmd = u"""
657 SELECT val_unit FROM clin.v_test_results
658 WHERE
659 pk_test_type = %(pk_type)s
660 AND
661 val_unit IS NOT NULL
662 ORDER BY
663 CASE
664 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
665 ELSE %(clin_when)s - clin_when
666 END
667 LIMIT 1"""
668 args = {
669 u'pk_type': self._payload[self._idx['pk_test_type']],
670 u'clin_when': timestamp
671 }
672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
673 if len(rows) == 0:
674 return None
675 return rows[0]['val_unit']
676
677 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x)
678
679
740
741
743 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
745 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
746
747
749
750 if (abbrev is None) and (name is None):
751 raise ValueError('must have <abbrev> and/or <name> set')
752
753 where_snippets = []
754
755 if lab is None:
756 where_snippets.append('pk_test_org IS NULL')
757 else:
758 try:
759 int(lab)
760 where_snippets.append('pk_test_org = %(lab)s')
761 except (TypeError, ValueError):
762 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
763
764 if abbrev is not None:
765 where_snippets.append('abbrev = %(abbrev)s')
766
767 if name is not None:
768 where_snippets.append('name = %(name)s')
769
770 where_clause = u' and '.join(where_snippets)
771 cmd = u"select * from clin.v_test_types where %s" % where_clause
772 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
773
774 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
775
776 if len(rows) == 0:
777 return None
778
779 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
780 return tt
781
782
784 cmd = u'delete from clin.test_type where pk = %(pk)s'
785 args = {'pk': measurement_type}
786 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
787
788
790 """Create or get test type."""
791
792 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
793
794 if ttype is not None:
795 return ttype
796
797 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
798
799
800 if unit is None:
801 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
802 raise ValueError('need <unit> to create test type')
803
804
805 cols = []
806 val_snippets = []
807 vals = {}
808
809
810 if lab is None:
811 lab = create_test_org()['pk_test_org']
812
813 cols.append('fk_test_org')
814 try:
815 vals['lab'] = int(lab)
816 val_snippets.append('%(lab)s')
817 except:
818 vals['lab'] = lab
819 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
820
821
822 cols.append('abbrev')
823 val_snippets.append('%(abbrev)s')
824 vals['abbrev'] = abbrev
825
826
827 cols.append('conversion_unit')
828 val_snippets.append('%(unit)s')
829 vals['unit'] = unit
830
831
832 if name is not None:
833 cols.append('name')
834 val_snippets.append('%(name)s')
835 vals['name'] = name
836
837 col_clause = u', '.join(cols)
838 val_clause = u', '.join(val_snippets)
839 queries = [
840 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
841 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
842 ]
843 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
844 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
845
846 return ttype
847
848
849 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
850 """Represents one test result."""
851
852 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
853
854 _cmds_store_payload = [
855 u"""update clin.test_result set
856 clin_when = %(clin_when)s,
857 narrative = nullif(trim(%(comment)s), ''),
858 val_num = %(val_num)s,
859 val_alpha = nullif(trim(%(val_alpha)s), ''),
860 val_unit = nullif(trim(%(val_unit)s), ''),
861 val_normal_min = %(val_normal_min)s,
862 val_normal_max = %(val_normal_max)s,
863 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
864 val_target_min = %(val_target_min)s,
865 val_target_max = %(val_target_max)s,
866 val_target_range = nullif(trim(%(val_target_range)s), ''),
867 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
868 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
869 note_test_org = nullif(trim(%(note_test_org)s), ''),
870 material = nullif(trim(%(material)s), ''),
871 material_detail = nullif(trim(%(material_detail)s), ''),
872 fk_intended_reviewer = %(pk_intended_reviewer)s,
873 fk_encounter = %(pk_encounter)s,
874 fk_episode = %(pk_episode)s,
875 fk_type = %(pk_test_type)s,
876 fk_request = %(pk_request)s
877 where
878 pk = %(pk_test_result)s and
879 xmin = %(xmin_test_result)s""",
880 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
881 ]
882
883 _updatable_fields = [
884 'clin_when',
885 'comment',
886 'val_num',
887 'val_alpha',
888 'val_unit',
889 'val_normal_min',
890 'val_normal_max',
891 'val_normal_range',
892 'val_target_min',
893 'val_target_max',
894 'val_target_range',
895 'abnormality_indicator',
896 'norm_ref_group',
897 'note_test_org',
898 'material',
899 'material_detail',
900 'pk_intended_reviewer',
901 'pk_encounter',
902 'pk_episode',
903 'pk_test_type',
904 'pk_request'
905 ]
906
1177
1179 """Returns the closest test result which does have normal range information."""
1180 if self._payload[self._idx['val_normal_min']] is not None:
1181 return self
1182 if self._payload[self._idx['val_normal_max']] is not None:
1183 return self
1184 if self._payload[self._idx['val_normal_range']] is not None:
1185 return self
1186 cmd = u"""
1187 SELECT * from clin.v_test_results
1188 WHERE
1189 pk_type = %(pk_type)s
1190 AND
1191 val_unit = %(unit)s
1192 AND
1193 (
1194 (val_normal_min IS NOT NULL)
1195 OR
1196 (val_normal_max IS NOT NULL)
1197 OR
1198 (val_normal_range IS NOT NULL)
1199 )
1200 ORDER BY
1201 CASE
1202 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
1203 ELSE %(clin_when)s - clin_when
1204 END
1205 LIMIT 1"""
1206 args = {
1207 u'pk_type': self._payload[self._idx['pk_test_type']],
1208 u'unit': self._payload[self._idx['val_unit']],
1209 u'clin_when': self._payload[self._idx['clin_when']]
1210 }
1211 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1212 if len(rows) == 0:
1213 return None
1214 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1215
1216 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x)
1217
1277
1278 formatted_range = property(_get_formatted_range, lambda x:x)
1279
1281 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1282
1283 test_type = property(_get_test_type, lambda x:x)
1284
1286
1287 if self._payload[self._idx['is_technically_abnormal']] is False:
1288 return False
1289
1290 indicator = self._payload[self._idx['abnormality_indicator']]
1291 if indicator is not None:
1292 indicator = indicator.strip()
1293 if indicator != u'':
1294 if indicator.strip(u'+') == u'':
1295 return True
1296 if indicator.strip(u'-') == u'':
1297 return False
1298
1299 if self._payload[self._idx['val_num']] is None:
1300 return None
1301
1302 target_max = self._payload[self._idx['val_target_max']]
1303 if target_max is not None:
1304 if target_max < self._payload[self._idx['val_num']]:
1305 return True
1306
1307 normal_max = self._payload[self._idx['val_normal_max']]
1308 if normal_max is not None:
1309 if normal_max < self._payload[self._idx['val_num']]:
1310 return True
1311 return None
1312
1313 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x)
1314
1316
1317 if self._payload[self._idx['is_technically_abnormal']] is False:
1318 return False
1319
1320 indicator = self._payload[self._idx['abnormality_indicator']]
1321 if indicator is not None:
1322 indicator = indicator.strip()
1323 if indicator != u'':
1324 if indicator.strip(u'+') == u'':
1325 return False
1326 if indicator.strip(u'-') == u'':
1327 return True
1328
1329 if self._payload[self._idx['val_num']] is None:
1330 return None
1331
1332 target_min = self._payload[self._idx['val_target_min']]
1333 if target_min is not None:
1334 if target_min > self._payload[self._idx['val_num']]:
1335 return True
1336
1337 normal_min = self._payload[self._idx['val_normal_min']]
1338 if normal_min is not None:
1339 if normal_min > self._payload[self._idx['val_num']]:
1340 return True
1341 return None
1342
1343 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x)
1344
1353
1354 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x)
1355
1392
1393 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x)
1394
1396 if self._payload[self._idx['val_alpha']] is None:
1397 return False
1398 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = u'\n', return_list = True)
1399 if len(lines) > 4:
1400 return True
1401 return False
1402
1403 is_long_text = property(_get_is_long_text, lambda x:x)
1404
1406 if self._payload[self._idx['val_alpha']] is None:
1407 return None
1408 val = self._payload[self._idx['val_alpha']].lstrip()
1409 if val[0] == u'<':
1410 factor = decimal.Decimal(0.5)
1411 val = val[1:]
1412 elif val[0] == u'>':
1413 factor = 2
1414 val = val[1:]
1415 else:
1416 return None
1417 success, val = gmTools.input2decimal(initial = val)
1418 if not success:
1419 return None
1420 return val * factor
1421
1422 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x)
1423
1424 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1425
1426
1427 if self._payload[self._idx['reviewed']]:
1428 self.__change_existing_review (
1429 technically_abnormal = technically_abnormal,
1430 clinically_relevant = clinically_relevant,
1431 comment = comment
1432 )
1433 else:
1434
1435
1436 if technically_abnormal is None:
1437 if clinically_relevant is None:
1438 comment = gmTools.none_if(comment, u'', strip_string = True)
1439 if comment is None:
1440 if make_me_responsible is False:
1441 return True
1442 self.__set_new_review (
1443 technically_abnormal = technically_abnormal,
1444 clinically_relevant = clinically_relevant,
1445 comment = comment
1446 )
1447
1448 if make_me_responsible is True:
1449 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
1450 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
1451 self['pk_intended_reviewer'] = rows[0][0]
1452 self.save_payload()
1453 return
1454
1455 self.refetch_payload()
1456
1457 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1458
1459 if desired_earlier_results < 1:
1460 raise ValueError('<desired_earlier_results> must be > 0')
1461
1462 if desired_later_results < 1:
1463 raise ValueError('<desired_later_results> must be > 0')
1464
1465 args = {
1466 'pat': self._payload[self._idx['pk_patient']],
1467 'ttyp': self._payload[self._idx['pk_test_type']],
1468 'tloinc': self._payload[self._idx['loinc_tt']],
1469 'mtyp': self._payload[self._idx['pk_meta_test_type']],
1470 'mloinc': self._payload[self._idx['loinc_meta']],
1471 'when': self._payload[self._idx['clin_when']],
1472 'offset': max_offset
1473 }
1474 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
1475 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
1476 if max_offset is not None:
1477 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1478 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1479
1480 SQL = u"""
1481 SELECT * FROM clin.v_test_results
1482 WHERE
1483 pk_patient = %%(pat)s
1484 AND
1485 clin_when %s %%(when)s
1486 AND
1487 %s
1488 ORDER BY clin_when
1489 LIMIT %s"""
1490
1491
1492 earlier_results = []
1493
1494 cmd = SQL % (u'<', WHERE, desired_earlier_results)
1495 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1496 if len(rows) > 0:
1497 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1498
1499 missing_results = desired_earlier_results - len(earlier_results)
1500 if missing_results > 0:
1501 cmd = SQL % (u'<', WHERE_meta, missing_results)
1502 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1503 if len(rows) > 0:
1504 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1505
1506
1507 later_results = []
1508
1509 cmd = SQL % (u'>', WHERE, desired_later_results)
1510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1511 if len(rows) > 0:
1512 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1513
1514 missing_results = desired_later_results - len(later_results)
1515 if missing_results > 0:
1516 cmd = SQL % (u'>', WHERE_meta, missing_results)
1517 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1518 if len(rows) > 0:
1519 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1520
1521 return earlier_results, later_results
1522
1523
1524
1525 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1526 """Add a review to a row.
1527
1528 - if technically abnormal is not provided/None it will be set
1529 to True if the lab's indicator has a meaningful value
1530 - if clinically relevant is not provided/None it is set to
1531 whatever technically abnormal is
1532 """
1533 if technically_abnormal is None:
1534 technically_abnormal = False
1535 if self._payload[self._idx['abnormality_indicator']] is not None:
1536 if self._payload[self._idx['abnormality_indicator']].strip() != u'':
1537 technically_abnormal = True
1538
1539 if clinically_relevant is None:
1540 clinically_relevant = technically_abnormal
1541
1542 cmd = u"""
1543 INSERT INTO clin.reviewed_test_results (
1544 fk_reviewed_row,
1545 is_technically_abnormal,
1546 clinically_relevant,
1547 comment
1548 ) VALUES (
1549 %(pk)s,
1550 %(abnormal)s,
1551 %(relevant)s,
1552 gm.nullify_empty_string(%(cmt)s)
1553 )"""
1554 args = {
1555 'pk': self._payload[self._idx['pk_test_result']],
1556 'abnormal': technically_abnormal,
1557 'relevant': clinically_relevant,
1558 'cmt': comment
1559 }
1560
1561 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1562
1564 """Change a review on a row.
1565
1566 - if technically abnormal/clinically relevant are
1567 None they are not set
1568 """
1569 args = {
1570 'pk_row': self._payload[self._idx['pk_test_result']],
1571 'abnormal': technically_abnormal,
1572 'relevant': clinically_relevant,
1573 'cmt': comment
1574 }
1575
1576 set_parts = [
1577 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
1578 u'comment = gm.nullify_empty_string(%(cmt)s)'
1579 ]
1580
1581 if technically_abnormal is not None:
1582 set_parts.append(u'is_technically_abnormal = %(abnormal)s')
1583
1584 if clinically_relevant is not None:
1585 set_parts.append(u'clinically_relevant = %(relevant)s')
1586
1587 cmd = u"""
1588 UPDATE clin.reviewed_test_results SET
1589 %s
1590 WHERE
1591 fk_reviewed_row = %%(pk_row)s
1592 """ % u',\n '.join(set_parts)
1593
1594 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1595
1596
1597 -def get_test_results(pk_patient=None, encounters=None, episodes=None, order_by=None):
1598
1599 where_parts = []
1600
1601 if pk_patient is not None:
1602 where_parts.append(u'pk_patient = %(pat)s')
1603 args = {'pat': pk_patient}
1604
1605
1606
1607
1608
1609 if encounters is not None:
1610 where_parts.append(u'pk_encounter IN %(encs)s')
1611 args['encs'] = tuple(encounters)
1612
1613 if episodes is not None:
1614 where_parts.append(u'pk_episode IN %(epis)s')
1615 args['epis'] = tuple(episodes)
1616
1617 if order_by is None:
1618 order_by = u''
1619 else:
1620 order_by = u'ORDER BY %s' % order_by
1621
1622 cmd = u"""
1623 SELECT * FROM clin.v_test_results
1624 WHERE %s
1625 %s
1626 """ % (
1627 u' AND '.join(where_parts),
1628 order_by
1629 )
1630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1631
1632 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1633 return tests
1634
1635
1637
1638 if order_by is None:
1639 order_by = u''
1640 else:
1641 order_by = u'ORDER BY %s' % order_by
1642
1643 args = {
1644 'pat': pk_patient,
1645 'pnl': pk_panel
1646 }
1647 cmd = u"""
1648 SELECT c_vtr.*
1649 FROM (
1650 -- max(clin_when) per test_type-in-panel for patient
1651 SELECT
1652 pk_test_type,
1653 MAX(clin_when) AS max_clin_when
1654 FROM clin.v_test_results
1655 WHERE
1656 pk_patient = %%(pat)s
1657 AND
1658 pk_test_type = ANY (
1659 (SELECT fk_test_types FROM clin.test_panel WHERE pk = %%(pnl)s)::int[]
1660 )
1661 GROUP BY pk_test_type
1662 ) AS latest_results
1663 INNER JOIN clin.v_test_results c_vtr ON c_vtr.pk_test_type = latest_results.pk_test_type AND c_vtr.clin_when = latest_results.max_clin_when
1664 %s
1665 """ % order_by
1666 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1667 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1668 return tests
1669
1670
1672
1673 if None not in [test_type, loinc]:
1674 raise ValueError('either <test_type> or <loinc> must be None')
1675
1676 args = {
1677 'pat': patient,
1678 'ttyp': test_type,
1679 'loinc': loinc,
1680 'ts': timestamp,
1681 'intv': tolerance_interval
1682 }
1683
1684 where_parts = [u'pk_patient = %(pat)s']
1685 if test_type is not None:
1686 where_parts.append(u'pk_test_type = %(ttyp)s')
1687 elif loinc is not None:
1688 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1689 args['loinc'] = tuple(loinc)
1690
1691 if tolerance_interval is None:
1692 where_parts.append(u'clin_when = %(ts)s')
1693 else:
1694 where_parts.append(u'clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)')
1695
1696 cmd = u"""
1697 SELECT * FROM clin.v_test_results
1698 WHERE
1699 %s
1700 ORDER BY
1701 abs(extract(epoch from age(clin_when, %%(ts)s)))
1702 LIMIT 1""" % u' AND '.join(where_parts)
1703
1704 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1705 if len(rows) == 0:
1706 return None
1707
1708 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1709
1710
1712
1713 if None not in [test_type, loinc]:
1714 raise ValueError('either <test_type> or <loinc> must be None')
1715
1716 if no_of_results < 1:
1717 raise ValueError('<no_of_results> must be > 0')
1718
1719 args = {
1720 'pat': patient,
1721 'ttyp': test_type,
1722 'loinc': loinc
1723 }
1724
1725 where_parts = [u'pk_patient = %(pat)s']
1726 if test_type is not None:
1727 where_parts.append(u'pk_test_type = %(ttyp)s')
1728 elif loinc is not None:
1729 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1730 args['loinc'] = tuple(loinc)
1731
1732 cmd = u"""
1733 SELECT * FROM clin.v_test_results
1734 WHERE
1735 %s
1736 ORDER BY clin_when DESC
1737 LIMIT %s""" % (
1738 u' AND '.join(where_parts),
1739 no_of_results
1740 )
1741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1742 if len(rows) == 0:
1743 return None
1744
1745 if no_of_results == 1:
1746 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1747
1748 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1749
1750
1752
1753 if None not in [test_type, loinc]:
1754 raise ValueError('either <test_type> or <loinc> must be None')
1755
1756 args = {
1757 'pat': patient,
1758 'ttyp': test_type,
1759 'loinc': loinc
1760 }
1761
1762 where_parts = [u'pk_patient = %(pat)s']
1763 if test_type is not None:
1764 where_parts.append(u'pk_test_type = %(ttyp)s')
1765 elif loinc is not None:
1766 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1767 args['loinc'] = tuple(loinc)
1768
1769 cmd = u"""
1770 SELECT * FROM clin.v_test_results
1771 WHERE
1772 %s
1773 ORDER BY clin_when
1774 LIMIT 1""" % u' AND '.join(where_parts)
1775 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1776 if len(rows) == 0:
1777 return None
1778
1779 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1780
1781
1783 try:
1784 pk = int(result)
1785 except (TypeError, AttributeError):
1786 pk = result['pk_test_result']
1787
1788 cmd = u'DELETE FROM clin.test_result WHERE pk = %(pk)s'
1789 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1790
1791
1792 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1793
1794 cmd1 = u"""
1795 insert into clin.test_result (
1796 fk_encounter,
1797 fk_episode,
1798 fk_type,
1799 fk_intended_reviewer,
1800 val_num,
1801 val_alpha,
1802 val_unit
1803 ) values (
1804 %(enc)s,
1805 %(epi)s,
1806 %(type)s,
1807 %(rev)s,
1808 %(v_num)s,
1809 %(v_alpha)s,
1810 %(unit)s
1811 )"""
1812
1813 cmd2 = u"""
1814 select *
1815 from
1816 clin.v_test_results
1817 where
1818 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
1819
1820 args = {
1821 u'enc': encounter,
1822 u'epi': episode,
1823 u'type': type,
1824 u'rev': intended_reviewer,
1825 u'v_num': val_num,
1826 u'v_alpha': val_alpha,
1827 u'unit': unit
1828 }
1829
1830 rows, idx = gmPG2.run_rw_queries (
1831 queries = [
1832 {'cmd': cmd1, 'args': args},
1833 {'cmd': cmd2}
1834 ],
1835 return_data = True,
1836 get_col_idx = True
1837 )
1838
1839 tr = cTestResult(row = {
1840 'pk_field': 'pk_test_result',
1841 'idx': idx,
1842 'data': rows[0]
1843 })
1844
1845 return tr
1846
1847
1858
1859
1860 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
1861
1862 if len(results) == 0:
1863 return u'\\begin{minipage}{%s} \\end{minipage}' % width
1864
1865 lines = []
1866 for t in results:
1867
1868 tmp = u''
1869
1870 if show_time:
1871 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
1872
1873 tmp += u'%.8s' % t['unified_val']
1874
1875 lines.append(tmp)
1876 tmp = u''
1877
1878 if show_range:
1879 has_range = (
1880 t['unified_target_range'] is not None
1881 or
1882 t['unified_target_min'] is not None
1883 or
1884 t['unified_target_max'] is not None
1885 )
1886 if has_range:
1887 if t['unified_target_range'] is not None:
1888 tmp += u'{\\tiny %s}' % t['unified_target_range']
1889 else:
1890 tmp += u'{\\tiny %s}' % (
1891 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
1892 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1893 )
1894 lines.append(tmp)
1895
1896 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
1897
1898
1900
1901 if len(results) == 0:
1902 return u''
1903
1904 lines = []
1905 for t in results:
1906
1907 tmp = u''
1908
1909 if show_time:
1910 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
1911
1912 tmp += u'\\normalsize %.8s' % t['unified_val']
1913
1914 lines.append(tmp)
1915 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
1916
1917 if not show_range:
1918 lines.append(tmp)
1919 continue
1920
1921 has_range = (
1922 t['unified_target_range'] is not None
1923 or
1924 t['unified_target_min'] is not None
1925 or
1926 t['unified_target_max'] is not None
1927 )
1928
1929 if not has_range:
1930 lines.append(tmp)
1931 continue
1932
1933 if t['unified_target_range'] is not None:
1934 tmp += u'[%s]' % t['unified_target_range']
1935 else:
1936 tmp += u'[%s%s]' % (
1937 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
1938 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1939 )
1940 lines.append(tmp)
1941
1942 return u' \\\\ '.join(lines)
1943
1944
2020
2021
2023
2024 if filename is None:
2025 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
2026
2027
2028 series = {}
2029 for r in results:
2030 try:
2031 series[r['unified_name']].append(r)
2032 except KeyError:
2033 series[r['unified_name']] = [r]
2034
2035 gp_data = codecs.open(filename, 'wb', 'utf8')
2036
2037 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
2038 gp_data.write(u'# -------------------------------------------------------------\n')
2039 gp_data.write(u'# first line of index: test type abbreviation & name\n')
2040 gp_data.write(u'#\n')
2041 gp_data.write(u'# clin_when at full precision\n')
2042 gp_data.write(u'# value\n')
2043 gp_data.write(u'# unit\n')
2044 gp_data.write(u'# unified (target or normal) range: lower bound\n')
2045 gp_data.write(u'# unified (target or normal) range: upper bound\n')
2046 gp_data.write(u'# normal range: lower bound\n')
2047 gp_data.write(u'# normal range: upper bound\n')
2048 gp_data.write(u'# target range: lower bound\n')
2049 gp_data.write(u'# target range: upper bound\n')
2050 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
2051 gp_data.write(u'# -------------------------------------------------------------\n')
2052
2053 for test_type in series.keys():
2054 if len(series[test_type]) == 0:
2055 continue
2056
2057 r = series[test_type][0]
2058 title = u'%s (%s)' % (
2059 r['unified_abbrev'],
2060 r['unified_name']
2061 )
2062 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
2063
2064 prev_date = None
2065 prev_year = None
2066 for r in series[test_type]:
2067 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days)
2068 if curr_date == prev_date:
2069 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values'))
2070 if show_year:
2071 if r['clin_when'].year == prev_year:
2072 when_template = '%b %d %H:%M'
2073 else:
2074 when_template = '%b %d %H:%M (%Y)'
2075 prev_year = r['clin_when'].year
2076 else:
2077 when_template = '%b %d'
2078 val = r['val_num']
2079 if val is None:
2080 val = r.estimate_numeric_value_from_alpha
2081 if val is None:
2082 continue
2083 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
2084
2085 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes),
2086 val,
2087 gmTools.coalesce(r['val_unit'], u'"<?>"'),
2088 gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
2089 gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
2090 gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
2091 gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
2092 gmTools.coalesce(r['val_target_min'], u'"<?>"'),
2093 gmTools.coalesce(r['val_target_max'], u'"<?>"'),
2094 gmDateTime.pydt_strftime (
2095 r['clin_when'],
2096 format = when_template,
2097 accuracy = gmDateTime.acc_minutes
2098 )
2099 ))
2100 prev_date = curr_date
2101
2102 gp_data.close()
2103
2104 return filename
2105
2106
2107 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
2108 """Represents one lab result."""
2109
2110 _cmd_fetch_payload = """
2111 select *, xmin_test_result from v_results4lab_req
2112 where pk_result=%s"""
2113 _cmds_lock_rows_for_update = [
2114 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
2115 ]
2116 _cmds_store_payload = [
2117 """update test_result set
2118 clin_when = %(val_when)s,
2119 narrative = %(progress_note_result)s,
2120 fk_type = %(pk_test_type)s,
2121 val_num = %(val_num)s::numeric,
2122 val_alpha = %(val_alpha)s,
2123 val_unit = %(val_unit)s,
2124 val_normal_min = %(val_normal_min)s,
2125 val_normal_max = %(val_normal_max)s,
2126 val_normal_range = %(val_normal_range)s,
2127 val_target_min = %(val_target_min)s,
2128 val_target_max = %(val_target_max)s,
2129 val_target_range = %(val_target_range)s,
2130 abnormality_indicator = %(abnormal)s,
2131 norm_ref_group = %(ref_group)s,
2132 note_provider = %(note_provider)s,
2133 material = %(material)s,
2134 material_detail = %(material_detail)s
2135 where pk = %(pk_result)s""",
2136 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
2137 ]
2138
2139 _updatable_fields = [
2140 'val_when',
2141 'progress_note_result',
2142 'val_num',
2143 'val_alpha',
2144 'val_unit',
2145 'val_normal_min',
2146 'val_normal_max',
2147 'val_normal_range',
2148 'val_target_min',
2149 'val_target_max',
2150 'val_target_range',
2151 'abnormal',
2152 'ref_group',
2153 'note_provider',
2154 'material',
2155 'material_detail'
2156 ]
2157
2158 - def __init__(self, aPK_obj=None, row=None):
2159 """Instantiate.
2160
2161 aPK_obj as dict:
2162 - patient_id
2163 - when_field (see view definition)
2164 - when
2165 - test_type
2166 - val_num
2167 - val_alpha
2168 - unit
2169 """
2170
2171 if aPK_obj is None:
2172 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2173 return
2174 pk = aPK_obj
2175
2176 if type(aPK_obj) == types.DictType:
2177
2178 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
2179 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
2180 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
2181 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
2182
2183 where_snippets = [
2184 'pk_patient=%(patient_id)s',
2185 'pk_test_type=%(test_type)s',
2186 '%s=%%(when)s' % aPK_obj['when_field'],
2187 'val_unit=%(unit)s'
2188 ]
2189 if aPK_obj['val_num'] is not None:
2190 where_snippets.append('val_num=%(val_num)s::numeric')
2191 if aPK_obj['val_alpha'] is not None:
2192 where_snippets.append('val_alpha=%(val_alpha)s')
2193
2194 where_clause = ' and '.join(where_snippets)
2195 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
2196 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2197 if data is None:
2198 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
2199 if len(data) == 0:
2200 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
2201 pk = data[0][0]
2202
2203 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2204
2206 cmd = """
2207 select
2208 %s,
2209 vbp.title,
2210 vbp.firstnames,
2211 vbp.lastnames,
2212 vbp.dob
2213 from v_basic_person vbp
2214 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
2215 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
2216 return pat[0]
2217
2218 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
2219 """Represents one lab request."""
2220
2221 _cmd_fetch_payload = """
2222 select *, xmin_lab_request from v_lab_requests
2223 where pk_request=%s"""
2224 _cmds_lock_rows_for_update = [
2225 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
2226 ]
2227 _cmds_store_payload = [
2228 """update lab_request set
2229 request_id=%(request_id)s,
2230 lab_request_id=%(lab_request_id)s,
2231 clin_when=%(sampled_when)s,
2232 lab_rxd_when=%(lab_rxd_when)s,
2233 results_reported_when=%(results_reported_when)s,
2234 request_status=%(request_status)s,
2235 is_pending=%(is_pending)s::bool,
2236 narrative=%(progress_note)s
2237 where pk=%(pk_request)s""",
2238 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
2239 ]
2240 _updatable_fields = [
2241 'request_id',
2242 'lab_request_id',
2243 'sampled_when',
2244 'lab_rxd_when',
2245 'results_reported_when',
2246 'request_status',
2247 'is_pending',
2248 'progress_note'
2249 ]
2250
2251 - def __init__(self, aPK_obj=None, row=None):
2252 """Instantiate lab request.
2253
2254 The aPK_obj can be either a dict with the keys "req_id"
2255 and "lab" or a simple primary key.
2256 """
2257
2258 if aPK_obj is None:
2259 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2260 return
2261 pk = aPK_obj
2262
2263 if type(aPK_obj) == types.DictType:
2264
2265 try:
2266 aPK_obj['req_id']
2267 aPK_obj['lab']
2268 except:
2269 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
2270 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
2271
2272 where_snippets = []
2273 vals = {}
2274 where_snippets.append('request_id=%(req_id)s')
2275 if type(aPK_obj['lab']) == types.IntType:
2276 where_snippets.append('pk_test_org=%(lab)s')
2277 else:
2278 where_snippets.append('lab_name=%(lab)s')
2279 where_clause = ' and '.join(where_snippets)
2280 cmd = "select pk_request from v_lab_requests where %s" % where_clause
2281
2282 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2283 if data is None:
2284 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
2285 if len(data) == 0:
2286 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
2287 pk = data[0][0]
2288
2289 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2290
2292 cmd = """
2293 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
2294 from v_pat_items vpi, v_basic_person vbp
2295 where
2296 vpi.pk_item=%s
2297 and
2298 vbp.pk_identity=vpi.pk_patient"""
2299 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
2300 if pat is None:
2301 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
2302 return None
2303 if len(pat) == 0:
2304 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
2305 return None
2306 return pat[0]
2307
2308
2309
2310 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2311 """Create or get lab request.
2312
2313 returns tuple (status, value):
2314 (True, lab request instance)
2315 (False, error message)
2316 (None, housekeeping_todo primary key)
2317 """
2318 req = None
2319 aPK_obj = {
2320 'lab': lab,
2321 'req_id': req_id
2322 }
2323 try:
2324 req = cLabRequest (aPK_obj)
2325 except gmExceptions.NoSuchClinItemError, msg:
2326 _log.info('%s: will try to create lab request' % str(msg))
2327 except gmExceptions.ConstructorError, msg:
2328 _log.exception(str(msg), sys.exc_info(), verbose=0)
2329 return (False, msg)
2330
2331 if req is not None:
2332 db_pat = req.get_patient()
2333 if db_pat is None:
2334 _log.error('cannot cross-check patient on lab request')
2335 return (None, '')
2336
2337 if pat_id != db_pat[0]:
2338 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
2339 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
2340 to = 'user'
2341 prob = _('The lab request already exists but belongs to a different patient.')
2342 sol = _('Verify which patient this lab request really belongs to.')
2343 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
2344 cat = 'lab'
2345 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
2346 return (None, data)
2347 return (True, req)
2348
2349 queries = []
2350 if type(lab) is types.IntType:
2351 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
2352 else:
2353 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)"
2354 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
2355 cmd = "select currval('lab_request_pk_seq')"
2356 queries.append((cmd, []))
2357
2358 result, err = gmPG.run_commit('historica', queries, True)
2359 if result is None:
2360 return (False, err)
2361 try:
2362 req = cLabRequest(aPK_obj=result[0][0])
2363 except gmExceptions.ConstructorError, msg:
2364 _log.exception(str(msg), sys.exc_info(), verbose=0)
2365 return (False, msg)
2366 return (True, req)
2367
2368 -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):
2369 tres = None
2370 data = {
2371 'patient_id': patient_id,
2372 'when_field': when_field,
2373 'when': when,
2374 'test_type': test_type,
2375 'val_num': val_num,
2376 'val_alpha': val_alpha,
2377 'unit': unit
2378 }
2379 try:
2380 tres = cLabResult(aPK_obj=data)
2381
2382 _log.error('will not overwrite existing test result')
2383 _log.debug(str(tres))
2384 return (None, tres)
2385 except gmExceptions.NoSuchClinItemError:
2386 _log.debug('test result not found - as expected, will create it')
2387 except gmExceptions.ConstructorError, msg:
2388 _log.exception(str(msg), sys.exc_info(), verbose=0)
2389 return (False, msg)
2390 if request is None:
2391 return (False, _('need lab request when inserting lab result'))
2392
2393 if encounter_id is None:
2394 encounter_id = request['pk_encounter']
2395 queries = []
2396 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
2397 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
2398 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
2399 queries.append((cmd, [request['pk_request']]))
2400 cmd = "select currval('test_result_pk_seq')"
2401 queries.append((cmd, []))
2402
2403 result, err = gmPG.run_commit('historica', queries, True)
2404 if result is None:
2405 return (False, err)
2406 try:
2407 tres = cLabResult(aPK_obj=result[0][0])
2408 except gmExceptions.ConstructorError, msg:
2409 _log.exception(str(msg), sys.exc_info(), verbose=0)
2410 return (False, msg)
2411 return (True, tres)
2412
2414
2415 if limit < 1:
2416 limit = 1
2417
2418 lim = limit + 1
2419 cmd = """
2420 select pk_result
2421 from v_results4lab_req
2422 where reviewed is false
2423 order by pk_patient
2424 limit %s""" % lim
2425 rows = gmPG.run_ro_query('historica', cmd)
2426 if rows is None:
2427 _log.error('error retrieving unreviewed lab results')
2428 return (None, _('error retrieving unreviewed lab results'))
2429 if len(rows) == 0:
2430 return (False, [])
2431
2432 if len(rows) == lim:
2433 more_avail = True
2434
2435 del rows[limit]
2436 else:
2437 more_avail = False
2438 results = []
2439 for row in rows:
2440 try:
2441 results.append(cLabResult(aPK_obj=row[0]))
2442 except gmExceptions.ConstructorError:
2443 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
2444 return (more_avail, results)
2445
2447 lim = limit + 1
2448 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
2449 rows = gmPG.run_ro_query('historica', cmd)
2450 if rows is None:
2451 _log.error('error retrieving pending lab requests')
2452 return (None, None)
2453 if len(rows) == 0:
2454 return (False, [])
2455 results = []
2456
2457 if len(rows) == lim:
2458 too_many = True
2459
2460 del rows[limit]
2461 else:
2462 too_many = False
2463 requests = []
2464 for row in rows:
2465 try:
2466 requests.append(cLabRequest(aPK_obj=row[0]))
2467 except gmExceptions.ConstructorError:
2468 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
2469 return (too_many, requests)
2470
2472 """Get logically next request ID for given lab.
2473
2474 - incrementor_func:
2475 - if not supplied the next ID is guessed
2476 - if supplied it is applied to the most recently used ID
2477 """
2478 if type(lab) == types.IntType:
2479 lab_snippet = 'vlr.fk_test_org=%s'
2480 else:
2481 lab_snippet = 'vlr.lab_name=%s'
2482 lab = str(lab)
2483 cmd = """
2484 select request_id
2485 from lab_request lr0
2486 where lr0.clin_when = (
2487 select max(vlr.sampled_when)
2488 from v_lab_requests vlr
2489 where %s
2490 )""" % lab_snippet
2491 rows = gmPG.run_ro_query('historica', cmd, None, lab)
2492 if rows is None:
2493 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
2494 return ''
2495 if len(rows) == 0:
2496 return ''
2497 most_recent = rows[0][0]
2498
2499 if incrementor_func is not None:
2500 try:
2501 next = incrementor_func(most_recent)
2502 except TypeError:
2503 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
2504 return most_recent
2505 return next
2506
2507 for pos in range(len(most_recent)):
2508 header = most_recent[:pos]
2509 trailer = most_recent[pos:]
2510 try:
2511 return '%s%s' % (header, str(int(trailer) + 1))
2512 except ValueError:
2513 header = most_recent[:-1]
2514 trailer = most_recent[-1:]
2515 return '%s%s' % (header, chr(ord(trailer) + 1))
2516
2518 """Calculate BMI.
2519
2520 mass: kg
2521 height: cm
2522 age: not yet used
2523
2524 returns:
2525 (True/False, data)
2526 True: data = (bmi, lower_normal, upper_normal)
2527 False: data = error message
2528 """
2529 converted, mass = gmTools.input2decimal(mass)
2530 if not converted:
2531 return False, u'mass: cannot convert <%s> to Decimal' % mass
2532
2533 converted, height = gmTools.input2decimal(height)
2534 if not converted:
2535 return False, u'height: cannot convert <%s> to Decimal' % height
2536
2537 approx_surface = (height / decimal.Decimal(100))**2
2538 bmi = mass / approx_surface
2539
2540 print mass, height, '->', approx_surface, '->', bmi
2541
2542 lower_normal_mass = 20.0 * approx_surface
2543 upper_normal_mass = 25.0 * approx_surface
2544
2545 return True, (bmi, lower_normal_mass, upper_normal_mass)
2546
2547
2548
2549 if __name__ == '__main__':
2550
2551 if len(sys.argv) < 2:
2552 sys.exit()
2553
2554 if sys.argv[1] != 'test':
2555 sys.exit()
2556
2557 import time
2558
2559 gmI18N.activate_locale()
2560 gmI18N.install_domain()
2561
2562
2564 tr = create_test_result (
2565 encounter = 1,
2566 episode = 1,
2567 type = 1,
2568 intended_reviewer = 1,
2569 val_num = '12',
2570 val_alpha=None,
2571 unit = 'mg/dl'
2572 )
2573 print tr
2574 return tr
2575
2579
2587
2589 print "test_result()"
2590
2591 data = {
2592 'patient_id': 12,
2593 'when_field': 'val_when',
2594 'when': '2000-09-17 18:23:00+02',
2595 'test_type': 9,
2596 'val_num': 17.3,
2597 'val_alpha': None,
2598 'unit': 'mg/l'
2599 }
2600 lab_result = cLabResult(aPK_obj=data)
2601 print lab_result
2602 fields = lab_result.get_fields()
2603 for field in fields:
2604 print field, ':', lab_result[field]
2605 print "updatable:", lab_result.get_updatable_fields()
2606 print time.time()
2607 print lab_result.get_patient()
2608 print time.time()
2609
2611 print "test_request()"
2612 try:
2613
2614
2615 data = {
2616 'req_id': 'EML#SC937-0176-CEC#11',
2617 'lab': 'Enterprise Main Lab'
2618 }
2619 lab_req = cLabRequest(aPK_obj=data)
2620 except gmExceptions.ConstructorError, msg:
2621 print "no such lab request:", msg
2622 return
2623 print lab_req
2624 fields = lab_req.get_fields()
2625 for field in fields:
2626 print field, ':', lab_req[field]
2627 print "updatable:", lab_req.get_updatable_fields()
2628 print time.time()
2629 print lab_req.get_patient()
2630 print time.time()
2631
2636
2641
2649
2654
2659
2668
2670 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
2671 bmi, low, high = data
2672
2673 print "BMI:", bmi
2674 print "low:", low, "kg"
2675 print "hi :", high, "kg"
2676
2681
2686
2687
2688 test_result()
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705