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