| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed measurements related business objects."""
2
3 # FIXME: use UCUM from Regenstrief Institute
4 #============================================================
5 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL"
7
8
9 import types
10 import sys
11 import logging
12 import codecs
13 import decimal
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18
19 from Gnumed.pycommon import gmDateTime
20 if __name__ == '__main__':
21 from Gnumed.pycommon import gmLog2
22 from Gnumed.pycommon import gmI18N
23 gmDateTime.init()
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmBusinessDBObject
26 from Gnumed.pycommon import gmPG2
27 from Gnumed.pycommon import gmTools
28 from Gnumed.pycommon import gmDispatcher
29 from Gnumed.pycommon import gmHooks
30 from Gnumed.business import gmOrganization
31 from Gnumed.business import gmCoding
32
33
34 _log = logging.getLogger('gm.lab')
35
36 #============================================================
38 """Always relates to the active patient."""
39 gmHooks.run_hook_script(hook = u'after_test_result_modified')
40
41 gmDispatcher.connect(_on_test_result_modified, u'clin.test_result_mod_db')
42
43 #============================================================
45 """Represents one test org/lab."""
46 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
47 _cmds_store_payload = [
48 u"""UPDATE clin.test_org SET
49 fk_org_unit = %(pk_org_unit)s,
50 contact = gm.nullify_empty_string(%(test_org_contact)s),
51 comment = gm.nullify_empty_string(%(comment)s)
52 WHERE
53 pk = %(pk_test_org)s
54 AND
55 xmin = %(xmin_test_org)s
56 RETURNING
57 xmin AS xmin_test_org
58 """
59 ]
60 _updatable_fields = [
61 u'pk_org_unit',
62 u'test_org_contact',
63 u'comment'
64 ]
65 #------------------------------------------------------------
67
68 if name is None:
69 name = u'unassigned lab'
70
71 # get org unit
72 if pk_org_unit is None:
73 org = gmOrganization.org_exists(organization = name)
74 if org is None:
75 org = gmOrganization.create_org (
76 organization = name,
77 category = u'Laboratory'
78 )
79 org_unit = gmOrganization.create_org_unit (
80 pk_organization = org['pk_org'],
81 unit = name
82 )
83 pk_org_unit = org_unit['pk_org_unit']
84
85 # test org exists ?
86 args = {'pk_unit': pk_org_unit}
87 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
89
90 if len(rows) == 0:
91 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
92 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
93
94 test_org = cTestOrg(aPK_obj = rows[0][0])
95 if comment is not None:
96 comment = comment.strip()
97 test_org['comment'] = comment
98 test_org.save()
99
100 return test_org
101 #------------------------------------------------------------
103 args = {'pk': test_org}
104 cmd = u"""
105 DELETE FROM clin.test_org
106 WHERE
107 pk = %(pk)s
108 AND
109 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
110 AND
111 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
112 """
113 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
114 #------------------------------------------------------------
116 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
118 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
119
120 #============================================================
121 # test panels / profiles
122 #------------------------------------------------------------
123 _SQL_get_test_panels = u"SELECT * FROM clin.v_test_panels WHERE %s"
124
126 """Represents a grouping/listing of tests into a panel."""
127
128 _cmd_fetch_payload = _SQL_get_test_panels % u"pk_test_panel = %s"
129 _cmds_store_payload = [
130 u"""
131 UPDATE clin.test_panel SET
132 description = gm.nullify_empty_string(%(description)s),
133 comment = gm.nullify_empty_string(%(comment)s),
134 fk_test_types = %(pk_test_types)s
135 WHERE
136 pk = %(pk_test_panel)s
137 AND
138 xmin = %(xmin_test_panel)s
139 RETURNING
140 xmin AS xmin_test_panel
141 """
142 ]
143 _updatable_fields = [
144 u'description',
145 u'comment',
146 u'pk_test_types'
147 ]
148 #--------------------------------------------------------
150 txt = _('Test panel "%s" [#%s]\n') % (
151 self._payload[self._idx['description']],
152 self._payload[self._idx['pk_test_panel']]
153 )
154
155 if self._payload[self._idx['comment']] is not None:
156 txt += u'\n'
157 txt += gmTools.wrap (
158 text = self._payload[self._idx['comment']],
159 width = 50,
160 initial_indent = u' ',
161 subsequent_indent = u' '
162 )
163 txt += u'\n'
164
165 tts = self.test_types
166 if tts is not None:
167 txt += u'\n'
168 txt += _('Included test types:\n')
169 for tt in tts:
170 txt += u' %s: %s\n' % (
171 tt['abbrev'],
172 tt['name']
173 )
174
175 codes = self.generic_codes
176 if len(codes) > 0:
177 txt += u'\n'
178 for c in codes:
179 txt += u'%s %s: %s (%s - %s)\n' % (
180 (u' ' * left_margin),
181 c['code'],
182 c['term'],
183 c['name_short'],
184 c['version']
185 )
186
187 return txt
188 #--------------------------------------------------------
190 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
191 cmd = u"INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)"
192 args = {
193 'tp': self._payload[self._idx['pk_test_panel']],
194 'code': pk_code
195 }
196 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
197 return True
198 #--------------------------------------------------------
200 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
201 cmd = u"DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s"
202 args = {
203 'tp': self._payload[self._idx['pk_test_panel']],
204 'code': pk_code
205 }
206 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
207 return True
208 #--------------------------------------------------------
209 # properties
210 #--------------------------------------------------------
212 if self._payload[self._idx['pk_test_types']] is None:
213 return None
214
215 rows, idx = gmPG2.run_ro_queries (
216 queries = [{
217 'cmd': _SQL_get_test_types % u'pk_test_type IN %(pks)s ORDER BY unified_abbrev',
218 'args': {'pks': tuple(self._payload[self._idx['pk_test_types']])}
219 }],
220 get_col_idx = True
221 )
222 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
223
224 test_types = property(_get_test_types, lambda x:x)
225 #--------------------------------------------------------
227 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
228 return []
229
230 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
231 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
232 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
233 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
234
236 queries = []
237 # remove all codes
238 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
239 queries.append ({
240 'cmd': u'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s',
241 'args': {
242 'tp': self._payload[self._idx['pk_test_panel']],
243 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
244 }
245 })
246 # add new codes
247 for pk_code in pk_codes:
248 queries.append ({
249 'cmd': u'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)',
250 'args': {
251 'tp': self._payload[self._idx['pk_test_panel']],
252 'pk_code': pk_code
253 }
254 })
255 if len(queries) == 0:
256 return
257 # run it all in one transaction
258 rows, idx = gmPG2.run_rw_queries(queries = queries)
259 return
260
261 generic_codes = property(_get_generic_codes, _set_generic_codes)
262 #--------------------------------------------------------
264 return get_most_recent_results_for_panel (
265 pk_patient = pk_patient,
266 pk_panel = self._payload[self._idx['pk_test_panel']],
267 order_by = order_by
268 )
269 #------------------------------------------------------------
271 if order_by is None:
272 order_by = u'true'
273 else:
274 order_by = u'true ORDER BY %s' % order_by
275
276 cmd = _SQL_get_test_panels % order_by
277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
278 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
279
280 #------------------------------------------------------------
282
283 args = {u'desc': description.strip()}
284 cmd = u"""
285 INSERT INTO clin.test_panel (description)
286 VALUES (gm.nullify_empty_string(%(desc)s))
287 RETURNING pk
288 """
289 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
290
291 return cTestPanel(aPK_obj = rows[0]['pk'])
292
293 #------------------------------------------------------------
295 args = {'pk': pk}
296 cmd = u"DELETE FROM clin.test_panel WHERE pk = %(pk)s"
297 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
298 return True
299
300 #============================================================
302 """Represents one meta test type under which actual test types can be aggregated."""
303
304 _cmd_fetch_payload = u"SELECT *, xmin FROM clin.meta_test_type WHERE pk = %s"
305 _cmds_store_payload = [u"""
306 UPDATE clin.meta_test_type SET
307 abbrev = %(abbrev)s,
308 name = %(name)s,
309 loinc = gm.nullify_empty_string(%(loinc)s),
310 comment = gm.nullify_empty_string(%(comment)s)
311 WHERE
312 pk = %(pk)s
313 AND
314 xmin = %(xmin)s
315 RETURNING
316 xmin
317 """]
318 _updatable_fields = [
319 u'abbrev',
320 u'name',
321 u'loinc',
322 u'comment'
323 ]
324 #--------------------------------------------------------
326 txt = _('Meta (%s=aggregate) test type [#%s]\n\n') % (gmTools.u_sum, self._payload[self._idx['pk']])
327 txt += _(' Name: %s (%s)\n') % (
328 self._payload[self._idx['abbrev']],
329 self._payload[self._idx['name']]
330 )
331 if self._payload[self._idx['loinc']] is not None:
332 txt += u' LOINC: %s (%s)\n' % self._payload[self._idx['loinc']]
333 if self._payload[self._idx['loinc']] is not None:
334 txt += _(' Comment: %s\n') % self._payload[self._idx['comment']]
335 if with_tests:
336 ttypes = self.included_test_types
337 if len(ttypes) > 0:
338 txt += _(' Aggregates the following test types:\n')
339 for ttype in ttypes:
340 txt += u' %s (%s)%s%s [#%s]\n' % (
341 ttype['abbrev'],
342 ttype['name'],
343 gmTools.coalesce(ttype['conversion_unit'], u'', ', %s'),
344 gmTools.coalesce(ttype['loinc'], u'', u', LOINC: %s'),
345 ttype['pk_test_type']
346 )
347 if patient is not None:
348 txt += u'\n'
349 most_recent = self.get_most_recent_result(patient = patient)
350 if most_recent is not None:
351 txt += _(' Most recent (%s): %s%s%s') % (
352 most_recent['clin_when'].strftime('%Y %b %d'),
353 most_recent['unified_val'],
354 gmTools.coalesce(most_recent['val_unit'], u'', u' %s'),
355 gmTools.coalesce(most_recent['abnormality_indicator'], u'', u' (%s)')
356 )
357 oldest = self.get_oldest_result(patient = patient)
358 if oldest is not None:
359 txt += u'\n'
360 txt += _(' Oldest (%s): %s%s%s') % (
361 oldest['clin_when'].strftime('%Y %b %d'),
362 oldest['unified_val'],
363 gmTools.coalesce(oldest['val_unit'], u'', u' %s'),
364 gmTools.coalesce(oldest['abnormality_indicator'], u'', u' (%s)')
365 )
366 return txt
367
368 #--------------------------------------------------------
370 args = {
371 'pat': patient,
372 'mttyp': self._payload[self._idx['pk']]
373 }
374 cmd = u"""
375 SELECT * FROM clin.v_test_results
376 WHERE
377 pk_patient = %(pat)s
378 AND
379 pk_meta_test_type = %(mttyp)s
380 ORDER BY clin_when DESC
381 LIMIT 1"""
382 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
383 if len(rows) == 0:
384 return None
385
386 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
387
388 #--------------------------------------------------------
390 args = {
391 'pat': patient,
392 'mttyp': self._payload[self._idx['pk']]
393 }
394 cmd = u"""
395 SELECT * FROM clin.v_test_results
396 WHERE
397 pk_patient = %(pat)s
398 AND
399 pk_meta_test_type = %(mttyp)s
400 ORDER BY clin_when
401 LIMIT 1"""
402 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
403 if len(rows) == 0:
404 return None
405
406 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
407
408 #--------------------------------------------------------
409 # properties
410 #--------------------------------------------------------
412 cmd = _SQL_get_test_types % u'pk_meta_test_type = %(pk_meta)s'
413 args = {u'pk_meta': self._payload[self._idx['pk']]}
414 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
415 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
416
417 included_test_types = property(_get_included_test_types, lambda x:x)
418
419 #------------------------------------------------------------
421 cmd = u"""
422 INSERT INTO clin.meta_test_type (name, abbrev)
423 SELECT
424 %(name)s,
425 %(abbr)s
426 WHERE NOT EXISTS (
427 SELECT 1 FROM clin.meta_test_type
428 WHERE
429 name = %(name)s
430 AND
431 abbrev = %(abbr)s
432 )
433 RETURNING *, xmin
434 """
435 args = {
436 'name': name.strip(),
437 'abbr': abbreviation.strip()
438 }
439 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True, return_data = True)
440 if len(rows) == 0:
441 if not return_existing:
442 return None
443 cmd = u"SELECT *, xmin FROM clin.meta_test_type WHERE name = %(name)s and %(abbr)s"
444 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
445
446 return cMetaTestType(row = {'pk_field': 'pk', 'idx': idx, 'data': rows[0]})
447
448 #------------------------------------------------------------
450 cmd = u"""
451 DELETE FROM clin.meta_test_type
452 WHERE
453 pk = %(pk)s
454 AND
455 NOT EXISTS (
456 SELECT 1 FROM clin.test_type
457 WHERE fk_meta_test_type = %(pk)s
458 )"""
459 args = {'pk': meta_type}
460 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
461
462 #------------------------------------------------------------
464 cmd = u'SELECT *, xmin FROM clin.meta_test_type'
465 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
466 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
467
468 #============================================================
469 _SQL_get_test_types = u"SELECT * FROM clin.v_test_types WHERE %s"
470
472 """Represents one test result type."""
473
474 _cmd_fetch_payload = _SQL_get_test_types % u"pk_test_type = %s"
475
476 _cmds_store_payload = [
477 u"""UPDATE clin.test_type SET
478 abbrev = gm.nullify_empty_string(%(abbrev)s),
479 name = gm.nullify_empty_string(%(name)s),
480 loinc = gm.nullify_empty_string(%(loinc)s),
481 comment = gm.nullify_empty_string(%(comment_type)s),
482 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
483 fk_test_org = %(pk_test_org)s,
484 fk_meta_test_type = %(pk_meta_test_type)s
485 WHERE
486 pk = %(pk_test_type)s
487 AND
488 xmin = %(xmin_test_type)s
489 RETURNING
490 xmin AS xmin_test_type"""
491 ]
492
493 _updatable_fields = [
494 'abbrev',
495 'name',
496 'loinc',
497 'comment_type',
498 'conversion_unit',
499 'pk_test_org',
500 'pk_meta_test_type'
501 ]
502 #--------------------------------------------------------
503 # properties
504 #--------------------------------------------------------
506 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
507 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
508 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
509 return rows[0][0]
510
511 in_use = property(_get_in_use, lambda x:x)
512 #--------------------------------------------------------
514 results = get_most_recent_results (
515 test_type = self._payload[self._idx['pk_test_type']],
516 loinc = None,
517 no_of_results = no_of_results,
518 patient = patient
519 )
520 if results is None:
521 if self._payload[self._idx['loinc']] is not None:
522 results = get_most_recent_results (
523 test_type = None,
524 loinc = self._payload[self._idx['loinc']],
525 no_of_results = no_of_results,
526 patient = patient
527 )
528 return results
529
530 #--------------------------------------------------------
532 result = get_oldest_result (
533 test_type = self._payload[self._idx['pk_test_type']],
534 loinc = None,
535 patient = patient
536 )
537 if result is None:
538 if self._payload[self._idx['loinc']] is not None:
539 result = get_oldest_result (
540 test_type = None,
541 loinc = self._payload[self._idx['loinc']],
542 patient = patient
543 )
544 return result
545
546 #--------------------------------------------------------
548 if self._payload[self._idx['pk_test_panels']] is None:
549 return None
550
551 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
552
553 test_panels = property(_get_test_panels, lambda x:x)
554
555 #--------------------------------------------------------
557 if real_one_only is False:
558 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
559 if self._payload[self._idx['is_fake_meta_type']]:
560 return None
561 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
562
563 meta_test_type = property(get_meta_test_type, lambda x:x)
564 #--------------------------------------------------------
566 """Returns the closest test result which does have normal range information.
567
568 - needs <unit>
569 - if <timestamp> is None it will assume now() and thus return the most recent
570 """
571 if timestamp is None:
572 timestamp = gmDateTime.pydt_now_here()
573 cmd = u"""
574 SELECT * FROM clin.v_test_results
575 WHERE
576 pk_test_type = %(pk_type)s
577 AND
578 val_unit = %(unit)s
579 AND
580 (
581 (val_normal_min IS NOT NULL)
582 OR
583 (val_normal_max IS NOT NULL)
584 OR
585 (val_normal_range IS NOT NULL)
586 )
587 ORDER BY
588 CASE
589 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
590 ELSE %(clin_when)s - clin_when
591 END
592 LIMIT 1"""
593 args = {
594 u'pk_type': self._payload[self._idx['pk_test_type']],
595 u'unit': unit,
596 u'clin_when': timestamp
597 }
598 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
599 if len(rows) == 0:
600 return None
601 r = rows[0]
602 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
603
604 #--------------------------------------------------------
606 """Returns the closest test result which does have target range information.
607
608 - needs <unit>
609 - needs <patient> (as target will be per-patient)
610 - if <timestamp> is None it will assume now() and thus return the most recent
611 """
612 if timestamp is None:
613 timestamp = gmDateTime.pydt_now_here()
614 cmd = u"""
615 SELECT * FROM clin.v_test_results
616 WHERE
617 pk_test_type = %(pk_type)s
618 AND
619 val_unit = %(unit)s
620 AND
621 pk_patient = %(pat)s
622 AND
623 (
624 (val_target_min IS NOT NULL)
625 OR
626 (val_target_max IS NOT NULL)
627 OR
628 (val_target_range IS NOT NULL)
629 )
630 ORDER BY
631 CASE
632 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
633 ELSE %(clin_when)s - clin_when
634 END
635 LIMIT 1"""
636 args = {
637 u'pk_type': self._payload[self._idx['pk_test_type']],
638 u'unit': unit,
639 u'pat': patient,
640 u'clin_when': timestamp
641 }
642 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
643 if len(rows) == 0:
644 return None
645 r = rows[0]
646 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
647
648 #--------------------------------------------------------
650 """Returns the unit of the closest test result.
651
652 - if <timestamp> is None it will assume now() and thus return the most recent
653 """
654 if timestamp is None:
655 timestamp = gmDateTime.pydt_now_here()
656 cmd = u"""
657 SELECT val_unit FROM clin.v_test_results
658 WHERE
659 pk_test_type = %(pk_type)s
660 AND
661 val_unit IS NOT NULL
662 ORDER BY
663 CASE
664 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
665 ELSE %(clin_when)s - clin_when
666 END
667 LIMIT 1"""
668 args = {
669 u'pk_type': self._payload[self._idx['pk_test_type']],
670 u'clin_when': timestamp
671 }
672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
673 if len(rows) == 0:
674 return None
675 return rows[0]['val_unit']
676
677 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x)
678
679 #--------------------------------------------------------
681 tt = u''
682 tt += _('Test type "%s" (%s) [#%s]\n') % (
683 self._payload[self._idx['name']],
684 self._payload[self._idx['abbrev']],
685 self._payload[self._idx['pk_test_type']]
686 )
687 tt += u'\n'
688 tt += gmTools.coalesce(self._payload[self._idx['loinc']], u'', u' LOINC: %s\n')
689 tt += gmTools.coalesce(self._payload[self._idx['conversion_unit']], u'', _(' Conversion unit: %s\n'))
690 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], u'', _(' Comment: %s\n'))
691
692 tt += u'\n'
693 tt += _('Lab details:\n')
694 tt += _(' Name: %s\n') % self._payload[self._idx['name_org']]
695 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], u'', _(' Contact: %s\n'))
696 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], u'', _(' Comment: %s\n'))
697
698 if self._payload[self._idx['is_fake_meta_type']] is False:
699 tt += u'\n'
700 tt += _('Aggregated under meta type:\n')
701 tt += _(' Name: %s - %s [#%s]\n') % (
702 self._payload[self._idx['abbrev_meta']],
703 self._payload[self._idx['name_meta']],
704 self._payload[self._idx['pk_meta_test_type']]
705 )
706 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], u'', u' LOINC: %s\n')
707 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], u'', _(' Comment: %s\n'))
708
709 panels = self.test_panels
710 if panels is not None:
711 tt += u'\n'
712 tt += _('Listed in test panels:\n')
713 for panel in panels:
714 tt += _(' Panel "%s" [#%s]\n') % (
715 panel['description'],
716 panel['pk_test_panel']
717 )
718
719 if patient is not None:
720 tt += u'\n'
721 result = self.get_most_recent_results(patient = patient, no_of_results = 1)
722 if result is not None:
723 tt += _(' Most recent (%s): %s%s%s') % (
724 result['clin_when'].strftime('%Y-%m-%d'),
725 result['unified_val'],
726 gmTools.coalesce(result['val_unit'], u'', u' %s'),
727 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)')
728 )
729 result = self.get_oldest_result(patient = patient)
730 if result is not None:
731 tt += u'\n'
732 tt += _(' Oldest (%s): %s%s%s') % (
733 result['clin_when'].strftime('%Y-%m-%d'),
734 result['unified_val'],
735 gmTools.coalesce(result['val_unit'], u'', u' %s'),
736 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)')
737 )
738
739 return tt
740
741 #------------------------------------------------------------
743 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
744 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
745 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
746
747 #------------------------------------------------------------
749
750 if (abbrev is None) and (name is None):
751 raise ValueError('must have <abbrev> and/or <name> set')
752
753 where_snippets = []
754
755 if lab is None:
756 where_snippets.append('pk_test_org IS NULL')
757 else:
758 try:
759 int(lab)
760 where_snippets.append('pk_test_org = %(lab)s')
761 except (TypeError, ValueError):
762 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
763
764 if abbrev is not None:
765 where_snippets.append('abbrev = %(abbrev)s')
766
767 if name is not None:
768 where_snippets.append('name = %(name)s')
769
770 where_clause = u' and '.join(where_snippets)
771 cmd = u"select * from clin.v_test_types where %s" % where_clause
772 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
773
774 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
775
776 if len(rows) == 0:
777 return None
778
779 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
780 return tt
781
782 #------------------------------------------------------------
784 cmd = u'delete from clin.test_type where pk = %(pk)s'
785 args = {'pk': measurement_type}
786 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
787
788 #------------------------------------------------------------
790 """Create or get test type."""
791
792 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
793 # found ?
794 if ttype is not None:
795 return ttype
796
797 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
798
799 # not found, so create it
800 if unit is None:
801 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
802 raise ValueError('need <unit> to create test type')
803
804 # make query
805 cols = []
806 val_snippets = []
807 vals = {}
808
809 # lab
810 if lab is None:
811 lab = create_test_org()['pk_test_org']
812
813 cols.append('fk_test_org')
814 try:
815 vals['lab'] = int(lab)
816 val_snippets.append('%(lab)s')
817 except:
818 vals['lab'] = lab
819 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
820
821 # code
822 cols.append('abbrev')
823 val_snippets.append('%(abbrev)s')
824 vals['abbrev'] = abbrev
825
826 # unit
827 cols.append('conversion_unit')
828 val_snippets.append('%(unit)s')
829 vals['unit'] = unit
830
831 # name
832 if name is not None:
833 cols.append('name')
834 val_snippets.append('%(name)s')
835 vals['name'] = name
836
837 col_clause = u', '.join(cols)
838 val_clause = u', '.join(val_snippets)
839 queries = [
840 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
841 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
842 ]
843 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
844 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
845
846 return ttype
847
848 #============================================================
850 """Represents one test result."""
851
852 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
853
854 _cmds_store_payload = [
855 u"""update clin.test_result set
856 clin_when = %(clin_when)s,
857 narrative = nullif(trim(%(comment)s), ''),
858 val_num = %(val_num)s,
859 val_alpha = nullif(trim(%(val_alpha)s), ''),
860 val_unit = nullif(trim(%(val_unit)s), ''),
861 val_normal_min = %(val_normal_min)s,
862 val_normal_max = %(val_normal_max)s,
863 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
864 val_target_min = %(val_target_min)s,
865 val_target_max = %(val_target_max)s,
866 val_target_range = nullif(trim(%(val_target_range)s), ''),
867 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
868 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
869 note_test_org = nullif(trim(%(note_test_org)s), ''),
870 material = nullif(trim(%(material)s), ''),
871 material_detail = nullif(trim(%(material_detail)s), ''),
872 fk_intended_reviewer = %(pk_intended_reviewer)s,
873 fk_encounter = %(pk_encounter)s,
874 fk_episode = %(pk_episode)s,
875 fk_type = %(pk_test_type)s,
876 fk_request = %(pk_request)s
877 where
878 pk = %(pk_test_result)s and
879 xmin = %(xmin_test_result)s""",
880 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
881 ]
882
883 _updatable_fields = [
884 'clin_when',
885 'comment',
886 'val_num',
887 'val_alpha',
888 'val_unit',
889 'val_normal_min',
890 'val_normal_max',
891 'val_normal_range',
892 'val_target_min',
893 'val_target_max',
894 'val_target_range',
895 'abnormality_indicator',
896 'norm_ref_group',
897 'note_test_org',
898 'material',
899 'material_detail',
900 'pk_intended_reviewer',
901 'pk_encounter',
902 'pk_episode',
903 'pk_test_type',
904 'pk_request'
905 ]
906 #--------------------------------------------------------
907 - def format(self, with_review=True, with_evaluation=True, with_ranges=True, with_episode=True, with_type_details=True, date_format='%Y %b %d %H:%M'):
908
909 # FIXME: add battery, request details
910
911 has_normal_min_or_max = (
912 self._payload[self._idx['val_normal_min']] is not None
913 ) or (
914 self._payload[self._idx['val_normal_max']] is not None
915 )
916 if has_normal_min_or_max:
917 normal_min_max = u'%s - %s' % (
918 gmTools.coalesce(self._payload[self._idx['val_normal_min']], u'?'),
919 gmTools.coalesce(self._payload[self._idx['val_normal_max']], u'?')
920 )
921 else:
922 normal_min_max = u''
923
924 has_clinical_min_or_max = (
925 self._payload[self._idx['val_target_min']] is not None
926 ) or (
927 self._payload[self._idx['val_target_max']] is not None
928 )
929 if has_clinical_min_or_max:
930 clinical_min_max = u'%s - %s' % (
931 gmTools.coalesce(self._payload[self._idx['val_target_min']], u'?'),
932 gmTools.coalesce(self._payload[self._idx['val_target_max']], u'?')
933 )
934 else:
935 clinical_min_max = u''
936
937 # header
938 tt = _(u'Result from %s \n') % gmDateTime.pydt_strftime (
939 self._payload[self._idx['clin_when']],
940 date_format
941 )
942
943 # basics
944 tt += u' ' + _(u'Type: "%(name)s" (%(abbr)s) [#%(pk_type)s]\n') % ({
945 'name': self._payload[self._idx['name_tt']],
946 'abbr': self._payload[self._idx['abbrev_tt']],
947 'pk_type': self._payload[self._idx['pk_test_type']]
948 })
949 tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({
950 'val': self._payload[self._idx['unified_val']],
951 'unit': gmTools.coalesce(self._payload[self._idx['val_unit']], u'', u' %s'),
952 'ind': gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)'),
953 'pk_result': self._payload[self._idx['pk_test_result']]
954 })
955 tmp = (u'%s%s' % (
956 gmTools.coalesce(self._payload[self._idx['name_test_org']], u''),
957 gmTools.coalesce(self._payload[self._idx['contact_test_org']], u'', u' (%s)'),
958 )).strip()
959 if tmp != u'':
960 tt += u' ' + _(u'Source: %s\n') % tmp
961 tt += u'\n'
962
963 if with_evaluation:
964 norm_eval = None
965 if self._payload[self._idx['val_num']] is not None:
966 # 1) normal range
967 # lowered ?
968 if (self._payload[self._idx['val_normal_min']] is not None) and (self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]):
969 try:
970 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_normal_min']]
971 except ZeroDivisionError:
972 percent = None
973 if percent is not None:
974 if percent < 6:
975 norm_eval = _(u'%.1f %% of the normal lower limit') % percent
976 else:
977 norm_eval = _(u'%.0f %% of the normal lower limit') % percent
978 # raised ?
979 if (self._payload[self._idx['val_normal_max']] is not None) and (self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]):
980 try:
981 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_normal_max']]
982 except ZeroDivisionError:
983 x_times = None
984 if x_times is not None:
985 if x_times < 10:
986 norm_eval = _(u'%.1f times the normal upper limit') % x_times
987 else:
988 norm_eval = _(u'%.0f times the normal upper limit') % x_times
989 if norm_eval is not None:
990 tt += u' (%s)\n' % norm_eval
991 # #-------------------------------------
992 # # this idea was shot down on the list
993 # #-------------------------------------
994 # # bandwidth of deviation
995 # if None not in [self._payload[self._idx['val_normal_min']], self._payload[self._idx['val_normal_max']]]:
996 # normal_width = self._payload[self._idx['val_normal_max']] - self._payload[self._idx['val_normal_min']]
997 # deviation_from_normal_range = None
998 # # below ?
999 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]:
1000 # deviation_from_normal_range = self._payload[self._idx['val_normal_min']] - self._payload[self._idx['val_num']]
1001 # # above ?
1002 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]:
1003 # deviation_from_normal_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_normal_max']]
1004 # if deviation_from_normal_range is None:
1005 # try:
1006 # times_deviation = deviation_from_normal_range / normal_width
1007 # except ZeroDivisionError:
1008 # times_deviation = None
1009 # if times_deviation is not None:
1010 # if times_deviation < 10:
1011 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation
1012 # else:
1013 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation
1014 # #-------------------------------------
1015
1016 # 2) clinical target range
1017 norm_eval = None
1018 # lowered ?
1019 if (self._payload[self._idx['val_target_min']] is not None) and (self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]):
1020 try:
1021 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_target_min']]
1022 except ZeroDivisionError:
1023 percent = None
1024 if percent is not None:
1025 if percent < 6:
1026 norm_eval = _(u'%.1f %% of the target lower limit') % percent
1027 else:
1028 norm_eval = _(u'%.0f %% of the target lower limit') % percent
1029 # raised ?
1030 if (self._payload[self._idx['val_target_max']] is not None) and (self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]):
1031 try:
1032 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_target_max']]
1033 except ZeroDivisionError:
1034 x_times = None
1035 if x_times is not None:
1036 if x_times < 10:
1037 norm_eval = _(u'%.1f times the target upper limit') % x_times
1038 else:
1039 norm_eval = _(u'%.0f times the target upper limit') % x_times
1040 if norm_eval is not None:
1041 tt += u' (%s)\n' % norm_eval
1042 # #-------------------------------------
1043 # # this idea was shot down on the list
1044 # #-------------------------------------
1045 # # bandwidth of deviation
1046 # if None not in [self._payload[self._idx['val_target_min']], self._payload[self._idx['val_target_max']]]:
1047 # normal_width = self._payload[self._idx['val_target_max']] - self._payload[self._idx['val_target_min']]
1048 # deviation_from_target_range = None
1049 # # below ?
1050 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]:
1051 # deviation_from_target_range = self._payload[self._idx['val_target_min']] - self._payload[self._idx['val_num']]
1052 # # above ?
1053 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]:
1054 # deviation_from_target_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_target_max']]
1055 # if deviation_from_target_range is None:
1056 # try:
1057 # times_deviation = deviation_from_target_range / normal_width
1058 # except ZeroDivisionError:
1059 # times_deviation = None
1060 # if times_deviation is not None:
1061 # if times_deviation < 10:
1062 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation
1063 # else:
1064 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation
1065 # #-------------------------------------
1066
1067 if with_ranges:
1068 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({
1069 'norm_min_max': normal_min_max,
1070 'norm_range': gmTools.coalesce (
1071 self._payload[self._idx['val_normal_range']],
1072 u'',
1073 gmTools.bool2subst (
1074 has_normal_min_or_max,
1075 u' / %s',
1076 u'%s'
1077 )
1078 )
1079 })
1080 if self._payload[self._idx['norm_ref_group']] is not None:
1081 tt += u' ' + _(u'Reference group: %s\n') % self._payload[self._idx['norm_ref_group']]
1082 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({
1083 'clin_min_max': clinical_min_max,
1084 'clin_range': gmTools.coalesce (
1085 self._payload[self._idx['val_target_range']],
1086 u'',
1087 gmTools.bool2subst (
1088 has_clinical_min_or_max,
1089 u' / %s',
1090 u'%s'
1091 )
1092 )
1093 })
1094
1095 # metadata
1096 if self._payload[self._idx['comment']] is not None:
1097 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(self._payload[self._idx['comment']].split(u'\n'))
1098 if self._payload[self._idx['note_test_org']] is not None:
1099 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(self._payload[self._idx['note_test_org']].split(u'\n'))
1100 if with_episode:
1101 tt += u' ' + _(u'Episode: %s\n') % self._payload[self._idx['episode']]
1102 if self._payload[self._idx['health_issue']] is not None:
1103 tt += u' ' + _(u'Issue: %s\n') % self._payload[self._idx['health_issue']]
1104 if self._payload[self._idx['material']] is not None:
1105 tt += u' ' + _(u'Material: %s\n') % self._payload[self._idx['material']]
1106 if self._payload[self._idx['material_detail']] is not None:
1107 tt += u' ' + _(u'Details: %s\n') % self._payload[self._idx['material_detail']]
1108 tt += u'\n'
1109
1110 if with_review:
1111 if self._payload[self._idx['reviewed']]:
1112 review = gmDateTime.pydt_strftime (
1113 self._payload[self._idx['last_reviewed']],
1114 date_format
1115 )
1116 else:
1117 review = _('not yet')
1118 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({
1119 'sig_hand': gmTools.u_writing_hand,
1120 'reviewed': review
1121 })
1122 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst (
1123 self._payload[self._idx['you_are_responsible']],
1124 _('you'),
1125 self._payload[self._idx['responsible_reviewer']]
1126 )
1127 if self._payload[self._idx['reviewed']]:
1128 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({
1129 'reviewer': gmTools.bool2subst (
1130 self._payload[self._idx['review_by_you']],
1131 _('you'),
1132 gmTools.coalesce(self._payload[self._idx['last_reviewer']], u'?')
1133 )
1134 })
1135 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({
1136 'abnormal': gmTools.bool2subst (
1137 self._payload[self._idx['is_technically_abnormal']],
1138 _('yes'),
1139 _('no'),
1140 u'?'
1141 )
1142 })
1143 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({
1144 'relevant': gmTools.bool2subst (
1145 self._payload[self._idx['is_clinically_relevant']],
1146 _('yes'),
1147 _('no'),
1148 u'?'
1149 )
1150 })
1151 if self._payload[self._idx['review_comment']] is not None:
1152 tt += u' ' + _(u' Comment: %s\n') % self._payload[self._idx['review_comment']].strip()
1153 tt += u'\n'
1154
1155 # type
1156 if with_type_details:
1157 tt += _(u'Test type details:\n')
1158 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({
1159 'name_meta': gmTools.coalesce(self._payload[self._idx['name_meta']], u''),
1160 'abbrev_meta': gmTools.coalesce(self._payload[self._idx['abbrev_meta']], u''),
1161 'pk_u_type': self._payload[self._idx['pk_meta_test_type']]
1162 })
1163 if self._payload[self._idx['comment_tt']] is not None:
1164 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(self._payload[self._idx['comment_tt']].split(u'\n'))
1165 if self._payload[self._idx['comment_meta']] is not None:
1166 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(self._payload[self._idx['comment_meta']].split(u'\n'))
1167 tt += u'\n'
1168
1169 if with_review:
1170 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({
1171 'row_ver': self._payload[self._idx['row_version']],
1172 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format),
1173 'mod_by': self._payload[self._idx['modified_by']]
1174 })
1175
1176 return tt
1177 #--------------------------------------------------------
1179 """Returns the closest test result which does have normal range information."""
1180 if self._payload[self._idx['val_normal_min']] is not None:
1181 return self
1182 if self._payload[self._idx['val_normal_max']] is not None:
1183 return self
1184 if self._payload[self._idx['val_normal_range']] is not None:
1185 return self
1186 cmd = u"""
1187 SELECT * from clin.v_test_results
1188 WHERE
1189 pk_type = %(pk_type)s
1190 AND
1191 val_unit = %(unit)s
1192 AND
1193 (
1194 (val_normal_min IS NOT NULL)
1195 OR
1196 (val_normal_max IS NOT NULL)
1197 OR
1198 (val_normal_range IS NOT NULL)
1199 )
1200 ORDER BY
1201 CASE
1202 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
1203 ELSE %(clin_when)s - clin_when
1204 END
1205 LIMIT 1"""
1206 args = {
1207 u'pk_type': self._payload[self._idx['pk_test_type']],
1208 u'unit': self._payload[self._idx['val_unit']],
1209 u'clin_when': self._payload[self._idx['clin_when']]
1210 }
1211 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1212 if len(rows) == 0:
1213 return None
1214 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1215
1216 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x)
1217 #--------------------------------------------------------
1219
1220 has_normal_min_or_max = (
1221 self._payload[self._idx['val_normal_min']] is not None
1222 ) or (
1223 self._payload[self._idx['val_normal_max']] is not None
1224 )
1225 if has_normal_min_or_max:
1226 normal_min_max = u'%s - %s' % (
1227 gmTools.coalesce(self._payload[self._idx['val_normal_min']], u'?'),
1228 gmTools.coalesce(self._payload[self._idx['val_normal_max']], u'?')
1229 )
1230
1231 has_clinical_min_or_max = (
1232 self._payload[self._idx['val_target_min']] is not None
1233 ) or (
1234 self._payload[self._idx['val_target_max']] is not None
1235 )
1236 if has_clinical_min_or_max:
1237 clinical_min_max = u'%s - %s' % (
1238 gmTools.coalesce(self._payload[self._idx['val_target_min']], u'?'),
1239 gmTools.coalesce(self._payload[self._idx['val_target_max']], u'?')
1240 )
1241
1242 if has_clinical_min_or_max:
1243 return _('Target: %(clin_min_max)s%(clin_range)s') % ({
1244 'clin_min_max': clinical_min_max,
1245 'clin_range': gmTools.coalesce (
1246 self._payload[self._idx['val_target_range']],
1247 u'',
1248 gmTools.bool2subst (
1249 has_clinical_min_or_max,
1250 u' / %s',
1251 u'%s'
1252 )
1253 )
1254 })
1255
1256 if has_normal_min_or_max:
1257 return _('Norm: %(norm_min_max)s%(norm_range)s') % ({
1258 'norm_min_max': normal_min_max,
1259 'norm_range': gmTools.coalesce (
1260 self._payload[self._idx['val_normal_range']],
1261 u'',
1262 gmTools.bool2subst (
1263 has_normal_min_or_max,
1264 u' / %s',
1265 u'%s'
1266 )
1267 )
1268 })
1269
1270 if self._payload[self._idx['val_target_range']] is not None:
1271 return _('Target: %s') % self._payload[self._idx['val_target_range']],
1272
1273 if self._payload[self._idx['val_normal_range']] is not None:
1274 return _('Norm: %s') % self._payload[self._idx['val_normal_range']]
1275
1276 return None
1277
1278 formatted_range = property(_get_formatted_range, lambda x:x)
1279 #--------------------------------------------------------
1281 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1282
1283 test_type = property(_get_test_type, lambda x:x)
1284 #--------------------------------------------------------
1286 # 1) the user is right (review)
1287 if self._payload[self._idx['is_technically_abnormal']] is False:
1288 return False
1289 # 2) the lab is right (result.abnormality_indicator)
1290 indicator = self._payload[self._idx['abnormality_indicator']]
1291 if indicator is not None:
1292 indicator = indicator.strip()
1293 if indicator != u'':
1294 if indicator.strip(u'+') == u'':
1295 return True
1296 if indicator.strip(u'-') == u'':
1297 return False
1298 # 3) non-numerical value ?
1299 if self._payload[self._idx['val_num']] is None:
1300 return None
1301 # 4) the target range is right
1302 target_max = self._payload[self._idx['val_target_max']]
1303 if target_max is not None:
1304 if target_max < self._payload[self._idx['val_num']]:
1305 return True
1306 # 4) the normal range is right
1307 normal_max = self._payload[self._idx['val_normal_max']]
1308 if normal_max is not None:
1309 if normal_max < self._payload[self._idx['val_num']]:
1310 return True
1311 return None
1312
1313 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x)
1314 #--------------------------------------------------------
1316 # 1) the user is right (review)
1317 if self._payload[self._idx['is_technically_abnormal']] is False:
1318 return False
1319 # 2) the lab is right (result.abnormality_indicator)
1320 indicator = self._payload[self._idx['abnormality_indicator']]
1321 if indicator is not None:
1322 indicator = indicator.strip()
1323 if indicator != u'':
1324 if indicator.strip(u'+') == u'':
1325 return False
1326 if indicator.strip(u'-') == u'':
1327 return True
1328 # 3) non-numerical value ?
1329 if self._payload[self._idx['val_num']] is None:
1330 return None
1331 # 4) the target range is right
1332 target_min = self._payload[self._idx['val_target_min']]
1333 if target_min is not None:
1334 if target_min > self._payload[self._idx['val_num']]:
1335 return True
1336 # 4) the normal range is right
1337 normal_min = self._payload[self._idx['val_normal_min']]
1338 if normal_min is not None:
1339 if normal_min > self._payload[self._idx['val_num']]:
1340 return True
1341 return None
1342
1343 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x)
1344 #--------------------------------------------------------
1346 if self.is_considered_lowered is True:
1347 return True
1348 if self.is_considered_elevated is True:
1349 return True
1350 if (self.is_considered_lowered is False) and (self.is_considered_elevated is False):
1351 return False
1352 return self._payload[self._idx['is_technically_abnormal']]
1353
1354 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x)
1355 #--------------------------------------------------------
1357 # 1) the user is right
1358 if self._payload[self._idx['is_technically_abnormal']] is False:
1359 return u''
1360 # 2) the lab is right (result.abnormality_indicator)
1361 indicator = self._payload[self._idx['abnormality_indicator']]
1362 if indicator is not None:
1363 indicator = indicator.strip()
1364 if indicator != u'':
1365 return indicator
1366 # 3) non-numerical value ? then we can't know more
1367 if self._payload[self._idx['val_num']] is None:
1368 return None
1369 # 4) the target range is right
1370 target_min = self._payload[self._idx['val_target_min']]
1371 if target_min is not None:
1372 if target_min > self._payload[self._idx['val_num']]:
1373 return u'-'
1374 target_max = self._payload[self._idx['val_target_max']]
1375 if target_max is not None:
1376 if target_max < self._payload[self._idx['val_num']]:
1377 return u'+'
1378 # 4) the normal range is right
1379 normal_min = self._payload[self._idx['val_normal_min']]
1380 if normal_min is not None:
1381 if normal_min > self._payload[self._idx['val_num']]:
1382 return u'-'
1383 normal_max = self._payload[self._idx['val_normal_max']]
1384 if normal_max is not None:
1385 if normal_max < self._payload[self._idx['val_num']]:
1386 return u'+'
1387 # reviewed, abnormal, but no indicator available
1388 if self._payload[self._idx['is_technically_abnormal']] is True:
1389 return gmTools.u_plus_minus
1390
1391 return None
1392
1393 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x)
1394 #--------------------------------------------------------
1396 if self._payload[self._idx['val_alpha']] is None:
1397 return False
1398 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = u'\n', return_list = True)
1399 if len(lines) > 4:
1400 return True
1401 return False
1402
1403 is_long_text = property(_get_is_long_text, lambda x:x)
1404 #--------------------------------------------------------
1406 if self._payload[self._idx['val_alpha']] is None:
1407 return None
1408 val = self._payload[self._idx['val_alpha']].lstrip()
1409 if val[0] == u'<':
1410 factor = decimal.Decimal(0.5)
1411 val = val[1:]
1412 elif val[0] == u'>':
1413 factor = 2
1414 val = val[1:]
1415 else:
1416 return None
1417 success, val = gmTools.input2decimal(initial = val)
1418 if not success:
1419 return None
1420 return val * factor
1421
1422 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x)
1423 #--------------------------------------------------------
1424 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1425
1426 # FIXME: this is not concurrency safe
1427 if self._payload[self._idx['reviewed']]:
1428 self.__change_existing_review (
1429 technically_abnormal = technically_abnormal,
1430 clinically_relevant = clinically_relevant,
1431 comment = comment
1432 )
1433 else:
1434 # do not sign off unreviewed results if
1435 # NOTHING AT ALL is known about them
1436 if technically_abnormal is None:
1437 if clinically_relevant is None:
1438 comment = gmTools.none_if(comment, u'', strip_string = True)
1439 if comment is None:
1440 if make_me_responsible is False:
1441 return True
1442 self.__set_new_review (
1443 technically_abnormal = technically_abnormal,
1444 clinically_relevant = clinically_relevant,
1445 comment = comment
1446 )
1447
1448 if make_me_responsible is True:
1449 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
1450 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
1451 self['pk_intended_reviewer'] = rows[0][0]
1452 self.save_payload()
1453 return
1454
1455 self.refetch_payload()
1456 #--------------------------------------------------------
1457 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1458
1459 if desired_earlier_results < 1:
1460 raise ValueError('<desired_earlier_results> must be > 0')
1461
1462 if desired_later_results < 1:
1463 raise ValueError('<desired_later_results> must be > 0')
1464
1465 args = {
1466 'pat': self._payload[self._idx['pk_patient']],
1467 'ttyp': self._payload[self._idx['pk_test_type']],
1468 'tloinc': self._payload[self._idx['loinc_tt']],
1469 'mtyp': self._payload[self._idx['pk_meta_test_type']],
1470 'mloinc': self._payload[self._idx['loinc_meta']],
1471 'when': self._payload[self._idx['clin_when']],
1472 'offset': max_offset
1473 }
1474 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
1475 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
1476 if max_offset is not None:
1477 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1478 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1479
1480 SQL = u"""
1481 SELECT * FROM clin.v_test_results
1482 WHERE
1483 pk_patient = %%(pat)s
1484 AND
1485 clin_when %s %%(when)s
1486 AND
1487 %s
1488 ORDER BY clin_when
1489 LIMIT %s"""
1490
1491 # get earlier results
1492 earlier_results = []
1493 # by type
1494 cmd = SQL % (u'<', WHERE, desired_earlier_results)
1495 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1496 if len(rows) > 0:
1497 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1498 # by meta type ?
1499 missing_results = desired_earlier_results - len(earlier_results)
1500 if missing_results > 0:
1501 cmd = SQL % (u'<', WHERE_meta, missing_results)
1502 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1503 if len(rows) > 0:
1504 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1505
1506 # get later results
1507 later_results = []
1508 # by type
1509 cmd = SQL % (u'>', WHERE, desired_later_results)
1510 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1511 if len(rows) > 0:
1512 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1513 # by meta type ?
1514 missing_results = desired_later_results - len(later_results)
1515 if missing_results > 0:
1516 cmd = SQL % (u'>', WHERE_meta, missing_results)
1517 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1518 if len(rows) > 0:
1519 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1520
1521 return earlier_results, later_results
1522 #--------------------------------------------------------
1523 # internal API
1524 #--------------------------------------------------------
1525 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1526 """Add a review to a row.
1527
1528 - if technically abnormal is not provided/None it will be set
1529 to True if the lab's indicator has a meaningful value
1530 - if clinically relevant is not provided/None it is set to
1531 whatever technically abnormal is
1532 """
1533 if technically_abnormal is None:
1534 technically_abnormal = False
1535 if self._payload[self._idx['abnormality_indicator']] is not None:
1536 if self._payload[self._idx['abnormality_indicator']].strip() != u'':
1537 technically_abnormal = True
1538
1539 if clinically_relevant is None:
1540 clinically_relevant = technically_abnormal
1541
1542 cmd = u"""
1543 INSERT INTO clin.reviewed_test_results (
1544 fk_reviewed_row,
1545 is_technically_abnormal,
1546 clinically_relevant,
1547 comment
1548 ) VALUES (
1549 %(pk)s,
1550 %(abnormal)s,
1551 %(relevant)s,
1552 gm.nullify_empty_string(%(cmt)s)
1553 )"""
1554 args = {
1555 'pk': self._payload[self._idx['pk_test_result']],
1556 'abnormal': technically_abnormal,
1557 'relevant': clinically_relevant,
1558 'cmt': comment
1559 }
1560
1561 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1562 #--------------------------------------------------------
1563 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1564 """Change a review on a row.
1565
1566 - if technically abnormal/clinically relevant are
1567 None they are not set
1568 """
1569 args = {
1570 'pk_row': self._payload[self._idx['pk_test_result']],
1571 'abnormal': technically_abnormal,
1572 'relevant': clinically_relevant,
1573 'cmt': comment
1574 }
1575
1576 set_parts = [
1577 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
1578 u'comment = gm.nullify_empty_string(%(cmt)s)'
1579 ]
1580
1581 if technically_abnormal is not None:
1582 set_parts.append(u'is_technically_abnormal = %(abnormal)s')
1583
1584 if clinically_relevant is not None:
1585 set_parts.append(u'clinically_relevant = %(relevant)s')
1586
1587 cmd = u"""
1588 UPDATE clin.reviewed_test_results SET
1589 %s
1590 WHERE
1591 fk_reviewed_row = %%(pk_row)s
1592 """ % u',\n '.join(set_parts)
1593
1594 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1595
1596 #------------------------------------------------------------
1598
1599 where_parts = []
1600
1601 if pk_patient is not None:
1602 where_parts.append(u'pk_patient = %(pat)s')
1603 args = {'pat': pk_patient}
1604
1605 # if tests is not None:
1606 # where_parts.append(u'pk_test_type IN %(tests)s')
1607 # args['tests'] = tuple(tests)
1608
1609 if encounters is not None:
1610 where_parts.append(u'pk_encounter IN %(encs)s')
1611 args['encs'] = tuple(encounters)
1612
1613 if episodes is not None:
1614 where_parts.append(u'pk_episode IN %(epis)s')
1615 args['epis'] = tuple(episodes)
1616
1617 if order_by is None:
1618 order_by = u''
1619 else:
1620 order_by = u'ORDER BY %s' % order_by
1621
1622 cmd = u"""
1623 SELECT * FROM clin.v_test_results
1624 WHERE %s
1625 %s
1626 """ % (
1627 u' AND '.join(where_parts),
1628 order_by
1629 )
1630 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1631
1632 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1633 return tests
1634
1635 #------------------------------------------------------------
1637
1638 if order_by is None:
1639 order_by = u''
1640 else:
1641 order_by = u'ORDER BY %s' % order_by
1642
1643 args = {
1644 'pat': pk_patient,
1645 'pnl': pk_panel
1646 }
1647 cmd = u"""
1648 SELECT c_vtr.*
1649 FROM (
1650 -- max(clin_when) per test_type-in-panel for patient
1651 SELECT
1652 pk_test_type,
1653 MAX(clin_when) AS max_clin_when
1654 FROM clin.v_test_results
1655 WHERE
1656 pk_patient = %%(pat)s
1657 AND
1658 pk_test_type = ANY (
1659 (SELECT fk_test_types FROM clin.test_panel WHERE pk = %%(pnl)s)::int[]
1660 )
1661 GROUP BY pk_test_type
1662 ) AS latest_results
1663 INNER JOIN clin.v_test_results c_vtr ON c_vtr.pk_test_type = latest_results.pk_test_type AND c_vtr.clin_when = latest_results.max_clin_when
1664 %s
1665 """ % order_by
1666 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1667 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1668 return tests
1669
1670 #------------------------------------------------------------
1671 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
1672
1673 if None not in [test_type, loinc]:
1674 raise ValueError('either <test_type> or <loinc> must be None')
1675
1676 args = {
1677 'pat': patient,
1678 'ttyp': test_type,
1679 'loinc': loinc,
1680 'ts': timestamp,
1681 'intv': tolerance_interval
1682 }
1683
1684 where_parts = [u'pk_patient = %(pat)s']
1685 if test_type is not None:
1686 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
1687 elif loinc is not None:
1688 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1689 args['loinc'] = tuple(loinc)
1690
1691 if tolerance_interval is None:
1692 where_parts.append(u'clin_when = %(ts)s')
1693 else:
1694 where_parts.append(u'clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)')
1695
1696 cmd = u"""
1697 SELECT * FROM clin.v_test_results
1698 WHERE
1699 %s
1700 ORDER BY
1701 abs(extract(epoch from age(clin_when, %%(ts)s)))
1702 LIMIT 1""" % u' AND '.join(where_parts)
1703
1704 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1705 if len(rows) == 0:
1706 return None
1707
1708 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1709
1710 #------------------------------------------------------------
1712
1713 if None not in [test_type, loinc]:
1714 raise ValueError('either <test_type> or <loinc> must be None')
1715
1716 if no_of_results < 1:
1717 raise ValueError('<no_of_results> must be > 0')
1718
1719 args = {
1720 'pat': patient,
1721 'ttyp': test_type,
1722 'loinc': loinc
1723 }
1724
1725 where_parts = [u'pk_patient = %(pat)s']
1726 if test_type is not None:
1727 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
1728 elif loinc is not None:
1729 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1730 args['loinc'] = tuple(loinc)
1731
1732 cmd = u"""
1733 SELECT * FROM clin.v_test_results
1734 WHERE
1735 %s
1736 ORDER BY clin_when DESC
1737 LIMIT %s""" % (
1738 u' AND '.join(where_parts),
1739 no_of_results
1740 )
1741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1742 if len(rows) == 0:
1743 return None
1744
1745 if no_of_results == 1:
1746 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1747
1748 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1749
1750 #------------------------------------------------------------
1752
1753 if None not in [test_type, loinc]:
1754 raise ValueError('either <test_type> or <loinc> must be None')
1755
1756 args = {
1757 'pat': patient,
1758 'ttyp': test_type,
1759 'loinc': loinc
1760 }
1761
1762 where_parts = [u'pk_patient = %(pat)s']
1763 if test_type is not None:
1764 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
1765 elif loinc is not None:
1766 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1767 args['loinc'] = tuple(loinc)
1768
1769 cmd = u"""
1770 SELECT * FROM clin.v_test_results
1771 WHERE
1772 %s
1773 ORDER BY clin_when
1774 LIMIT 1""" % u' AND '.join(where_parts)
1775 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1776 if len(rows) == 0:
1777 return None
1778
1779 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1780
1781 #------------------------------------------------------------
1783 try:
1784 pk = int(result)
1785 except (TypeError, AttributeError):
1786 pk = result['pk_test_result']
1787
1788 cmd = u'DELETE FROM clin.test_result WHERE pk = %(pk)s'
1789 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1790
1791 #------------------------------------------------------------
1792 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1793
1794 cmd1 = u"""
1795 insert into clin.test_result (
1796 fk_encounter,
1797 fk_episode,
1798 fk_type,
1799 fk_intended_reviewer,
1800 val_num,
1801 val_alpha,
1802 val_unit
1803 ) values (
1804 %(enc)s,
1805 %(epi)s,
1806 %(type)s,
1807 %(rev)s,
1808 %(v_num)s,
1809 %(v_alpha)s,
1810 %(unit)s
1811 )"""
1812
1813 cmd2 = u"""
1814 select *
1815 from
1816 clin.v_test_results
1817 where
1818 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
1819
1820 args = {
1821 u'enc': encounter,
1822 u'epi': episode,
1823 u'type': type,
1824 u'rev': intended_reviewer,
1825 u'v_num': val_num,
1826 u'v_alpha': val_alpha,
1827 u'unit': unit
1828 }
1829
1830 rows, idx = gmPG2.run_rw_queries (
1831 queries = [
1832 {'cmd': cmd1, 'args': args},
1833 {'cmd': cmd2}
1834 ],
1835 return_data = True,
1836 get_col_idx = True
1837 )
1838
1839 tr = cTestResult(row = {
1840 'pk_field': 'pk_test_result',
1841 'idx': idx,
1842 'data': rows[0]
1843 })
1844
1845 return tr
1846
1847 #------------------------------------------------------------
1849
1850 _log.debug(u'formatting test results into [%s]', output_format)
1851
1852 if output_format == u'latex':
1853 return __format_test_results_latex(results = results)
1854
1855 msg = _('unknown test results output format [%s]') % output_format
1856 _log.error(msg)
1857 return msg
1858
1859 #------------------------------------------------------------
1861
1862 if len(results) == 0:
1863 return u'\\begin{minipage}{%s} \\end{minipage}' % width
1864
1865 lines = []
1866 for t in results:
1867
1868 tmp = u''
1869
1870 if show_time:
1871 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
1872
1873 tmp += u'%.8s' % t['unified_val']
1874
1875 lines.append(tmp)
1876 tmp = u''
1877
1878 if show_range:
1879 has_range = (
1880 t['unified_target_range'] is not None
1881 or
1882 t['unified_target_min'] is not None
1883 or
1884 t['unified_target_max'] is not None
1885 )
1886 if has_range:
1887 if t['unified_target_range'] is not None:
1888 tmp += u'{\\tiny %s}' % t['unified_target_range']
1889 else:
1890 tmp += u'{\\tiny %s}' % (
1891 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
1892 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1893 )
1894 lines.append(tmp)
1895
1896 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
1897
1898 #------------------------------------------------------------
1900
1901 if len(results) == 0:
1902 return u''
1903
1904 lines = []
1905 for t in results:
1906
1907 tmp = u''
1908
1909 if show_time:
1910 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
1911
1912 tmp += u'\\normalsize %.8s' % t['unified_val']
1913
1914 lines.append(tmp)
1915 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
1916
1917 if not show_range:
1918 lines.append(tmp)
1919 continue
1920
1921 has_range = (
1922 t['unified_target_range'] is not None
1923 or
1924 t['unified_target_min'] is not None
1925 or
1926 t['unified_target_max'] is not None
1927 )
1928
1929 if not has_range:
1930 lines.append(tmp)
1931 continue
1932
1933 if t['unified_target_range'] is not None:
1934 tmp += u'[%s]' % t['unified_target_range']
1935 else:
1936 tmp += u'[%s%s]' % (
1937 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
1938 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1939 )
1940 lines.append(tmp)
1941
1942 return u' \\\\ '.join(lines)
1943
1944 #------------------------------------------------------------
1946
1947 if len(results) == 0:
1948 return u'\\noindent %s' % _('No test results to format.')
1949
1950 # discover the columns and rows
1951 dates = {}
1952 tests = {}
1953 grid = {}
1954 for result in results:
1955 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name'])
1956 row_label = result['unified_abbrev']
1957 tests[row_label] = None
1958 col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d')
1959 dates[col_label] = None
1960 try:
1961 grid[row_label]
1962 except KeyError:
1963 grid[row_label] = {}
1964 try:
1965 grid[row_label][col_label].append(result)
1966 except KeyError:
1967 grid[row_label][col_label] = [result]
1968
1969 col_labels = sorted(dates.keys(), reverse = True)
1970 del dates
1971 row_labels = sorted(tests.keys())
1972 del tests
1973
1974 col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|'
1975
1976 # format them
1977 tex = u"""\\noindent %s
1978
1979 \\noindent \\begin{tabular}{|l|%s}
1980 \\hline
1981 & %s \\tabularnewline
1982 \\hline
1983
1984 %%s \\tabularnewline
1985
1986 \\hline
1987
1988 \\end{tabular}""" % (
1989 _('Test results'),
1990 col_def,
1991 u' & '.join(col_labels)
1992 )
1993
1994 rows = []
1995
1996 # loop over rows
1997 for rl in row_labels:
1998 cells = [rl]
1999 # loop over cols per row
2000 for cl in col_labels:
2001 try:
2002 # get tests for this (row/col) position
2003 tests = grid[rl][cl]
2004 except KeyError:
2005 # none there, so insert empty cell
2006 cells.append(u' ')
2007 continue
2008
2009 cells.append (
2010 __tests2latex_cell (
2011 results = tests,
2012 show_time = (len(tests) > 1),
2013 show_range = True
2014 )
2015 )
2016
2017 rows.append(u' & '.join(cells))
2018
2019 return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
2020
2021 #============================================================
2023
2024 if filename is None:
2025 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
2026
2027 # sort results into series by test type
2028 series = {}
2029 for r in results:
2030 try:
2031 series[r['unified_name']].append(r)
2032 except KeyError:
2033 series[r['unified_name']] = [r]
2034
2035 gp_data = codecs.open(filename, 'wb', 'utf8')
2036
2037 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
2038 gp_data.write(u'# -------------------------------------------------------------\n')
2039 gp_data.write(u'# first line of index: test type abbreviation & name\n')
2040 gp_data.write(u'#\n')
2041 gp_data.write(u'# clin_when at full precision\n')
2042 gp_data.write(u'# value\n')
2043 gp_data.write(u'# unit\n')
2044 gp_data.write(u'# unified (target or normal) range: lower bound\n')
2045 gp_data.write(u'# unified (target or normal) range: upper bound\n')
2046 gp_data.write(u'# normal range: lower bound\n')
2047 gp_data.write(u'# normal range: upper bound\n')
2048 gp_data.write(u'# target range: lower bound\n')
2049 gp_data.write(u'# target range: upper bound\n')
2050 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
2051 gp_data.write(u'# -------------------------------------------------------------\n')
2052
2053 for test_type in series.keys():
2054 if len(series[test_type]) == 0:
2055 continue
2056
2057 r = series[test_type][0]
2058 title = u'%s (%s)' % (
2059 r['unified_abbrev'],
2060 r['unified_name']
2061 )
2062 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
2063
2064 prev_date = None
2065 prev_year = None
2066 for r in series[test_type]:
2067 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days)
2068 if curr_date == prev_date:
2069 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values'))
2070 if show_year:
2071 if r['clin_when'].year == prev_year:
2072 when_template = '%b %d %H:%M'
2073 else:
2074 when_template = '%b %d %H:%M (%Y)'
2075 prev_year = r['clin_when'].year
2076 else:
2077 when_template = '%b %d'
2078 val = r['val_num']
2079 if val is None:
2080 val = r.estimate_numeric_value_from_alpha
2081 if val is None:
2082 continue # skip distinctly non-numericable values
2083 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
2084 #r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
2085 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes),
2086 val,
2087 gmTools.coalesce(r['val_unit'], u'"<?>"'),
2088 gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
2089 gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
2090 gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
2091 gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
2092 gmTools.coalesce(r['val_target_min'], u'"<?>"'),
2093 gmTools.coalesce(r['val_target_max'], u'"<?>"'),
2094 gmDateTime.pydt_strftime (
2095 r['clin_when'],
2096 format = when_template,
2097 accuracy = gmDateTime.acc_minutes
2098 )
2099 ))
2100 prev_date = curr_date
2101
2102 gp_data.close()
2103
2104 return filename
2105
2106 #============================================================
2108 """Represents one lab result."""
2109
2110 _cmd_fetch_payload = """
2111 select *, xmin_test_result from v_results4lab_req
2112 where pk_result=%s"""
2113 _cmds_lock_rows_for_update = [
2114 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
2115 ]
2116 _cmds_store_payload = [
2117 """update test_result set
2118 clin_when = %(val_when)s,
2119 narrative = %(progress_note_result)s,
2120 fk_type = %(pk_test_type)s,
2121 val_num = %(val_num)s::numeric,
2122 val_alpha = %(val_alpha)s,
2123 val_unit = %(val_unit)s,
2124 val_normal_min = %(val_normal_min)s,
2125 val_normal_max = %(val_normal_max)s,
2126 val_normal_range = %(val_normal_range)s,
2127 val_target_min = %(val_target_min)s,
2128 val_target_max = %(val_target_max)s,
2129 val_target_range = %(val_target_range)s,
2130 abnormality_indicator = %(abnormal)s,
2131 norm_ref_group = %(ref_group)s,
2132 note_provider = %(note_provider)s,
2133 material = %(material)s,
2134 material_detail = %(material_detail)s
2135 where pk = %(pk_result)s""",
2136 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
2137 ]
2138
2139 _updatable_fields = [
2140 'val_when',
2141 'progress_note_result',
2142 'val_num',
2143 'val_alpha',
2144 'val_unit',
2145 'val_normal_min',
2146 'val_normal_max',
2147 'val_normal_range',
2148 'val_target_min',
2149 'val_target_max',
2150 'val_target_range',
2151 'abnormal',
2152 'ref_group',
2153 'note_provider',
2154 'material',
2155 'material_detail'
2156 ]
2157 #--------------------------------------------------------
2159 """Instantiate.
2160
2161 aPK_obj as dict:
2162 - patient_id
2163 - when_field (see view definition)
2164 - when
2165 - test_type
2166 - val_num
2167 - val_alpha
2168 - unit
2169 """
2170 # instantiate from row data ?
2171 if aPK_obj is None:
2172 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2173 return
2174 pk = aPK_obj
2175 # find PK from row data ?
2176 if type(aPK_obj) == types.DictType:
2177 # sanity checks
2178 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
2179 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
2180 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
2181 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
2182 # get PK
2183 where_snippets = [
2184 'pk_patient=%(patient_id)s',
2185 'pk_test_type=%(test_type)s',
2186 '%s=%%(when)s' % aPK_obj['when_field'],
2187 'val_unit=%(unit)s'
2188 ]
2189 if aPK_obj['val_num'] is not None:
2190 where_snippets.append('val_num=%(val_num)s::numeric')
2191 if aPK_obj['val_alpha'] is not None:
2192 where_snippets.append('val_alpha=%(val_alpha)s')
2193
2194 where_clause = ' and '.join(where_snippets)
2195 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
2196 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2197 if data is None:
2198 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
2199 if len(data) == 0:
2200 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
2201 pk = data[0][0]
2202 # instantiate class
2203 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2204 #--------------------------------------------------------
2206 cmd = """
2207 select
2208 %s,
2209 vbp.title,
2210 vbp.firstnames,
2211 vbp.lastnames,
2212 vbp.dob
2213 from v_basic_person vbp
2214 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
2215 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
2216 return pat[0]
2217 #============================================================
2219 """Represents one lab request."""
2220
2221 _cmd_fetch_payload = """
2222 select *, xmin_lab_request from v_lab_requests
2223 where pk_request=%s"""
2224 _cmds_lock_rows_for_update = [
2225 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
2226 ]
2227 _cmds_store_payload = [
2228 """update lab_request set
2229 request_id=%(request_id)s,
2230 lab_request_id=%(lab_request_id)s,
2231 clin_when=%(sampled_when)s,
2232 lab_rxd_when=%(lab_rxd_when)s,
2233 results_reported_when=%(results_reported_when)s,
2234 request_status=%(request_status)s,
2235 is_pending=%(is_pending)s::bool,
2236 narrative=%(progress_note)s
2237 where pk=%(pk_request)s""",
2238 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
2239 ]
2240 _updatable_fields = [
2241 'request_id',
2242 'lab_request_id',
2243 'sampled_when',
2244 'lab_rxd_when',
2245 'results_reported_when',
2246 'request_status',
2247 'is_pending',
2248 'progress_note'
2249 ]
2250 #--------------------------------------------------------
2252 """Instantiate lab request.
2253
2254 The aPK_obj can be either a dict with the keys "req_id"
2255 and "lab" or a simple primary key.
2256 """
2257 # instantiate from row data ?
2258 if aPK_obj is None:
2259 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2260 return
2261 pk = aPK_obj
2262 # instantiate from "req_id" and "lab" ?
2263 if type(aPK_obj) == types.DictType:
2264 # sanity check
2265 try:
2266 aPK_obj['req_id']
2267 aPK_obj['lab']
2268 except:
2269 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
2270 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
2271 # generate query
2272 where_snippets = []
2273 vals = {}
2274 where_snippets.append('request_id=%(req_id)s')
2275 if type(aPK_obj['lab']) == types.IntType:
2276 where_snippets.append('pk_test_org=%(lab)s')
2277 else:
2278 where_snippets.append('lab_name=%(lab)s')
2279 where_clause = ' and '.join(where_snippets)
2280 cmd = "select pk_request from v_lab_requests where %s" % where_clause
2281 # get pk
2282 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2283 if data is None:
2284 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
2285 if len(data) == 0:
2286 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
2287 pk = data[0][0]
2288 # instantiate class
2289 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2290 #--------------------------------------------------------
2292 cmd = """
2293 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
2294 from v_pat_items vpi, v_basic_person vbp
2295 where
2296 vpi.pk_item=%s
2297 and
2298 vbp.pk_identity=vpi.pk_patient"""
2299 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
2300 if pat is None:
2301 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
2302 return None
2303 if len(pat) == 0:
2304 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
2305 return None
2306 return pat[0]
2307 #============================================================
2308 # convenience functions
2309 #------------------------------------------------------------
2310 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2311 """Create or get lab request.
2312
2313 returns tuple (status, value):
2314 (True, lab request instance)
2315 (False, error message)
2316 (None, housekeeping_todo primary key)
2317 """
2318 req = None
2319 aPK_obj = {
2320 'lab': lab,
2321 'req_id': req_id
2322 }
2323 try:
2324 req = cLabRequest (aPK_obj)
2325 except gmExceptions.NoSuchClinItemError, msg:
2326 _log.info('%s: will try to create lab request' % str(msg))
2327 except gmExceptions.ConstructorError, msg:
2328 _log.exception(str(msg), sys.exc_info(), verbose=0)
2329 return (False, msg)
2330 # found
2331 if req is not None:
2332 db_pat = req.get_patient()
2333 if db_pat is None:
2334 _log.error('cannot cross-check patient on lab request')
2335 return (None, '')
2336 # yes but ambigous
2337 if pat_id != db_pat[0]:
2338 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
2339 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
2340 to = 'user'
2341 prob = _('The lab request already exists but belongs to a different patient.')
2342 sol = _('Verify which patient this lab request really belongs to.')
2343 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
2344 cat = 'lab'
2345 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
2346 return (None, data)
2347 return (True, req)
2348 # not found
2349 queries = []
2350 if type(lab) is types.IntType:
2351 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
2352 else:
2353 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)"
2354 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
2355 cmd = "select currval('lab_request_pk_seq')"
2356 queries.append((cmd, []))
2357 # insert new
2358 result, err = gmPG.run_commit('historica', queries, True)
2359 if result is None:
2360 return (False, err)
2361 try:
2362 req = cLabRequest(aPK_obj=result[0][0])
2363 except gmExceptions.ConstructorError, msg:
2364 _log.exception(str(msg), sys.exc_info(), verbose=0)
2365 return (False, msg)
2366 return (True, req)
2367 #------------------------------------------------------------
2368 -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):
2369 tres = None
2370 data = {
2371 'patient_id': patient_id,
2372 'when_field': when_field,
2373 'when': when,
2374 'test_type': test_type,
2375 'val_num': val_num,
2376 'val_alpha': val_alpha,
2377 'unit': unit
2378 }
2379 try:
2380 tres = cLabResult(aPK_obj=data)
2381 # exists already, so fail
2382 _log.error('will not overwrite existing test result')
2383 _log.debug(str(tres))
2384 return (None, tres)
2385 except gmExceptions.NoSuchClinItemError:
2386 _log.debug('test result not found - as expected, will create it')
2387 except gmExceptions.ConstructorError, msg:
2388 _log.exception(str(msg), sys.exc_info(), verbose=0)
2389 return (False, msg)
2390 if request is None:
2391 return (False, _('need lab request when inserting lab result'))
2392 # not found
2393 if encounter_id is None:
2394 encounter_id = request['pk_encounter']
2395 queries = []
2396 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
2397 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
2398 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
2399 queries.append((cmd, [request['pk_request']]))
2400 cmd = "select currval('test_result_pk_seq')"
2401 queries.append((cmd, []))
2402 # insert new
2403 result, err = gmPG.run_commit('historica', queries, True)
2404 if result is None:
2405 return (False, err)
2406 try:
2407 tres = cLabResult(aPK_obj=result[0][0])
2408 except gmExceptions.ConstructorError, msg:
2409 _log.exception(str(msg), sys.exc_info(), verbose=0)
2410 return (False, msg)
2411 return (True, tres)
2412 #------------------------------------------------------------
2414 # sanity check
2415 if limit < 1:
2416 limit = 1
2417 # retrieve one more row than needed so we know there's more available ;-)
2418 lim = limit + 1
2419 cmd = """
2420 select pk_result
2421 from v_results4lab_req
2422 where reviewed is false
2423 order by pk_patient
2424 limit %s""" % lim
2425 rows = gmPG.run_ro_query('historica', cmd)
2426 if rows is None:
2427 _log.error('error retrieving unreviewed lab results')
2428 return (None, _('error retrieving unreviewed lab results'))
2429 if len(rows) == 0:
2430 return (False, [])
2431 # more than LIMIT rows ?
2432 if len(rows) == lim:
2433 more_avail = True
2434 # but deliver only LIMIT rows so that our assumption holds true...
2435 del rows[limit]
2436 else:
2437 more_avail = False
2438 results = []
2439 for row in rows:
2440 try:
2441 results.append(cLabResult(aPK_obj=row[0]))
2442 except gmExceptions.ConstructorError:
2443 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
2444 return (more_avail, results)
2445 #------------------------------------------------------------
2447 lim = limit + 1
2448 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
2449 rows = gmPG.run_ro_query('historica', cmd)
2450 if rows is None:
2451 _log.error('error retrieving pending lab requests')
2452 return (None, None)
2453 if len(rows) == 0:
2454 return (False, [])
2455 results = []
2456 # more than LIMIT rows ?
2457 if len(rows) == lim:
2458 too_many = True
2459 # but deliver only LIMIT rows so that our assumption holds true...
2460 del rows[limit]
2461 else:
2462 too_many = False
2463 requests = []
2464 for row in rows:
2465 try:
2466 requests.append(cLabRequest(aPK_obj=row[0]))
2467 except gmExceptions.ConstructorError:
2468 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
2469 return (too_many, requests)
2470 #------------------------------------------------------------
2472 """Get logically next request ID for given lab.
2473
2474 - incrementor_func:
2475 - if not supplied the next ID is guessed
2476 - if supplied it is applied to the most recently used ID
2477 """
2478 if type(lab) == types.IntType:
2479 lab_snippet = 'vlr.fk_test_org=%s'
2480 else:
2481 lab_snippet = 'vlr.lab_name=%s'
2482 lab = str(lab)
2483 cmd = """
2484 select request_id
2485 from lab_request lr0
2486 where lr0.clin_when = (
2487 select max(vlr.sampled_when)
2488 from v_lab_requests vlr
2489 where %s
2490 )""" % lab_snippet
2491 rows = gmPG.run_ro_query('historica', cmd, None, lab)
2492 if rows is None:
2493 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
2494 return ''
2495 if len(rows) == 0:
2496 return ''
2497 most_recent = rows[0][0]
2498 # apply supplied incrementor
2499 if incrementor_func is not None:
2500 try:
2501 next = incrementor_func(most_recent)
2502 except TypeError:
2503 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
2504 return most_recent
2505 return next
2506 # try to be smart ourselves
2507 for pos in range(len(most_recent)):
2508 header = most_recent[:pos]
2509 trailer = most_recent[pos:]
2510 try:
2511 return '%s%s' % (header, str(int(trailer) + 1))
2512 except ValueError:
2513 header = most_recent[:-1]
2514 trailer = most_recent[-1:]
2515 return '%s%s' % (header, chr(ord(trailer) + 1))
2516 #============================================================
2518 """Calculate BMI.
2519
2520 mass: kg
2521 height: cm
2522 age: not yet used
2523
2524 returns:
2525 (True/False, data)
2526 True: data = (bmi, lower_normal, upper_normal)
2527 False: data = error message
2528 """
2529 converted, mass = gmTools.input2decimal(mass)
2530 if not converted:
2531 return False, u'mass: cannot convert <%s> to Decimal' % mass
2532
2533 converted, height = gmTools.input2decimal(height)
2534 if not converted:
2535 return False, u'height: cannot convert <%s> to Decimal' % height
2536
2537 approx_surface = (height / decimal.Decimal(100))**2
2538 bmi = mass / approx_surface
2539
2540 print mass, height, '->', approx_surface, '->', bmi
2541
2542 lower_normal_mass = 20.0 * approx_surface
2543 upper_normal_mass = 25.0 * approx_surface
2544
2545 return True, (bmi, lower_normal_mass, upper_normal_mass)
2546 #============================================================
2547 # main - unit testing
2548 #------------------------------------------------------------
2549 if __name__ == '__main__':
2550
2551 if len(sys.argv) < 2:
2552 sys.exit()
2553
2554 if sys.argv[1] != 'test':
2555 sys.exit()
2556
2557 import time
2558
2559 gmI18N.activate_locale()
2560 gmI18N.install_domain()
2561
2562 #------------------------------------------
2564 tr = create_test_result (
2565 encounter = 1,
2566 episode = 1,
2567 type = 1,
2568 intended_reviewer = 1,
2569 val_num = '12',
2570 val_alpha=None,
2571 unit = 'mg/dl'
2572 )
2573 print tr
2574 return tr
2575 #------------------------------------------
2579 #------------------------------------------
2581 r = cTestResult(aPK_obj=6)
2582 #print r
2583 #print r.reference_ranges
2584 #print r.formatted_range
2585 #print r.temporally_closest_normal_range
2586 print r.estimate_numeric_value_from_alpha
2587 #------------------------------------------
2589 print "test_result()"
2590 # lab_result = cLabResult(aPK_obj=4)
2591 data = {
2592 'patient_id': 12,
2593 'when_field': 'val_when',
2594 'when': '2000-09-17 18:23:00+02',
2595 'test_type': 9,
2596 'val_num': 17.3,
2597 'val_alpha': None,
2598 'unit': 'mg/l'
2599 }
2600 lab_result = cLabResult(aPK_obj=data)
2601 print lab_result
2602 fields = lab_result.get_fields()
2603 for field in fields:
2604 print field, ':', lab_result[field]
2605 print "updatable:", lab_result.get_updatable_fields()
2606 print time.time()
2607 print lab_result.get_patient()
2608 print time.time()
2609 #------------------------------------------
2611 print "test_request()"
2612 try:
2613 # lab_req = cLabRequest(aPK_obj=1)
2614 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2)
2615 data = {
2616 'req_id': 'EML#SC937-0176-CEC#11',
2617 'lab': 'Enterprise Main Lab'
2618 }
2619 lab_req = cLabRequest(aPK_obj=data)
2620 except gmExceptions.ConstructorError, msg:
2621 print "no such lab request:", msg
2622 return
2623 print lab_req
2624 fields = lab_req.get_fields()
2625 for field in fields:
2626 print field, ':', lab_req[field]
2627 print "updatable:", lab_req.get_updatable_fields()
2628 print time.time()
2629 print lab_req.get_patient()
2630 print time.time()
2631 #--------------------------------------------------------
2636 #--------------------------------------------------------
2641 #--------------------------------------------------------
2643 print create_measurement_type (
2644 lab = None,
2645 abbrev = u'tBZ2',
2646 unit = u'mg%',
2647 name = 'BZ (test 2)'
2648 )
2649 #--------------------------------------------------------
2654 #--------------------------------------------------------
2659 #--------------------------------------------------------
2661 results = [
2662 cTestResult(aPK_obj=1),
2663 cTestResult(aPK_obj=2),
2664 cTestResult(aPK_obj=3)
2665 # cTestResult(aPK_obj=4)
2666 ]
2667 print format_test_results(results = results)
2668 #--------------------------------------------------------
2670 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
2671 bmi, low, high = data
2672
2673 print "BMI:", bmi
2674 print "low:", low, "kg"
2675 print "hi :", high, "kg"
2676 #--------------------------------------------------------
2681 #--------------------------------------------------------
2683 tp = cTestPanel(aPK_obj = 1)
2684 print tp.format()
2685 print len(tp.get_most_recent_results(pk_patient=12))
2686 #--------------------------------------------------------
2687
2688 test_result()
2689 #test_create_test_result()
2690 #test_delete_test_result()
2691 #test_create_measurement_type()
2692 #test_lab_result()
2693 #test_request()
2694 #test_create_result()
2695 #test_unreviewed()
2696 #test_pending()
2697 #test_meta_test_type()
2698 #test_test_type()
2699 #test_format_test_results()
2700 #test_calculate_bmi()
2701 #test_test_panel()
2702 #test_get_most_recent_results_for_panel()
2703
2704 #============================================================
2705
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:57:21 2013 | http://epydoc.sourceforge.net |