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