| 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 sys
10 import logging
11 import io
12 import decimal
13 import re as regex
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 gmI18N.activate_locale()
24 gmI18N.install_domain('gnumed')
25 gmDateTime.init()
26 from Gnumed.pycommon import gmExceptions
27 from Gnumed.pycommon import gmBusinessDBObject
28 from Gnumed.pycommon import gmPG2
29 from Gnumed.pycommon import gmTools
30 from Gnumed.pycommon import gmDispatcher
31 from Gnumed.pycommon import gmHooks
32
33 from Gnumed.business import gmOrganization
34 from Gnumed.business import gmCoding
35
36 _log = logging.getLogger('gm.lab')
37
38 #============================================================
39 HL7_RESULT_STATI = {
40 None: _('unknown'),
41 '': _('empty status'),
42 'C': _('C (HL7: Correction, replaces previous final)'),
43 'D': _('D (HL7: Deletion)'),
44 'F': _('F (HL7: Final)'),
45 'I': _('I (HL7: pending, specimen In lab)'),
46 'P': _('P (HL7: Preliminary)'),
47 'R': _('R (HL7: result entered, not yet verified)'),
48 'S': _('S (HL7: partial)'),
49 'X': _('X (HL7: cannot obtain results for this observation)'),
50 'U': _('U (HL7: mark as final (I/P/R/S -> F, value Unchanged)'),
51 'W': _('W (HL7: original Wrong (say, wrong patient))')
52 }
53
54 URL_test_result_information = 'http://www.laborlexikon.de'
55 URL_test_result_information_search = "http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de"
56
57 #============================================================
59 """Always relates to the active patient."""
60 gmHooks.run_hook_script(hook = 'after_test_result_modified')
61
62 gmDispatcher.connect(_on_test_result_modified, 'clin.test_result_mod_db')
63
64 #============================================================
65 _SQL_get_test_orgs = "SELECT * FROM clin.v_test_orgs WHERE %s"
66
68 """Represents one test org/lab."""
69 _cmd_fetch_payload = _SQL_get_test_orgs % 'pk_test_org = %s'
70 _cmds_store_payload = [
71 """UPDATE clin.test_org SET
72 fk_org_unit = %(pk_org_unit)s,
73 contact = gm.nullify_empty_string(%(test_org_contact)s),
74 comment = gm.nullify_empty_string(%(comment)s)
75 WHERE
76 pk = %(pk_test_org)s
77 AND
78 xmin = %(xmin_test_org)s
79 RETURNING
80 xmin AS xmin_test_org
81 """
82 ]
83 _updatable_fields = [
84 'pk_org_unit',
85 'test_org_contact',
86 'comment'
87 ]
88 #------------------------------------------------------------
90
91 _log.debug('creating test org [%s:%s:%s]', name, comment, pk_org_unit)
92
93 if name is None:
94 name = 'unassigned lab'
95
96 # get org unit
97 if pk_org_unit is None:
98 org = gmOrganization.create_org (
99 link_obj = link_obj,
100 organization = name,
101 category = 'Laboratory'
102 )
103 org_unit = gmOrganization.create_org_unit (
104 link_obj = link_obj,
105 pk_organization = org['pk_org'],
106 unit = name
107 )
108 pk_org_unit = org_unit['pk_org_unit']
109
110 # test org exists ?
111 args = {'pk_unit': pk_org_unit}
112 cmd = 'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
113 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}])
114
115 if len(rows) == 0:
116 cmd = 'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
117 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True)
118
119 test_org = cTestOrg(link_obj = link_obj, aPK_obj = rows[0][0])
120 if comment is not None:
121 comment = comment.strip()
122 test_org['comment'] = comment
123 test_org.save(conn = link_obj)
124
125 return test_org
126 #------------------------------------------------------------
128 args = {'pk': test_org}
129 cmd = """
130 DELETE FROM clin.test_org
131 WHERE
132 pk = %(pk)s
133 AND
134 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
135 AND
136 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
137 """
138 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
139 #------------------------------------------------------------
141 cmd = 'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
142 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
143 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
144
145 #============================================================
146 # test panels / profiles
147 #------------------------------------------------------------
148 _SQL_get_test_panels = "SELECT * FROM clin.v_test_panels WHERE %s"
149
151 """Represents a grouping/listing of tests into a panel."""
152
153 _cmd_fetch_payload = _SQL_get_test_panels % "pk_test_panel = %s"
154 _cmds_store_payload = [
155 """
156 UPDATE clin.test_panel SET
157 description = gm.nullify_empty_string(%(description)s),
158 comment = gm.nullify_empty_string(%(comment)s)
159 WHERE
160 pk = %(pk_test_panel)s
161 AND
162 xmin = %(xmin_test_panel)s
163 RETURNING
164 xmin AS xmin_test_panel
165 """
166 ]
167 _updatable_fields = [
168 'description',
169 'comment'
170 ]
171 #--------------------------------------------------------
173 txt = _('Test panel "%s" [#%s]\n') % (
174 self._payload[self._idx['description']],
175 self._payload[self._idx['pk_test_panel']]
176 )
177
178 if self._payload[self._idx['comment']] is not None:
179 txt += '\n'
180 txt += gmTools.wrap (
181 text = self._payload[self._idx['comment']],
182 width = 50,
183 initial_indent = ' ',
184 subsequent_indent = ' '
185 )
186 txt += '\n'
187
188 txt += '\n'
189 txt += _('Includes:\n')
190 if len(self.included_loincs) == 0:
191 txt += _('no tests')
192 else:
193 tts_by_loinc = {}
194 for loinc in self._payload[self._idx['loincs']]:
195 tts_by_loinc[loinc] = []
196 for ttype in self.test_types:
197 tts_by_loinc[ttype['loinc']].append(ttype)
198 for loinc, ttypes in tts_by_loinc.items():
199 # maybe resolve LOINC, too
200 txt += _(' %s: %s\n') % (
201 loinc,
202 '; '.join([ '%(abbrev)s@%(name_org)s [#%(pk_test_type)s]' % tt for tt in ttypes ])
203 )
204
205 codes = self.generic_codes
206 if len(codes) > 0:
207 txt += '\n'
208 for c in codes:
209 txt += '%s %s: %s (%s - %s)\n' % (
210 (' ' * left_margin),
211 c['code'],
212 c['term'],
213 c['name_short'],
214 c['version']
215 )
216
217 return txt
218
219 #--------------------------------------------------------
221 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
222 cmd = "INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)"
223 args = {
224 'tp': self._payload[self._idx['pk_test_panel']],
225 'code': pk_code
226 }
227 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
228 return True
229
230 #--------------------------------------------------------
232 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
233 cmd = "DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s"
234 args = {
235 'tp': self._payload[self._idx['pk_test_panel']],
236 'code': pk_code
237 }
238 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
239 return True
240
241 #--------------------------------------------------------
243 """Retrieve data about test types on this panel (for which this patient has results)."""
244
245 if order_by is None:
246 order_by = ''
247 else:
248 order_by = 'ORDER BY %s' % order_by
249
250 if unique_meta_types:
251 cmd = """
252 SELECT * FROM clin.v_test_types c_vtt
253 WHERE c_vtt.pk_test_type IN (
254 SELECT DISTINCT ON (c_vtr1.pk_meta_test_type) c_vtr1.pk_test_type
255 FROM clin.v_test_results c_vtr1
256 WHERE
257 c_vtr1.pk_test_type IN %%(pks)s
258 AND
259 c_vtr1.pk_patient = %%(pat)s
260 AND
261 c_vtr1.pk_meta_test_type IS NOT NULL
262 UNION ALL
263 SELECT DISTINCT ON (c_vtr2.pk_test_type) c_vtr2.pk_test_type
264 FROM clin.v_test_results c_vtr2
265 WHERE
266 c_vtr2.pk_test_type IN %%(pks)s
267 AND
268 c_vtr2.pk_patient = %%(pat)s
269 AND
270 c_vtr2.pk_meta_test_type IS NULL
271 )
272 %s""" % order_by
273 else:
274 cmd = """
275 SELECT * FROM clin.v_test_types c_vtt
276 WHERE c_vtt.pk_test_type IN (
277 SELECT DISTINCT ON (c_vtr.pk_test_type) c_vtr.pk_test_type
278 FROM clin.v_test_results c_vtr
279 WHERE
280 c_vtr.pk_test_type IN %%(pks)s
281 AND
282 c_vtr.pk_patient = %%(pat)s
283 )
284 %s""" % order_by
285
286 args = {
287 'pat': pk_patient,
288 'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ])
289 }
290 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
291 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'idx': idx, 'data': r}) for r in rows ]
292
293 #--------------------------------------------------------
296
298 queries = []
299 # remove those which don't belong
300 if len(loincs) == 0:
301 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s'
302 else:
303 cmd = 'DELETE FROM clin.lnk_loinc2test_panel WHERE fk_test_panel = %(pk_pnl)s AND loinc NOT IN %(loincs)s'
304 queries.append({'cmd': cmd, 'args': {'loincs': tuple(loincs), 'pk_pnl': self._payload[self._idx['pk_test_panel']]}})
305 # add those not there yet
306 if len(loincs) > 0:
307 for loinc in loincs:
308 cmd = """INSERT INTO clin.lnk_loinc2test_panel (fk_test_panel, loinc)
309 SELECT %(pk_pnl)s, %(loinc)s WHERE NOT EXISTS (
310 SELECT 1 FROM clin.lnk_loinc2test_panel WHERE
311 fk_test_panel = %(pk_pnl)s
312 AND
313 loinc = %(loinc)s
314 )"""
315 queries.append({'cmd': cmd, 'args': {'loinc': loinc, 'pk_pnl': self._payload[self._idx['pk_test_panel']]}})
316 return gmPG2.run_rw_queries(queries = queries)
317
318 included_loincs = property(_get_included_loincs, _set_included_loincs)
319
320 #--------------------------------------------------------
321 # properties
322 #--------------------------------------------------------
324 if len(self._payload[self._idx['test_types']]) == 0:
325 return []
326
327 rows, idx = gmPG2.run_ro_queries (
328 queries = [{
329 'cmd': _SQL_get_test_types % 'pk_test_type IN %(pks)s ORDER BY unified_abbrev',
330 'args': {'pks': tuple([ tt['pk_test_type'] for tt in self._payload[self._idx['test_types']] ])}
331 }],
332 get_col_idx = True
333 )
334 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
335
336 test_types = property(_get_test_types, lambda x:x)
337
338 #--------------------------------------------------------
340 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
341 return []
342
343 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
344 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
345 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
346 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
347
349 queries = []
350 # remove all codes
351 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
352 queries.append ({
353 'cmd': 'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s',
354 'args': {
355 'tp': self._payload[self._idx['pk_test_panel']],
356 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
357 }
358 })
359 # add new codes
360 for pk_code in pk_codes:
361 queries.append ({
362 'cmd': 'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)',
363 'args': {
364 'tp': self._payload[self._idx['pk_test_panel']],
365 'pk_code': pk_code
366 }
367 })
368 if len(queries) == 0:
369 return
370 # run it all in one transaction
371 rows, idx = gmPG2.run_rw_queries(queries = queries)
372 return
373
374 generic_codes = property(_get_generic_codes, _set_generic_codes)
375
376 #--------------------------------------------------------
378 return get_most_recent_results_for_panel (
379 pk_patient = pk_patient,
380 pk_panel = self._payload[self._idx['pk_test_panel']],
381 order_by = order_by,
382 group_by_meta_type = group_by_meta_type
383 )
384
385 #------------------------------------------------------------
387 if order_by is None:
388 order_by = 'true'
389 else:
390 order_by = 'true ORDER BY %s' % order_by
391
392 cmd = _SQL_get_test_panels % order_by
393 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
394 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
395
396 #------------------------------------------------------------
398
399 args = {'desc': description.strip()}
400 cmd = """
401 INSERT INTO clin.test_panel (description)
402 VALUES (gm.nullify_empty_string(%(desc)s))
403 RETURNING pk
404 """
405 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
406
407 return cTestPanel(aPK_obj = rows[0]['pk'])
408
409 #------------------------------------------------------------
411 args = {'pk': pk}
412 cmd = "DELETE FROM clin.test_panel WHERE pk = %(pk)s"
413 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
414 return True
415
416 #============================================================
418 """Represents one meta test type under which actual test types can be aggregated."""
419
420 _cmd_fetch_payload = "SELECT *, xmin FROM clin.meta_test_type WHERE pk = %s"
421 _cmds_store_payload = ["""
422 UPDATE clin.meta_test_type SET
423 abbrev = %(abbrev)s,
424 name = %(name)s,
425 loinc = gm.nullify_empty_string(%(loinc)s),
426 comment = gm.nullify_empty_string(%(comment)s)
427 WHERE
428 pk = %(pk)s
429 AND
430 xmin = %(xmin)s
431 RETURNING
432 xmin
433 """]
434 _updatable_fields = [
435 'abbrev',
436 'name',
437 'loinc',
438 'comment'
439 ]
440 #--------------------------------------------------------
442 txt = _('Meta (%s=aggregate) test type [#%s]\n\n') % (gmTools.u_sum, self._payload[self._idx['pk']])
443 txt += _(' Name: %s (%s)\n') % (
444 self._payload[self._idx['abbrev']],
445 self._payload[self._idx['name']]
446 )
447 if self._payload[self._idx['loinc']] is not None:
448 txt += ' LOINC: %s\n' % self._payload[self._idx['loinc']]
449 if self._payload[self._idx['comment']] is not None:
450 txt += _(' Comment: %s\n') % self._payload[self._idx['comment']]
451 if with_tests:
452 ttypes = self.included_test_types
453 if len(ttypes) > 0:
454 txt += _(' Aggregates the following test types:\n')
455 for ttype in ttypes:
456 txt += ' - %s (%s)%s%s%s [#%s]\n' % (
457 ttype['name'],
458 ttype['abbrev'],
459 gmTools.coalesce(ttype['reference_unit'], '', ', %s'),
460 gmTools.coalesce(ttype['name_org'], '', ' (%s)'),
461 gmTools.coalesce(ttype['loinc'], '', ', LOINC: %s'),
462 ttype['pk_test_type']
463 )
464 if patient is not None:
465 txt += '\n'
466 most_recent = self.get_most_recent_result(patient = patient)
467 if most_recent is not None:
468 txt += _(' Most recent (%s): %s%s%s') % (
469 gmDateTime.pydt_strftime(most_recent['clin_when'], '%Y %b %d'),
470 most_recent['unified_val'],
471 gmTools.coalesce(most_recent['val_unit'], '', ' %s'),
472 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)')
473 )
474 oldest = self.get_oldest_result(patient = patient)
475 if oldest is not None:
476 txt += '\n'
477 txt += _(' Oldest (%s): %s%s%s') % (
478 gmDateTime.pydt_strftime(oldest['clin_when'], '%Y %b %d'),
479 oldest['unified_val'],
480 gmTools.coalesce(oldest['val_unit'], '', ' %s'),
481 gmTools.coalesce(oldest['abnormality_indicator'], '', ' (%s)')
482 )
483 return txt
484
485 #--------------------------------------------------------
487 args = {
488 'pat': patient,
489 'mttyp': self._payload[self._idx['pk']]
490 }
491 cmd = """
492 SELECT * FROM clin.v_test_results
493 WHERE
494 pk_patient = %(pat)s
495 AND
496 pk_meta_test_type = %(mttyp)s
497 ORDER BY clin_when DESC
498 LIMIT 1"""
499 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
500 if len(rows) == 0:
501 return None
502
503 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
504
505 #--------------------------------------------------------
507 args = {
508 'pat': patient,
509 'mttyp': self._payload[self._idx['pk']]
510 }
511 cmd = """
512 SELECT * FROM clin.v_test_results
513 WHERE
514 pk_patient = %(pat)s
515 AND
516 pk_meta_test_type = %(mttyp)s
517 ORDER BY clin_when
518 LIMIT 1"""
519 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
520 if len(rows) == 0:
521 return None
522
523 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
524
525 #--------------------------------------------------------
527
528 args = {
529 'pat': pk_patient,
530 'mtyp': self._payload[self._idx['pk']],
531 'mloinc': self._payload[self._idx['loinc']],
532 'when': date
533 }
534 WHERE_meta = ''
535
536 SQL = """
537 SELECT * FROM clin.v_test_results
538 WHERE
539 pk_patient = %%(pat)s
540 AND
541 clin_when %s %%(when)s
542 AND
543 ((pk_meta_test_type = %%(mtyp)s) OR (loinc_meta = %%(mloinc)s))
544 ORDER BY clin_when
545 LIMIT 1"""
546
547 # get earlier results by meta type
548 earlier_result = None
549 cmd = SQL % '<'
550 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
551 if len(rows) > 0:
552 earlier_result = cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
553
554 # get later results by meta type ?
555 later_result = None
556 cmd = SQL % '>'
557 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
558 if len(rows) > 0:
559 later_result = cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
560
561 if earlier_result is None:
562 return later_result
563 if later_result is None:
564 return earlier_result
565
566 earlier_ago = date - earlier_result['clin_when']
567 later_ago = later_result['clin_when'] - date
568 if earlier_ago < later_ago:
569 return earlier_result
570 return later_result
571
572 #--------------------------------------------------------
573 # properties
574 #--------------------------------------------------------
576 cmd = _SQL_get_test_types % 'pk_meta_test_type = %(pk_meta)s'
577 args = {'pk_meta': self._payload[self._idx['pk']]}
578 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
579 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
580
581 included_test_types = property(_get_included_test_types, lambda x:x)
582
583 #------------------------------------------------------------
585 cmd = """
586 INSERT INTO clin.meta_test_type (name, abbrev)
587 SELECT
588 %(name)s,
589 %(abbr)s
590 WHERE NOT EXISTS (
591 SELECT 1 FROM clin.meta_test_type
592 WHERE
593 name = %(name)s
594 AND
595 abbrev = %(abbr)s
596 )
597 RETURNING *, xmin
598 """
599 args = {
600 'name': name.strip(),
601 'abbr': abbreviation.strip()
602 }
603 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True, return_data = True)
604 if len(rows) == 0:
605 if not return_existing:
606 return None
607 cmd = "SELECT *, xmin FROM clin.meta_test_type WHERE name = %(name)s and %(abbr)s"
608 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
609
610 return cMetaTestType(row = {'pk_field': 'pk', 'idx': idx, 'data': rows[0]})
611
612 #------------------------------------------------------------
614 cmd = """
615 DELETE FROM clin.meta_test_type
616 WHERE
617 pk = %(pk)s
618 AND
619 NOT EXISTS (
620 SELECT 1 FROM clin.test_type
621 WHERE fk_meta_test_type = %(pk)s
622 )"""
623 args = {'pk': meta_type}
624 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
625
626 #------------------------------------------------------------
628 cmd = 'SELECT *, xmin FROM clin.meta_test_type'
629 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
630 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
631
632 #============================================================
633 _SQL_get_test_types = "SELECT * FROM clin.v_test_types WHERE %s"
634
636 """Represents one test result type."""
637
638 _cmd_fetch_payload = _SQL_get_test_types % "pk_test_type = %s"
639
640 _cmds_store_payload = [
641 """UPDATE clin.test_type SET
642 abbrev = gm.nullify_empty_string(%(abbrev)s),
643 name = gm.nullify_empty_string(%(name)s),
644 loinc = gm.nullify_empty_string(%(loinc)s),
645 comment = gm.nullify_empty_string(%(comment_type)s),
646 reference_unit = gm.nullify_empty_string(%(reference_unit)s),
647 fk_test_org = %(pk_test_org)s,
648 fk_meta_test_type = %(pk_meta_test_type)s
649 WHERE
650 pk = %(pk_test_type)s
651 AND
652 xmin = %(xmin_test_type)s
653 RETURNING
654 xmin AS xmin_test_type"""
655 ]
656
657 _updatable_fields = [
658 'abbrev',
659 'name',
660 'loinc',
661 'comment_type',
662 'reference_unit',
663 'pk_test_org',
664 'pk_meta_test_type'
665 ]
666 #--------------------------------------------------------
667 # properties
668 #--------------------------------------------------------
670 cmd = 'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
671 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
672 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
673 return rows[0][0]
674
675 in_use = property(_get_in_use, lambda x:x)
676
677 #--------------------------------------------------------
679 results = get_most_recent_results (
680 test_type = self._payload[self._idx['pk_test_type']],
681 loinc = None,
682 no_of_results = no_of_results,
683 patient = patient
684 )
685 if results is None:
686 if self._payload[self._idx['loinc']] is not None:
687 results = get_most_recent_results (
688 test_type = None,
689 loinc = self._payload[self._idx['loinc']],
690 no_of_results = no_of_results,
691 patient = patient
692 )
693 return results
694
695 #--------------------------------------------------------
697 result = get_oldest_result (
698 test_type = self._payload[self._idx['pk_test_type']],
699 loinc = None,
700 patient = patient
701 )
702 if result is None:
703 if self._payload[self._idx['loinc']] is not None:
704 result = get_oldest_result (
705 test_type = None,
706 loinc = self._payload[self._idx['loinc']],
707 patient = patient
708 )
709 return result
710
711 #--------------------------------------------------------
713 if self._payload[self._idx['pk_test_panels']] is None:
714 return None
715
716 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
717
718 test_panels = property(_get_test_panels, lambda x:x)
719
720 #--------------------------------------------------------
722 if real_one_only is False:
723 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
724 if self._payload[self._idx['is_fake_meta_type']]:
725 return None
726 return cMetaTestType(aPK_obj = self._payload[self._idx['pk_meta_test_type']])
727
728 meta_test_type = property(get_meta_test_type, lambda x:x)
729
730 #--------------------------------------------------------
732 """Returns the closest test result which does have normal range information.
733
734 - needs <unit>
735 - if <timestamp> is None it will assume now() and thus return the most recent
736 """
737 if timestamp is None:
738 timestamp = gmDateTime.pydt_now_here()
739 cmd = """
740 SELECT * FROM clin.v_test_results
741 WHERE
742 pk_test_type = %(pk_type)s
743 AND
744 val_unit = %(unit)s
745 AND
746 (
747 (val_normal_min IS NOT NULL)
748 OR
749 (val_normal_max IS NOT NULL)
750 OR
751 (val_normal_range IS NOT NULL)
752 )
753 ORDER BY
754 CASE
755 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
756 ELSE %(clin_when)s - clin_when
757 END
758 LIMIT 1"""
759 args = {
760 'pk_type': self._payload[self._idx['pk_test_type']],
761 'unit': unit,
762 'clin_when': timestamp
763 }
764 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
765 if len(rows) == 0:
766 return None
767 r = rows[0]
768 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
769
770 #--------------------------------------------------------
772 """Returns the closest test result which does have target range information.
773
774 - needs <unit>
775 - needs <patient> (as target will be per-patient)
776 - if <timestamp> is None it will assume now() and thus return the most recent
777 """
778 if timestamp is None:
779 timestamp = gmDateTime.pydt_now_here()
780 cmd = """
781 SELECT * FROM clin.v_test_results
782 WHERE
783 pk_test_type = %(pk_type)s
784 AND
785 val_unit = %(unit)s
786 AND
787 pk_patient = %(pat)s
788 AND
789 (
790 (val_target_min IS NOT NULL)
791 OR
792 (val_target_max IS NOT NULL)
793 OR
794 (val_target_range IS NOT NULL)
795 )
796 ORDER BY
797 CASE
798 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
799 ELSE %(clin_when)s - clin_when
800 END
801 LIMIT 1"""
802 args = {
803 'pk_type': self._payload[self._idx['pk_test_type']],
804 'unit': unit,
805 'pat': patient,
806 'clin_when': timestamp
807 }
808 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
809 if len(rows) == 0:
810 return None
811 r = rows[0]
812 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r})
813
814 #--------------------------------------------------------
816 """Returns the unit of the closest test result.
817
818 - if <timestamp> is None it will assume now() and thus return the most recent
819 """
820 if timestamp is None:
821 timestamp = gmDateTime.pydt_now_here()
822 cmd = """
823 SELECT val_unit FROM clin.v_test_results
824 WHERE
825 pk_test_type = %(pk_type)s
826 AND
827 val_unit IS NOT NULL
828 ORDER BY
829 CASE
830 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
831 ELSE %(clin_when)s - clin_when
832 END
833 LIMIT 1"""
834 args = {
835 'pk_type': self._payload[self._idx['pk_test_type']],
836 'clin_when': timestamp
837 }
838 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
839 if len(rows) == 0:
840 return None
841 return rows[0]['val_unit']
842
843 temporally_closest_unit = property(get_temporally_closest_unit, lambda x:x)
844
845 #--------------------------------------------------------
847 tt = ''
848 tt += _('Test type "%s" (%s) [#%s]\n') % (
849 self._payload[self._idx['name']],
850 self._payload[self._idx['abbrev']],
851 self._payload[self._idx['pk_test_type']]
852 )
853 tt += '\n'
854 tt += gmTools.coalesce(self._payload[self._idx['loinc']], '', ' LOINC: %s\n')
855 tt += gmTools.coalesce(self._payload[self._idx['reference_unit']], '', _(' Reference unit: %s\n'))
856 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], '', _(' Comment: %s\n'))
857
858 tt += '\n'
859 tt += _('Lab details:\n')
860 tt += _(' Name: %s\n') % gmTools.coalesce(self._payload[self._idx['name_org']], '')
861 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], '', _(' Contact: %s\n'))
862 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], '', _(' Comment: %s\n'))
863
864 if self._payload[self._idx['is_fake_meta_type']] is False:
865 tt += '\n'
866 tt += _('Aggregated under meta type:\n')
867 tt += _(' Name: %s - %s [#%s]\n') % (
868 self._payload[self._idx['abbrev_meta']],
869 self._payload[self._idx['name_meta']],
870 self._payload[self._idx['pk_meta_test_type']]
871 )
872 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], '', ' LOINC: %s\n')
873 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], '', _(' Comment: %s\n'))
874
875 panels = self.test_panels
876 if panels is not None:
877 tt += '\n'
878 tt += _('Listed in test panels:\n')
879 for panel in panels:
880 tt += _(' Panel "%s" [#%s]\n') % (
881 panel['description'],
882 panel['pk_test_panel']
883 )
884
885 if patient is not None:
886 tt += '\n'
887 result = self.get_most_recent_results(patient = patient, no_of_results = 1)
888 if result is not None:
889 tt += _(' Most recent (%s): %s%s%s') % (
890 gmDateTime.pydt_strftime(result['clin_when'], '%Y %b %d'),
891 result['unified_val'],
892 gmTools.coalesce(result['val_unit'], '', ' %s'),
893 gmTools.coalesce(result['abnormality_indicator'], '', ' (%s)')
894 )
895 result = self.get_oldest_result(patient = patient)
896 if result is not None:
897 tt += '\n'
898 tt += _(' Oldest (%s): %s%s%s') % (
899 gmDateTime.pydt_strftime(result['clin_when'], '%Y %b %d'),
900 result['unified_val'],
901 gmTools.coalesce(result['val_unit'], '', ' %s'),
902 gmTools.coalesce(result['abnormality_indicator'], '', ' (%s)')
903 )
904
905 return tt
906
907 #------------------------------------------------------------
909 args = {}
910 where_parts = []
911 if loincs is not None:
912 if len(loincs) > 0:
913 where_parts.append('loinc IN %(loincs)s')
914 args['loincs'] = tuple(loincs)
915 if len(where_parts) == 0:
916 where_parts.append('TRUE')
917 WHERE_clause = ' AND '.join(where_parts)
918 cmd = (_SQL_get_test_types % WHERE_clause) + gmTools.coalesce(order_by, '', ' ORDER BY %s')
919 #cmd = 'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, '', 'order by %s')
920 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
921 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
922
923 #------------------------------------------------------------
925
926 if (abbrev is None) and (name is None):
927 raise ValueError('must have <abbrev> and/or <name> set')
928
929 where_snippets = []
930
931 if lab is None:
932 where_snippets.append('pk_test_org IS NULL')
933 else:
934 try:
935 int(lab)
936 where_snippets.append('pk_test_org = %(lab)s')
937 except (TypeError, ValueError):
938 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
939
940 if abbrev is not None:
941 where_snippets.append('abbrev = %(abbrev)s')
942
943 if name is not None:
944 where_snippets.append('name = %(name)s')
945
946 where_clause = ' and '.join(where_snippets)
947 cmd = "select * from clin.v_test_types where %s" % where_clause
948 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
949
950 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
951
952 if len(rows) == 0:
953 return None
954
955 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
956 return tt
957
958 #------------------------------------------------------------
960 cmd = 'delete from clin.test_type where pk = %(pk)s'
961 args = {'pk': measurement_type}
962 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
963
964 #------------------------------------------------------------
966 """Create or get test type."""
967
968 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name, link_obj = link_obj)
969 # found ?
970 if ttype is not None:
971 return ttype
972
973 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
974
975 # not found, so create it
976 # if unit is None:
977 # _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
978 # raise ValueError('need <unit> to create test type')
979
980 # make query
981 cols = []
982 val_snippets = []
983 vals = {}
984
985 # lab
986 if lab is None:
987 lab = create_test_org(link_obj = link_obj)['pk_test_org']
988
989 cols.append('fk_test_org')
990 try:
991 vals['lab'] = int(lab)
992 val_snippets.append('%(lab)s')
993 except:
994 vals['lab'] = lab
995 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
996
997 # code
998 cols.append('abbrev')
999 val_snippets.append('%(abbrev)s')
1000 vals['abbrev'] = abbrev
1001
1002 # unit
1003 if unit is not None:
1004 cols.append('reference_unit')
1005 val_snippets.append('%(unit)s')
1006 vals['unit'] = unit
1007
1008 # name
1009 if name is not None:
1010 cols.append('name')
1011 val_snippets.append('%(name)s')
1012 vals['name'] = name
1013
1014 col_clause = ', '.join(cols)
1015 val_clause = ', '.join(val_snippets)
1016 queries = [
1017 {'cmd': 'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
1018 {'cmd': "select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
1019 ]
1020 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, get_col_idx = True, return_data = True)
1021 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
1022
1023 return ttype
1024
1025 #============================================================
1027 """Represents one test result."""
1028
1029 _cmd_fetch_payload = "select * from clin.v_test_results where pk_test_result = %s"
1030
1031 _cmds_store_payload = [
1032 """UPDATE clin.test_result SET
1033 clin_when = %(clin_when)s,
1034 narrative = nullif(trim(%(comment)s), ''),
1035 val_num = %(val_num)s,
1036 val_alpha = nullif(trim(%(val_alpha)s), ''),
1037 val_unit = nullif(trim(%(val_unit)s), ''),
1038 val_normal_min = %(val_normal_min)s,
1039 val_normal_max = %(val_normal_max)s,
1040 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
1041 val_target_min = %(val_target_min)s,
1042 val_target_max = %(val_target_max)s,
1043 val_target_range = nullif(trim(%(val_target_range)s), ''),
1044 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
1045 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
1046 note_test_org = nullif(trim(%(note_test_org)s), ''),
1047 material = nullif(trim(%(material)s), ''),
1048 material_detail = nullif(trim(%(material_detail)s), ''),
1049 status = gm.nullify_empty_string(%(status)s),
1050 val_grouping = gm.nullify_empty_string(%(val_grouping)s),
1051 source_data = gm.nullify_empty_string(%(source_data)s),
1052 fk_intended_reviewer = %(pk_intended_reviewer)s,
1053 fk_encounter = %(pk_encounter)s,
1054 fk_episode = %(pk_episode)s,
1055 fk_type = %(pk_test_type)s,
1056 fk_request = %(pk_request)s
1057 WHERE
1058 pk = %(pk_test_result)s AND
1059 xmin = %(xmin_test_result)s
1060 RETURNING
1061 xmin AS xmin_test_result
1062 """
1063 # , u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
1064 ]
1065
1066 _updatable_fields = [
1067 'clin_when',
1068 'comment',
1069 'val_num',
1070 'val_alpha',
1071 'val_unit',
1072 'val_normal_min',
1073 'val_normal_max',
1074 'val_normal_range',
1075 'val_target_min',
1076 'val_target_max',
1077 'val_target_range',
1078 'abnormality_indicator',
1079 'norm_ref_group',
1080 'note_test_org',
1081 'material',
1082 'material_detail',
1083 'status',
1084 'val_grouping',
1085 'source_data',
1086 'pk_intended_reviewer',
1087 'pk_encounter',
1088 'pk_episode',
1089 'pk_test_type',
1090 'pk_request'
1091 ]
1092
1093 #--------------------------------------------------------
1095 range_info = gmTools.coalesce (
1096 self.formatted_clinical_range,
1097 self.formatted_normal_range
1098 )
1099 review = gmTools.bool2subst (
1100 self._payload[self._idx['reviewed']],
1101 '',
1102 ' ' + gmTools.u_writing_hand,
1103 ' ' + gmTools.u_writing_hand
1104 )
1105 txt = '%s %s: %s%s%s%s%s%s' % (
1106 gmDateTime.pydt_strftime (
1107 self._payload[self._idx['clin_when']],
1108 date_format
1109 ),
1110 self._payload[self._idx['name_tt']],
1111 self._payload[self._idx['unified_val']],
1112 gmTools.coalesce(self._payload[self._idx['val_unit']], '', ' %s'),
1113 gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], '', ' %s'),
1114 gmTools.coalesce(range_info, '', ' (%s)'),
1115 gmTools.coalesce(self._payload[self._idx['status']], '', ' [%s]')[:2],
1116 review
1117 )
1118 if with_notes:
1119 txt += '\n'
1120 if self._payload[self._idx['note_test_org']] is not None:
1121 txt += ' ' + _('Lab comment: %s\n') % _('\n Lab comment: ').join(self._payload[self._idx['note_test_org']].split('\n'))
1122 if self._payload[self._idx['comment']] is not None:
1123 txt += ' ' + _('Praxis comment: %s\n') % _('\n Praxis comment: ').join(self._payload[self._idx['comment']].split('\n'))
1124
1125 return txt.strip('\n')
1126
1127 #--------------------------------------------------------
1128 - def format(self, with_review=True, with_evaluation=True, with_ranges=True, with_episode=True, with_type_details=True, with_source_data=False, date_format='%Y %b %d %H:%M'):
1129
1130 # FIXME: add battery, request details
1131
1132 # header
1133 tt = _('Result from %s \n') % gmDateTime.pydt_strftime (
1134 self._payload[self._idx['clin_when']],
1135 date_format
1136 )
1137
1138 # basics
1139 tt += ' ' + _('Type: "%(name)s" (%(abbr)s) [#%(pk_type)s]\n') % ({
1140 'name': self._payload[self._idx['name_tt']],
1141 'abbr': self._payload[self._idx['abbrev_tt']],
1142 'pk_type': self._payload[self._idx['pk_test_type']]
1143 })
1144 if self.is_long_text:
1145 sso = gmTools.u_superscript_one
1146 else:
1147 sso = ''
1148 tt += ' ' + _('%(sso)sResult: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({
1149 'sso': sso,
1150 'val': self._payload[self._idx['unified_val']],
1151 'unit': gmTools.coalesce(self._payload[self._idx['val_unit']], '', ' %s'),
1152 'ind': gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], '', ' (%s)'),
1153 'pk_result': self._payload[self._idx['pk_test_result']]
1154 })
1155
1156 if self._payload[self._idx['status']] is not None:
1157 try:
1158 stat = HL7_RESULT_STATI[self._payload[self._idx['status']]]
1159 except KeyError:
1160 stat = self._payload[self._idx['status']]
1161 tt += ' ' + _('Status: %s\n') % stat
1162 if self._payload[self._idx['val_grouping']] is not None:
1163 tt += ' ' + _('Grouping: %s\n') % self._payload[self._idx['val_grouping']]
1164
1165 if with_evaluation:
1166 norm_eval = None
1167 if self._payload[self._idx['val_num']] is not None:
1168 # 1) normal range
1169 # lowered ?
1170 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']]):
1171 try:
1172 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_normal_min']]
1173 except ZeroDivisionError:
1174 percent = None
1175 if percent is not None:
1176 if percent < 6:
1177 norm_eval = _('%.1f %% of the normal lower limit') % percent
1178 else:
1179 norm_eval = _('%.0f %% of the normal lower limit') % percent
1180 # raised ?
1181 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']]):
1182 try:
1183 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_normal_max']]
1184 except ZeroDivisionError:
1185 x_times = None
1186 if x_times is not None:
1187 if x_times < 10:
1188 norm_eval = _('%.1f times the normal upper limit') % x_times
1189 else:
1190 norm_eval = _('%.0f times the normal upper limit') % x_times
1191 if norm_eval is not None:
1192 tt += ' = %s\n' % norm_eval
1193 # #-------------------------------------
1194 # # this idea was shot down on the list
1195 # #-------------------------------------
1196 # # bandwidth of deviation
1197 # if None not in [self._payload[self._idx['val_normal_min']], self._payload[self._idx['val_normal_max']]]:
1198 # normal_width = self._payload[self._idx['val_normal_max']] - self._payload[self._idx['val_normal_min']]
1199 # deviation_from_normal_range = None
1200 # # below ?
1201 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]:
1202 # deviation_from_normal_range = self._payload[self._idx['val_normal_min']] - self._payload[self._idx['val_num']]
1203 # # above ?
1204 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]:
1205 # deviation_from_normal_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_normal_max']]
1206 # if deviation_from_normal_range is None:
1207 # try:
1208 # times_deviation = deviation_from_normal_range / normal_width
1209 # except ZeroDivisionError:
1210 # times_deviation = None
1211 # if times_deviation is not None:
1212 # if times_deviation < 10:
1213 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation
1214 # else:
1215 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation
1216 # #-------------------------------------
1217
1218 # 2) clinical target range
1219 norm_eval = None
1220 # lowered ?
1221 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']]):
1222 try:
1223 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_target_min']]
1224 except ZeroDivisionError:
1225 percent = None
1226 if percent is not None:
1227 if percent < 6:
1228 norm_eval = _('%.1f %% of the target lower limit') % percent
1229 else:
1230 norm_eval = _('%.0f %% of the target lower limit') % percent
1231 # raised ?
1232 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']]):
1233 try:
1234 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_target_max']]
1235 except ZeroDivisionError:
1236 x_times = None
1237 if x_times is not None:
1238 if x_times < 10:
1239 norm_eval = _('%.1f times the target upper limit') % x_times
1240 else:
1241 norm_eval = _('%.0f times the target upper limit') % x_times
1242 if norm_eval is not None:
1243 tt += ' = %s\n' % norm_eval
1244 # #-------------------------------------
1245 # # this idea was shot down on the list
1246 # #-------------------------------------
1247 # # bandwidth of deviation
1248 # if None not in [self._payload[self._idx['val_target_min']], self._payload[self._idx['val_target_max']]]:
1249 # normal_width = self._payload[self._idx['val_target_max']] - self._payload[self._idx['val_target_min']]
1250 # deviation_from_target_range = None
1251 # # below ?
1252 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]:
1253 # deviation_from_target_range = self._payload[self._idx['val_target_min']] - self._payload[self._idx['val_num']]
1254 # # above ?
1255 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]:
1256 # deviation_from_target_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_target_max']]
1257 # if deviation_from_target_range is None:
1258 # try:
1259 # times_deviation = deviation_from_target_range / normal_width
1260 # except ZeroDivisionError:
1261 # times_deviation = None
1262 # if times_deviation is not None:
1263 # if times_deviation < 10:
1264 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation
1265 # else:
1266 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation
1267 # #-------------------------------------
1268
1269 tmp = ('%s%s' % (
1270 gmTools.coalesce(self._payload[self._idx['name_test_org']], ''),
1271 gmTools.coalesce(self._payload[self._idx['contact_test_org']], '', ' (%s)'),
1272 )).strip()
1273 if tmp != '':
1274 tt += ' ' + _('Source: %s\n') % tmp
1275 tt += '\n'
1276 if self._payload[self._idx['note_test_org']] is not None:
1277 tt += ' ' + gmTools.u_superscript_one + _('Lab comment: %s\n') % _('\n Lab comment: ').join(self._payload[self._idx['note_test_org']].split('\n'))
1278 if self._payload[self._idx['comment']] is not None:
1279 tt += ' ' + gmTools.u_superscript_one + _('Praxis comment: %s\n') % _('\n Praxis comment: ').join(self._payload[self._idx['comment']].split('\n'))
1280
1281 if with_ranges:
1282 tt += gmTools.coalesce(self.formatted_normal_range, '', ' ' + _('Standard normal range: %s\n'))
1283 tt += gmTools.coalesce(self.formatted_clinical_range, '', ' ' + _('Clinical target range: %s\n'))
1284 tt += gmTools.coalesce(self._payload[self._idx['norm_ref_group']], '', ' ' + _('Reference group: %s\n'))
1285
1286 # metadata
1287 if with_episode:
1288 tt += ' ' + _('Episode: %s\n') % self._payload[self._idx['episode']]
1289 if self._payload[self._idx['health_issue']] is not None:
1290 tt += ' ' + _('Issue: %s\n') % self._payload[self._idx['health_issue']]
1291 if self._payload[self._idx['material']] is not None:
1292 tt += ' ' + _('Material: %s\n') % self._payload[self._idx['material']]
1293 if self._payload[self._idx['material_detail']] is not None:
1294 tt += ' ' + _('Details: %s\n') % self._payload[self._idx['material_detail']]
1295 tt += '\n'
1296
1297 if with_review:
1298 if self._payload[self._idx['reviewed']]:
1299 review = gmDateTime.pydt_strftime (
1300 self._payload[self._idx['last_reviewed']],
1301 date_format
1302 )
1303 else:
1304 review = _('not yet')
1305 tt += _('Signed (%(sig_hand)s): %(reviewed)s\n') % ({
1306 'sig_hand': gmTools.u_writing_hand,
1307 'reviewed': review
1308 })
1309 tt += ' ' + _('Responsible clinician: %s\n') % gmTools.bool2subst (
1310 self._payload[self._idx['you_are_responsible']],
1311 _('you'),
1312 self._payload[self._idx['responsible_reviewer']]
1313 )
1314 if self._payload[self._idx['reviewed']]:
1315 tt += ' ' + _('Last reviewer: %(reviewer)s\n') % ({
1316 'reviewer': gmTools.bool2subst (
1317 self._payload[self._idx['review_by_you']],
1318 _('you'),
1319 gmTools.coalesce(self._payload[self._idx['last_reviewer']], '?')
1320 )
1321 })
1322 tt += ' ' + _(' Technically abnormal: %(abnormal)s\n') % ({
1323 'abnormal': gmTools.bool2subst (
1324 self._payload[self._idx['is_technically_abnormal']],
1325 _('yes'),
1326 _('no'),
1327 '?'
1328 )
1329 })
1330 tt += ' ' + _(' Clinically relevant: %(relevant)s\n') % ({
1331 'relevant': gmTools.bool2subst (
1332 self._payload[self._idx['is_clinically_relevant']],
1333 _('yes'),
1334 _('no'),
1335 '?'
1336 )
1337 })
1338 if self._payload[self._idx['review_comment']] is not None:
1339 tt += ' ' + _(' Comment: %s\n') % self._payload[self._idx['review_comment']].strip()
1340 tt += '\n'
1341
1342 # type
1343 if with_type_details:
1344 has_details = None not in [self._payload[self._idx['comment_tt']], self._payload[self._idx['pk_meta_test_type']], self._payload[self._idx['comment_meta']]]
1345 if has_details:
1346 tt += _('Test type details:\n')
1347 if self._payload[self._idx['comment_tt']] is not None:
1348 tt += ' ' + _('Type comment: %s\n') % _('\n Type comment:').join(self._payload[self._idx['comment_tt']].split('\n'))
1349 if self._payload[self._idx['pk_meta_test_type']] is not None:
1350 tt += ' ' + _('Aggregated (%s) under: %s (%s) [#%s]\n') % (
1351 gmTools.u_sum,
1352 self._payload[self._idx['name_meta']],
1353 self._payload[self._idx['abbrev_meta']],
1354 self._payload[self._idx['pk_meta_test_type']]
1355 )
1356 if self._payload[self._idx['comment_meta']] is not None:
1357 tt += ' ' + _('Group comment: %s\n') % _('\n Group comment: ').join(self._payload[self._idx['comment_meta']].split('\n'))
1358 if has_details:
1359 tt += '\n'
1360
1361 if with_source_data:
1362 if self._payload[self._idx['source_data']] is not None:
1363 tt += _('Source data:\n')
1364 tt += ' ' + self._payload[self._idx['source_data']]
1365 tt += '\n\n'
1366
1367 if with_review:
1368 tt += _('Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({
1369 'row_ver': self._payload[self._idx['row_version']],
1370 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format),
1371 'mod_by': self._payload[self._idx['modified_by']]
1372 })
1373
1374 return tt
1375
1376 #--------------------------------------------------------
1378 return (
1379 self._payload[self._idx['val_normal_min']] is not None
1380 ) or (
1381 self._payload[self._idx['val_normal_max']] is not None
1382 )
1383
1384 has_normal_min_or_max = property(_get_has_normal_min_or_max, lambda x:x)
1385
1386 #--------------------------------------------------------
1388 has_range_info = (
1389 self._payload[self._idx['val_normal_min']] is not None
1390 ) or (
1391 self._payload[self._idx['val_normal_max']] is not None
1392 )
1393 if has_range_info is False:
1394 return None
1395
1396 return '%s - %s' % (
1397 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'),
1398 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?')
1399 )
1400
1401 normal_min_max = property(_get_normal_min_max, lambda x:x)
1402
1403 #--------------------------------------------------------
1405 has_numerical_range = (
1406 self._payload[self._idx['val_normal_min']] is not None
1407 ) or (
1408 self._payload[self._idx['val_normal_max']] is not None
1409 )
1410 if has_numerical_range:
1411 numerical_range = '%s - %s' % (
1412 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'),
1413 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?')
1414 )
1415 else:
1416 numerical_range = ''
1417 textual_range = gmTools.coalesce (
1418 self._payload[self._idx['val_normal_range']],
1419 '',
1420 gmTools.bool2subst (
1421 has_numerical_range,
1422 ' / %s',
1423 '%s'
1424 )
1425 )
1426 range_info = '%s%s' % (numerical_range, textual_range)
1427 if range_info == '':
1428 return None
1429 return range_info
1430
1431 formatted_normal_range = property(_get_formatted_normal_range, lambda x:x)
1432
1433 #--------------------------------------------------------
1435 return (
1436 self._payload[self._idx['val_target_min']] is not None
1437 ) or (
1438 self._payload[self._idx['val_target_max']] is not None
1439 )
1440
1441 has_clinical_min_or_max = property(_get_has_clinical_min_or_max, lambda x:x)
1442
1443 #--------------------------------------------------------
1445 has_range_info = (
1446 self._payload[self._idx['val_target_min']] is not None
1447 ) or (
1448 self._payload[self._idx['val_target_max']] is not None
1449 )
1450 if has_range_info is False:
1451 return None
1452
1453 return '%s - %s' % (
1454 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'),
1455 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?')
1456 )
1457
1458 clinical_min_max = property(_get_clinical_min_max, lambda x:x)
1459
1460 #--------------------------------------------------------
1462 has_numerical_range = (
1463 self._payload[self._idx['val_target_min']] is not None
1464 ) or (
1465 self._payload[self._idx['val_target_max']] is not None
1466 )
1467 if has_numerical_range:
1468 numerical_range = '%s - %s' % (
1469 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'),
1470 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?')
1471 )
1472 else:
1473 numerical_range = ''
1474 textual_range = gmTools.coalesce (
1475 self._payload[self._idx['val_target_range']],
1476 '',
1477 gmTools.bool2subst (
1478 has_numerical_range,
1479 ' / %s',
1480 '%s'
1481 )
1482 )
1483 range_info = '%s%s' % (numerical_range, textual_range)
1484 if range_info == '':
1485 return None
1486 return range_info
1487
1488 formatted_clinical_range = property(_get_formatted_clinical_range, lambda x:x)
1489
1490 #--------------------------------------------------------
1492 """Returns the closest test result which does have normal range information."""
1493 if self._payload[self._idx['val_normal_min']] is not None:
1494 return self
1495 if self._payload[self._idx['val_normal_max']] is not None:
1496 return self
1497 if self._payload[self._idx['val_normal_range']] is not None:
1498 return self
1499 cmd = """
1500 SELECT * from clin.v_test_results
1501 WHERE
1502 pk_type = %(pk_type)s
1503 AND
1504 val_unit = %(unit)s
1505 AND
1506 (
1507 (val_normal_min IS NOT NULL)
1508 OR
1509 (val_normal_max IS NOT NULL)
1510 OR
1511 (val_normal_range IS NOT NULL)
1512 )
1513 ORDER BY
1514 CASE
1515 WHEN clin_when > %(clin_when)s THEN clin_when - %(clin_when)s
1516 ELSE %(clin_when)s - clin_when
1517 END
1518 LIMIT 1"""
1519 args = {
1520 'pk_type': self._payload[self._idx['pk_test_type']],
1521 'unit': self._payload[self._idx['val_unit']],
1522 'clin_when': self._payload[self._idx['clin_when']]
1523 }
1524 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1525 if len(rows) == 0:
1526 return None
1527 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1528
1529 temporally_closest_normal_range = property(_get_temporally_closest_normal_range, lambda x:x)
1530
1531 #--------------------------------------------------------
1533
1534 has_normal_min_or_max = (
1535 self._payload[self._idx['val_normal_min']] is not None
1536 ) or (
1537 self._payload[self._idx['val_normal_max']] is not None
1538 )
1539 if has_normal_min_or_max:
1540 normal_min_max = '%s - %s' % (
1541 gmTools.coalesce(self._payload[self._idx['val_normal_min']], '?'),
1542 gmTools.coalesce(self._payload[self._idx['val_normal_max']], '?')
1543 )
1544
1545 has_clinical_min_or_max = (
1546 self._payload[self._idx['val_target_min']] is not None
1547 ) or (
1548 self._payload[self._idx['val_target_max']] is not None
1549 )
1550 if has_clinical_min_or_max:
1551 clinical_min_max = '%s - %s' % (
1552 gmTools.coalesce(self._payload[self._idx['val_target_min']], '?'),
1553 gmTools.coalesce(self._payload[self._idx['val_target_max']], '?')
1554 )
1555
1556 if has_clinical_min_or_max:
1557 return _('Target: %(clin_min_max)s%(clin_range)s') % ({
1558 'clin_min_max': clinical_min_max,
1559 'clin_range': gmTools.coalesce (
1560 self._payload[self._idx['val_target_range']],
1561 '',
1562 gmTools.bool2subst (
1563 has_clinical_min_or_max,
1564 ' / %s',
1565 '%s'
1566 )
1567 )
1568 })
1569
1570 if has_normal_min_or_max:
1571 return _('Norm: %(norm_min_max)s%(norm_range)s') % ({
1572 'norm_min_max': normal_min_max,
1573 'norm_range': gmTools.coalesce (
1574 self._payload[self._idx['val_normal_range']],
1575 '',
1576 gmTools.bool2subst (
1577 has_normal_min_or_max,
1578 ' / %s',
1579 '%s'
1580 )
1581 )
1582 })
1583
1584 if self._payload[self._idx['val_target_range']] is not None:
1585 return _('Target: %s') % self._payload[self._idx['val_target_range']],
1586
1587 if self._payload[self._idx['val_normal_range']] is not None:
1588 return _('Norm: %s') % self._payload[self._idx['val_normal_range']]
1589
1590 return None
1591
1592 formatted_range = property(_get_formatted_range, lambda x:x)
1593
1594 #--------------------------------------------------------
1596 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
1597
1598 test_type = property(_get_test_type, lambda x:x)
1599
1600 #--------------------------------------------------------
1602 # 1) the user is right (review)
1603 if self._payload[self._idx['is_technically_abnormal']] is False:
1604 return False
1605 # 2) the lab is right (result.abnormality_indicator)
1606 indicator = self._payload[self._idx['abnormality_indicator']]
1607 if indicator is not None:
1608 indicator = indicator.strip()
1609 if indicator != '':
1610 if indicator.strip('+') == '':
1611 return True
1612 if indicator.strip('-') == '':
1613 return False
1614 # 3) non-numerical value ?
1615 if self._payload[self._idx['val_num']] is None:
1616 return None
1617 # 4) the target range is right
1618 target_max = self._payload[self._idx['val_target_max']]
1619 if target_max is not None:
1620 if target_max < self._payload[self._idx['val_num']]:
1621 return True
1622 # 4) the normal range is right
1623 normal_max = self._payload[self._idx['val_normal_max']]
1624 if normal_max is not None:
1625 if normal_max < self._payload[self._idx['val_num']]:
1626 return True
1627 return None
1628
1629 is_considered_elevated = property(_get_is_considered_elevated, lambda x:x)
1630
1631 #--------------------------------------------------------
1633 # 1) the user is right (review)
1634 if self._payload[self._idx['is_technically_abnormal']] is False:
1635 return False
1636 # 2) the lab is right (result.abnormality_indicator)
1637 indicator = self._payload[self._idx['abnormality_indicator']]
1638 if indicator is not None:
1639 indicator = indicator.strip()
1640 if indicator != '':
1641 if indicator.strip('+') == '':
1642 return False
1643 if indicator.strip('-') == '':
1644 return True
1645 # 3) non-numerical value ?
1646 if self._payload[self._idx['val_num']] is None:
1647 return None
1648 # 4) the target range is right
1649 target_min = self._payload[self._idx['val_target_min']]
1650 if target_min is not None:
1651 if target_min > self._payload[self._idx['val_num']]:
1652 return True
1653 # 4) the normal range is right
1654 normal_min = self._payload[self._idx['val_normal_min']]
1655 if normal_min is not None:
1656 if normal_min > self._payload[self._idx['val_num']]:
1657 return True
1658 return None
1659
1660 is_considered_lowered = property(_get_is_considered_lowered, lambda x:x)
1661
1662 #--------------------------------------------------------
1664 if self.is_considered_lowered is True:
1665 return True
1666 if self.is_considered_elevated is True:
1667 return True
1668 if (self.is_considered_lowered is False) and (self.is_considered_elevated is False):
1669 return False
1670 return self._payload[self._idx['is_technically_abnormal']]
1671
1672 is_considered_abnormal = property(_get_is_considered_abnormal, lambda x:x)
1673
1674 #--------------------------------------------------------
1676 """Parse reference range from string.
1677
1678 Note: does NOT save the result.
1679 """
1680 ref_range = ref_range.strip().replace(' ', '')
1681
1682 is_range = regex.match('-{0,1}\d+[.,]{0,1}\d*--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1683 if is_range is not None:
1684 min_val = regex.match('-{0,1}\d+[.,]{0,1}\d*-', ref_range, regex.UNICODE).group(0).rstrip('-')
1685 success, min_val = gmTools.input2decimal(min_val)
1686 max_val = (regex.search('--{0,1}\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE).group(0))[1:]
1687 success, max_val = gmTools.input2decimal(max_val)
1688 self['val_normal_min'] = min_val
1689 self['val_normal_max'] = max_val
1690 return
1691
1692 if ref_range.startswith('<'):
1693 is_range = regex.match('<\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1694 if is_range is not None:
1695 max_val = ref_range[1:]
1696 success, max_val = gmTools.input2decimal(max_val)
1697 self['val_normal_min'] = 0
1698 self['val_normal_max'] = max_val
1699 return
1700
1701 if ref_range.startswith('<-'):
1702 is_range = regex.match('<-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1703 if is_range is not None:
1704 max_val = ref_range[1:]
1705 success, max_val = gmTools.input2decimal(max_val)
1706 self['val_normal_min'] = None
1707 self['val_normal_max'] = max_val
1708 return
1709
1710 if ref_range.startswith('>'):
1711 is_range = regex.match('>\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1712 if is_range is not None:
1713 min_val = ref_range[1:]
1714 success, min_val = gmTools.input2decimal(min_val)
1715 self['val_normal_min'] = min_val
1716 self['val_normal_max'] = None
1717 return
1718
1719 if ref_range.startswith('>-'):
1720 is_range = regex.match('>-\d+[.,]{0,1}\d*$', ref_range, regex.UNICODE)
1721 if is_range is not None:
1722 min_val = ref_range[1:]
1723 success, min_val = gmTools.input2decimal(min_val)
1724 self['val_normal_min'] = min_val
1725 self['val_normal_max'] = 0
1726 return
1727
1728 self['val_normal_range'] = ref_range
1729 return
1730
1731 reference_range = property(lambda x:x, _set_reference_range)
1732
1733 #--------------------------------------------------------
1735 # 1) the user is right
1736 if self._payload[self._idx['is_technically_abnormal']] is False:
1737 return ''
1738 # 2) the lab is right (result.abnormality_indicator)
1739 indicator = self._payload[self._idx['abnormality_indicator']]
1740 if indicator is not None:
1741 indicator = indicator.strip()
1742 if indicator != '':
1743 return indicator
1744 # 3) non-numerical value ? then we can't know more
1745 if self._payload[self._idx['val_num']] is None:
1746 return None
1747 # 4) the target range is right
1748 target_min = self._payload[self._idx['val_target_min']]
1749 if target_min is not None:
1750 if target_min > self._payload[self._idx['val_num']]:
1751 return '-'
1752 target_max = self._payload[self._idx['val_target_max']]
1753 if target_max is not None:
1754 if target_max < self._payload[self._idx['val_num']]:
1755 return '+'
1756 # 4) the normal range is right
1757 normal_min = self._payload[self._idx['val_normal_min']]
1758 if normal_min is not None:
1759 if normal_min > self._payload[self._idx['val_num']]:
1760 return '-'
1761 normal_max = self._payload[self._idx['val_normal_max']]
1762 if normal_max is not None:
1763 if normal_max < self._payload[self._idx['val_num']]:
1764 return '+'
1765 # reviewed, abnormal, but no indicator available
1766 if self._payload[self._idx['is_technically_abnormal']] is True:
1767 return gmTools.u_plus_minus
1768
1769 return None
1770
1771 formatted_abnormality_indicator = property(_get_formatted_abnormality_indicator, lambda x:x)
1772
1773 #--------------------------------------------------------
1775 if self._payload[self._idx['val_alpha']] is None:
1776 return False
1777 lines = gmTools.strip_empty_lines(text = self._payload[self._idx['val_alpha']], eol = '\n', return_list = True)
1778 if len(lines) > 4:
1779 return True
1780 return False
1781
1782 is_long_text = property(_get_is_long_text, lambda x:x)
1783
1784 #--------------------------------------------------------
1786 if self._payload[self._idx['val_alpha']] is None:
1787 return None
1788 val = self._payload[self._idx['val_alpha']].lstrip()
1789 if val[0] == '<':
1790 factor = decimal.Decimal(0.5)
1791 val = val[1:]
1792 elif val[0] == '>':
1793 factor = 2
1794 val = val[1:]
1795 else:
1796 return None
1797 success, val = gmTools.input2decimal(initial = val)
1798 if not success:
1799 return None
1800 return val * factor
1801
1802 estimate_numeric_value_from_alpha = property(_get_estimate_numeric_value_from_alpha, lambda x:x)
1803
1804 #--------------------------------------------------------
1805 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
1806
1807 # FIXME: this is not concurrency safe
1808 if self._payload[self._idx['reviewed']]:
1809 self.__change_existing_review (
1810 technically_abnormal = technically_abnormal,
1811 clinically_relevant = clinically_relevant,
1812 comment = comment
1813 )
1814 else:
1815 # do not sign off unreviewed results if
1816 # NOTHING AT ALL is known about them
1817 if technically_abnormal is None:
1818 if clinically_relevant is None:
1819 comment = gmTools.none_if(comment, '', strip_string = True)
1820 if comment is None:
1821 if make_me_responsible is False:
1822 return True
1823 self.__set_new_review (
1824 technically_abnormal = technically_abnormal,
1825 clinically_relevant = clinically_relevant,
1826 comment = comment
1827 )
1828
1829 if make_me_responsible is True:
1830 cmd = "SELECT pk FROM dem.staff WHERE db_user = current_user"
1831 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
1832 self['pk_intended_reviewer'] = rows[0][0]
1833 self.save_payload()
1834 return
1835
1836 self.refetch_payload()
1837
1838 #--------------------------------------------------------
1839 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
1840
1841 if desired_earlier_results < 1:
1842 raise ValueError('<desired_earlier_results> must be > 0')
1843
1844 if desired_later_results < 1:
1845 raise ValueError('<desired_later_results> must be > 0')
1846
1847 args = {
1848 'pat': self._payload[self._idx['pk_patient']],
1849 'ttyp': self._payload[self._idx['pk_test_type']],
1850 'tloinc': self._payload[self._idx['loinc_tt']],
1851 'mtyp': self._payload[self._idx['pk_meta_test_type']],
1852 'mloinc': self._payload[self._idx['loinc_meta']],
1853 'when': self._payload[self._idx['clin_when']],
1854 'offset': max_offset
1855 }
1856 WHERE = '((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
1857 WHERE_meta = '((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
1858 if max_offset is not None:
1859 WHERE = WHERE + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1860 WHERE_meta = WHERE_meta + ' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1861
1862 SQL = """
1863 SELECT * FROM clin.v_test_results
1864 WHERE
1865 pk_patient = %%(pat)s
1866 AND
1867 clin_when %s %%(when)s
1868 AND
1869 %s
1870 ORDER BY clin_when
1871 LIMIT %s"""
1872
1873 # get earlier results
1874 earlier_results = []
1875 # by type
1876 cmd = SQL % ('<', WHERE, desired_earlier_results)
1877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1878 if len(rows) > 0:
1879 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1880 # by meta type ?
1881 missing_results = desired_earlier_results - len(earlier_results)
1882 if missing_results > 0:
1883 cmd = SQL % ('<', WHERE_meta, missing_results)
1884 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1885 if len(rows) > 0:
1886 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1887
1888 # get later results
1889 later_results = []
1890 # by type
1891 cmd = SQL % ('>', WHERE, desired_later_results)
1892 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1893 if len(rows) > 0:
1894 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1895 # by meta type ?
1896 missing_results = desired_later_results - len(later_results)
1897 if missing_results > 0:
1898 cmd = SQL % ('>', WHERE_meta, missing_results)
1899 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1900 if len(rows) > 0:
1901 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1902
1903 return earlier_results, later_results
1904
1905 #--------------------------------------------------------
1906 # internal API
1907 #--------------------------------------------------------
1908 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1909 """Add a review to a row.
1910
1911 - if technically abnormal is not provided/None it will be set
1912 to True if the lab's indicator has a meaningful value
1913 - if clinically relevant is not provided/None it is set to
1914 whatever technically abnormal is
1915 """
1916 if technically_abnormal is None:
1917 technically_abnormal = False
1918 if self._payload[self._idx['abnormality_indicator']] is not None:
1919 if self._payload[self._idx['abnormality_indicator']].strip() != '':
1920 technically_abnormal = True
1921
1922 if clinically_relevant is None:
1923 clinically_relevant = technically_abnormal
1924
1925 cmd = """
1926 INSERT INTO clin.reviewed_test_results (
1927 fk_reviewed_row,
1928 is_technically_abnormal,
1929 clinically_relevant,
1930 comment
1931 ) VALUES (
1932 %(pk)s,
1933 %(abnormal)s,
1934 %(relevant)s,
1935 gm.nullify_empty_string(%(cmt)s)
1936 )"""
1937 args = {
1938 'pk': self._payload[self._idx['pk_test_result']],
1939 'abnormal': technically_abnormal,
1940 'relevant': clinically_relevant,
1941 'cmt': comment
1942 }
1943
1944 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1945
1946 #--------------------------------------------------------
1947 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1948 """Change a review on a row.
1949
1950 - if technically abnormal/clinically relevant are
1951 None they are not set
1952 """
1953 args = {
1954 'pk_row': self._payload[self._idx['pk_test_result']],
1955 'abnormal': technically_abnormal,
1956 'relevant': clinically_relevant,
1957 'cmt': comment
1958 }
1959
1960 set_parts = [
1961 'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
1962 'comment = gm.nullify_empty_string(%(cmt)s)'
1963 ]
1964
1965 if technically_abnormal is not None:
1966 set_parts.append('is_technically_abnormal = %(abnormal)s')
1967
1968 if clinically_relevant is not None:
1969 set_parts.append('clinically_relevant = %(relevant)s')
1970
1971 cmd = """
1972 UPDATE clin.reviewed_test_results SET
1973 %s
1974 WHERE
1975 fk_reviewed_row = %%(pk_row)s
1976 """ % ',\n '.join(set_parts)
1977
1978 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1979
1980 #------------------------------------------------------------
1982
1983 where_parts = []
1984
1985 if pk_patient is not None:
1986 where_parts.append('pk_patient = %(pat)s')
1987 args = {'pat': pk_patient}
1988
1989 # if tests is not None:
1990 # where_parts.append(u'pk_test_type IN %(tests)s')
1991 # args['tests'] = tuple(tests)
1992
1993 if encounters is not None:
1994 where_parts.append('pk_encounter IN %(encs)s')
1995 args['encs'] = tuple(encounters)
1996
1997 if episodes is not None:
1998 where_parts.append('pk_episode IN %(epis)s')
1999 args['epis'] = tuple(episodes)
2000
2001 if order_by is None:
2002 order_by = ''
2003 else:
2004 order_by = 'ORDER BY %s' % order_by
2005
2006 cmd = """
2007 SELECT * FROM clin.v_test_results
2008 WHERE %s
2009 %s
2010 """ % (
2011 ' AND '.join(where_parts),
2012 order_by
2013 )
2014 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2015
2016 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2017 return tests
2018
2019 #------------------------------------------------------------
2020 -def get_most_recent_results_for_panel(pk_patient=None, pk_panel=None, order_by=None, group_by_meta_type=False):
2021
2022 if order_by is None:
2023 order_by = ''
2024 else:
2025 order_by = 'ORDER BY %s' % order_by
2026
2027 args = {
2028 'pat': pk_patient,
2029 'pk_pnl': pk_panel
2030 }
2031
2032 if group_by_meta_type:
2033 # return most recent results in panel grouped by
2034 # meta test type if any, non-grouped results are
2035 # returned ungrouped :-)
2036 cmd = """
2037 SELECT c_vtr.*
2038 FROM (
2039 -- max(clin_when) per test_type-in-panel for patient
2040 SELECT
2041 pk_meta_test_type,
2042 MAX(clin_when) AS max_clin_when
2043 FROM clin.v_test_results
2044 WHERE
2045 pk_patient = %(pat)s
2046 AND
2047 pk_meta_test_type IS DISTINCT FROM NULL
2048 AND
2049 pk_test_type IN (
2050 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2051 )
2052 GROUP BY pk_meta_test_type
2053 ) AS latest_results
2054 INNER JOIN clin.v_test_results c_vtr ON
2055 c_vtr.pk_meta_test_type = latest_results.pk_meta_test_type
2056 AND
2057 c_vtr.clin_when = latest_results.max_clin_when
2058
2059 UNION ALL
2060
2061 SELECT c_vtr.*
2062 FROM (
2063 -- max(clin_when) per test_type-in-panel for patient
2064 SELECT
2065 pk_test_type,
2066 MAX(clin_when) AS max_clin_when
2067 FROM clin.v_test_results
2068 WHERE
2069 pk_patient = %(pat)s
2070 AND
2071 pk_meta_test_type IS NULL
2072 AND
2073 pk_test_type IN (
2074 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2075 )
2076 GROUP BY pk_test_type
2077 ) AS latest_results
2078 INNER JOIN clin.v_test_results c_vtr ON
2079 c_vtr.pk_test_type = latest_results.pk_test_type
2080 AND
2081 c_vtr.clin_when = latest_results.max_clin_when
2082 """
2083 else:
2084 # return most recent results in panel regardless of whether
2085 # distinct test types in this panel are grouped under the
2086 # same meta test type
2087 cmd = """
2088 SELECT c_vtr.*
2089 FROM (
2090 -- max(clin_when) per test_type-in-panel for patient
2091 SELECT
2092 pk_test_type,
2093 MAX(clin_when) AS max_clin_when
2094 FROM clin.v_test_results
2095 WHERE
2096 pk_patient = %(pat)s
2097 AND
2098 pk_test_type IN (
2099 (SELECT c_vtt4tp.pk_test_type FROM clin.v_test_types4test_panel c_vtt4tp WHERE c_vtt4tp.pk_test_panel = %(pk_pnl)s)
2100 )
2101 GROUP BY pk_test_type
2102 ) AS latest_results
2103 -- this INNER join makes certain we do not expand
2104 -- the row selection beyond the patient's rows
2105 -- which we constrained to inside the SELECT
2106 -- producing "latest_results"
2107 INNER JOIN clin.v_test_results c_vtr ON
2108 c_vtr.pk_test_type = latest_results.pk_test_type
2109 AND
2110 c_vtr.clin_when = latest_results.max_clin_when
2111 """
2112 cmd += order_by
2113 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2114 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2115
2116 #------------------------------------------------------------
2117 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
2118
2119 if None not in [test_type, loinc]:
2120 raise ValueError('either <test_type> or <loinc> must be None')
2121
2122 args = {
2123 'pat': patient,
2124 'ttyp': test_type,
2125 'loinc': loinc,
2126 'ts': timestamp,
2127 'intv': tolerance_interval
2128 }
2129
2130 where_parts = ['pk_patient = %(pat)s']
2131 if test_type is not None:
2132 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
2133 elif loinc is not None:
2134 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2135 args['loinc'] = tuple(loinc)
2136
2137 if tolerance_interval is None:
2138 where_parts.append('clin_when = %(ts)s')
2139 else:
2140 where_parts.append('clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)')
2141
2142 cmd = """
2143 SELECT * FROM clin.v_test_results
2144 WHERE
2145 %s
2146 ORDER BY
2147 abs(extract(epoch from age(clin_when, %%(ts)s)))
2148 LIMIT 1""" % ' AND '.join(where_parts)
2149
2150 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2151 if len(rows) == 0:
2152 return None
2153
2154 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2155
2156 #------------------------------------------------------------
2158
2159 args = {
2160 'pat': patient,
2161 'ts': timestamp
2162 }
2163
2164 where_parts = [
2165 'pk_patient = %(pat)s',
2166 "date_trunc('day'::text, clin_when) = date_trunc('day'::text, %(ts)s)"
2167 ]
2168
2169 cmd = """
2170 SELECT * FROM clin.v_test_results
2171 WHERE
2172 %s
2173 ORDER BY
2174 val_grouping,
2175 abbrev_tt,
2176 clin_when DESC
2177 """ % ' AND '.join(where_parts)
2178 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2179 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2180
2181 #------------------------------------------------------------
2183 args = {'pk_issue': pk_health_issue}
2184 where_parts = ['pk_health_issue = %(pk_issue)s']
2185 cmd = """
2186 SELECT * FROM clin.v_test_results
2187 WHERE %s
2188 ORDER BY
2189 val_grouping,
2190 abbrev_tt,
2191 clin_when DESC
2192 """ % ' AND '.join(where_parts)
2193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2194 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2195
2196 #------------------------------------------------------------
2198 args = {'pk_epi': pk_episode}
2199 where_parts = ['pk_episode = %(pk_epi)s']
2200 cmd = """
2201 SELECT * FROM clin.v_test_results
2202 WHERE %s
2203 ORDER BY
2204 val_grouping,
2205 abbrev_tt,
2206 clin_when DESC
2207 """ % ' AND '.join(where_parts)
2208 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2209 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2210
2211 #------------------------------------------------------------
2212 -def get_most_recent_results_by_loinc(loinc=None, no_of_results=1, patient=None, consider_meta_type=False, max_age=None):
2213 # <loinc> must be a list or tuple or set, NOT a single string
2214 # <max_age> must be a string holding a PG interval or else a pydt interval
2215
2216 if no_of_results < 1:
2217 raise ValueError('<no_of_results> must be > 0')
2218
2219 if not consider_meta_type:
2220 return get_most_recent_results (
2221 loinc = loinc,
2222 no_of_results = no_of_results,
2223 patient = patient
2224 )
2225
2226 args = {'pat': patient, 'loinc': tuple(loinc)}
2227 if max_age is None:
2228 max_age_cond = ''
2229 else:
2230 max_age_cond = 'AND clin_when > (now() - %(max_age)s::interval)'
2231 args['max_age'] = max_age
2232
2233 if consider_meta_type:
2234 rank_order = '_rank ASC'
2235 else:
2236 rank_order = '_rank DESC'
2237
2238 cmd = """
2239 SELECT DISTINCT ON (pk_test_type) * FROM (
2240 ( -- get results for meta type loinc
2241 SELECT *, 1 AS _rank
2242 FROM clin.v_test_results
2243 WHERE
2244 pk_patient = %%(pat)s
2245 AND
2246 loinc_meta IN %%(loinc)s
2247 %s
2248 -- no use weeding out duplicates by UNION-only, because _rank will make them unique anyway
2249 ) UNION ALL (
2250 -- get results for direct loinc
2251 SELECT *, 2 AS _rank
2252 FROM clin.v_test_results
2253 WHERE
2254 pk_patient = %%(pat)s
2255 AND
2256 loinc_tt IN %%(loinc)s
2257 %s
2258 )
2259 ORDER BY
2260 -- all of them by most-recent
2261 clin_when DESC,
2262 -- then by rank-of meta vs direct
2263 %s
2264 ) AS ordered_results
2265 -- then return only what's needed
2266 LIMIT %s""" % (
2267 max_age_cond,
2268 max_age_cond,
2269 rank_order,
2270 no_of_results
2271 )
2272 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2273 if no_of_results == 1:
2274 if len(rows) == 0:
2275 return None
2276 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2277 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2278
2279 #------------------------------------------------------------
2281 # <loinc> must be a list or tuple or set, NOT a single string
2282
2283 if None not in [test_type, loinc]:
2284 raise ValueError('either <test_type> or <loinc> must be None')
2285
2286 if no_of_results < 1:
2287 raise ValueError('<no_of_results> must be > 0')
2288
2289 args = {
2290 'pat': patient,
2291 'ttyp': test_type,
2292 'loinc': loinc
2293 }
2294 where_parts = ['pk_patient = %(pat)s']
2295 if test_type is not None:
2296 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
2297 elif loinc is not None:
2298 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2299 args['loinc'] = tuple(loinc)
2300 cmd = """
2301 SELECT * FROM clin.v_test_results
2302 WHERE
2303 %s
2304 ORDER BY clin_when DESC
2305 LIMIT %s""" % (
2306 ' AND '.join(where_parts),
2307 no_of_results
2308 )
2309 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2310 if no_of_results == 1:
2311 if len(rows) == 0:
2312 return None
2313 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2314 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2315
2316 #------------------------------------------------------------
2318
2319 if None not in [test_type, loinc]:
2320 raise ValueError('either <test_type> or <loinc> must be None')
2321
2322 args = {
2323 'pat': patient,
2324 'ttyp': test_type,
2325 'loinc': loinc
2326 }
2327
2328 where_parts = ['pk_patient = %(pat)s']
2329 if test_type is not None:
2330 where_parts.append('pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
2331 elif loinc is not None:
2332 where_parts.append('((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
2333 args['loinc'] = tuple(loinc)
2334
2335 cmd = """
2336 SELECT * FROM clin.v_test_results
2337 WHERE
2338 %s
2339 ORDER BY clin_when
2340 LIMIT 1""" % ' AND '.join(where_parts)
2341 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2342 if len(rows) == 0:
2343 return None
2344
2345 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
2346
2347 #------------------------------------------------------------
2349 try:
2350 pk = int(result)
2351 except (TypeError, AttributeError):
2352 pk = result['pk_test_result']
2353
2354 cmd = 'DELETE FROM clin.test_result WHERE pk = %(pk)s'
2355 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
2356
2357 #------------------------------------------------------------
2358 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None, link_obj=None):
2359
2360 cmd1 = """
2361 INSERT INTO clin.test_result (
2362 fk_encounter,
2363 fk_episode,
2364 fk_type,
2365 fk_intended_reviewer,
2366 val_num,
2367 val_alpha,
2368 val_unit
2369 ) VALUES (
2370 %(enc)s,
2371 %(epi)s,
2372 %(type)s,
2373 %(rev)s,
2374 %(v_num)s,
2375 %(v_alpha)s,
2376 %(unit)s
2377 )
2378 """
2379 cmd2 = "SELECT * from clin.v_test_results WHERE pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"
2380 args = {
2381 'enc': encounter,
2382 'epi': episode,
2383 'type': type,
2384 'rev': intended_reviewer,
2385 'v_num': val_num,
2386 'v_alpha': val_alpha,
2387 'unit': unit
2388 }
2389 rows, idx = gmPG2.run_rw_queries (
2390 link_obj = link_obj,
2391 queries = [
2392 {'cmd': cmd1, 'args': args},
2393 {'cmd': cmd2}
2394 ],
2395 return_data = True,
2396 get_col_idx = True
2397 )
2398 tr = cTestResult(row = {
2399 'pk_field': 'pk_test_result',
2400 'idx': idx,
2401 'data': rows[0]
2402 })
2403 return tr
2404
2405 #------------------------------------------------------------
2407
2408 _log.debug('formatting test results into [%s]', output_format)
2409
2410 if output_format == 'latex':
2411 return __format_test_results_latex(results = results)
2412
2413 msg = _('unknown test results output format [%s]') % output_format
2414 _log.error(msg)
2415 return msg
2416
2417 #------------------------------------------------------------
2419
2420 if len(results) == 0:
2421 return '\\begin{minipage}{%s} \\end{minipage}' % width
2422
2423 lines = []
2424 for t in results:
2425
2426 tmp = ''
2427
2428 if show_time:
2429 tmp += '{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
2430
2431 tmp += '%.8s' % t['unified_val']
2432
2433 lines.append(tmp)
2434 tmp = ''
2435
2436 if show_range:
2437 has_range = (
2438 t['unified_target_range'] is not None
2439 or
2440 t['unified_target_min'] is not None
2441 or
2442 t['unified_target_max'] is not None
2443 )
2444 if has_range:
2445 if t['unified_target_range'] is not None:
2446 tmp += '{\\tiny %s}' % t['unified_target_range']
2447 else:
2448 tmp += '{\\tiny %s}' % (
2449 gmTools.coalesce(t['unified_target_min'], '- ', '%s - '),
2450 gmTools.coalesce(t['unified_target_max'], '', '%s')
2451 )
2452 lines.append(tmp)
2453
2454 return '\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, ' \\\\ '.join(lines))
2455
2456 #------------------------------------------------------------
2458
2459 if len(results) == 0:
2460 return ''
2461
2462 lines = []
2463 for t in results:
2464
2465 tmp = ''
2466
2467 if show_time:
2468 tmp += '\\tiny %s ' % t['clin_when'].strftime('%H:%M')
2469
2470 tmp += '\\normalsize %.8s' % t['unified_val']
2471
2472 lines.append(tmp)
2473 tmp = '\\tiny %s' % gmTools.coalesce(t['val_unit'], '', '%s ')
2474
2475 if not show_range:
2476 lines.append(tmp)
2477 continue
2478
2479 has_range = (
2480 t['unified_target_range'] is not None
2481 or
2482 t['unified_target_min'] is not None
2483 or
2484 t['unified_target_max'] is not None
2485 )
2486
2487 if not has_range:
2488 lines.append(tmp)
2489 continue
2490
2491 if t['unified_target_range'] is not None:
2492 tmp += '[%s]' % t['unified_target_range']
2493 else:
2494 tmp += '[%s%s]' % (
2495 gmTools.coalesce(t['unified_target_min'], '--', '%s--'),
2496 gmTools.coalesce(t['unified_target_max'], '', '%s')
2497 )
2498 lines.append(tmp)
2499
2500 return ' \\\\ '.join(lines)
2501
2502 #------------------------------------------------------------
2504
2505 if len(results) == 0:
2506 return '\\noindent %s' % _('No test results to format.')
2507
2508 # discover the columns and rows
2509 dates = {}
2510 tests = {}
2511 grid = {}
2512 for result in results:
2513 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name'])
2514 row_label = result['unified_abbrev']
2515 tests[row_label] = None
2516 col_label = '{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d')
2517 dates[col_label] = None
2518 try:
2519 grid[row_label]
2520 except KeyError:
2521 grid[row_label] = {}
2522 try:
2523 grid[row_label][col_label].append(result)
2524 except KeyError:
2525 grid[row_label][col_label] = [result]
2526
2527 col_labels = sorted(dates.keys(), reverse = True)
2528 del dates
2529 row_labels = sorted(tests.keys())
2530 del tests
2531
2532 col_def = len(col_labels) * '>{\\raggedleft}p{1.7cm}|'
2533
2534 # format them
2535 tex = """\\noindent %s
2536
2537 \\noindent \\begin{tabular}{|l|%s}
2538 \\hline
2539 & %s \\tabularnewline
2540 \\hline
2541
2542 %%s \\tabularnewline
2543
2544 \\hline
2545
2546 \\end{tabular}""" % (
2547 _('Test results'),
2548 col_def,
2549 ' & '.join(col_labels)
2550 )
2551
2552 rows = []
2553
2554 # loop over rows
2555 for rl in row_labels:
2556 cells = [rl]
2557 # loop over cols per row
2558 for cl in col_labels:
2559 try:
2560 # get tests for this (row/col) position
2561 tests = grid[rl][cl]
2562 except KeyError:
2563 # none there, so insert empty cell
2564 cells.append(' ')
2565 continue
2566
2567 cells.append (
2568 __tests2latex_cell (
2569 results = tests,
2570 show_time = (len(tests) > 1),
2571 show_range = True
2572 )
2573 )
2574
2575 rows.append(' & '.join(cells))
2576
2577 return tex % ' \\tabularnewline\n \\hline\n'.join(rows)
2578
2579 #============================================================
2581
2582 if filename is None:
2583 filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.dat')
2584
2585 # sort results into series by test type
2586 series = {}
2587 for r in results:
2588 try:
2589 series[r['unified_name']].append(r)
2590 except KeyError:
2591 series[r['unified_name']] = [r]
2592
2593 gp_data = io.open(filename, mode = 'wt', encoding = 'utf8')
2594
2595 gp_data.write('# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
2596 gp_data.write('# -------------------------------------------------------------\n')
2597 gp_data.write('# first line of index: test type abbreviation & name\n')
2598 gp_data.write('#\n')
2599 gp_data.write('# clin_when at full precision\n')
2600 gp_data.write('# value\n')
2601 gp_data.write('# unit\n')
2602 gp_data.write('# unified (target or normal) range: lower bound\n')
2603 gp_data.write('# unified (target or normal) range: upper bound\n')
2604 gp_data.write('# normal range: lower bound\n')
2605 gp_data.write('# normal range: upper bound\n')
2606 gp_data.write('# target range: lower bound\n')
2607 gp_data.write('# target range: upper bound\n')
2608 gp_data.write('# clin_when formatted into string as x-axis tic label\n')
2609 gp_data.write('# -------------------------------------------------------------\n')
2610
2611 for test_type in series.keys():
2612 if len(series[test_type]) == 0:
2613 continue
2614
2615 r = series[test_type][0]
2616 title = '%s (%s)' % (
2617 r['unified_abbrev'],
2618 r['unified_name']
2619 )
2620 gp_data.write('\n\n"%s" "%s"\n' % (title, title))
2621
2622 prev_date = None
2623 prev_year = None
2624 for r in series[test_type]:
2625 curr_date = gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d', 'utf8', gmDateTime.acc_days)
2626 if curr_date == prev_date:
2627 gp_data.write('\n# %s\n' % _('blank line inserted to allow for discontinued line drawing of same-day values'))
2628 if show_year:
2629 if r['clin_when'].year == prev_year:
2630 when_template = '%b %d %H:%M'
2631 else:
2632 when_template = '%b %d %H:%M (%Y)'
2633 prev_year = r['clin_when'].year
2634 else:
2635 when_template = '%b %d'
2636 val = r['val_num']
2637 if val is None:
2638 val = r.estimate_numeric_value_from_alpha
2639 if val is None:
2640 continue # skip distinctly non-numericable values
2641 gp_data.write ('%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
2642 #r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
2643 gmDateTime.pydt_strftime(r['clin_when'], '%Y-%m-%d_%H:%M', 'utf8', gmDateTime.acc_minutes),
2644 val,
2645 gmTools.coalesce(r['val_unit'], '"<?>"'),
2646 gmTools.coalesce(r['unified_target_min'], '"<?>"'),
2647 gmTools.coalesce(r['unified_target_max'], '"<?>"'),
2648 gmTools.coalesce(r['val_normal_min'], '"<?>"'),
2649 gmTools.coalesce(r['val_normal_max'], '"<?>"'),
2650 gmTools.coalesce(r['val_target_min'], '"<?>"'),
2651 gmTools.coalesce(r['val_target_max'], '"<?>"'),
2652 gmDateTime.pydt_strftime (
2653 r['clin_when'],
2654 format = when_template,
2655 accuracy = gmDateTime.acc_minutes
2656 )
2657 ))
2658 prev_date = curr_date
2659
2660 gp_data.close()
2661
2662 return filename
2663
2664 #============================================================
2666 """Represents one lab result."""
2667
2668 _cmd_fetch_payload = """
2669 select *, xmin_test_result from v_results4lab_req
2670 where pk_result=%s"""
2671 _cmds_lock_rows_for_update = [
2672 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
2673 ]
2674 _cmds_store_payload = [
2675 """update test_result set
2676 clin_when = %(val_when)s,
2677 narrative = %(progress_note_result)s,
2678 fk_type = %(pk_test_type)s,
2679 val_num = %(val_num)s::numeric,
2680 val_alpha = %(val_alpha)s,
2681 val_unit = %(val_unit)s,
2682 val_normal_min = %(val_normal_min)s,
2683 val_normal_max = %(val_normal_max)s,
2684 val_normal_range = %(val_normal_range)s,
2685 val_target_min = %(val_target_min)s,
2686 val_target_max = %(val_target_max)s,
2687 val_target_range = %(val_target_range)s,
2688 abnormality_indicator = %(abnormal)s,
2689 norm_ref_group = %(ref_group)s,
2690 note_provider = %(note_provider)s,
2691 material = %(material)s,
2692 material_detail = %(material_detail)s
2693 where pk = %(pk_result)s""",
2694 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
2695 ]
2696
2697 _updatable_fields = [
2698 'val_when',
2699 'progress_note_result',
2700 'val_num',
2701 'val_alpha',
2702 'val_unit',
2703 'val_normal_min',
2704 'val_normal_max',
2705 'val_normal_range',
2706 'val_target_min',
2707 'val_target_max',
2708 'val_target_range',
2709 'abnormal',
2710 'ref_group',
2711 'note_provider',
2712 'material',
2713 'material_detail'
2714 ]
2715 #--------------------------------------------------------
2717 """Instantiate.
2718
2719 aPK_obj as dict:
2720 - patient_id
2721 - when_field (see view definition)
2722 - when
2723 - test_type
2724 - val_num
2725 - val_alpha
2726 - unit
2727 """
2728 # instantiate from row data ?
2729 if aPK_obj is None:
2730 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2731 return
2732 pk = aPK_obj
2733 # find PK from row data ?
2734 if type(aPK_obj) == dict:
2735 # sanity checks
2736 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
2737 raise gmExceptions.ConstructorError('parameter error: %s' % aPK_obj)
2738 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
2739 raise gmExceptions.ConstructorError('parameter error: val_num and val_alpha cannot both be None')
2740 # get PK
2741 where_snippets = [
2742 'pk_patient=%(patient_id)s',
2743 'pk_test_type=%(test_type)s',
2744 '%s=%%(when)s' % aPK_obj['when_field'],
2745 'val_unit=%(unit)s'
2746 ]
2747 if aPK_obj['val_num'] is not None:
2748 where_snippets.append('val_num=%(val_num)s::numeric')
2749 if aPK_obj['val_alpha'] is not None:
2750 where_snippets.append('val_alpha=%(val_alpha)s')
2751
2752 where_clause = ' and '.join(where_snippets)
2753 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
2754 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2755 if data is None:
2756 raise gmExceptions.ConstructorError('error getting lab result for: %s' % aPK_obj)
2757 if len(data) == 0:
2758 raise gmExceptions.NoSuchClinItemError('no lab result for: %s' % aPK_obj)
2759 pk = data[0][0]
2760 # instantiate class
2761 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2762 #--------------------------------------------------------
2764 cmd = """
2765 select
2766 %s,
2767 vbp.title,
2768 vbp.firstnames,
2769 vbp.lastnames,
2770 vbp.dob
2771 from v_active_persons vbp
2772 where vbp.pk_identity = %%s""" % self._payload[self._idx['pk_patient']]
2773 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
2774 return pat[0]
2775
2776 #============================================================
2778 """Represents one lab request."""
2779
2780 _cmd_fetch_payload = """
2781 select *, xmin_lab_request from v_lab_requests
2782 where pk_request=%s"""
2783 _cmds_lock_rows_for_update = [
2784 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
2785 ]
2786 _cmds_store_payload = [
2787 """update lab_request set
2788 request_id=%(request_id)s,
2789 lab_request_id=%(lab_request_id)s,
2790 clin_when=%(sampled_when)s,
2791 lab_rxd_when=%(lab_rxd_when)s,
2792 results_reported_when=%(results_reported_when)s,
2793 request_status=%(request_status)s,
2794 is_pending=%(is_pending)s::bool,
2795 narrative=%(progress_note)s
2796 where pk=%(pk_request)s""",
2797 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
2798 ]
2799 _updatable_fields = [
2800 'request_id',
2801 'lab_request_id',
2802 'sampled_when',
2803 'lab_rxd_when',
2804 'results_reported_when',
2805 'request_status',
2806 'is_pending',
2807 'progress_note'
2808 ]
2809 #--------------------------------------------------------
2811 """Instantiate lab request.
2812
2813 The aPK_obj can be either a dict with the keys "req_id"
2814 and "lab" or a simple primary key.
2815 """
2816 # instantiate from row data ?
2817 if aPK_obj is None:
2818 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
2819 return
2820 pk = aPK_obj
2821 # instantiate from "req_id" and "lab" ?
2822 if type(aPK_obj) == dict:
2823 # sanity check
2824 try:
2825 aPK_obj['req_id']
2826 aPK_obj['lab']
2827 except:
2828 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
2829 raise gmExceptions.ConstructorError('[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj))
2830 # generate query
2831 where_snippets = []
2832 vals = {}
2833 where_snippets.append('request_id=%(req_id)s')
2834 if type(aPK_obj['lab']) == int:
2835 where_snippets.append('pk_test_org=%(lab)s')
2836 else:
2837 where_snippets.append('lab_name=%(lab)s')
2838 where_clause = ' and '.join(where_snippets)
2839 cmd = "select pk_request from v_lab_requests where %s" % where_clause
2840 # get pk
2841 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
2842 if data is None:
2843 raise gmExceptions.ConstructorError('[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj))
2844 if len(data) == 0:
2845 raise gmExceptions.NoSuchClinItemError('[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj))
2846 pk = data[0][0]
2847 # instantiate class
2848 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2849 #--------------------------------------------------------
2851 cmd = """
2852 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
2853 from v_pat_items vpi, v_active_persons vbp
2854 where
2855 vpi.pk_item=%s
2856 and
2857 vbp.pk_identity=vpi.pk_patient"""
2858 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
2859 if pat is None:
2860 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
2861 return None
2862 if len(pat) == 0:
2863 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
2864 return None
2865 return pat[0]
2866
2867 #============================================================
2868 # convenience functions
2869 #------------------------------------------------------------
2870 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
2871 """Create or get lab request.
2872
2873 returns tuple (status, value):
2874 (True, lab request instance)
2875 (False, error message)
2876 (None, housekeeping_todo primary key)
2877 """
2878 req = None
2879 aPK_obj = {
2880 'lab': lab,
2881 'req_id': req_id
2882 }
2883 try:
2884 req = cLabRequest (aPK_obj)
2885 except gmExceptions.NoSuchClinItemError as msg:
2886 _log.info('%s: will try to create lab request' % str(msg))
2887 except gmExceptions.ConstructorError as msg:
2888 _log.exception(str(msg), sys.exc_info(), verbose=0)
2889 return (False, msg)
2890 # found
2891 if req is not None:
2892 db_pat = req.get_patient()
2893 if db_pat is None:
2894 _log.error('cannot cross-check patient on lab request')
2895 return (None, '')
2896 # yes but ambigous
2897 if pat_id != db_pat[0]:
2898 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
2899 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
2900 to = 'user'
2901 prob = _('The lab request already exists but belongs to a different patient.')
2902 sol = _('Verify which patient this lab request really belongs to.')
2903 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
2904 cat = 'lab'
2905 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
2906 return (None, data)
2907 return (True, req)
2908 # not found
2909 queries = []
2910 if type(lab) is int:
2911 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
2912 else:
2913 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)"
2914 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
2915 cmd = "select currval('lab_request_pk_seq')"
2916 queries.append((cmd, []))
2917 # insert new
2918 result, err = gmPG.run_commit('historica', queries, True)
2919 if result is None:
2920 return (False, err)
2921 try:
2922 req = cLabRequest(aPK_obj=result[0][0])
2923 except gmExceptions.ConstructorError as msg:
2924 _log.exception(str(msg), sys.exc_info(), verbose=0)
2925 return (False, msg)
2926 return (True, req)
2927 #------------------------------------------------------------
2928 -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):
2929 tres = None
2930 data = {
2931 'patient_id': patient_id,
2932 'when_field': when_field,
2933 'when': when,
2934 'test_type': test_type,
2935 'val_num': val_num,
2936 'val_alpha': val_alpha,
2937 'unit': unit
2938 }
2939 try:
2940 tres = cLabResult(aPK_obj=data)
2941 # exists already, so fail
2942 _log.error('will not overwrite existing test result')
2943 _log.debug(str(tres))
2944 return (None, tres)
2945 except gmExceptions.NoSuchClinItemError:
2946 _log.debug('test result not found - as expected, will create it')
2947 except gmExceptions.ConstructorError as msg:
2948 _log.exception(str(msg), sys.exc_info(), verbose=0)
2949 return (False, msg)
2950 if request is None:
2951 return (False, _('need lab request when inserting lab result'))
2952 # not found
2953 if encounter_id is None:
2954 encounter_id = request['pk_encounter']
2955 queries = []
2956 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
2957 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
2958 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
2959 queries.append((cmd, [request['pk_request']]))
2960 cmd = "select currval('test_result_pk_seq')"
2961 queries.append((cmd, []))
2962 # insert new
2963 result, err = gmPG.run_commit('historica', queries, True)
2964 if result is None:
2965 return (False, err)
2966 try:
2967 tres = cLabResult(aPK_obj=result[0][0])
2968 except gmExceptions.ConstructorError as msg:
2969 _log.exception(str(msg), sys.exc_info(), verbose=0)
2970 return (False, msg)
2971 return (True, tres)
2972 #------------------------------------------------------------
2974 # sanity check
2975 if limit < 1:
2976 limit = 1
2977 # retrieve one more row than needed so we know there's more available ;-)
2978 lim = limit + 1
2979 cmd = """
2980 select pk_result
2981 from v_results4lab_req
2982 where reviewed is false
2983 order by pk_patient
2984 limit %s""" % lim
2985 rows = gmPG.run_ro_query('historica', cmd)
2986 if rows is None:
2987 _log.error('error retrieving unreviewed lab results')
2988 return (None, _('error retrieving unreviewed lab results'))
2989 if len(rows) == 0:
2990 return (False, [])
2991 # more than LIMIT rows ?
2992 if len(rows) == lim:
2993 more_avail = True
2994 # but deliver only LIMIT rows so that our assumption holds true...
2995 del rows[limit]
2996 else:
2997 more_avail = False
2998 results = []
2999 for row in rows:
3000 try:
3001 results.append(cLabResult(aPK_obj=row[0]))
3002 except gmExceptions.ConstructorError:
3003 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
3004 return (more_avail, results)
3005
3006 #------------------------------------------------------------
3008 lim = limit + 1
3009 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
3010 rows = gmPG.run_ro_query('historica', cmd)
3011 if rows is None:
3012 _log.error('error retrieving pending lab requests')
3013 return (None, None)
3014 if len(rows) == 0:
3015 return (False, [])
3016 results = []
3017 # more than LIMIT rows ?
3018 if len(rows) == lim:
3019 too_many = True
3020 # but deliver only LIMIT rows so that our assumption holds true...
3021 del rows[limit]
3022 else:
3023 too_many = False
3024 requests = []
3025 for row in rows:
3026 try:
3027 requests.append(cLabRequest(aPK_obj=row[0]))
3028 except gmExceptions.ConstructorError:
3029 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
3030 return (too_many, requests)
3031
3032 #------------------------------------------------------------
3034 """Get logically next request ID for given lab.
3035
3036 - incrementor_func:
3037 - if not supplied the next ID is guessed
3038 - if supplied it is applied to the most recently used ID
3039 """
3040 if type(lab) == int:
3041 lab_snippet = 'vlr.fk_test_org=%s'
3042 else:
3043 lab_snippet = 'vlr.lab_name=%s'
3044 lab = str(lab)
3045 cmd = """
3046 select request_id
3047 from lab_request lr0
3048 where lr0.clin_when = (
3049 select max(vlr.sampled_when)
3050 from v_lab_requests vlr
3051 where %s
3052 )""" % lab_snippet
3053 rows = gmPG.run_ro_query('historica', cmd, None, lab)
3054 if rows is None:
3055 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
3056 return ''
3057 if len(rows) == 0:
3058 return ''
3059 most_recent = rows[0][0]
3060 # apply supplied incrementor
3061 if incrementor_func is not None:
3062 try:
3063 next = incrementor_func(most_recent)
3064 except TypeError:
3065 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
3066 return most_recent
3067 return next
3068 # try to be smart ourselves
3069 for pos in range(len(most_recent)):
3070 header = most_recent[:pos]
3071 trailer = most_recent[pos:]
3072 try:
3073 return '%s%s' % (header, str(int(trailer) + 1))
3074 except ValueError:
3075 header = most_recent[:-1]
3076 trailer = most_recent[-1:]
3077 return '%s%s' % (header, chr(ord(trailer) + 1))
3078
3079 #============================================================
3081 """Calculate BMI.
3082
3083 mass: kg
3084 height: cm
3085 age: not yet used
3086
3087 returns:
3088 (True/False, data)
3089 True: data = (bmi, lower_normal, upper_normal)
3090 False: data = error message
3091 """
3092 converted, mass = gmTools.input2decimal(mass)
3093 if not converted:
3094 return False, 'mass: cannot convert <%s> to Decimal' % mass
3095
3096 converted, height = gmTools.input2decimal(height)
3097 if not converted:
3098 return False, 'height: cannot convert <%s> to Decimal' % height
3099
3100 approx_surface = (height / decimal.Decimal(100))**2
3101 bmi = mass / approx_surface
3102
3103 print(mass, height, '->', approx_surface, '->', bmi)
3104
3105 lower_normal_mass = 20.0 * approx_surface
3106 upper_normal_mass = 25.0 * approx_surface
3107
3108 return True, (bmi, lower_normal_mass, upper_normal_mass)
3109
3110 #============================================================
3111 # main - unit testing
3112 #------------------------------------------------------------
3113 if __name__ == '__main__':
3114
3115 if len(sys.argv) < 2:
3116 sys.exit()
3117
3118 if sys.argv[1] != 'test':
3119 sys.exit()
3120
3121 import time
3122
3123 gmI18N.activate_locale()
3124 gmI18N.install_domain()
3125
3126 #------------------------------------------
3128 tr = create_test_result (
3129 encounter = 1,
3130 episode = 1,
3131 type = 1,
3132 intended_reviewer = 1,
3133 val_num = '12',
3134 val_alpha=None,
3135 unit = 'mg/dl'
3136 )
3137 print(tr)
3138 return tr
3139 #------------------------------------------
3143 #------------------------------------------
3145 r = cTestResult(aPK_obj=6)
3146 #print r
3147 #print r.reference_ranges
3148 #print r.formatted_range
3149 #print r.temporally_closest_normal_range
3150 print(r.estimate_numeric_value_from_alpha)
3151 #------------------------------------------
3153 print("test_result()")
3154 # lab_result = cLabResult(aPK_obj=4)
3155 data = {
3156 'patient_id': 12,
3157 'when_field': 'val_when',
3158 'when': '2000-09-17 18:23:00+02',
3159 'test_type': 9,
3160 'val_num': 17.3,
3161 'val_alpha': None,
3162 'unit': 'mg/l'
3163 }
3164 lab_result = cLabResult(aPK_obj=data)
3165 print(lab_result)
3166 fields = lab_result.get_fields()
3167 for field in fields:
3168 print(field, ':', lab_result[field])
3169 print("updatable:", lab_result.get_updatable_fields())
3170 print(time.time())
3171 print(lab_result.get_patient())
3172 print(time.time())
3173 #------------------------------------------
3175 print("test_request()")
3176 try:
3177 # lab_req = cLabRequest(aPK_obj=1)
3178 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2)
3179 data = {
3180 'req_id': 'EML#SC937-0176-CEC#11',
3181 'lab': 'Enterprise Main Lab'
3182 }
3183 lab_req = cLabRequest(aPK_obj=data)
3184 except gmExceptions.ConstructorError as msg:
3185 print("no such lab request:", msg)
3186 return
3187 print(lab_req)
3188 fields = lab_req.get_fields()
3189 for field in fields:
3190 print(field, ':', lab_req[field])
3191 print("updatable:", lab_req.get_updatable_fields())
3192 print(time.time())
3193 print(lab_req.get_patient())
3194 print(time.time())
3195 #--------------------------------------------------------
3200 #--------------------------------------------------------
3205 #--------------------------------------------------------
3207 print(create_measurement_type (
3208 lab = None,
3209 abbrev = 'tBZ2',
3210 unit = 'mg%',
3211 name = 'BZ (test 2)'
3212 ))
3213 #--------------------------------------------------------
3218 #--------------------------------------------------------
3223 #--------------------------------------------------------
3225 results = [
3226 cTestResult(aPK_obj=1),
3227 cTestResult(aPK_obj=2),
3228 cTestResult(aPK_obj=3)
3229 # cTestResult(aPK_obj=4)
3230 ]
3231 print(format_test_results(results = results))
3232 #--------------------------------------------------------
3234 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
3235 bmi, low, high = data
3236 print("BMI:", bmi)
3237 print("low:", low, "kg")
3238 print("hi :", high, "kg")
3239
3240 #--------------------------------------------------------
3246
3247 #--------------------------------------------------------
3249 tp = cTestPanel(aPK_obj = 1)
3250 #print tp.included_loincs
3251 #tp = cTestPanel(aPK_obj = 3)
3252 print(tp.format())
3253 #most_recent = tp.get_most_recent_results(pk_patient = 12, group_by_meta_type = False)
3254 #most_recent = tp.get_most_recent_results(pk_patient = 138, group_by_meta_type = False)
3255 #print len(most_recent)
3256 most_recent = tp.get_most_recent_results(pk_patient = 12, group_by_meta_type = True)
3257 #most_recent = tp.get_most_recent_results(pk_patient = 138, group_by_meta_type = True)
3258 print('found:', len(most_recent))
3259
3260 for t in most_recent:
3261 print('--------------')
3262 if t['pk_meta_test_type'] is None:
3263 print("standalone")
3264 else:
3265 print("meta")
3266 print(t.format())
3267
3268 #--------------------------------------------------------
3270 most_recent = get_most_recent_results_by_loinc (
3271 #loinc = [u'pseudo LOINC [C-reactive protein (EML)::9] (v21->v22 test panel conversion)'],
3272 loinc = ['8867-4'],
3273 no_of_results = 2,
3274 patient = 12,
3275 consider_meta_type = True
3276 #consider_meta_type = False
3277 )
3278 for t in most_recent:
3279 if t['pk_meta_test_type'] is None:
3280 print("---- standalone ----")
3281 else:
3282 print("---- meta ----")
3283 print(t.format())
3284
3285 #--------------------------------------------------------
3286
3287 #test_result()
3288 #test_create_test_result()
3289 #test_delete_test_result()
3290 #test_create_measurement_type()
3291 #test_lab_result()
3292 #test_request()
3293 #test_create_result()
3294 #test_unreviewed()
3295 #test_pending()
3296 #test_meta_test_type()
3297 #test_test_type()
3298 #test_format_test_results()
3299 #test_calculate_bmi()
3300 test_test_panel()
3301 #test_get_most_recent_results_for_panel()
3302 #test_get_most_recent_results_by_loinc()
3303
3304 #============================================================
3305
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu May 10 01:55:20 2018 | http://epydoc.sourceforge.net |