1 """GNUmed measurements related business objects."""
2
3 __version__ = "$Revision: 1.81 $"
4 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
5 __license__ = "GPL"
6
7
8 import types, sys, logging, codecs, decimal
9
10 if __name__ == '__main__':
11 sys.path.insert(0, '../../')
12
13 from Gnumed.pycommon import gmDateTime
14 if __name__ == '__main__':
15 from Gnumed.pycommon import gmLog2
16 from Gnumed.pycommon import gmI18N
17 gmDateTime.init()
18 from Gnumed.pycommon import gmExceptions
19 from Gnumed.pycommon import gmBusinessDBObject
20 from Gnumed.pycommon import gmPG2
21 from Gnumed.pycommon import gmTools
22 from Gnumed.pycommon import gmDispatcher
23 from Gnumed.pycommon import gmHooks
24 from Gnumed.business import gmOrganization
25
26
27 _log = logging.getLogger('gm.lab')
28 _log.info(__version__)
29
30
31
32
36
37 gmDispatcher.connect(_on_test_result_modified, u'test_result_mod_db')
38
39
40 -class cTestOrg(gmBusinessDBObject.cBusinessDBObject):
41 """Represents one test org/lab."""
42 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
43 _cmds_store_payload = [
44 u"""UPDATE clin.test_org SET
45 fk_org_unit = %(pk_org_unit)s,
46 contact = gm.nullify_empty_string(%(test_org_contact)s),
47 comment = gm.nullify_empty_string(%(comment)s)
48 WHERE
49 pk = %(pk_test_org)s
50 AND
51 xmin = %(xmin_test_org)s
52 RETURNING
53 xmin AS xmin_test_org
54 """
55 ]
56 _updatable_fields = [
57 u'pk_org_unit',
58 u'test_org_contact',
59 u'comment'
60 ]
61
63
64 if name is None:
65 name = _('inhouse lab')
66 comment = _('auto-generated')
67
68
69 if pk_org_unit is None:
70 org = gmOrganization.org_exists(organization = name)
71 if org is None:
72 org = gmOrganization.create_org (
73 organization = name,
74 category = u'Laboratory'
75 )
76 org_unit = gmOrganization.create_org_unit (
77 pk_organization = org['pk_org'],
78 unit = name
79 )
80 pk_org_unit = org_unit['pk_org_unit']
81
82
83 args = {'pk_unit': pk_org_unit}
84 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
85 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
86
87 if len(rows) == 0:
88 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
89 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
90
91 test_org = cTestOrg(aPK_obj = rows[0][0])
92 if comment is not None:
93 comment = comment.strip()
94 test_org['comment'] = comment
95 test_org.save()
96
97 return test_org
98
100 args = {'pk': test_org}
101 cmd = u"""
102 DELETE FROM clin.test_org
103 WHERE
104 pk = %(pk)s
105 AND
106 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
107 AND
108 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
109 """
110 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
111
113 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
114 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
115 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
116
125
130
135
137 """Represents one unified test type."""
138
139
140 _cmd_fetch_payload = u"""select * from clin.v_unified_test_types where pk_test_type = %s"""
141
142 _cmds_store_payload = []
143
144 _updatable_fields = []
145
147 cmd = u"""
148 SELECT pk_test_result, clin_when
149 FROM clin.v_test_results
150 WHERE
151 pk_patient = %(pat)s
152 AND
153 pk_meta_test_type = %(pkmtt)s
154 ORDER BY clin_when DESC
155 LIMIT 1
156 """
157 args = {'pat': pk_patient, 'pkmtt': self._payload[self._idx['pk_meta_test_type']]}
158 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
159 if len(rows) == 0:
160 return None
161 return cTestResult(aPK_obj = rows[0]['pk_test_result'])
162
164 """Represents one test result type."""
165
166 _cmd_fetch_payload = u"""select * from clin.v_test_types where pk_test_type = %s"""
167
168 _cmds_store_payload = [
169 u"""update clin.test_type set
170 abbrev = %(abbrev)s,
171 name = %(name)s,
172 loinc = gm.nullify_empty_string(%(loinc)s),
173 code = gm.nullify_empty_string(%(code)s),
174 coding_system = gm.nullify_empty_string(%(coding_system)s),
175 comment = gm.nullify_empty_string(%(comment_type)s),
176 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
177 fk_test_org = %(pk_test_org)s
178 where pk = %(pk_test_type)s""",
179 u"""select xmin_test_type from clin.v_test_types where pk_test_type = %(pk_test_type)s"""
180 ]
181
182 _updatable_fields = [
183 'abbrev',
184 'name',
185 'loinc',
186 'code',
187 'coding_system',
188 'comment_type',
189 'conversion_unit',
190 'pk_test_org'
191 ]
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
209 cmd = u'select exists(select 1 from clin.test_result where fk_type = %(pk_type)s)'
210 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
211 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
212 return rows[0][0]
213
214 in_use = property(_get_in_use, lambda x:x)
215
217 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
218 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
219 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
220
222
223 if (abbrev is None) and (name is None):
224 raise ValueError('must have <abbrev> and/or <name> set')
225
226 where_snippets = []
227
228 if lab is None:
229 where_snippets.append('pk_test_org IS NULL')
230 else:
231 try:
232 int(lab)
233 where_snippets.append('pk_test_org = %(lab)s')
234 except (TypeError, ValueError):
235 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
236
237 if abbrev is not None:
238 where_snippets.append('abbrev = %(abbrev)s')
239
240 if name is not None:
241 where_snippets.append('name = %(name)s')
242
243 where_clause = u' and '.join(where_snippets)
244 cmd = u"select * from clin.v_test_types where %s" % where_clause
245 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
246
247 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
248
249 if len(rows) == 0:
250 return None
251
252 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
253 return tt
254
256 cmd = u'delete from clin.test_type where pk = %(pk)s'
257 args = {'pk': measurement_type}
258 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
259
261 """Create or get test type."""
262
263 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
264
265 if ttype is not None:
266 return ttype
267
268 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
269
270
271 if unit is None:
272 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
273 raise ValueError('need <unit> to create test type')
274
275
276 cols = []
277 val_snippets = []
278 vals = {}
279
280
281 if lab is None:
282 lab = create_test_org()['pk_test_org']
283
284 cols.append('fk_test_org')
285 try:
286 vals['lab'] = int(lab)
287 val_snippets.append('%(lab)s')
288 except:
289 vals['lab'] = lab
290 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
291
292
293 cols.append('abbrev')
294 val_snippets.append('%(abbrev)s')
295 vals['abbrev'] = abbrev
296
297
298 cols.append('conversion_unit')
299 val_snippets.append('%(unit)s')
300 vals['unit'] = unit
301
302
303 if name is not None:
304 cols.append('name')
305 val_snippets.append('%(name)s')
306 vals['name'] = name
307
308 col_clause = u', '.join(cols)
309 val_clause = u', '.join(val_snippets)
310 queries = [
311 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
312 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
313 ]
314 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
315 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
316
317 return ttype
318
319 -class cTestResult(gmBusinessDBObject.cBusinessDBObject):
320 """Represents one test result."""
321
322 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
323
324 _cmds_store_payload = [
325 u"""update clin.test_result set
326 clin_when = %(clin_when)s,
327 narrative = nullif(trim(%(comment)s), ''),
328 val_num = %(val_num)s,
329 val_alpha = nullif(trim(%(val_alpha)s), ''),
330 val_unit = nullif(trim(%(val_unit)s), ''),
331 val_normal_min = %(val_normal_min)s,
332 val_normal_max = %(val_normal_max)s,
333 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
334 val_target_min = %(val_target_min)s,
335 val_target_max = %(val_target_max)s,
336 val_target_range = nullif(trim(%(val_target_range)s), ''),
337 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
338 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
339 note_test_org = nullif(trim(%(note_test_org)s), ''),
340 material = nullif(trim(%(material)s), ''),
341 material_detail = nullif(trim(%(material_detail)s), ''),
342 fk_intended_reviewer = %(pk_intended_reviewer)s,
343 fk_encounter = %(pk_encounter)s,
344 fk_episode = %(pk_episode)s,
345 fk_type = %(pk_test_type)s,
346 fk_request = %(pk_request)s
347 where
348 pk = %(pk_test_result)s and
349 xmin = %(xmin_test_result)s""",
350 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
351 ]
352
353 _updatable_fields = [
354 'clin_when',
355 'comment',
356 'val_num',
357 'val_alpha',
358 'val_unit',
359 'val_normal_min',
360 'val_normal_max',
361 'val_normal_range',
362 'val_target_min',
363 'val_target_max',
364 'val_target_range',
365 'abnormality_indicator',
366 'norm_ref_group',
367 'note_test_org',
368 'material',
369 'material_detail',
370 'pk_intended_reviewer',
371 'pk_encounter',
372 'pk_episode',
373 'pk_test_type',
374 'pk_request'
375 ]
376
412
414
415 cmd = u"""
416 select
417 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)
418 pk_patient,
419 val_unit,
420 val_normal_min, val_normal_max, val_normal_range,
421 val_target_min, val_target_max, val_target_range,
422 norm_ref_group,
423 coalesce(norm_ref_group, '') as norm_ref_group_str
424 from
425 clin.v_test_results
426 where
427 pk_test_type = %(pk_type)s
428 """
429 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
430 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
431 return rows
432
434 raise AttributeError('[%s]: reference ranges not settable') % self.__class__.__name__
435
436 reference_ranges = property(_get_reference_ranges, _set_reference_ranges)
437
438 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
439
440
441 if self._payload[self._idx['reviewed']]:
442 self.__change_existing_review (
443 technically_abnormal = technically_abnormal,
444 clinically_relevant = clinically_relevant,
445 comment = comment
446 )
447 else:
448
449
450 if technically_abnormal is None:
451 if clinically_relevant is None:
452 comment = gmTools.none_if(comment, u'', strip_string = True)
453 if comment is None:
454 if make_me_responsible is False:
455 return True
456 self.__set_new_review (
457 technically_abnormal = technically_abnormal,
458 clinically_relevant = clinically_relevant,
459 comment = comment
460 )
461
462 if make_me_responsible is True:
463 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
464 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
465 self['pk_intended_reviewer'] = rows[0][0]
466 self.save_payload()
467 return
468
469 self.refetch_payload()
470
471
472
473 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
474 """Add a review to a row.
475
476 - if technically abnormal is not provided/None it will be set
477 to True if the lab's indicator has a meaningful value
478 - if clinically relevant is not provided/None it is set to
479 whatever technically abnormal is
480 """
481 if technically_abnormal is None:
482 technically_abnormal = False
483 if self._payload[self._idx['abnormality_indicator']] is not None:
484 if self._payload[self._idx['abnormality_indicator']].strip() != u'':
485 technically_abnormal = True
486
487 if clinically_relevant is None:
488 clinically_relevant = technically_abnormal
489
490 cmd = u"""
491 INSERT INTO clin.reviewed_test_results (
492 fk_reviewed_row,
493 is_technically_abnormal,
494 clinically_relevant,
495 comment
496 ) VALUES (
497 %(pk)s,
498 %(abnormal)s,
499 %(relevant)s,
500 gm.nullify_empty_string(%(cmt)s)
501 )"""
502 args = {
503 'pk': self._payload[self._idx['pk_test_result']],
504 'abnormal': technically_abnormal,
505 'relevant': clinically_relevant,
506 'cmt': comment
507 }
508
509 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
510
512 """Change a review on a row.
513
514 - if technically abnormal/clinically relevant are
515 None they are not set
516 """
517 args = {
518 'pk_row': self._payload[self._idx['pk_test_result']],
519 'abnormal': technically_abnormal,
520 'relevant': clinically_relevant,
521 'cmt': comment
522 }
523
524 set_parts = [
525 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
526 u'comment = gm.nullify_empty_string(%(cmt)s)'
527 ]
528
529 if technically_abnormal is not None:
530 set_parts.append(u'is_technically_abnormal = %(abnormal)s')
531
532 if clinically_relevant is not None:
533 set_parts.append(u'clinically_relevant = %(relevant)s')
534
535 cmd = u"""
536 UPDATE clin.reviewed_test_results SET
537 %s
538 WHERE
539 fk_reviewed_row = %%(pk_row)s
540 """ % u',\n '.join(set_parts)
541
542 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
543
545
546 try:
547 pk = int(result)
548 except (TypeError, AttributeError):
549 pk = result['pk_test_result']
550
551 cmd = u'delete from clin.test_result where pk = %(pk)s'
552 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
553
554 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
555
556 cmd1 = u"""
557 insert into clin.test_result (
558 fk_encounter,
559 fk_episode,
560 fk_type,
561 fk_intended_reviewer,
562 val_num,
563 val_alpha,
564 val_unit
565 ) values (
566 %(enc)s,
567 %(epi)s,
568 %(type)s,
569 %(rev)s,
570 %(v_num)s,
571 %(v_alpha)s,
572 %(unit)s
573 )"""
574
575 cmd2 = u"""
576 select *
577 from
578 clin.v_test_results
579 where
580 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
581
582 args = {
583 u'enc': encounter,
584 u'epi': episode,
585 u'type': type,
586 u'rev': intended_reviewer,
587 u'v_num': val_num,
588 u'v_alpha': val_alpha,
589 u'unit': unit
590 }
591
592 rows, idx = gmPG2.run_rw_queries (
593 queries = [
594 {'cmd': cmd1, 'args': args},
595 {'cmd': cmd2}
596 ],
597 return_data = True,
598 get_col_idx = True
599 )
600
601 tr = cTestResult(row = {
602 'pk_field': 'pk_test_result',
603 'idx': idx,
604 'data': rows[0]
605 })
606
607 return tr
608
619
620 -def __tests2latex_minipage(results=None, width=u'1.5cm', show_time=False, show_range=True):
621
622 if len(results) == 0:
623 return u'\\begin{minipage}{%s} \\end{minipage}' % width
624
625 lines = []
626 for t in results:
627
628 tmp = u''
629
630 if show_time:
631 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
632
633 tmp += u'%.8s' % t['unified_val']
634
635 lines.append(tmp)
636 tmp = u''
637
638 if show_range:
639 has_range = (
640 t['unified_target_range'] is not None
641 or
642 t['unified_target_min'] is not None
643 or
644 t['unified_target_max'] is not None
645 )
646 if has_range:
647 if t['unified_target_range'] is not None:
648 tmp += u'{\\tiny %s}' % t['unified_target_range']
649 else:
650 tmp += u'{\\tiny %s}' % (
651 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
652 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
653 )
654 lines.append(tmp)
655
656 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
657
659
660 if len(results) == 0:
661 return u''
662
663 lines = []
664 for t in results:
665
666 tmp = u''
667
668 if show_time:
669 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
670
671 tmp += u'\\normalsize %.8s' % t['unified_val']
672
673 lines.append(tmp)
674 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
675
676 if not show_range:
677 lines.append(tmp)
678 continue
679
680 has_range = (
681 t['unified_target_range'] is not None
682 or
683 t['unified_target_min'] is not None
684 or
685 t['unified_target_max'] is not None
686 )
687
688 if not has_range:
689 lines.append(tmp)
690 continue
691
692 if t['unified_target_range'] is not None:
693 tmp += u'[%s]' % t['unified_target_range']
694 else:
695 tmp += u'[%s%s]' % (
696 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
697 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
698 )
699 lines.append(tmp)
700
701 return u' \\\\ '.join(lines)
702
778
779
781
782 if filename is None:
783 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
784
785
786 series = {}
787 for r in results:
788 try:
789 series[r['unified_name']].append(r)
790 except KeyError:
791 series[r['unified_name']] = [r]
792
793 gp_data = codecs.open(filename, 'wb', 'utf8')
794
795 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
796 gp_data.write(u'# -------------------------------------------------------------\n')
797 gp_data.write(u'# first line of index: test type abbreviation & name\n')
798 gp_data.write(u'#\n')
799 gp_data.write(u'# clin_when at full precision\n')
800 gp_data.write(u'# value\n')
801 gp_data.write(u'# unit\n')
802 gp_data.write(u'# unified (target or normal) range: lower bound\n')
803 gp_data.write(u'# unified (target or normal) range: upper bound\n')
804 gp_data.write(u'# normal range: lower bound\n')
805 gp_data.write(u'# normal range: upper bound\n')
806 gp_data.write(u'# target range: lower bound\n')
807 gp_data.write(u'# target range: upper bound\n')
808 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
809 gp_data.write(u'# -------------------------------------------------------------\n')
810
811 for test_type in series.keys():
812 if len(series[test_type]) == 0:
813 continue
814
815 r = series[test_type][0]
816 title = u'%s (%s)' % (
817 r['unified_abbrev'],
818 r['unified_name']
819 )
820 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
821
822 prev_date = None
823 prev_year = None
824 for r in series[test_type]:
825 curr_date = r['clin_when'].strftime('%Y-%m-%d')
826 if curr_date == prev_date:
827 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values'))
828 if r['clin_when'].year == prev_year:
829 when_template = '%b %d %H:%M'
830 else:
831 when_template = '%b %d %H:%M (%Y)'
832 prev_year = r['clin_when'].year
833 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
834 r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
835 r['unified_val'],
836 gmTools.coalesce(r['val_unit'], u'"<?>"'),
837 gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
838 gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
839 gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
840 gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
841 gmTools.coalesce(r['val_target_min'], u'"<?>"'),
842 gmTools.coalesce(r['val_target_max'], u'"<?>"'),
843 gmDateTime.pydt_strftime (
844 r['clin_when'],
845 format = when_template,
846 accuracy = gmDateTime.acc_minutes
847 )
848 ))
849 prev_date = curr_date
850
851 gp_data.close()
852
853 return filename
854
855 -class cLabResult(gmBusinessDBObject.cBusinessDBObject):
856 """Represents one lab result."""
857
858 _cmd_fetch_payload = """
859 select *, xmin_test_result from v_results4lab_req
860 where pk_result=%s"""
861 _cmds_lock_rows_for_update = [
862 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
863 ]
864 _cmds_store_payload = [
865 """update test_result set
866 clin_when = %(val_when)s,
867 narrative = %(progress_note_result)s,
868 fk_type = %(pk_test_type)s,
869 val_num = %(val_num)s::numeric,
870 val_alpha = %(val_alpha)s,
871 val_unit = %(val_unit)s,
872 val_normal_min = %(val_normal_min)s,
873 val_normal_max = %(val_normal_max)s,
874 val_normal_range = %(val_normal_range)s,
875 val_target_min = %(val_target_min)s,
876 val_target_max = %(val_target_max)s,
877 val_target_range = %(val_target_range)s,
878 abnormality_indicator = %(abnormal)s,
879 norm_ref_group = %(ref_group)s,
880 note_provider = %(note_provider)s,
881 material = %(material)s,
882 material_detail = %(material_detail)s
883 where pk = %(pk_result)s""",
884 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
885 ]
886
887 _updatable_fields = [
888 'val_when',
889 'progress_note_result',
890 'val_num',
891 'val_alpha',
892 'val_unit',
893 'val_normal_min',
894 'val_normal_max',
895 'val_normal_range',
896 'val_target_min',
897 'val_target_max',
898 'val_target_range',
899 'abnormal',
900 'ref_group',
901 'note_provider',
902 'material',
903 'material_detail'
904 ]
905
906 - def __init__(self, aPK_obj=None, row=None):
907 """Instantiate.
908
909 aPK_obj as dict:
910 - patient_id
911 - when_field (see view definition)
912 - when
913 - test_type
914 - val_num
915 - val_alpha
916 - unit
917 """
918
919 if aPK_obj is None:
920 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
921 return
922 pk = aPK_obj
923
924 if type(aPK_obj) == types.DictType:
925
926 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
927 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
928 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
929 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
930
931 where_snippets = [
932 'pk_patient=%(patient_id)s',
933 'pk_test_type=%(test_type)s',
934 '%s=%%(when)s' % aPK_obj['when_field'],
935 'val_unit=%(unit)s'
936 ]
937 if aPK_obj['val_num'] is not None:
938 where_snippets.append('val_num=%(val_num)s::numeric')
939 if aPK_obj['val_alpha'] is not None:
940 where_snippets.append('val_alpha=%(val_alpha)s')
941
942 where_clause = ' and '.join(where_snippets)
943 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
944 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
945 if data is None:
946 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
947 if len(data) == 0:
948 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
949 pk = data[0][0]
950
951 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
952
954 cmd = """
955 select
956 %s,
957 vbp.title,
958 vbp.firstnames,
959 vbp.lastnames,
960 vbp.dob
961 from v_basic_person vbp
962 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
963 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
964 return pat[0]
965
966 -class cLabRequest(gmBusinessDBObject.cBusinessDBObject):
967 """Represents one lab request."""
968
969 _cmd_fetch_payload = """
970 select *, xmin_lab_request from v_lab_requests
971 where pk_request=%s"""
972 _cmds_lock_rows_for_update = [
973 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
974 ]
975 _cmds_store_payload = [
976 """update lab_request set
977 request_id=%(request_id)s,
978 lab_request_id=%(lab_request_id)s,
979 clin_when=%(sampled_when)s,
980 lab_rxd_when=%(lab_rxd_when)s,
981 results_reported_when=%(results_reported_when)s,
982 request_status=%(request_status)s,
983 is_pending=%(is_pending)s::bool,
984 narrative=%(progress_note)s
985 where pk=%(pk_request)s""",
986 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
987 ]
988 _updatable_fields = [
989 'request_id',
990 'lab_request_id',
991 'sampled_when',
992 'lab_rxd_when',
993 'results_reported_when',
994 'request_status',
995 'is_pending',
996 'progress_note'
997 ]
998
999 - def __init__(self, aPK_obj=None, row=None):
1000 """Instantiate lab request.
1001
1002 The aPK_obj can be either a dict with the keys "req_id"
1003 and "lab" or a simple primary key.
1004 """
1005
1006 if aPK_obj is None:
1007 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
1008 return
1009 pk = aPK_obj
1010
1011 if type(aPK_obj) == types.DictType:
1012
1013 try:
1014 aPK_obj['req_id']
1015 aPK_obj['lab']
1016 except:
1017 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
1018 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
1019
1020 where_snippets = []
1021 vals = {}
1022 where_snippets.append('request_id=%(req_id)s')
1023 if type(aPK_obj['lab']) == types.IntType:
1024 where_snippets.append('pk_test_org=%(lab)s')
1025 else:
1026 where_snippets.append('lab_name=%(lab)s')
1027 where_clause = ' and '.join(where_snippets)
1028 cmd = "select pk_request from v_lab_requests where %s" % where_clause
1029
1030 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
1031 if data is None:
1032 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1033 if len(data) == 0:
1034 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1035 pk = data[0][0]
1036
1037 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1038
1040 cmd = """
1041 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
1042 from v_pat_items vpi, v_basic_person vbp
1043 where
1044 vpi.pk_item=%s
1045 and
1046 vbp.pk_identity=vpi.pk_patient"""
1047 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
1048 if pat is None:
1049 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
1050 return None
1051 if len(pat) == 0:
1052 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
1053 return None
1054 return pat[0]
1055
1056
1057
1058 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
1059 """Create or get lab request.
1060
1061 returns tuple (status, value):
1062 (True, lab request instance)
1063 (False, error message)
1064 (None, housekeeping_todo primary key)
1065 """
1066 req = None
1067 aPK_obj = {
1068 'lab': lab,
1069 'req_id': req_id
1070 }
1071 try:
1072 req = cLabRequest (aPK_obj)
1073 except gmExceptions.NoSuchClinItemError, msg:
1074 _log.info('%s: will try to create lab request' % str(msg))
1075 except gmExceptions.ConstructorError, msg:
1076 _log.exception(str(msg), sys.exc_info(), verbose=0)
1077 return (False, msg)
1078
1079 if req is not None:
1080 db_pat = req.get_patient()
1081 if db_pat is None:
1082 _log.error('cannot cross-check patient on lab request')
1083 return (None, '')
1084
1085 if pat_id != db_pat[0]:
1086 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
1087 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
1088 to = 'user'
1089 prob = _('The lab request already exists but belongs to a different patient.')
1090 sol = _('Verify which patient this lab request really belongs to.')
1091 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
1092 cat = 'lab'
1093 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
1094 return (None, data)
1095 return (True, req)
1096
1097 queries = []
1098 if type(lab) is types.IntType:
1099 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
1100 else:
1101 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)"
1102 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
1103 cmd = "select currval('lab_request_pk_seq')"
1104 queries.append((cmd, []))
1105
1106 result, err = gmPG.run_commit('historica', queries, True)
1107 if result is None:
1108 return (False, err)
1109 try:
1110 req = cLabRequest(aPK_obj=result[0][0])
1111 except gmExceptions.ConstructorError, msg:
1112 _log.exception(str(msg), sys.exc_info(), verbose=0)
1113 return (False, msg)
1114 return (True, req)
1115
1116 -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):
1117 tres = None
1118 data = {
1119 'patient_id': patient_id,
1120 'when_field': when_field,
1121 'when': when,
1122 'test_type': test_type,
1123 'val_num': val_num,
1124 'val_alpha': val_alpha,
1125 'unit': unit
1126 }
1127 try:
1128 tres = cLabResult(aPK_obj=data)
1129
1130 _log.error('will not overwrite existing test result')
1131 _log.debug(str(tres))
1132 return (None, tres)
1133 except gmExceptions.NoSuchClinItemError:
1134 _log.debug('test result not found - as expected, will create it')
1135 except gmExceptions.ConstructorError, msg:
1136 _log.exception(str(msg), sys.exc_info(), verbose=0)
1137 return (False, msg)
1138 if request is None:
1139 return (False, _('need lab request when inserting lab result'))
1140
1141 if encounter_id is None:
1142 encounter_id = request['pk_encounter']
1143 queries = []
1144 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
1145 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
1146 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
1147 queries.append((cmd, [request['pk_request']]))
1148 cmd = "select currval('test_result_pk_seq')"
1149 queries.append((cmd, []))
1150
1151 result, err = gmPG.run_commit('historica', queries, True)
1152 if result is None:
1153 return (False, err)
1154 try:
1155 tres = cLabResult(aPK_obj=result[0][0])
1156 except gmExceptions.ConstructorError, msg:
1157 _log.exception(str(msg), sys.exc_info(), verbose=0)
1158 return (False, msg)
1159 return (True, tres)
1160
1162
1163 if limit < 1:
1164 limit = 1
1165
1166 lim = limit + 1
1167 cmd = """
1168 select pk_result
1169 from v_results4lab_req
1170 where reviewed is false
1171 order by pk_patient
1172 limit %s""" % lim
1173 rows = gmPG.run_ro_query('historica', cmd)
1174 if rows is None:
1175 _log.error('error retrieving unreviewed lab results')
1176 return (None, _('error retrieving unreviewed lab results'))
1177 if len(rows) == 0:
1178 return (False, [])
1179
1180 if len(rows) == lim:
1181 more_avail = True
1182
1183 del rows[limit]
1184 else:
1185 more_avail = False
1186 results = []
1187 for row in rows:
1188 try:
1189 results.append(cLabResult(aPK_obj=row[0]))
1190 except gmExceptions.ConstructorError:
1191 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
1192 return (more_avail, results)
1193
1195 lim = limit + 1
1196 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
1197 rows = gmPG.run_ro_query('historica', cmd)
1198 if rows is None:
1199 _log.error('error retrieving pending lab requests')
1200 return (None, None)
1201 if len(rows) == 0:
1202 return (False, [])
1203 results = []
1204
1205 if len(rows) == lim:
1206 too_many = True
1207
1208 del rows[limit]
1209 else:
1210 too_many = False
1211 requests = []
1212 for row in rows:
1213 try:
1214 requests.append(cLabRequest(aPK_obj=row[0]))
1215 except gmExceptions.ConstructorError:
1216 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
1217 return (too_many, requests)
1218
1220 """Get logically next request ID for given lab.
1221
1222 - incrementor_func:
1223 - if not supplied the next ID is guessed
1224 - if supplied it is applied to the most recently used ID
1225 """
1226 if type(lab) == types.IntType:
1227 lab_snippet = 'vlr.fk_test_org=%s'
1228 else:
1229 lab_snippet = 'vlr.lab_name=%s'
1230 lab = str(lab)
1231 cmd = """
1232 select request_id
1233 from lab_request lr0
1234 where lr0.clin_when = (
1235 select max(vlr.sampled_when)
1236 from v_lab_requests vlr
1237 where %s
1238 )""" % lab_snippet
1239 rows = gmPG.run_ro_query('historica', cmd, None, lab)
1240 if rows is None:
1241 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
1242 return ''
1243 if len(rows) == 0:
1244 return ''
1245 most_recent = rows[0][0]
1246
1247 if incrementor_func is not None:
1248 try:
1249 next = incrementor_func(most_recent)
1250 except TypeError:
1251 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
1252 return most_recent
1253 return next
1254
1255 for pos in range(len(most_recent)):
1256 header = most_recent[:pos]
1257 trailer = most_recent[pos:]
1258 try:
1259 return '%s%s' % (header, str(int(trailer) + 1))
1260 except ValueError:
1261 header = most_recent[:-1]
1262 trailer = most_recent[-1:]
1263 return '%s%s' % (header, chr(ord(trailer) + 1))
1264
1266 """Calculate BMI.
1267
1268 mass: kg
1269 height: cm
1270 age: not yet used
1271
1272 returns:
1273 (True/False, data)
1274 True: data = (bmi, lower_normal, upper_normal)
1275 False: data = error message
1276 """
1277 converted, mass = gmTools.input2decimal(mass)
1278 if not converted:
1279 return False, u'mass: cannot convert <%s> to Decimal' % mass
1280
1281 converted, height = gmTools.input2decimal(height)
1282 if not converted:
1283 return False, u'height: cannot convert <%s> to Decimal' % height
1284
1285 approx_surface = (height / decimal.Decimal(100))**2
1286 bmi = mass / approx_surface
1287
1288 print mass, height, '->', approx_surface, '->', bmi
1289
1290 lower_normal_mass = 20.0 * approx_surface
1291 upper_normal_mass = 25.0 * approx_surface
1292
1293 return True, (bmi, lower_normal_mass, upper_normal_mass)
1294
1295
1296
1297 if __name__ == '__main__':
1298
1299 if len(sys.argv) < 2:
1300 sys.exit()
1301
1302 if sys.argv[1] != 'test':
1303 sys.exit()
1304
1305 import time
1306
1307 gmI18N.activate_locale()
1308 gmI18N.install_domain()
1309
1310
1312 tr = create_test_result (
1313 encounter = 1,
1314 episode = 1,
1315 type = 1,
1316 intended_reviewer = 1,
1317 val_num = '12',
1318 val_alpha=None,
1319 unit = 'mg/dl'
1320 )
1321 print tr
1322 return tr
1323
1327
1332
1334 print "test_result()"
1335
1336 data = {
1337 'patient_id': 12,
1338 'when_field': 'val_when',
1339 'when': '2000-09-17 18:23:00+02',
1340 'test_type': 9,
1341 'val_num': 17.3,
1342 'val_alpha': None,
1343 'unit': 'mg/l'
1344 }
1345 lab_result = cLabResult(aPK_obj=data)
1346 print lab_result
1347 fields = lab_result.get_fields()
1348 for field in fields:
1349 print field, ':', lab_result[field]
1350 print "updatable:", lab_result.get_updatable_fields()
1351 print time.time()
1352 print lab_result.get_patient()
1353 print time.time()
1354
1356 print "test_request()"
1357 try:
1358
1359
1360 data = {
1361 'req_id': 'EML#SC937-0176-CEC#11',
1362 'lab': 'Enterprise Main Lab'
1363 }
1364 lab_req = cLabRequest(aPK_obj=data)
1365 except gmExceptions.ConstructorError, msg:
1366 print "no such lab request:", msg
1367 return
1368 print lab_req
1369 fields = lab_req.get_fields()
1370 for field in fields:
1371 print field, ':', lab_req[field]
1372 print "updatable:", lab_req.get_updatable_fields()
1373 print time.time()
1374 print lab_req.get_patient()
1375 print time.time()
1376
1381
1386
1394
1399
1404
1413
1415 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
1416 bmi, low, high = data
1417
1418 print "BMI:", bmi
1419 print "low:", low, "kg"
1420 print "hi :", high, "kg"
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436 test_calculate_bmi()
1437
1438
1439