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