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
32
33 _log = logging.getLogger('gm.lab')
34
35
39
40 gmDispatcher.connect(_on_test_result_modified, u'test_result_mod_db')
41
42
43 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
44 """Represents one test org/lab."""
45 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
46 _cmds_store_payload = [
47 u"""UPDATE clin.test_org SET
48 fk_org_unit = %(pk_org_unit)s,
49 contact = gm.nullify_empty_string(%(test_org_contact)s),
50 comment = gm.nullify_empty_string(%(comment)s)
51 WHERE
52 pk = %(pk_test_org)s
53 AND
54 xmin = %(xmin_test_org)s
55 RETURNING
56 xmin AS xmin_test_org
57 """
58 ]
59 _updatable_fields = [
60 u'pk_org_unit',
61 u'test_org_contact',
62 u'comment'
63 ]
64
66
67 if name is None:
68 name = _('inhouse lab')
69 comment = _('auto-generated')
70
71
72 if pk_org_unit is None:
73 org = gmOrganization.org_exists(organization = name)
74 if org is None:
75 org = gmOrganization.create_org (
76 organization = name,
77 category = u'Laboratory'
78 )
79 org_unit = gmOrganization.create_org_unit (
80 pk_organization = org['pk_org'],
81 unit = name
82 )
83 pk_org_unit = org_unit['pk_org_unit']
84
85
86 args = {'pk_unit': pk_org_unit}
87 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
89
90 if len(rows) == 0:
91 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
92 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
93
94 test_org = cTestOrg(aPK_obj = rows[0][0])
95 if comment is not None:
96 comment = comment.strip()
97 test_org['comment'] = comment
98 test_org.save()
99
100 return test_org
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
128
133
138
139
141 """Represents one test result type."""
142
143 _cmd_fetch_payload = u"""select * from clin.v_test_types where pk_test_type = %s"""
144
145 _cmds_store_payload = [
146 u"""UPDATE clin.test_type SET
147 abbrev = %(abbrev)s,
148 name = %(name)s,
149 loinc = gm.nullify_empty_string(%(loinc)s),
150 comment = gm.nullify_empty_string(%(comment_type)s),
151 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
152 fk_test_org = %(pk_test_org)s,
153 fk_meta_test_type = %(pk_meta_test_type)s
154 WHERE
155 pk = %(pk_test_type)s
156 AND
157 xmin = %(xmin_test_type)s
158 RETURNING
159 xmin AS xmin_test_type"""
160 ]
161
162 _updatable_fields = [
163 'abbrev',
164 'name',
165 'loinc',
166 'comment_type',
167 'conversion_unit',
168 'pk_test_org',
169 'pk_meta_test_type'
170 ]
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
188 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
189 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
190 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
191 return rows[0][0]
192
193 in_use = property(_get_in_use, lambda x:x)
194
211
254
255
257 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
258 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
259 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
260
262
263 if (abbrev is None) and (name is None):
264 raise ValueError('must have <abbrev> and/or <name> set')
265
266 where_snippets = []
267
268 if lab is None:
269 where_snippets.append('pk_test_org IS NULL')
270 else:
271 try:
272 int(lab)
273 where_snippets.append('pk_test_org = %(lab)s')
274 except (TypeError, ValueError):
275 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
276
277 if abbrev is not None:
278 where_snippets.append('abbrev = %(abbrev)s')
279
280 if name is not None:
281 where_snippets.append('name = %(name)s')
282
283 where_clause = u' and '.join(where_snippets)
284 cmd = u"select * from clin.v_test_types where %s" % where_clause
285 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
286
287 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
288
289 if len(rows) == 0:
290 return None
291
292 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
293 return tt
294
296 cmd = u'delete from clin.test_type where pk = %(pk)s'
297 args = {'pk': measurement_type}
298 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
299
301 """Create or get test type."""
302
303 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
304
305 if ttype is not None:
306 return ttype
307
308 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
309
310
311 if unit is None:
312 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
313 raise ValueError('need <unit> to create test type')
314
315
316 cols = []
317 val_snippets = []
318 vals = {}
319
320
321 if lab is None:
322 lab = create_test_org()['pk_test_org']
323
324 cols.append('fk_test_org')
325 try:
326 vals['lab'] = int(lab)
327 val_snippets.append('%(lab)s')
328 except:
329 vals['lab'] = lab
330 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
331
332
333 cols.append('abbrev')
334 val_snippets.append('%(abbrev)s')
335 vals['abbrev'] = abbrev
336
337
338 cols.append('conversion_unit')
339 val_snippets.append('%(unit)s')
340 vals['unit'] = unit
341
342
343 if name is not None:
344 cols.append('name')
345 val_snippets.append('%(name)s')
346 vals['name'] = name
347
348 col_clause = u', '.join(cols)
349 val_clause = u', '.join(val_snippets)
350 queries = [
351 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
352 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
353 ]
354 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
355 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
356
357 return ttype
358
359
360 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
361 """Represents one test result."""
362
363 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
364
365 _cmds_store_payload = [
366 u"""update clin.test_result set
367 clin_when = %(clin_when)s,
368 narrative = nullif(trim(%(comment)s), ''),
369 val_num = %(val_num)s,
370 val_alpha = nullif(trim(%(val_alpha)s), ''),
371 val_unit = nullif(trim(%(val_unit)s), ''),
372 val_normal_min = %(val_normal_min)s,
373 val_normal_max = %(val_normal_max)s,
374 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
375 val_target_min = %(val_target_min)s,
376 val_target_max = %(val_target_max)s,
377 val_target_range = nullif(trim(%(val_target_range)s), ''),
378 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
379 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
380 note_test_org = nullif(trim(%(note_test_org)s), ''),
381 material = nullif(trim(%(material)s), ''),
382 material_detail = nullif(trim(%(material_detail)s), ''),
383 fk_intended_reviewer = %(pk_intended_reviewer)s,
384 fk_encounter = %(pk_encounter)s,
385 fk_episode = %(pk_episode)s,
386 fk_type = %(pk_test_type)s,
387 fk_request = %(pk_request)s
388 where
389 pk = %(pk_test_result)s and
390 xmin = %(xmin_test_result)s""",
391 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
392 ]
393
394 _updatable_fields = [
395 'clin_when',
396 'comment',
397 'val_num',
398 'val_alpha',
399 'val_unit',
400 'val_normal_min',
401 'val_normal_max',
402 'val_normal_range',
403 'val_target_min',
404 'val_target_max',
405 'val_target_range',
406 'abnormality_indicator',
407 'norm_ref_group',
408 'note_test_org',
409 'material',
410 'material_detail',
411 'pk_intended_reviewer',
412 'pk_encounter',
413 'pk_episode',
414 'pk_test_type',
415 'pk_request'
416 ]
417
453
455
456 cmd = u"""
457 select
458 distinct on (norm_ref_group_str, val_unit, val_normal_min, val_normal_max, val_normal_range, val_target_min, val_target_max, val_target_range)
459 pk_patient,
460 val_unit,
461 val_normal_min, val_normal_max, val_normal_range,
462 val_target_min, val_target_max, val_target_range,
463 norm_ref_group,
464 coalesce(norm_ref_group, '') as norm_ref_group_str
465 from
466 clin.v_test_results
467 where
468 pk_test_type = %(pk_type)s
469 """
470 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
471 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
472 return rows
473
475 raise AttributeError('[%s]: reference ranges not settable') % self.__class__.__name__
476
477 reference_ranges = property(_get_reference_ranges, _set_reference_ranges)
478
481
482 test_type = property(_get_test_type, lambda x:x)
483
484 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
485
486
487 if self._payload[self._idx['reviewed']]:
488 self.__change_existing_review (
489 technically_abnormal = technically_abnormal,
490 clinically_relevant = clinically_relevant,
491 comment = comment
492 )
493 else:
494
495
496 if technically_abnormal is None:
497 if clinically_relevant is None:
498 comment = gmTools.none_if(comment, u'', strip_string = True)
499 if comment is None:
500 if make_me_responsible is False:
501 return True
502 self.__set_new_review (
503 technically_abnormal = technically_abnormal,
504 clinically_relevant = clinically_relevant,
505 comment = comment
506 )
507
508 if make_me_responsible is True:
509 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
511 self['pk_intended_reviewer'] = rows[0][0]
512 self.save_payload()
513 return
514
515 self.refetch_payload()
516
517 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
518
519 if desired_earlier_results < 1:
520 raise ValueError('<desired_earlier_results> must be > 0')
521
522 if desired_later_results < 1:
523 raise ValueError('<desired_later_results> must be > 0')
524
525 args = {
526 'pat': self._payload[self._idx['pk_patient']],
527 'ttyp': self._payload[self._idx['pk_test_type']],
528 'tloinc': self._payload[self._idx['loinc_tt']],
529 'mtyp': self._payload[self._idx['pk_meta_test_type']],
530 'mloinc': self._payload[self._idx['loinc_meta']],
531 'when': self._payload[self._idx['clin_when']],
532 'offset': max_offset
533 }
534 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
535 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
536 if max_offset is not None:
537 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
538 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
539
540 SQL = u"""
541 SELECT * FROM clin.v_test_results
542 WHERE
543 pk_patient = %%(pat)s
544 AND
545 clin_when %s %%(when)s
546 AND
547 %s
548 ORDER BY clin_when
549 LIMIT %s"""
550
551
552 earlier_results = []
553
554 cmd = SQL % (u'<', WHERE, desired_earlier_results)
555 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
556 if len(rows) > 0:
557 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
558
559 missing_results = desired_earlier_results - len(earlier_results)
560 if missing_results > 0:
561 cmd = SQL % (u'<', WHERE_meta, missing_results)
562 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
563 if len(rows) > 0:
564 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
565
566
567 later_results = []
568
569 cmd = SQL % (u'>', WHERE, desired_later_results)
570 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
571 if len(rows) > 0:
572 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
573
574 missing_results = desired_later_results - len(later_results)
575 if missing_results > 0:
576 cmd = SQL % (u'>', WHERE_meta, missing_results)
577 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
578 if len(rows) > 0:
579 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
580
581 return earlier_results, later_results
582
583
584
585 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
586 """Add a review to a row.
587
588 - if technically abnormal is not provided/None it will be set
589 to True if the lab's indicator has a meaningful value
590 - if clinically relevant is not provided/None it is set to
591 whatever technically abnormal is
592 """
593 if technically_abnormal is None:
594 technically_abnormal = False
595 if self._payload[self._idx['abnormality_indicator']] is not None:
596 if self._payload[self._idx['abnormality_indicator']].strip() != u'':
597 technically_abnormal = True
598
599 if clinically_relevant is None:
600 clinically_relevant = technically_abnormal
601
602 cmd = u"""
603 INSERT INTO clin.reviewed_test_results (
604 fk_reviewed_row,
605 is_technically_abnormal,
606 clinically_relevant,
607 comment
608 ) VALUES (
609 %(pk)s,
610 %(abnormal)s,
611 %(relevant)s,
612 gm.nullify_empty_string(%(cmt)s)
613 )"""
614 args = {
615 'pk': self._payload[self._idx['pk_test_result']],
616 'abnormal': technically_abnormal,
617 'relevant': clinically_relevant,
618 'cmt': comment
619 }
620
621 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
622
624 """Change a review on a row.
625
626 - if technically abnormal/clinically relevant are
627 None they are not set
628 """
629 args = {
630 'pk_row': self._payload[self._idx['pk_test_result']],
631 'abnormal': technically_abnormal,
632 'relevant': clinically_relevant,
633 'cmt': comment
634 }
635
636 set_parts = [
637 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
638 u'comment = gm.nullify_empty_string(%(cmt)s)'
639 ]
640
641 if technically_abnormal is not None:
642 set_parts.append(u'is_technically_abnormal = %(abnormal)s')
643
644 if clinically_relevant is not None:
645 set_parts.append(u'clinically_relevant = %(relevant)s')
646
647 cmd = u"""
648 UPDATE clin.reviewed_test_results SET
649 %s
650 WHERE
651 fk_reviewed_row = %%(pk_row)s
652 """ % u',\n '.join(set_parts)
653
654 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
655
656
658
659 if None not in [test_type, loinc]:
660 raise ValueError('either <test_type> or <loinc> must be None')
661
662 if no_of_results < 1:
663 raise ValueError('<no_of_results> must be > 0')
664
665 args = {
666 'pat': patient,
667 'ttyp': test_type,
668 'loinc': loinc
669 }
670
671 where_parts = [u'pk_patient = %(pat)s']
672 if test_type is not None:
673 where_parts.append(u'pk_test_type = %(ttyp)s')
674 elif loinc is not None:
675 where_parts.append(u'((loinc_tt = %(loinc)s) OR (loinc_meta = %(loinc)s))')
676
677 cmd = u"""
678 SELECT * FROM clin.v_test_results
679 WHERE
680 %s
681 ORDER BY clin_when DESC
682 LIMIT %s""" % (
683 u' AND '.join(where_parts),
684 no_of_results
685 )
686 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
687 if len(rows) == 0:
688 return None
689
690 if no_of_results == 1:
691 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
692
693 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
694
696
697 try:
698 pk = int(result)
699 except (TypeError, AttributeError):
700 pk = result['pk_test_result']
701
702 cmd = u'delete from clin.test_result where pk = %(pk)s'
703 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
704
705 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
706
707 cmd1 = u"""
708 insert into clin.test_result (
709 fk_encounter,
710 fk_episode,
711 fk_type,
712 fk_intended_reviewer,
713 val_num,
714 val_alpha,
715 val_unit
716 ) values (
717 %(enc)s,
718 %(epi)s,
719 %(type)s,
720 %(rev)s,
721 %(v_num)s,
722 %(v_alpha)s,
723 %(unit)s
724 )"""
725
726 cmd2 = u"""
727 select *
728 from
729 clin.v_test_results
730 where
731 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
732
733 args = {
734 u'enc': encounter,
735 u'epi': episode,
736 u'type': type,
737 u'rev': intended_reviewer,
738 u'v_num': val_num,
739 u'v_alpha': val_alpha,
740 u'unit': unit
741 }
742
743 rows, idx = gmPG2.run_rw_queries (
744 queries = [
745 {'cmd': cmd1, 'args': args},
746 {'cmd': cmd2}
747 ],
748 return_data = True,
749 get_col_idx = True
750 )
751
752 tr = cTestResult(row = {
753 'pk_field': 'pk_test_result',
754 'idx': idx,
755 'data': rows[0]
756 })
757
758 return tr
759
770
771 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
772
773 if len(results) == 0:
774 return u'\\begin{minipage}{%s} \\end{minipage}' % width
775
776 lines = []
777 for t in results:
778
779 tmp = u''
780
781 if show_time:
782 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
783
784 tmp += u'%.8s' % t['unified_val']
785
786 lines.append(tmp)
787 tmp = u''
788
789 if show_range:
790 has_range = (
791 t['unified_target_range'] is not None
792 or
793 t['unified_target_min'] is not None
794 or
795 t['unified_target_max'] is not None
796 )
797 if has_range:
798 if t['unified_target_range'] is not None:
799 tmp += u'{\\tiny %s}' % t['unified_target_range']
800 else:
801 tmp += u'{\\tiny %s}' % (
802 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
803 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
804 )
805 lines.append(tmp)
806
807 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
808
810
811 if len(results) == 0:
812 return u''
813
814 lines = []
815 for t in results:
816
817 tmp = u''
818
819 if show_time:
820 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
821
822 tmp += u'\\normalsize %.8s' % t['unified_val']
823
824 lines.append(tmp)
825 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
826
827 if not show_range:
828 lines.append(tmp)
829 continue
830
831 has_range = (
832 t['unified_target_range'] is not None
833 or
834 t['unified_target_min'] is not None
835 or
836 t['unified_target_max'] is not None
837 )
838
839 if not has_range:
840 lines.append(tmp)
841 continue
842
843 if t['unified_target_range'] is not None:
844 tmp += u'[%s]' % t['unified_target_range']
845 else:
846 tmp += u'[%s%s]' % (
847 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
848 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
849 )
850 lines.append(tmp)
851
852 return u' \\\\ '.join(lines)
853
929
930
932
933 if filename is None:
934 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
935
936
937 series = {}
938 for r in results:
939 try:
940 series[r['unified_name']].append(r)
941 except KeyError:
942 series[r['unified_name']] = [r]
943
944 gp_data = codecs.open(filename, 'wb', 'utf8')
945
946 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
947 gp_data.write(u'# -------------------------------------------------------------\n')
948 gp_data.write(u'# first line of index: test type abbreviation & name\n')
949 gp_data.write(u'#\n')
950 gp_data.write(u'# clin_when at full precision\n')
951 gp_data.write(u'# value\n')
952 gp_data.write(u'# unit\n')
953 gp_data.write(u'# unified (target or normal) range: lower bound\n')
954 gp_data.write(u'# unified (target or normal) range: upper bound\n')
955 gp_data.write(u'# normal range: lower bound\n')
956 gp_data.write(u'# normal range: upper bound\n')
957 gp_data.write(u'# target range: lower bound\n')
958 gp_data.write(u'# target range: upper bound\n')
959 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
960 gp_data.write(u'# -------------------------------------------------------------\n')
961
962 for test_type in series.keys():
963 if len(series[test_type]) == 0:
964 continue
965
966 r = series[test_type][0]
967 title = u'%s (%s)' % (
968 r['unified_abbrev'],
969 r['unified_name']
970 )
971 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
972
973 prev_date = None
974 prev_year = None
975 for r in series[test_type]:
976 curr_date = r['clin_when'].strftime('%Y-%m-%d')
977 if curr_date == prev_date:
978 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values'))
979 if show_year:
980 if r['clin_when'].year == prev_year:
981 when_template = '%b %d %H:%M'
982 else:
983 when_template = '%b %d %H:%M (%Y)'
984 prev_year = r['clin_when'].year
985 else:
986 when_template = '%b %d'
987 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
988 r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
989 r['unified_val'],
990 gmTools.coalesce(r['val_unit'], u'"<?>"'),
991 gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
992 gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
993 gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
994 gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
995 gmTools.coalesce(r['val_target_min'], u'"<?>"'),
996 gmTools.coalesce(r['val_target_max'], u'"<?>"'),
997 gmDateTime.pydt_strftime (
998 r['clin_when'],
999 format = when_template,
1000 accuracy = gmDateTime.acc_minutes
1001 )
1002 ))
1003 prev_date = curr_date
1004
1005 gp_data.close()
1006
1007 return filename
1008
1009
1010 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
1011 """Represents one lab result."""
1012
1013 _cmd_fetch_payload = """
1014 select *, xmin_test_result from v_results4lab_req
1015 where pk_result=%s"""
1016 _cmds_lock_rows_for_update = [
1017 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
1018 ]
1019 _cmds_store_payload = [
1020 """update test_result set
1021 clin_when = %(val_when)s,
1022 narrative = %(progress_note_result)s,
1023 fk_type = %(pk_test_type)s,
1024 val_num = %(val_num)s::numeric,
1025 val_alpha = %(val_alpha)s,
1026 val_unit = %(val_unit)s,
1027 val_normal_min = %(val_normal_min)s,
1028 val_normal_max = %(val_normal_max)s,
1029 val_normal_range = %(val_normal_range)s,
1030 val_target_min = %(val_target_min)s,
1031 val_target_max = %(val_target_max)s,
1032 val_target_range = %(val_target_range)s,
1033 abnormality_indicator = %(abnormal)s,
1034 norm_ref_group = %(ref_group)s,
1035 note_provider = %(note_provider)s,
1036 material = %(material)s,
1037 material_detail = %(material_detail)s
1038 where pk = %(pk_result)s""",
1039 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
1040 ]
1041
1042 _updatable_fields = [
1043 'val_when',
1044 'progress_note_result',
1045 'val_num',
1046 'val_alpha',
1047 'val_unit',
1048 'val_normal_min',
1049 'val_normal_max',
1050 'val_normal_range',
1051 'val_target_min',
1052 'val_target_max',
1053 'val_target_range',
1054 'abnormal',
1055 'ref_group',
1056 'note_provider',
1057 'material',
1058 'material_detail'
1059 ]
1060
1061 - def __init__(self, aPK_obj=None, row=None):
1062 """Instantiate.
1063
1064 aPK_obj as dict:
1065 - patient_id
1066 - when_field (see view definition)
1067 - when
1068 - test_type
1069 - val_num
1070 - val_alpha
1071 - unit
1072 """
1073
1074 if aPK_obj is None:
1075 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
1076 return
1077 pk = aPK_obj
1078
1079 if type(aPK_obj) == types.DictType:
1080
1081 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
1082 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
1083 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
1084 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
1085
1086 where_snippets = [
1087 'pk_patient=%(patient_id)s',
1088 'pk_test_type=%(test_type)s',
1089 '%s=%%(when)s' % aPK_obj['when_field'],
1090 'val_unit=%(unit)s'
1091 ]
1092 if aPK_obj['val_num'] is not None:
1093 where_snippets.append('val_num=%(val_num)s::numeric')
1094 if aPK_obj['val_alpha'] is not None:
1095 where_snippets.append('val_alpha=%(val_alpha)s')
1096
1097 where_clause = ' and '.join(where_snippets)
1098 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
1099 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
1100 if data is None:
1101 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
1102 if len(data) == 0:
1103 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
1104 pk = data[0][0]
1105
1106 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1107
1109 cmd = """
1110 select
1111 %s,
1112 vbp.title,
1113 vbp.firstnames,
1114 vbp.lastnames,
1115 vbp.dob
1116 from v_basic_person vbp
1117 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
1118 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
1119 return pat[0]
1120
1121 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
1122 """Represents one lab request."""
1123
1124 _cmd_fetch_payload = """
1125 select *, xmin_lab_request from v_lab_requests
1126 where pk_request=%s"""
1127 _cmds_lock_rows_for_update = [
1128 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
1129 ]
1130 _cmds_store_payload = [
1131 """update lab_request set
1132 request_id=%(request_id)s,
1133 lab_request_id=%(lab_request_id)s,
1134 clin_when=%(sampled_when)s,
1135 lab_rxd_when=%(lab_rxd_when)s,
1136 results_reported_when=%(results_reported_when)s,
1137 request_status=%(request_status)s,
1138 is_pending=%(is_pending)s::bool,
1139 narrative=%(progress_note)s
1140 where pk=%(pk_request)s""",
1141 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
1142 ]
1143 _updatable_fields = [
1144 'request_id',
1145 'lab_request_id',
1146 'sampled_when',
1147 'lab_rxd_when',
1148 'results_reported_when',
1149 'request_status',
1150 'is_pending',
1151 'progress_note'
1152 ]
1153
1154 - def __init__(self, aPK_obj=None, row=None):
1155 """Instantiate lab request.
1156
1157 The aPK_obj can be either a dict with the keys "req_id"
1158 and "lab" or a simple primary key.
1159 """
1160
1161 if aPK_obj is None:
1162 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
1163 return
1164 pk = aPK_obj
1165
1166 if type(aPK_obj) == types.DictType:
1167
1168 try:
1169 aPK_obj['req_id']
1170 aPK_obj['lab']
1171 except:
1172 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
1173 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
1174
1175 where_snippets = []
1176 vals = {}
1177 where_snippets.append('request_id=%(req_id)s')
1178 if type(aPK_obj['lab']) == types.IntType:
1179 where_snippets.append('pk_test_org=%(lab)s')
1180 else:
1181 where_snippets.append('lab_name=%(lab)s')
1182 where_clause = ' and '.join(where_snippets)
1183 cmd = "select pk_request from v_lab_requests where %s" % where_clause
1184
1185 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
1186 if data is None:
1187 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1188 if len(data) == 0:
1189 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1190 pk = data[0][0]
1191
1192 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1193
1195 cmd = """
1196 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
1197 from v_pat_items vpi, v_basic_person vbp
1198 where
1199 vpi.pk_item=%s
1200 and
1201 vbp.pk_identity=vpi.pk_patient"""
1202 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
1203 if pat is None:
1204 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
1205 return None
1206 if len(pat) == 0:
1207 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
1208 return None
1209 return pat[0]
1210
1211
1212
1213 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
1214 """Create or get lab request.
1215
1216 returns tuple (status, value):
1217 (True, lab request instance)
1218 (False, error message)
1219 (None, housekeeping_todo primary key)
1220 """
1221 req = None
1222 aPK_obj = {
1223 'lab': lab,
1224 'req_id': req_id
1225 }
1226 try:
1227 req = cLabRequest (aPK_obj)
1228 except gmExceptions.NoSuchClinItemError, msg:
1229 _log.info('%s: will try to create lab request' % str(msg))
1230 except gmExceptions.ConstructorError, msg:
1231 _log.exception(str(msg), sys.exc_info(), verbose=0)
1232 return (False, msg)
1233
1234 if req is not None:
1235 db_pat = req.get_patient()
1236 if db_pat is None:
1237 _log.error('cannot cross-check patient on lab request')
1238 return (None, '')
1239
1240 if pat_id != db_pat[0]:
1241 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
1242 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
1243 to = 'user'
1244 prob = _('The lab request already exists but belongs to a different patient.')
1245 sol = _('Verify which patient this lab request really belongs to.')
1246 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
1247 cat = 'lab'
1248 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
1249 return (None, data)
1250 return (True, req)
1251
1252 queries = []
1253 if type(lab) is types.IntType:
1254 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
1255 else:
1256 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)"
1257 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
1258 cmd = "select currval('lab_request_pk_seq')"
1259 queries.append((cmd, []))
1260
1261 result, err = gmPG.run_commit('historica', queries, True)
1262 if result is None:
1263 return (False, err)
1264 try:
1265 req = cLabRequest(aPK_obj=result[0][0])
1266 except gmExceptions.ConstructorError, msg:
1267 _log.exception(str(msg), sys.exc_info(), verbose=0)
1268 return (False, msg)
1269 return (True, req)
1270
1271 -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):
1272 tres = None
1273 data = {
1274 'patient_id': patient_id,
1275 'when_field': when_field,
1276 'when': when,
1277 'test_type': test_type,
1278 'val_num': val_num,
1279 'val_alpha': val_alpha,
1280 'unit': unit
1281 }
1282 try:
1283 tres = cLabResult(aPK_obj=data)
1284
1285 _log.error('will not overwrite existing test result')
1286 _log.debug(str(tres))
1287 return (None, tres)
1288 except gmExceptions.NoSuchClinItemError:
1289 _log.debug('test result not found - as expected, will create it')
1290 except gmExceptions.ConstructorError, msg:
1291 _log.exception(str(msg), sys.exc_info(), verbose=0)
1292 return (False, msg)
1293 if request is None:
1294 return (False, _('need lab request when inserting lab result'))
1295
1296 if encounter_id is None:
1297 encounter_id = request['pk_encounter']
1298 queries = []
1299 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
1300 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
1301 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
1302 queries.append((cmd, [request['pk_request']]))
1303 cmd = "select currval('test_result_pk_seq')"
1304 queries.append((cmd, []))
1305
1306 result, err = gmPG.run_commit('historica', queries, True)
1307 if result is None:
1308 return (False, err)
1309 try:
1310 tres = cLabResult(aPK_obj=result[0][0])
1311 except gmExceptions.ConstructorError, msg:
1312 _log.exception(str(msg), sys.exc_info(), verbose=0)
1313 return (False, msg)
1314 return (True, tres)
1315
1317
1318 if limit < 1:
1319 limit = 1
1320
1321 lim = limit + 1
1322 cmd = """
1323 select pk_result
1324 from v_results4lab_req
1325 where reviewed is false
1326 order by pk_patient
1327 limit %s""" % lim
1328 rows = gmPG.run_ro_query('historica', cmd)
1329 if rows is None:
1330 _log.error('error retrieving unreviewed lab results')
1331 return (None, _('error retrieving unreviewed lab results'))
1332 if len(rows) == 0:
1333 return (False, [])
1334
1335 if len(rows) == lim:
1336 more_avail = True
1337
1338 del rows[limit]
1339 else:
1340 more_avail = False
1341 results = []
1342 for row in rows:
1343 try:
1344 results.append(cLabResult(aPK_obj=row[0]))
1345 except gmExceptions.ConstructorError:
1346 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
1347 return (more_avail, results)
1348
1350 lim = limit + 1
1351 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
1352 rows = gmPG.run_ro_query('historica', cmd)
1353 if rows is None:
1354 _log.error('error retrieving pending lab requests')
1355 return (None, None)
1356 if len(rows) == 0:
1357 return (False, [])
1358 results = []
1359
1360 if len(rows) == lim:
1361 too_many = True
1362
1363 del rows[limit]
1364 else:
1365 too_many = False
1366 requests = []
1367 for row in rows:
1368 try:
1369 requests.append(cLabRequest(aPK_obj=row[0]))
1370 except gmExceptions.ConstructorError:
1371 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
1372 return (too_many, requests)
1373
1375 """Get logically next request ID for given lab.
1376
1377 - incrementor_func:
1378 - if not supplied the next ID is guessed
1379 - if supplied it is applied to the most recently used ID
1380 """
1381 if type(lab) == types.IntType:
1382 lab_snippet = 'vlr.fk_test_org=%s'
1383 else:
1384 lab_snippet = 'vlr.lab_name=%s'
1385 lab = str(lab)
1386 cmd = """
1387 select request_id
1388 from lab_request lr0
1389 where lr0.clin_when = (
1390 select max(vlr.sampled_when)
1391 from v_lab_requests vlr
1392 where %s
1393 )""" % lab_snippet
1394 rows = gmPG.run_ro_query('historica', cmd, None, lab)
1395 if rows is None:
1396 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
1397 return ''
1398 if len(rows) == 0:
1399 return ''
1400 most_recent = rows[0][0]
1401
1402 if incrementor_func is not None:
1403 try:
1404 next = incrementor_func(most_recent)
1405 except TypeError:
1406 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
1407 return most_recent
1408 return next
1409
1410 for pos in range(len(most_recent)):
1411 header = most_recent[:pos]
1412 trailer = most_recent[pos:]
1413 try:
1414 return '%s%s' % (header, str(int(trailer) + 1))
1415 except ValueError:
1416 header = most_recent[:-1]
1417 trailer = most_recent[-1:]
1418 return '%s%s' % (header, chr(ord(trailer) + 1))
1419
1421 """Calculate BMI.
1422
1423 mass: kg
1424 height: cm
1425 age: not yet used
1426
1427 returns:
1428 (True/False, data)
1429 True: data = (bmi, lower_normal, upper_normal)
1430 False: data = error message
1431 """
1432 converted, mass = gmTools.input2decimal(mass)
1433 if not converted:
1434 return False, u'mass: cannot convert <%s> to Decimal' % mass
1435
1436 converted, height = gmTools.input2decimal(height)
1437 if not converted:
1438 return False, u'height: cannot convert <%s> to Decimal' % height
1439
1440 approx_surface = (height / decimal.Decimal(100))**2
1441 bmi = mass / approx_surface
1442
1443 print mass, height, '->', approx_surface, '->', bmi
1444
1445 lower_normal_mass = 20.0 * approx_surface
1446 upper_normal_mass = 25.0 * approx_surface
1447
1448 return True, (bmi, lower_normal_mass, upper_normal_mass)
1449
1450
1451
1452 if __name__ == '__main__':
1453
1454 if len(sys.argv) < 2:
1455 sys.exit()
1456
1457 if sys.argv[1] != 'test':
1458 sys.exit()
1459
1460 import time
1461
1462 gmI18N.activate_locale()
1463 gmI18N.install_domain()
1464
1465
1467 tr = create_test_result (
1468 encounter = 1,
1469 episode = 1,
1470 type = 1,
1471 intended_reviewer = 1,
1472 val_num = '12',
1473 val_alpha=None,
1474 unit = 'mg/dl'
1475 )
1476 print tr
1477 return tr
1478
1482
1487
1489 print "test_result()"
1490
1491 data = {
1492 'patient_id': 12,
1493 'when_field': 'val_when',
1494 'when': '2000-09-17 18:23:00+02',
1495 'test_type': 9,
1496 'val_num': 17.3,
1497 'val_alpha': None,
1498 'unit': 'mg/l'
1499 }
1500 lab_result = cLabResult(aPK_obj=data)
1501 print lab_result
1502 fields = lab_result.get_fields()
1503 for field in fields:
1504 print field, ':', lab_result[field]
1505 print "updatable:", lab_result.get_updatable_fields()
1506 print time.time()
1507 print lab_result.get_patient()
1508 print time.time()
1509
1511 print "test_request()"
1512 try:
1513
1514
1515 data = {
1516 'req_id': 'EML#SC937-0176-CEC#11',
1517 'lab': 'Enterprise Main Lab'
1518 }
1519 lab_req = cLabRequest(aPK_obj=data)
1520 except gmExceptions.ConstructorError, msg:
1521 print "no such lab request:", msg
1522 return
1523 print lab_req
1524 fields = lab_req.get_fields()
1525 for field in fields:
1526 print field, ':', lab_req[field]
1527 print "updatable:", lab_req.get_updatable_fields()
1528 print time.time()
1529 print lab_req.get_patient()
1530 print time.time()
1531
1536
1541
1549
1554
1559
1568
1570 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
1571 bmi, low, high = data
1572
1573 print "BMI:", bmi
1574 print "low:", low, "kg"
1575 print "hi :", high, "kg"
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591 test_calculate_bmi()
1592
1593
1594