| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed measurements related business objects."""
2
3 # FIXME: use UCUM from Regenstrief Institute
4 #============================================================
5 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
6 __license__ = "GPL"
7
8
9 import types
10 import sys
11 import logging
12 import codecs
13 import decimal
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18
19 from Gnumed.pycommon import gmDateTime
20 if __name__ == '__main__':
21 from Gnumed.pycommon import gmLog2
22 from Gnumed.pycommon import gmI18N
23 gmDateTime.init()
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmBusinessDBObject
26 from Gnumed.pycommon import gmPG2
27 from Gnumed.pycommon import gmTools
28 from Gnumed.pycommon import gmDispatcher
29 from Gnumed.pycommon import gmHooks
30 from Gnumed.business import gmOrganization
31 from Gnumed.business import gmCoding
32
33
34 _log = logging.getLogger('gm.lab')
35
36 #============================================================
38 """Always relates to the active patient."""
39 gmHooks.run_hook_script(hook = u'after_test_result_modified')
40
41 gmDispatcher.connect(_on_test_result_modified, u'test_result_mod_db')
42
43 #============================================================
45 """Represents one test org/lab."""
46 _cmd_fetch_payload = u"""SELECT * FROM clin.v_test_orgs WHERE pk_test_org = %s"""
47 _cmds_store_payload = [
48 u"""UPDATE clin.test_org SET
49 fk_org_unit = %(pk_org_unit)s,
50 contact = gm.nullify_empty_string(%(test_org_contact)s),
51 comment = gm.nullify_empty_string(%(comment)s)
52 WHERE
53 pk = %(pk_test_org)s
54 AND
55 xmin = %(xmin_test_org)s
56 RETURNING
57 xmin AS xmin_test_org
58 """
59 ]
60 _updatable_fields = [
61 u'pk_org_unit',
62 u'test_org_contact',
63 u'comment'
64 ]
65 #------------------------------------------------------------
67
68 if name is None:
69 name = _('inhouse lab')
70 comment = _('auto-generated')
71
72 # get org unit
73 if pk_org_unit is None:
74 org = gmOrganization.org_exists(organization = name)
75 if org is None:
76 org = gmOrganization.create_org (
77 organization = name,
78 category = u'Laboratory'
79 )
80 org_unit = gmOrganization.create_org_unit (
81 pk_organization = org['pk_org'],
82 unit = name
83 )
84 pk_org_unit = org_unit['pk_org_unit']
85
86 # test org exists ?
87 args = {'pk_unit': pk_org_unit}
88 cmd = u'SELECT pk_test_org FROM clin.v_test_orgs WHERE pk_org_unit = %(pk_unit)s'
89 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
90
91 if len(rows) == 0:
92 cmd = u'INSERT INTO clin.test_org (fk_org_unit) VALUES (%(pk_unit)s) RETURNING pk'
93 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True)
94
95 test_org = cTestOrg(aPK_obj = rows[0][0])
96 if comment is not None:
97 comment = comment.strip()
98 test_org['comment'] = comment
99 test_org.save()
100
101 return test_org
102 #------------------------------------------------------------
104 args = {'pk': test_org}
105 cmd = u"""
106 DELETE FROM clin.test_org
107 WHERE
108 pk = %(pk)s
109 AND
110 NOT EXISTS (SELECT 1 FROM clin.lab_request WHERE fk_test_org = %(pk)s LIMIT 1)
111 AND
112 NOT EXISTS (SELECT 1 FROM clin.test_type WHERE fk_test_org = %(pk)s LIMIT 1)
113 """
114 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
115 #------------------------------------------------------------
117 cmd = u'SELECT * FROM clin.v_test_orgs ORDER BY %s' % order_by
118 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
119 return [ cTestOrg(row = {'pk_field': 'pk_test_org', 'data': r, 'idx': idx}) for r in rows ]
120
121 #============================================================
122 # test panels / profiles
123 #------------------------------------------------------------
124 _SQL_get_test_panels = u"SELECT * FROM clin.v_test_panels WHERE %s"
125
127 """Represents a grouping/listing of tests into a panel."""
128
129 _cmd_fetch_payload = _SQL_get_test_panels % u"pk_test_panel = %s"
130 _cmds_store_payload = [
131 u"""
132 UPDATE clin.test_panel SET
133 description = gm.nullify_empty_string(%(description)s),
134 comment = gm.nullify_empty_string(%(comment)s),
135 fk_test_types = %(pk_test_types)s
136 WHERE
137 pk = %(pk_test_panel)s
138 AND
139 xmin = %(xmin_test_panel)s
140 RETURNING
141 xmin AS xmin_test_panel
142 """
143 ]
144 _updatable_fields = [
145 u'description',
146 u'comment',
147 u'pk_test_types'
148 ]
149 #--------------------------------------------------------
151 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
152 cmd = u"INSERT INTO clin.lnk_code2tst_pnl (fk_item, fk_generic_code) values (%(tp)s, %(code)s)"
153 args = {
154 'tp': self._payload[self._idx['pk_test_panel']],
155 'code': pk_code
156 }
157 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
158 return True
159 #--------------------------------------------------------
161 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
162 cmd = u"DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code = %(code)s"
163 args = {
164 'tp': self._payload[self._idx['pk_test_panel']],
165 'code': pk_code
166 }
167 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
168 return True
169 #--------------------------------------------------------
170 # properties
171 #--------------------------------------------------------
173 if self._payload[self._idx['pk_test_types']] is None:
174 return None
175
176 rows, idx = gmPG2.run_ro_queries (
177 queries = [{
178 'cmd': _SQL_get_test_types % u'pk_test_type IN %(pks)s ORDER BY unified_abbrev',
179 'args': {'pks': tuple(self._payload[self._idx['pk_test_types']])}
180 }],
181 get_col_idx = True
182 )
183 return [ cMeasurementType(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_type'}) for r in rows ]
184
185 test_types = property(_get_test_types, lambda x:x)
186 #--------------------------------------------------------
188 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
189 return []
190
191 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
192 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
194 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
195
197 queries = []
198 # remove all codes
199 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
200 queries.append ({
201 'cmd': u'DELETE FROM clin.lnk_code2tst_pnl WHERE fk_item = %(tp)s AND fk_generic_code IN %(codes)s',
202 'args': {
203 'tp': self._payload[self._idx['pk_test_panel']],
204 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
205 }
206 })
207 # add new codes
208 for pk_code in pk_codes:
209 queries.append ({
210 'cmd': u'INSERT INTO clin.lnk_code2test_panel (fk_item, fk_generic_code) VALUES (%(tp)s, %(pk_code)s)',
211 'args': {
212 'tp': self._payload[self._idx['pk_test_panel']],
213 'pk_code': pk_code
214 }
215 })
216 if len(queries) == 0:
217 return
218 # run it all in one transaction
219 rows, idx = gmPG2.run_rw_queries(queries = queries)
220 return
221
222 generic_codes = property(_get_generic_codes, _set_generic_codes)
223 #--------------------------------------------------------
225 txt = _('Test panel "%s" [#%s]\n') % (
226 self._payload[self._idx['description']],
227 self._payload[self._idx['pk_test_panel']]
228 )
229
230 if self._payload[self._idx['comment']] is not None:
231 txt += u'\n'
232 txt += gmTools.wrap (
233 text = self._payload[self._idx['comment']],
234 width = 50,
235 initial_indent = u' ',
236 subsequent_indent = u' '
237 )
238 txt += u'\n'
239
240 tts = self.test_types
241 if tts is not None:
242 txt += u'\n'
243 txt += _('Included test types:\n')
244 for tt in tts:
245 txt += u' %s: %s\n' % (
246 tt['abbrev'],
247 tt['name']
248 )
249
250 codes = self.generic_codes
251 if len(codes) > 0:
252 txt += u'\n'
253 for c in codes:
254 txt += u'%s %s: %s (%s - %s)\n' % (
255 (u' ' * left_margin),
256 c['code'],
257 c['term'],
258 c['name_short'],
259 c['version']
260 )
261
262 return txt
263 #------------------------------------------------------------
265 if order_by is None:
266 order_by = u'true'
267 else:
268 order_by = u'true ORDER BY %s' % order_by
269
270 cmd = _SQL_get_test_panels % order_by
271 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
272 return [ cTestPanel(row = {'data': r, 'idx': idx, 'pk_field': 'pk_test_panel'}) for r in rows ]
273 #------------------------------------------------------------
275
276 args = {u'desc': description.strip()}
277 cmd = u"""
278 INSERT INTO clin.test_panel (description)
279 VALUES (gm.nullify_empty_string(%(desc)s))
280 RETURNING pk
281 """
282 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False)
283
284 return cTestPanel(aPK_obj = rows[0]['pk'])
285 #------------------------------------------------------------
287 args = {'pk': pk}
288 cmd = u"DELETE FROM clin.test_panel WHERE pk = %(pk)s"
289 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
290 return True
291
292 #============================================================
294 """Represents one meta test type under which actual test types can be aggregated."""
295
296 _cmd_fetch_payload = u"""select * from clin.meta_test_type where pk = %s"""
297
298 _cmds_store_payload = []
299
300 _updatable_fields = []
301 #------------------------------------------------------------
303 cmd = u'delete from clin.meta_test_type where pk = %(pk)s'
304 args = {'pk': meta_type}
305 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
306 #------------------------------------------------------------
308 cmd = u'select * from clin.meta_test_type'
309 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
310 return [ cMetaTestType(row = {'pk_field': 'pk', 'data': r, 'idx': idx}) for r in rows ]
311
312 #============================================================
313 _SQL_get_test_types = u"SELECT * FROM clin.v_test_types WHERE %s"
314
316 """Represents one test result type."""
317
318 _cmd_fetch_payload = _SQL_get_test_types % u"pk_test_type = %s"
319
320 _cmds_store_payload = [
321 u"""UPDATE clin.test_type SET
322 abbrev = gm.nullify_empty_string(%(abbrev)s),
323 name = gm.nullify_empty_string(%(name)s),
324 loinc = gm.nullify_empty_string(%(loinc)s),
325 comment = gm.nullify_empty_string(%(comment_type)s),
326 conversion_unit = gm.nullify_empty_string(%(conversion_unit)s),
327 fk_test_org = %(pk_test_org)s,
328 fk_meta_test_type = %(pk_meta_test_type)s
329 WHERE
330 pk = %(pk_test_type)s
331 AND
332 xmin = %(xmin_test_type)s
333 RETURNING
334 xmin AS xmin_test_type"""
335 ]
336
337 _updatable_fields = [
338 'abbrev',
339 'name',
340 'loinc',
341 'comment_type',
342 'conversion_unit',
343 'pk_test_org',
344 'pk_meta_test_type'
345 ]
346 #--------------------------------------------------------
347 # def __setitem__(self, attribute, value):
348 #
349 # # find fk_test_org from name
350 # if (attribute == 'fk_test_org') and (value is not None):
351 # try:
352 # int(value)
353 # except:
354 # cmd = u"select pk from clin.test_org where internal _name = %(val)s"
355 # rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'val': value}}])
356 # if len(rows) == 0:
357 # raise ValueError('[%s]: no test org for [%s], cannot set <%s>' % (self.__class__.__name__, value, attribute))
358 # value = rows[0][0]
359 #
360 # gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
361 #--------------------------------------------------------
363 cmd = u'SELECT EXISTS(SELECT 1 FROM clin.test_result WHERE fk_type = %(pk_type)s)'
364 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
365 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
366 return rows[0][0]
367
368 in_use = property(_get_in_use, lambda x:x)
369 #--------------------------------------------------------
371 results = get_most_recent_results (
372 test_type = self._payload[self._idx['pk_test_type']],
373 loinc = None,
374 no_of_results = no_of_results,
375 patient = patient
376 )
377 if results is None:
378 if self._payload[self._idx['loinc']] is not None:
379 results = get_most_recent_results (
380 test_type = None,
381 loinc = self._payload[self._idx['loinc']],
382 no_of_results = no_of_results,
383 patient = patient
384 )
385 return results
386 #--------------------------------------------------------
388 if self._payload[self._idx['pk_test_panels']] is None:
389 return None
390
391 return [ cTestPanel(aPK_obj = pk) for pk in self._payload[self._idx['pk_test_panels']] ]
392
393 test_panels = property(_get_test_panels, lambda x:x)
394 #--------------------------------------------------------
396 tt = u''
397 tt += _('Test type "%s" (%s) [#%s]\n') % (
398 self._payload[self._idx['name']],
399 self._payload[self._idx['abbrev']],
400 self._payload[self._idx['pk_test_type']]
401 )
402 tt += u'\n'
403 tt += gmTools.coalesce(self._payload[self._idx['loinc']], u'', u' LOINC: %s\n')
404 tt += gmTools.coalesce(self._payload[self._idx['conversion_unit']], u'', _(' Conversion unit: %s\n'))
405 tt += gmTools.coalesce(self._payload[self._idx['comment_type']], u'', _(' Comment: %s\n'))
406
407 tt += u'\n'
408 tt += _('Lab details:\n')
409 tt += _(' Name: %s\n') % self._payload[self._idx['name_org']]
410 tt += gmTools.coalesce(self._payload[self._idx['contact_org']], u'', _(' Contact: %s\n'))
411 tt += gmTools.coalesce(self._payload[self._idx['comment_org']], u'', _(' Comment: %s\n'))
412
413 if self._payload[self._idx['is_fake_meta_type']] is False:
414 tt += u'\n'
415 tt += _('Aggregated under meta type:\n')
416 tt += _(' Name: %s - %s [#%s]\n') % (
417 self._payload[self._idx['abbrev_meta']],
418 self._payload[self._idx['name_meta']],
419 self._payload[self._idx['pk_meta_test_type']]
420 )
421 tt += gmTools.coalesce(self._payload[self._idx['loinc_meta']], u'', u' LOINC: %s\n')
422 tt += gmTools.coalesce(self._payload[self._idx['comment_meta']], u'', _(' Comment: %s\n'))
423
424 panels = self.test_panels
425 if panels is not None:
426 tt += u'\n'
427 tt += _('Listed in test panels:\n')
428 for panel in panels:
429 tt += _(' Panel "%s" [#%s]\n') % (
430 panel['description'],
431 panel['pk_test_panel']
432 )
433
434 if patient is not None:
435 result = self.get_most_recent_results(patient = patient, no_of_results = 1)
436 if result is not None:
437 tt += u'\n'
438 tt += _('Most recent result:\n')
439 tt += _(' %s: %s%s%s') % (
440 result['clin_when'].strftime('%Y-%m-%d'),
441 result['unified_val'],
442 gmTools.coalesce(result['val_unit'], u'', u' %s'),
443 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)')
444 )
445
446 return tt
447
448 #------------------------------------------------------------
450 cmd = u'select * from clin.v_test_types %s' % gmTools.coalesce(order_by, u'', u'order by %s')
451 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = True)
452 return [ cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': r, 'idx': idx}) for r in rows ]
453 #------------------------------------------------------------
455
456 if (abbrev is None) and (name is None):
457 raise ValueError('must have <abbrev> and/or <name> set')
458
459 where_snippets = []
460
461 if lab is None:
462 where_snippets.append('pk_test_org IS NULL')
463 else:
464 try:
465 int(lab)
466 where_snippets.append('pk_test_org = %(lab)s')
467 except (TypeError, ValueError):
468 where_snippets.append('pk_test_org = (SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
469
470 if abbrev is not None:
471 where_snippets.append('abbrev = %(abbrev)s')
472
473 if name is not None:
474 where_snippets.append('name = %(name)s')
475
476 where_clause = u' and '.join(where_snippets)
477 cmd = u"select * from clin.v_test_types where %s" % where_clause
478 args = {'lab': lab, 'abbrev': abbrev, 'name': name}
479
480 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
481
482 if len(rows) == 0:
483 return None
484
485 tt = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
486 return tt
487 #------------------------------------------------------------
489 cmd = u'delete from clin.test_type where pk = %(pk)s'
490 args = {'pk': measurement_type}
491 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
492 #------------------------------------------------------------
494 """Create or get test type."""
495
496 ttype = find_measurement_type(lab = lab, abbrev = abbrev, name = name)
497 # found ?
498 if ttype is not None:
499 return ttype
500
501 _log.debug('creating test type [%s:%s:%s:%s]', lab, abbrev, name, unit)
502
503 # not found, so create it
504 if unit is None:
505 _log.error('need <unit> to create test type: %s:%s:%s:%s' % (lab, abbrev, name, unit))
506 raise ValueError('need <unit> to create test type')
507
508 # make query
509 cols = []
510 val_snippets = []
511 vals = {}
512
513 # lab
514 if lab is None:
515 lab = create_test_org()['pk_test_org']
516
517 cols.append('fk_test_org')
518 try:
519 vals['lab'] = int(lab)
520 val_snippets.append('%(lab)s')
521 except:
522 vals['lab'] = lab
523 val_snippets.append('(SELECT pk_test_org FROM clin.v_test_orgs WHERE unit = %(lab)s)')
524
525 # code
526 cols.append('abbrev')
527 val_snippets.append('%(abbrev)s')
528 vals['abbrev'] = abbrev
529
530 # unit
531 cols.append('conversion_unit')
532 val_snippets.append('%(unit)s')
533 vals['unit'] = unit
534
535 # name
536 if name is not None:
537 cols.append('name')
538 val_snippets.append('%(name)s')
539 vals['name'] = name
540
541 col_clause = u', '.join(cols)
542 val_clause = u', '.join(val_snippets)
543 queries = [
544 {'cmd': u'insert into clin.test_type (%s) values (%s)' % (col_clause, val_clause), 'args': vals},
545 {'cmd': u"select * from clin.v_test_types where pk_test_type = currval(pg_get_serial_sequence('clin.test_type', 'pk'))"}
546 ]
547 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True)
548 ttype = cMeasurementType(row = {'pk_field': 'pk_test_type', 'data': rows[0], 'idx': idx})
549
550 return ttype
551
552 #============================================================
554 """Represents one test result."""
555
556 _cmd_fetch_payload = u"select * from clin.v_test_results where pk_test_result = %s"
557
558 _cmds_store_payload = [
559 u"""update clin.test_result set
560 clin_when = %(clin_when)s,
561 narrative = nullif(trim(%(comment)s), ''),
562 val_num = %(val_num)s,
563 val_alpha = nullif(trim(%(val_alpha)s), ''),
564 val_unit = nullif(trim(%(val_unit)s), ''),
565 val_normal_min = %(val_normal_min)s,
566 val_normal_max = %(val_normal_max)s,
567 val_normal_range = nullif(trim(%(val_normal_range)s), ''),
568 val_target_min = %(val_target_min)s,
569 val_target_max = %(val_target_max)s,
570 val_target_range = nullif(trim(%(val_target_range)s), ''),
571 abnormality_indicator = nullif(trim(%(abnormality_indicator)s), ''),
572 norm_ref_group = nullif(trim(%(norm_ref_group)s), ''),
573 note_test_org = nullif(trim(%(note_test_org)s), ''),
574 material = nullif(trim(%(material)s), ''),
575 material_detail = nullif(trim(%(material_detail)s), ''),
576 fk_intended_reviewer = %(pk_intended_reviewer)s,
577 fk_encounter = %(pk_encounter)s,
578 fk_episode = %(pk_episode)s,
579 fk_type = %(pk_test_type)s,
580 fk_request = %(pk_request)s
581 where
582 pk = %(pk_test_result)s and
583 xmin = %(xmin_test_result)s""",
584 u"""select xmin_test_result from clin.v_test_results where pk_test_result = %(pk_test_result)s"""
585 ]
586
587 _updatable_fields = [
588 'clin_when',
589 'comment',
590 'val_num',
591 'val_alpha',
592 'val_unit',
593 'val_normal_min',
594 'val_normal_max',
595 'val_normal_range',
596 'val_target_min',
597 'val_target_max',
598 'val_target_range',
599 'abnormality_indicator',
600 'norm_ref_group',
601 'note_test_org',
602 'material',
603 'material_detail',
604 'pk_intended_reviewer',
605 'pk_encounter',
606 'pk_episode',
607 'pk_test_type',
608 'pk_request'
609 ]
610 #--------------------------------------------------------
611 # def format_old(self, with_review=True, with_comments=True, date_format='%Y-%m-%d %H:%M'):
612 #
613 # lines = []
614 #
615 # lines.append(u' %s %s (%s): %s %s%s' % (
616 # self._payload[self._idx['clin_when']].strftime(date_format),
617 # self._payload[self._idx['unified_abbrev']],
618 # self._payload[self._idx['unified_name']],
619 # self._payload[self._idx['unified_val']],
620 # self._payload[self._idx['val_unit']],
621 # gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)')
622 # ))
623 #
624 # if with_comments:
625 # if gmTools.coalesce(self._payload[self._idx['comment']], u'').strip() != u'':
626 # lines.append(_(' Doc: %s') % self._payload[self._idx['comment']].strip())
627 # if gmTools.coalesce(self._payload[self._idx['note_test_org']], u'').strip() != u'':
628 # lines.append(_(' MTA: %s') % self._payload[self._idx['note_test_org']].strip())
629 #
630 # if with_review:
631 # if self._payload[self._idx['reviewed']]:
632 # if self._payload[self._idx['is_clinically_relevant']]:
633 # lines.append(u' %s %s: %s' % (
634 # self._payload[self._idx['last_reviewer']],
635 # self._payload[self._idx['last_reviewed']].strftime('%Y-%m-%d %H:%M'),
636 # gmTools.bool2subst (
637 # self._payload[self._idx['is_technically_abnormal']],
638 # _('abnormal and relevant'),
639 # _('normal but relevant')
640 # )
641 # ))
642 # else:
643 # lines.append(_(' unreviewed'))
644 #
645 # return lines
646 #--------------------------------------------------------
647 - def format(self, with_review=True, with_evaluation=True, with_ranges=True, with_episode=True, with_type_details=True, date_format='%Y %b %d %H:%M'):
648
649 # FIXME: add battery, request details
650
651 has_normal_min_or_max = (
652 self._payload[self._idx['val_normal_min']] is not None
653 ) or (
654 self._payload[self._idx['val_normal_max']] is not None
655 )
656 if has_normal_min_or_max:
657 normal_min_max = u'%s - %s' % (
658 gmTools.coalesce(self._payload[self._idx['val_normal_min']], u'?'),
659 gmTools.coalesce(self._payload[self._idx['val_normal_max']], u'?')
660 )
661 else:
662 normal_min_max = u''
663
664 has_clinical_min_or_max = (
665 self._payload[self._idx['val_target_min']] is not None
666 ) or (
667 self._payload[self._idx['val_target_max']] is not None
668 )
669 if has_clinical_min_or_max:
670 clinical_min_max = u'%s - %s' % (
671 gmTools.coalesce(self._payload[self._idx['val_target_min']], u'?'),
672 gmTools.coalesce(self._payload[self._idx['val_target_max']], u'?')
673 )
674 else:
675 clinical_min_max = u''
676
677 # header
678 tt = _(u'Result from %s \n') % gmDateTime.pydt_strftime (
679 self._payload[self._idx['clin_when']],
680 date_format
681 )
682
683 # basics
684 tt += u' ' + _(u'Type: "%(name)s" (%(abbr)s) [#%(pk_type)s]\n') % ({
685 'name': self._payload[self._idx['name_tt']],
686 'abbr': self._payload[self._idx['abbrev_tt']],
687 'pk_type': self._payload[self._idx['pk_test_type']]
688 })
689 tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({
690 'val': self._payload[self._idx['unified_val']],
691 'unit': gmTools.coalesce(self._payload[self._idx['val_unit']], u'', u' %s'),
692 'ind': gmTools.coalesce(self._payload[self._idx['abnormality_indicator']], u'', u' (%s)'),
693 'pk_result': self._payload[self._idx['pk_test_result']]
694 })
695 tmp = (u'%s%s' % (
696 gmTools.coalesce(self._payload[self._idx['name_test_org']], u''),
697 gmTools.coalesce(self._payload[self._idx['contact_test_org']], u'', u' (%s)'),
698 )).strip()
699 if tmp != u'':
700 tt += u' ' + _(u'Source: %s\n') % tmp
701 tt += u'\n'
702
703 if with_evaluation:
704 norm_eval = None
705 if self._payload[self._idx['val_num']] is not None:
706 # 1) normal range
707 # lowered ?
708 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']]):
709 try:
710 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_normal_min']]
711 except ZeroDivisionError:
712 percent = None
713 if percent is not None:
714 if percent < 6:
715 norm_eval = _(u'%.1f %% of the normal lower limit') % percent
716 else:
717 norm_eval = _(u'%.0f %% of the normal lower limit') % percent
718 # raised ?
719 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']]):
720 try:
721 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_normal_max']]
722 except ZeroDivisionError:
723 x_times = None
724 if x_times is not None:
725 if x_times < 10:
726 norm_eval = _(u'%.1f times the normal upper limit') % x_times
727 else:
728 norm_eval = _(u'%.0f times the normal upper limit') % x_times
729 if norm_eval is not None:
730 tt += u' (%s)\n' % norm_eval
731 # #-------------------------------------
732 # # this idea was shot down on the list
733 # #-------------------------------------
734 # # bandwidth of deviation
735 # if None not in [self._payload[self._idx['val_normal_min']], self._payload[self._idx['val_normal_max']]]:
736 # normal_width = self._payload[self._idx['val_normal_max']] - self._payload[self._idx['val_normal_min']]
737 # deviation_from_normal_range = None
738 # # below ?
739 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_normal_min']]:
740 # deviation_from_normal_range = self._payload[self._idx['val_normal_min']] - self._payload[self._idx['val_num']]
741 # # above ?
742 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_normal_max']]:
743 # deviation_from_normal_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_normal_max']]
744 # if deviation_from_normal_range is None:
745 # try:
746 # times_deviation = deviation_from_normal_range / normal_width
747 # except ZeroDivisionError:
748 # times_deviation = None
749 # if times_deviation is not None:
750 # if times_deviation < 10:
751 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation
752 # else:
753 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation
754 # #-------------------------------------
755
756 # 2) clinical target range
757 norm_eval = None
758 # lowered ?
759 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']]):
760 try:
761 percent = (self._payload[self._idx['val_num']] * 100) / self._payload[self._idx['val_target_min']]
762 except ZeroDivisionError:
763 percent = None
764 if percent is not None:
765 if percent < 6:
766 norm_eval = _(u'%.1f %% of the target lower limit') % percent
767 else:
768 norm_eval = _(u'%.0f %% of the target lower limit') % percent
769 # raised ?
770 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']]):
771 try:
772 x_times = self._payload[self._idx['val_num']] / self._payload[self._idx['val_target_max']]
773 except ZeroDivisionError:
774 x_times = None
775 if x_times is not None:
776 if x_times < 10:
777 norm_eval = _(u'%.1f times the target upper limit') % x_times
778 else:
779 norm_eval = _(u'%.0f times the target upper limit') % x_times
780 if norm_eval is not None:
781 tt += u' (%s)\n' % norm_eval
782 # #-------------------------------------
783 # # this idea was shot down on the list
784 # #-------------------------------------
785 # # bandwidth of deviation
786 # if None not in [self._payload[self._idx['val_target_min']], self._payload[self._idx['val_target_max']]]:
787 # normal_width = self._payload[self._idx['val_target_max']] - self._payload[self._idx['val_target_min']]
788 # deviation_from_target_range = None
789 # # below ?
790 # if self._payload[self._idx['val_num']] < self._payload[self._idx['val_target_min']]:
791 # deviation_from_target_range = self._payload[self._idx['val_target_min']] - self._payload[self._idx['val_num']]
792 # # above ?
793 # elif self._payload[self._idx['val_num']] > self._payload[self._idx['val_target_max']]:
794 # deviation_from_target_range = self._payload[self._idx['val_num']] - self._payload[self._idx['val_target_max']]
795 # if deviation_from_target_range is None:
796 # try:
797 # times_deviation = deviation_from_target_range / normal_width
798 # except ZeroDivisionError:
799 # times_deviation = None
800 # if times_deviation is not None:
801 # if times_deviation < 10:
802 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation
803 # else:
804 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation
805 # #-------------------------------------
806
807 if with_ranges:
808 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({
809 'norm_min_max': normal_min_max,
810 'norm_range': gmTools.coalesce (
811 self._payload[self._idx['val_normal_range']],
812 u'',
813 gmTools.bool2subst (
814 has_normal_min_or_max,
815 u' / %s',
816 u'%s'
817 )
818 )
819 })
820 if self._payload[self._idx['norm_ref_group']] is not None:
821 tt += u' ' + _(u'Reference group: %s\n') % self._payload[self._idx['norm_ref_group']]
822 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({
823 'clin_min_max': clinical_min_max,
824 'clin_range': gmTools.coalesce (
825 self._payload[self._idx['val_target_range']],
826 u'',
827 gmTools.bool2subst (
828 has_clinical_min_or_max,
829 u' / %s',
830 u'%s'
831 )
832 )
833 })
834
835 # metadata
836 if self._payload[self._idx['comment']] is not None:
837 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(self._payload[self._idx['comment']].split(u'\n'))
838 if self._payload[self._idx['note_test_org']] is not None:
839 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(self._payload[self._idx['note_test_org']].split(u'\n'))
840 if with_episode:
841 tt += u' ' + _(u'Episode: %s\n') % self._payload[self._idx['episode']]
842 if self._payload[self._idx['health_issue']] is not None:
843 tt += u' ' + _(u'Issue: %s\n') % self._payload[self._idx['health_issue']]
844 if self._payload[self._idx['material']] is not None:
845 tt += u' ' + _(u'Material: %s\n') % self._payload[self._idx['material']]
846 if self._payload[self._idx['material_detail']] is not None:
847 tt += u' ' + _(u'Details: %s\n') % self._payload[self._idx['material_detail']]
848 tt += u'\n'
849
850 if with_review:
851 if self._payload[self._idx['reviewed']]:
852 review = gmDateTime.pydt_strftime (
853 self._payload[self._idx['last_reviewed']],
854 date_format
855 )
856 else:
857 review = _('not yet')
858 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({
859 'sig_hand': gmTools.u_writing_hand,
860 'reviewed': review
861 })
862 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst (
863 self._payload[self._idx['you_are_responsible']],
864 _('you'),
865 self._payload[self._idx['responsible_reviewer']]
866 )
867 if self._payload[self._idx['reviewed']]:
868 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({
869 'reviewer': gmTools.bool2subst (
870 self._payload[self._idx['review_by_you']],
871 _('you'),
872 gmTools.coalesce(self._payload[self._idx['last_reviewer']], u'?')
873 )
874 })
875 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({
876 'abnormal': gmTools.bool2subst (
877 self._payload[self._idx['is_technically_abnormal']],
878 _('yes'),
879 _('no'),
880 u'?'
881 )
882 })
883 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({
884 'relevant': gmTools.bool2subst (
885 self._payload[self._idx['is_clinically_relevant']],
886 _('yes'),
887 _('no'),
888 u'?'
889 )
890 })
891 if self._payload[self._idx['review_comment']] is not None:
892 tt += u' ' + _(u' Comment: %s\n') % self._payload[self._idx['review_comment']].strip()
893 tt += u'\n'
894
895 # type
896 if with_type_details:
897 tt += _(u'Test type details:\n')
898 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({
899 'name_meta': gmTools.coalesce(self._payload[self._idx['name_meta']], u''),
900 'abbrev_meta': gmTools.coalesce(self._payload[self._idx['abbrev_meta']], u''),
901 'pk_u_type': self._payload[self._idx['pk_meta_test_type']]
902 })
903 if self._payload[self._idx['comment_tt']] is not None:
904 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(self._payload[self._idx['comment_tt']].split(u'\n'))
905 if self._payload[self._idx['comment_meta']] is not None:
906 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(self._payload[self._idx['comment_meta']].split(u'\n'))
907 tt += u'\n'
908
909 if with_review:
910 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({
911 'row_ver': self._payload[self._idx['row_version']],
912 'mod_when': gmDateTime.pydt_strftime(self._payload[self._idx['modified_when']],date_format),
913 'mod_by': self._payload[self._idx['modified_by']]
914 })
915
916 return tt
917 #--------------------------------------------------------
919
920 cmd = u"""
921 select
922 distinct on (norm_ref_group_str, val_unit, val_normal_min, val_normal_max, val_normal_range, val_target_min, val_target_max, val_target_range)
923 pk_patient,
924 val_unit,
925 val_normal_min, val_normal_max, val_normal_range,
926 val_target_min, val_target_max, val_target_range,
927 norm_ref_group,
928 coalesce(norm_ref_group, '') as norm_ref_group_str
929 from
930 clin.v_test_results
931 where
932 pk_test_type = %(pk_type)s
933 """
934 args = {'pk_type': self._payload[self._idx['pk_test_type']]}
935 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
936 return rows
937
940
941 reference_ranges = property(_get_reference_ranges, _set_reference_ranges)
942 #--------------------------------------------------------
944 return cMeasurementType(aPK_obj = self._payload[self._idx['pk_test_type']])
945
946 test_type = property(_get_test_type, lambda x:x)
947 #--------------------------------------------------------
948 - def set_review(self, technically_abnormal=None, clinically_relevant=None, comment=None, make_me_responsible=False):
949
950 # FIXME: this is not concurrency safe
951 if self._payload[self._idx['reviewed']]:
952 self.__change_existing_review (
953 technically_abnormal = technically_abnormal,
954 clinically_relevant = clinically_relevant,
955 comment = comment
956 )
957 else:
958 # do not sign off unreviewed results if
959 # NOTHING AT ALL is known about them
960 if technically_abnormal is None:
961 if clinically_relevant is None:
962 comment = gmTools.none_if(comment, u'', strip_string = True)
963 if comment is None:
964 if make_me_responsible is False:
965 return True
966 self.__set_new_review (
967 technically_abnormal = technically_abnormal,
968 clinically_relevant = clinically_relevant,
969 comment = comment
970 )
971
972 if make_me_responsible is True:
973 cmd = u"SELECT pk FROM dem.staff WHERE db_user = current_user"
974 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
975 self['pk_intended_reviewer'] = rows[0][0]
976 self.save_payload()
977 return
978
979 self.refetch_payload()
980 #--------------------------------------------------------
981 - def get_adjacent_results(self, desired_earlier_results=1, desired_later_results=1, max_offset=None):
982
983 if desired_earlier_results < 1:
984 raise ValueError('<desired_earlier_results> must be > 0')
985
986 if desired_later_results < 1:
987 raise ValueError('<desired_later_results> must be > 0')
988
989 args = {
990 'pat': self._payload[self._idx['pk_patient']],
991 'ttyp': self._payload[self._idx['pk_test_type']],
992 'tloinc': self._payload[self._idx['loinc_tt']],
993 'mtyp': self._payload[self._idx['pk_meta_test_type']],
994 'mloinc': self._payload[self._idx['loinc_meta']],
995 'when': self._payload[self._idx['clin_when']],
996 'offset': max_offset
997 }
998 WHERE = u'((pk_test_type = %(ttyp)s) OR (loinc_tt = %(tloinc)s))'
999 WHERE_meta = u'((pk_meta_test_type = %(mtyp)s) OR (loinc_meta = %(mloinc)s))'
1000 if max_offset is not None:
1001 WHERE = WHERE + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1002 WHERE_meta = WHERE_meta + u' AND (clin_when BETWEEN (%(when)s - %(offset)s) AND (%(when)s + %(offset)s))'
1003
1004 SQL = u"""
1005 SELECT * FROM clin.v_test_results
1006 WHERE
1007 pk_patient = %%(pat)s
1008 AND
1009 clin_when %s %%(when)s
1010 AND
1011 %s
1012 ORDER BY clin_when
1013 LIMIT %s"""
1014
1015 # get earlier results
1016 earlier_results = []
1017 # by type
1018 cmd = SQL % (u'<', WHERE, desired_earlier_results)
1019 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1020 if len(rows) > 0:
1021 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1022 # by meta type ?
1023 missing_results = desired_earlier_results - len(earlier_results)
1024 if missing_results > 0:
1025 cmd = SQL % (u'<', WHERE_meta, missing_results)
1026 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1027 if len(rows) > 0:
1028 earlier_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1029
1030 # get later results
1031 later_results = []
1032 # by type
1033 cmd = SQL % (u'>', WHERE, desired_later_results)
1034 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1035 if len(rows) > 0:
1036 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1037 # by meta type ?
1038 missing_results = desired_later_results - len(later_results)
1039 if missing_results > 0:
1040 cmd = SQL % (u'>', WHERE_meta, missing_results)
1041 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1042 if len(rows) > 0:
1043 later_results.extend([ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ])
1044
1045 return earlier_results, later_results
1046 #--------------------------------------------------------
1047 # internal API
1048 #--------------------------------------------------------
1049 - def __set_new_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1050 """Add a review to a row.
1051
1052 - if technically abnormal is not provided/None it will be set
1053 to True if the lab's indicator has a meaningful value
1054 - if clinically relevant is not provided/None it is set to
1055 whatever technically abnormal is
1056 """
1057 if technically_abnormal is None:
1058 technically_abnormal = False
1059 if self._payload[self._idx['abnormality_indicator']] is not None:
1060 if self._payload[self._idx['abnormality_indicator']].strip() != u'':
1061 technically_abnormal = True
1062
1063 if clinically_relevant is None:
1064 clinically_relevant = technically_abnormal
1065
1066 cmd = u"""
1067 INSERT INTO clin.reviewed_test_results (
1068 fk_reviewed_row,
1069 is_technically_abnormal,
1070 clinically_relevant,
1071 comment
1072 ) VALUES (
1073 %(pk)s,
1074 %(abnormal)s,
1075 %(relevant)s,
1076 gm.nullify_empty_string(%(cmt)s)
1077 )"""
1078 args = {
1079 'pk': self._payload[self._idx['pk_test_result']],
1080 'abnormal': technically_abnormal,
1081 'relevant': clinically_relevant,
1082 'cmt': comment
1083 }
1084
1085 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1086 #--------------------------------------------------------
1087 - def __change_existing_review(self, technically_abnormal=None, clinically_relevant=None, comment=None):
1088 """Change a review on a row.
1089
1090 - if technically abnormal/clinically relevant are
1091 None they are not set
1092 """
1093 args = {
1094 'pk_row': self._payload[self._idx['pk_test_result']],
1095 'abnormal': technically_abnormal,
1096 'relevant': clinically_relevant,
1097 'cmt': comment
1098 }
1099
1100 set_parts = [
1101 u'fk_reviewer = (SELECT pk FROM dem.staff WHERE db_user = current_user)',
1102 u'comment = gm.nullify_empty_string(%(cmt)s)'
1103 ]
1104
1105 if technically_abnormal is not None:
1106 set_parts.append(u'is_technically_abnormal = %(abnormal)s')
1107
1108 if clinically_relevant is not None:
1109 set_parts.append(u'clinically_relevant = %(relevant)s')
1110
1111 cmd = u"""
1112 UPDATE clin.reviewed_test_results SET
1113 %s
1114 WHERE
1115 fk_reviewed_row = %%(pk_row)s
1116 """ % u',\n '.join(set_parts)
1117
1118 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1119
1120 #------------------------------------------------------------
1122
1123 where_parts = []
1124
1125 if pk_patient is not None:
1126 where_parts.append(u'pk_patient = %(pat)s')
1127 args = {'pat': pk_patient}
1128
1129 # if tests is not None:
1130 # where_parts.append(u'pk_test_type IN %(tests)s')
1131 # args['tests'] = tuple(tests)
1132
1133 if encounters is not None:
1134 where_parts.append(u'pk_encounter IN %(encs)s')
1135 args['encs'] = tuple(encounters)
1136
1137 if episodes is not None:
1138 where_parts.append(u'pk_episode IN %(epis)s')
1139 args['epis'] = tuple(episodes)
1140
1141 if order_by is None:
1142 order_by = u''
1143 else:
1144 order_by = u'ORDER BY %s' % order_by
1145
1146 cmd = u"""
1147 SELECT * FROM clin.v_test_results
1148 WHERE %s
1149 %s
1150 """ % (
1151 u' AND '.join(where_parts),
1152 order_by
1153 )
1154 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1155
1156 tests = [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1157 return tests
1158
1159 #------------------------------------------------------------
1160 -def get_result_at_timestamp(timestamp=None, test_type=None, loinc=None, tolerance_interval=None, patient=None):
1161
1162 if None not in [test_type, loinc]:
1163 raise ValueError('either <test_type> or <loinc> must be None')
1164
1165 args = {
1166 'pat': patient,
1167 'ttyp': test_type,
1168 'loinc': loinc,
1169 'ts': timestamp,
1170 'intv': tolerance_interval
1171 }
1172
1173 where_parts = [u'pk_patient = %(pat)s']
1174 if test_type is not None:
1175 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
1176 elif loinc is not None:
1177 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1178 args['loinc'] = tuple(loinc)
1179
1180 if tolerance_interval is None:
1181 where_parts.append(u'clin_when = %(ts)s')
1182 else:
1183 where_parts.append(u'clin_when between (%(ts)s - %(intv)s::interval) AND (%(ts)s + %(intv)s::interval)')
1184
1185 cmd = u"""
1186 SELECT * FROM clin.v_test_results
1187 WHERE
1188 %s
1189 ORDER BY
1190 abs(extract(epoch from age(clin_when, %%(ts)s)))
1191 LIMIT 1""" % u' AND '.join(where_parts)
1192
1193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1194 if len(rows) == 0:
1195 return None
1196
1197 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1198
1199 #------------------------------------------------------------
1201
1202 if None not in [test_type, loinc]:
1203 raise ValueError('either <test_type> or <loinc> must be None')
1204
1205 if no_of_results < 1:
1206 raise ValueError('<no_of_results> must be > 0')
1207
1208 args = {
1209 'pat': patient,
1210 'ttyp': test_type,
1211 'loinc': loinc
1212 }
1213
1214 where_parts = [u'pk_patient = %(pat)s']
1215 if test_type is not None:
1216 where_parts.append(u'pk_test_type = %(ttyp)s') # consider: pk_meta_test_type = %(pkmtt)s / self._payload[self._idx['pk_meta_test_type']]
1217 elif loinc is not None:
1218 where_parts.append(u'((loinc_tt IN %(loinc)s) OR (loinc_meta IN %(loinc)s))')
1219 args['loinc'] = tuple(loinc)
1220
1221 cmd = u"""
1222 SELECT * FROM clin.v_test_results
1223 WHERE
1224 %s
1225 ORDER BY clin_when DESC
1226 LIMIT %s""" % (
1227 u' AND '.join(where_parts),
1228 no_of_results
1229 )
1230 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1231 if len(rows) == 0:
1232 return None
1233
1234 if no_of_results == 1:
1235 return cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': rows[0]})
1236
1237 return [ cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1238
1239 #------------------------------------------------------------
1241 try:
1242 pk = int(result)
1243 except (TypeError, AttributeError):
1244 pk = result['pk_test_result']
1245
1246 cmd = u'DELETE FROM clin.test_result WHERE pk = %(pk)s'
1247 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1248
1249 #------------------------------------------------------------
1250 -def create_test_result(encounter=None, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1251
1252 cmd1 = u"""
1253 insert into clin.test_result (
1254 fk_encounter,
1255 fk_episode,
1256 fk_type,
1257 fk_intended_reviewer,
1258 val_num,
1259 val_alpha,
1260 val_unit
1261 ) values (
1262 %(enc)s,
1263 %(epi)s,
1264 %(type)s,
1265 %(rev)s,
1266 %(v_num)s,
1267 %(v_alpha)s,
1268 %(unit)s
1269 )"""
1270
1271 cmd2 = u"""
1272 select *
1273 from
1274 clin.v_test_results
1275 where
1276 pk_test_result = currval(pg_get_serial_sequence('clin.test_result', 'pk'))"""
1277
1278 args = {
1279 u'enc': encounter,
1280 u'epi': episode,
1281 u'type': type,
1282 u'rev': intended_reviewer,
1283 u'v_num': val_num,
1284 u'v_alpha': val_alpha,
1285 u'unit': unit
1286 }
1287
1288 rows, idx = gmPG2.run_rw_queries (
1289 queries = [
1290 {'cmd': cmd1, 'args': args},
1291 {'cmd': cmd2}
1292 ],
1293 return_data = True,
1294 get_col_idx = True
1295 )
1296
1297 tr = cTestResult(row = {
1298 'pk_field': 'pk_test_result',
1299 'idx': idx,
1300 'data': rows[0]
1301 })
1302
1303 return tr
1304
1305 #------------------------------------------------------------
1307
1308 _log.debug(u'formatting test results into [%s]', output_format)
1309
1310 if output_format == u'latex':
1311 return __format_test_results_latex(results = results)
1312
1313 msg = _('unknown test results output format [%s]') % output_format
1314 _log.error(msg)
1315 return msg
1316
1317 #------------------------------------------------------------
1319
1320 if len(results) == 0:
1321 return u'\\begin{minipage}{%s} \\end{minipage}' % width
1322
1323 lines = []
1324 for t in results:
1325
1326 tmp = u''
1327
1328 if show_time:
1329 tmp += u'{\\tiny (%s)} ' % t['clin_when'].strftime('%H:%M')
1330
1331 tmp += u'%.8s' % t['unified_val']
1332
1333 lines.append(tmp)
1334 tmp = u''
1335
1336 if show_range:
1337 has_range = (
1338 t['unified_target_range'] is not None
1339 or
1340 t['unified_target_min'] is not None
1341 or
1342 t['unified_target_max'] is not None
1343 )
1344 if has_range:
1345 if t['unified_target_range'] is not None:
1346 tmp += u'{\\tiny %s}' % t['unified_target_range']
1347 else:
1348 tmp += u'{\\tiny %s}' % (
1349 gmTools.coalesce(t['unified_target_min'], u'- ', u'%s - '),
1350 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1351 )
1352 lines.append(tmp)
1353
1354 return u'\\begin{minipage}{%s} \\begin{flushright} %s \\end{flushright} \\end{minipage}' % (width, u' \\\\ '.join(lines))
1355
1356 #------------------------------------------------------------
1358
1359 if len(results) == 0:
1360 return u''
1361
1362 lines = []
1363 for t in results:
1364
1365 tmp = u''
1366
1367 if show_time:
1368 tmp += u'\\tiny %s ' % t['clin_when'].strftime('%H:%M')
1369
1370 tmp += u'\\normalsize %.8s' % t['unified_val']
1371
1372 lines.append(tmp)
1373 tmp = u'\\tiny %s' % gmTools.coalesce(t['val_unit'], u'', u'%s ')
1374
1375 if not show_range:
1376 lines.append(tmp)
1377 continue
1378
1379 has_range = (
1380 t['unified_target_range'] is not None
1381 or
1382 t['unified_target_min'] is not None
1383 or
1384 t['unified_target_max'] is not None
1385 )
1386
1387 if not has_range:
1388 lines.append(tmp)
1389 continue
1390
1391 if t['unified_target_range'] is not None:
1392 tmp += u'[%s]' % t['unified_target_range']
1393 else:
1394 tmp += u'[%s%s]' % (
1395 gmTools.coalesce(t['unified_target_min'], u'--', u'%s--'),
1396 gmTools.coalesce(t['unified_target_max'], u'', u'%s')
1397 )
1398 lines.append(tmp)
1399
1400 return u' \\\\ '.join(lines)
1401
1402 #------------------------------------------------------------
1404
1405 if len(results) == 0:
1406 return u'\\noindent %s' % _('No test results to format.')
1407
1408 # discover the columns and rows
1409 dates = {}
1410 tests = {}
1411 grid = {}
1412 for result in results:
1413 # row_label = u'%s \\ \\tiny (%s)}' % (result['unified_abbrev'], result['unified_name'])
1414 row_label = result['unified_abbrev']
1415 tests[row_label] = None
1416 col_label = u'{\\scriptsize %s}' % result['clin_when'].strftime('%Y-%m-%d')
1417 dates[col_label] = None
1418 try:
1419 grid[row_label]
1420 except KeyError:
1421 grid[row_label] = {}
1422 try:
1423 grid[row_label][col_label].append(result)
1424 except KeyError:
1425 grid[row_label][col_label] = [result]
1426
1427 col_labels = sorted(dates.keys(), reverse = True)
1428 del dates
1429 row_labels = sorted(tests.keys())
1430 del tests
1431
1432 col_def = len(col_labels) * u'>{\\raggedleft}p{1.7cm}|'
1433
1434 # format them
1435 tex = u"""\\noindent %s
1436
1437 \\noindent \\begin{tabular}{|l|%s}
1438 \\hline
1439 & %s \\tabularnewline
1440 \\hline
1441
1442 %%s \\tabularnewline
1443
1444 \\hline
1445
1446 \\end{tabular}""" % (
1447 _('Test results'),
1448 col_def,
1449 u' & '.join(col_labels)
1450 )
1451
1452 rows = []
1453
1454 # loop over rows
1455 for rl in row_labels:
1456 cells = [rl]
1457 # loop over cols per row
1458 for cl in col_labels:
1459 try:
1460 # get tests for this (row/col) position
1461 tests = grid[rl][cl]
1462 except KeyError:
1463 # none there, so insert empty cell
1464 cells.append(u' ')
1465 continue
1466
1467 cells.append (
1468 __tests2latex_cell (
1469 results = tests,
1470 show_time = (len(tests) > 1),
1471 show_range = True
1472 )
1473 )
1474
1475 rows.append(u' & '.join(cells))
1476
1477 return tex % u' \\tabularnewline\n \\hline\n'.join(rows)
1478
1479 #============================================================
1481
1482 if filename is None:
1483 filename = gmTools.get_unique_filename(prefix = u'gm2gpl-', suffix = '.dat')
1484
1485 # sort results into series by test type
1486 series = {}
1487 for r in results:
1488 try:
1489 series[r['unified_name']].append(r)
1490 except KeyError:
1491 series[r['unified_name']] = [r]
1492
1493 gp_data = codecs.open(filename, 'wb', 'utf8')
1494
1495 gp_data.write(u'# %s\n' % _('GNUmed test results export for Gnuplot plotting'))
1496 gp_data.write(u'# -------------------------------------------------------------\n')
1497 gp_data.write(u'# first line of index: test type abbreviation & name\n')
1498 gp_data.write(u'#\n')
1499 gp_data.write(u'# clin_when at full precision\n')
1500 gp_data.write(u'# value\n')
1501 gp_data.write(u'# unit\n')
1502 gp_data.write(u'# unified (target or normal) range: lower bound\n')
1503 gp_data.write(u'# unified (target or normal) range: upper bound\n')
1504 gp_data.write(u'# normal range: lower bound\n')
1505 gp_data.write(u'# normal range: upper bound\n')
1506 gp_data.write(u'# target range: lower bound\n')
1507 gp_data.write(u'# target range: upper bound\n')
1508 gp_data.write(u'# clin_when formatted into string as x-axis tic label\n')
1509 gp_data.write(u'# -------------------------------------------------------------\n')
1510
1511 for test_type in series.keys():
1512 if len(series[test_type]) == 0:
1513 continue
1514
1515 r = series[test_type][0]
1516 title = u'%s (%s)' % (
1517 r['unified_abbrev'],
1518 r['unified_name']
1519 )
1520 gp_data.write(u'\n\n"%s" "%s"\n' % (title, title))
1521
1522 prev_date = None
1523 prev_year = None
1524 for r in series[test_type]:
1525 curr_date = r['clin_when'].strftime('%Y-%m-%d')
1526 if curr_date == prev_date:
1527 gp_data.write(u'\n# %s\n' % _('blank line inserted to allow for discontinued line drawing for same-day values'))
1528 if show_year:
1529 if r['clin_when'].year == prev_year:
1530 when_template = '%b %d %H:%M'
1531 else:
1532 when_template = '%b %d %H:%M (%Y)'
1533 prev_year = r['clin_when'].year
1534 else:
1535 when_template = '%b %d'
1536 gp_data.write (u'%s %s "%s" %s %s %s %s %s %s "%s"\n' % (
1537 r['clin_when'].strftime('%Y-%m-%d_%H:%M'),
1538 r['unified_val'],
1539 gmTools.coalesce(r['val_unit'], u'"<?>"'),
1540 gmTools.coalesce(r['unified_target_min'], u'"<?>"'),
1541 gmTools.coalesce(r['unified_target_max'], u'"<?>"'),
1542 gmTools.coalesce(r['val_normal_min'], u'"<?>"'),
1543 gmTools.coalesce(r['val_normal_max'], u'"<?>"'),
1544 gmTools.coalesce(r['val_target_min'], u'"<?>"'),
1545 gmTools.coalesce(r['val_target_max'], u'"<?>"'),
1546 gmDateTime.pydt_strftime (
1547 r['clin_when'],
1548 format = when_template,
1549 accuracy = gmDateTime.acc_minutes
1550 )
1551 ))
1552 prev_date = curr_date
1553
1554 gp_data.close()
1555
1556 return filename
1557
1558 #============================================================
1560 """Represents one lab result."""
1561
1562 _cmd_fetch_payload = """
1563 select *, xmin_test_result from v_results4lab_req
1564 where pk_result=%s"""
1565 _cmds_lock_rows_for_update = [
1566 """select 1 from test_result where pk=%(pk_result)s and xmin=%(xmin_test_result)s for update"""
1567 ]
1568 _cmds_store_payload = [
1569 """update test_result set
1570 clin_when = %(val_when)s,
1571 narrative = %(progress_note_result)s,
1572 fk_type = %(pk_test_type)s,
1573 val_num = %(val_num)s::numeric,
1574 val_alpha = %(val_alpha)s,
1575 val_unit = %(val_unit)s,
1576 val_normal_min = %(val_normal_min)s,
1577 val_normal_max = %(val_normal_max)s,
1578 val_normal_range = %(val_normal_range)s,
1579 val_target_min = %(val_target_min)s,
1580 val_target_max = %(val_target_max)s,
1581 val_target_range = %(val_target_range)s,
1582 abnormality_indicator = %(abnormal)s,
1583 norm_ref_group = %(ref_group)s,
1584 note_provider = %(note_provider)s,
1585 material = %(material)s,
1586 material_detail = %(material_detail)s
1587 where pk = %(pk_result)s""",
1588 """select xmin_test_result from v_results4lab_req where pk_result=%(pk_result)s"""
1589 ]
1590
1591 _updatable_fields = [
1592 'val_when',
1593 'progress_note_result',
1594 'val_num',
1595 'val_alpha',
1596 'val_unit',
1597 'val_normal_min',
1598 'val_normal_max',
1599 'val_normal_range',
1600 'val_target_min',
1601 'val_target_max',
1602 'val_target_range',
1603 'abnormal',
1604 'ref_group',
1605 'note_provider',
1606 'material',
1607 'material_detail'
1608 ]
1609 #--------------------------------------------------------
1611 """Instantiate.
1612
1613 aPK_obj as dict:
1614 - patient_id
1615 - when_field (see view definition)
1616 - when
1617 - test_type
1618 - val_num
1619 - val_alpha
1620 - unit
1621 """
1622 # instantiate from row data ?
1623 if aPK_obj is None:
1624 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
1625 return
1626 pk = aPK_obj
1627 # find PK from row data ?
1628 if type(aPK_obj) == types.DictType:
1629 # sanity checks
1630 if None in [aPK_obj['patient_id'], aPK_obj['when'], aPK_obj['when_field'], aPK_obj['test_type'], aPK_obj['unit']]:
1631 raise gmExceptions.ConstructorError, 'parameter error: %s' % aPK_obj
1632 if (aPK_obj['val_num'] is None) and (aPK_obj['val_alpha'] is None):
1633 raise gmExceptions.ConstructorError, 'parameter error: val_num and val_alpha cannot both be None'
1634 # get PK
1635 where_snippets = [
1636 'pk_patient=%(patient_id)s',
1637 'pk_test_type=%(test_type)s',
1638 '%s=%%(when)s' % aPK_obj['when_field'],
1639 'val_unit=%(unit)s'
1640 ]
1641 if aPK_obj['val_num'] is not None:
1642 where_snippets.append('val_num=%(val_num)s::numeric')
1643 if aPK_obj['val_alpha'] is not None:
1644 where_snippets.append('val_alpha=%(val_alpha)s')
1645
1646 where_clause = ' and '.join(where_snippets)
1647 cmd = "select pk_result from v_results4lab_req where %s" % where_clause
1648 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
1649 if data is None:
1650 raise gmExceptions.ConstructorError, 'error getting lab result for: %s' % aPK_obj
1651 if len(data) == 0:
1652 raise gmExceptions.NoSuchClinItemError, 'no lab result for: %s' % aPK_obj
1653 pk = data[0][0]
1654 # instantiate class
1655 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1656 #--------------------------------------------------------
1658 cmd = """
1659 select
1660 %s,
1661 vbp.title,
1662 vbp.firstnames,
1663 vbp.lastnames,
1664 vbp.dob
1665 from v_basic_person vbp
1666 where vbp.pk_identity=%%s""" % self._payload[self._idx['pk_patient']]
1667 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_patient']])
1668 return pat[0]
1669 #============================================================
1671 """Represents one lab request."""
1672
1673 _cmd_fetch_payload = """
1674 select *, xmin_lab_request from v_lab_requests
1675 where pk_request=%s"""
1676 _cmds_lock_rows_for_update = [
1677 """select 1 from lab_request where pk=%(pk_request)s and xmin=%(xmin_lab_request)s for update"""
1678 ]
1679 _cmds_store_payload = [
1680 """update lab_request set
1681 request_id=%(request_id)s,
1682 lab_request_id=%(lab_request_id)s,
1683 clin_when=%(sampled_when)s,
1684 lab_rxd_when=%(lab_rxd_when)s,
1685 results_reported_when=%(results_reported_when)s,
1686 request_status=%(request_status)s,
1687 is_pending=%(is_pending)s::bool,
1688 narrative=%(progress_note)s
1689 where pk=%(pk_request)s""",
1690 """select xmin_lab_request from v_lab_requests where pk_request=%(pk_request)s"""
1691 ]
1692 _updatable_fields = [
1693 'request_id',
1694 'lab_request_id',
1695 'sampled_when',
1696 'lab_rxd_when',
1697 'results_reported_when',
1698 'request_status',
1699 'is_pending',
1700 'progress_note'
1701 ]
1702 #--------------------------------------------------------
1704 """Instantiate lab request.
1705
1706 The aPK_obj can be either a dict with the keys "req_id"
1707 and "lab" or a simple primary key.
1708 """
1709 # instantiate from row data ?
1710 if aPK_obj is None:
1711 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=row)
1712 return
1713 pk = aPK_obj
1714 # instantiate from "req_id" and "lab" ?
1715 if type(aPK_obj) == types.DictType:
1716 # sanity check
1717 try:
1718 aPK_obj['req_id']
1719 aPK_obj['lab']
1720 except:
1721 _log.exception('[%s:??]: faulty <aPK_obj> structure: [%s]' % (self.__class__.__name__, aPK_obj), sys.exc_info())
1722 raise gmExceptions.ConstructorError, '[%s:??]: cannot derive PK from [%s]' % (self.__class__.__name__, aPK_obj)
1723 # generate query
1724 where_snippets = []
1725 vals = {}
1726 where_snippets.append('request_id=%(req_id)s')
1727 if type(aPK_obj['lab']) == types.IntType:
1728 where_snippets.append('pk_test_org=%(lab)s')
1729 else:
1730 where_snippets.append('lab_name=%(lab)s')
1731 where_clause = ' and '.join(where_snippets)
1732 cmd = "select pk_request from v_lab_requests where %s" % where_clause
1733 # get pk
1734 data = gmPG.run_ro_query('historica', cmd, None, aPK_obj)
1735 if data is None:
1736 raise gmExceptions.ConstructorError, '[%s:??]: error getting lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1737 if len(data) == 0:
1738 raise gmExceptions.NoSuchClinItemError, '[%s:??]: no lab request for [%s]' % (self.__class__.__name__, aPK_obj)
1739 pk = data[0][0]
1740 # instantiate class
1741 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
1742 #--------------------------------------------------------
1744 cmd = """
1745 select vpi.pk_patient, vbp.title, vbp.firstnames, vbp.lastnames, vbp.dob
1746 from v_pat_items vpi, v_basic_person vbp
1747 where
1748 vpi.pk_item=%s
1749 and
1750 vbp.pk_identity=vpi.pk_patient"""
1751 pat = gmPG.run_ro_query('historica', cmd, None, self._payload[self._idx['pk_item']])
1752 if pat is None:
1753 _log.error('cannot get patient for lab request [%s]' % self._payload[self._idx['pk_item']])
1754 return None
1755 if len(pat) == 0:
1756 _log.error('no patient associated with lab request [%s]' % self._payload[self._idx['pk_item']])
1757 return None
1758 return pat[0]
1759 #============================================================
1760 # convenience functions
1761 #------------------------------------------------------------
1762 -def create_lab_request(lab=None, req_id=None, pat_id=None, encounter_id=None, episode_id=None):
1763 """Create or get lab request.
1764
1765 returns tuple (status, value):
1766 (True, lab request instance)
1767 (False, error message)
1768 (None, housekeeping_todo primary key)
1769 """
1770 req = None
1771 aPK_obj = {
1772 'lab': lab,
1773 'req_id': req_id
1774 }
1775 try:
1776 req = cLabRequest (aPK_obj)
1777 except gmExceptions.NoSuchClinItemError, msg:
1778 _log.info('%s: will try to create lab request' % str(msg))
1779 except gmExceptions.ConstructorError, msg:
1780 _log.exception(str(msg), sys.exc_info(), verbose=0)
1781 return (False, msg)
1782 # found
1783 if req is not None:
1784 db_pat = req.get_patient()
1785 if db_pat is None:
1786 _log.error('cannot cross-check patient on lab request')
1787 return (None, '')
1788 # yes but ambigous
1789 if pat_id != db_pat[0]:
1790 _log.error('lab request found for [%s:%s] but patient mismatch: expected [%s], in DB [%s]' % (lab, req_id, pat_id, db_pat))
1791 me = '$RCSfile: gmPathLab.py,v $ $Revision: 1.81 $'
1792 to = 'user'
1793 prob = _('The lab request already exists but belongs to a different patient.')
1794 sol = _('Verify which patient this lab request really belongs to.')
1795 ctxt = _('lab [%s], request ID [%s], expected link with patient [%s], currently linked to patient [%s]') % (lab, req_id, pat_id, db_pat)
1796 cat = 'lab'
1797 status, data = gmPG.add_housekeeping_todo(me, to, prob, sol, ctxt, cat)
1798 return (None, data)
1799 return (True, req)
1800 # not found
1801 queries = []
1802 if type(lab) is types.IntType:
1803 cmd = "insert into lab_request (fk_encounter, fk_episode, fk_test_org, request_id) values (%s, %s, %s, %s)"
1804 else:
1805 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)"
1806 queries.append((cmd, [encounter_id, episode_id, str(lab), req_id]))
1807 cmd = "select currval('lab_request_pk_seq')"
1808 queries.append((cmd, []))
1809 # insert new
1810 result, err = gmPG.run_commit('historica', queries, True)
1811 if result is None:
1812 return (False, err)
1813 try:
1814 req = cLabRequest(aPK_obj=result[0][0])
1815 except gmExceptions.ConstructorError, msg:
1816 _log.exception(str(msg), sys.exc_info(), verbose=0)
1817 return (False, msg)
1818 return (True, req)
1819 #------------------------------------------------------------
1820 -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):
1821 tres = None
1822 data = {
1823 'patient_id': patient_id,
1824 'when_field': when_field,
1825 'when': when,
1826 'test_type': test_type,
1827 'val_num': val_num,
1828 'val_alpha': val_alpha,
1829 'unit': unit
1830 }
1831 try:
1832 tres = cLabResult(aPK_obj=data)
1833 # exists already, so fail
1834 _log.error('will not overwrite existing test result')
1835 _log.debug(str(tres))
1836 return (None, tres)
1837 except gmExceptions.NoSuchClinItemError:
1838 _log.debug('test result not found - as expected, will create it')
1839 except gmExceptions.ConstructorError, msg:
1840 _log.exception(str(msg), sys.exc_info(), verbose=0)
1841 return (False, msg)
1842 if request is None:
1843 return (False, _('need lab request when inserting lab result'))
1844 # not found
1845 if encounter_id is None:
1846 encounter_id = request['pk_encounter']
1847 queries = []
1848 cmd = "insert into test_result (fk_encounter, fk_episode, fk_type, val_num, val_alpha, val_unit) values (%s, %s, %s, %s, %s, %s)"
1849 queries.append((cmd, [encounter_id, request['pk_episode'], test_type, val_num, val_alpha, unit]))
1850 cmd = "insert into lnk_result2lab_req (fk_result, fk_request) values ((select currval('test_result_pk_seq')), %s)"
1851 queries.append((cmd, [request['pk_request']]))
1852 cmd = "select currval('test_result_pk_seq')"
1853 queries.append((cmd, []))
1854 # insert new
1855 result, err = gmPG.run_commit('historica', queries, True)
1856 if result is None:
1857 return (False, err)
1858 try:
1859 tres = cLabResult(aPK_obj=result[0][0])
1860 except gmExceptions.ConstructorError, msg:
1861 _log.exception(str(msg), sys.exc_info(), verbose=0)
1862 return (False, msg)
1863 return (True, tres)
1864 #------------------------------------------------------------
1866 # sanity check
1867 if limit < 1:
1868 limit = 1
1869 # retrieve one more row than needed so we know there's more available ;-)
1870 lim = limit + 1
1871 cmd = """
1872 select pk_result
1873 from v_results4lab_req
1874 where reviewed is false
1875 order by pk_patient
1876 limit %s""" % lim
1877 rows = gmPG.run_ro_query('historica', cmd)
1878 if rows is None:
1879 _log.error('error retrieving unreviewed lab results')
1880 return (None, _('error retrieving unreviewed lab results'))
1881 if len(rows) == 0:
1882 return (False, [])
1883 # more than LIMIT rows ?
1884 if len(rows) == lim:
1885 more_avail = True
1886 # but deliver only LIMIT rows so that our assumption holds true...
1887 del rows[limit]
1888 else:
1889 more_avail = False
1890 results = []
1891 for row in rows:
1892 try:
1893 results.append(cLabResult(aPK_obj=row[0]))
1894 except gmExceptions.ConstructorError:
1895 _log.exception('skipping unreviewed lab result [%s]' % row[0], sys.exc_info(), verbose=0)
1896 return (more_avail, results)
1897 #------------------------------------------------------------
1899 lim = limit + 1
1900 cmd = "select pk from lab_request where is_pending is true limit %s" % lim
1901 rows = gmPG.run_ro_query('historica', cmd)
1902 if rows is None:
1903 _log.error('error retrieving pending lab requests')
1904 return (None, None)
1905 if len(rows) == 0:
1906 return (False, [])
1907 results = []
1908 # more than LIMIT rows ?
1909 if len(rows) == lim:
1910 too_many = True
1911 # but deliver only LIMIT rows so that our assumption holds true...
1912 del rows[limit]
1913 else:
1914 too_many = False
1915 requests = []
1916 for row in rows:
1917 try:
1918 requests.append(cLabRequest(aPK_obj=row[0]))
1919 except gmExceptions.ConstructorError:
1920 _log.exception('skipping pending lab request [%s]' % row[0], sys.exc_info(), verbose=0)
1921 return (too_many, requests)
1922 #------------------------------------------------------------
1924 """Get logically next request ID for given lab.
1925
1926 - incrementor_func:
1927 - if not supplied the next ID is guessed
1928 - if supplied it is applied to the most recently used ID
1929 """
1930 if type(lab) == types.IntType:
1931 lab_snippet = 'vlr.fk_test_org=%s'
1932 else:
1933 lab_snippet = 'vlr.lab_name=%s'
1934 lab = str(lab)
1935 cmd = """
1936 select request_id
1937 from lab_request lr0
1938 where lr0.clin_when = (
1939 select max(vlr.sampled_when)
1940 from v_lab_requests vlr
1941 where %s
1942 )""" % lab_snippet
1943 rows = gmPG.run_ro_query('historica', cmd, None, lab)
1944 if rows is None:
1945 _log.warning('error getting most recently used request ID for lab [%s]' % lab)
1946 return ''
1947 if len(rows) == 0:
1948 return ''
1949 most_recent = rows[0][0]
1950 # apply supplied incrementor
1951 if incrementor_func is not None:
1952 try:
1953 next = incrementor_func(most_recent)
1954 except TypeError:
1955 _log.error('cannot call incrementor function [%s]' % str(incrementor_func))
1956 return most_recent
1957 return next
1958 # try to be smart ourselves
1959 for pos in range(len(most_recent)):
1960 header = most_recent[:pos]
1961 trailer = most_recent[pos:]
1962 try:
1963 return '%s%s' % (header, str(int(trailer) + 1))
1964 except ValueError:
1965 header = most_recent[:-1]
1966 trailer = most_recent[-1:]
1967 return '%s%s' % (header, chr(ord(trailer) + 1))
1968 #============================================================
1970 """Calculate BMI.
1971
1972 mass: kg
1973 height: cm
1974 age: not yet used
1975
1976 returns:
1977 (True/False, data)
1978 True: data = (bmi, lower_normal, upper_normal)
1979 False: data = error message
1980 """
1981 converted, mass = gmTools.input2decimal(mass)
1982 if not converted:
1983 return False, u'mass: cannot convert <%s> to Decimal' % mass
1984
1985 converted, height = gmTools.input2decimal(height)
1986 if not converted:
1987 return False, u'height: cannot convert <%s> to Decimal' % height
1988
1989 approx_surface = (height / decimal.Decimal(100))**2
1990 bmi = mass / approx_surface
1991
1992 print mass, height, '->', approx_surface, '->', bmi
1993
1994 lower_normal_mass = 20.0 * approx_surface
1995 upper_normal_mass = 25.0 * approx_surface
1996
1997 return True, (bmi, lower_normal_mass, upper_normal_mass)
1998 #============================================================
1999 # main - unit testing
2000 #------------------------------------------------------------
2001 if __name__ == '__main__':
2002
2003 if len(sys.argv) < 2:
2004 sys.exit()
2005
2006 if sys.argv[1] != 'test':
2007 sys.exit()
2008
2009 import time
2010
2011 gmI18N.activate_locale()
2012 gmI18N.install_domain()
2013
2014 #------------------------------------------
2016 tr = create_test_result (
2017 encounter = 1,
2018 episode = 1,
2019 type = 1,
2020 intended_reviewer = 1,
2021 val_num = '12',
2022 val_alpha=None,
2023 unit = 'mg/dl'
2024 )
2025 print tr
2026 return tr
2027 #------------------------------------------
2031 #------------------------------------------
2036 #------------------------------------------
2038 print "test_result()"
2039 # lab_result = cLabResult(aPK_obj=4)
2040 data = {
2041 'patient_id': 12,
2042 'when_field': 'val_when',
2043 'when': '2000-09-17 18:23:00+02',
2044 'test_type': 9,
2045 'val_num': 17.3,
2046 'val_alpha': None,
2047 'unit': 'mg/l'
2048 }
2049 lab_result = cLabResult(aPK_obj=data)
2050 print lab_result
2051 fields = lab_result.get_fields()
2052 for field in fields:
2053 print field, ':', lab_result[field]
2054 print "updatable:", lab_result.get_updatable_fields()
2055 print time.time()
2056 print lab_result.get_patient()
2057 print time.time()
2058 #------------------------------------------
2060 print "test_request()"
2061 try:
2062 # lab_req = cLabRequest(aPK_obj=1)
2063 # lab_req = cLabRequest(req_id='EML#SC937-0176-CEC#11', lab=2)
2064 data = {
2065 'req_id': 'EML#SC937-0176-CEC#11',
2066 'lab': 'Enterprise Main Lab'
2067 }
2068 lab_req = cLabRequest(aPK_obj=data)
2069 except gmExceptions.ConstructorError, msg:
2070 print "no such lab request:", msg
2071 return
2072 print lab_req
2073 fields = lab_req.get_fields()
2074 for field in fields:
2075 print field, ':', lab_req[field]
2076 print "updatable:", lab_req.get_updatable_fields()
2077 print time.time()
2078 print lab_req.get_patient()
2079 print time.time()
2080 #--------------------------------------------------------
2085 #--------------------------------------------------------
2090 #--------------------------------------------------------
2092 print create_measurement_type (
2093 lab = None,
2094 abbrev = u'tBZ2',
2095 unit = u'mg%',
2096 name = 'BZ (test 2)'
2097 )
2098 #--------------------------------------------------------
2103 #--------------------------------------------------------
2108 #--------------------------------------------------------
2110 results = [
2111 cTestResult(aPK_obj=1),
2112 cTestResult(aPK_obj=2),
2113 cTestResult(aPK_obj=3)
2114 # cTestResult(aPK_obj=4)
2115 ]
2116 print format_test_results(results = results)
2117 #--------------------------------------------------------
2119 done, data = calculate_bmi(mass = sys.argv[2], height = sys.argv[3])
2120 bmi, low, high = data
2121
2122 print "BMI:", bmi
2123 print "low:", low, "kg"
2124 print "hi :", high, "kg"
2125 #--------------------------------------------------------
2130 #--------------------------------------------------------
2131
2132 #test_result()
2133 #test_create_test_result()
2134 #test_delete_test_result()
2135 #test_create_measurement_type()
2136 #test_lab_result()
2137 #test_request()
2138 #test_create_result()
2139 #test_unreviewed()
2140 #test_pending()
2141 #test_meta_test_type()
2142 #test_test_type()
2143 #test_format_test_results()
2144 #test_calculate_bmi()
2145 test_test_panel()
2146
2147 #============================================================
2148
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 10 03:57:03 2013 | http://epydoc.sourceforge.net |