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