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