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