| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 """GNUmed health related business object.
3
4 license: GPL v2 or later
5 """
6 #============================================================
7 __version__ = "$Revision: 1.157 $"
8 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>"
9
10 import types, sys, string, datetime, logging, time
11
12
13 if __name__ == '__main__':
14 sys.path.insert(0, '../../')
15 from Gnumed.pycommon import gmPG2
16 from Gnumed.pycommon import gmI18N
17 from Gnumed.pycommon import gmTools
18 from Gnumed.pycommon import gmDateTime
19 from Gnumed.pycommon import gmBusinessDBObject
20 from Gnumed.pycommon import gmNull
21 from Gnumed.pycommon import gmExceptions
22
23 from Gnumed.business import gmClinNarrative
24 from Gnumed.business import gmCoding
25
26
27 _log = logging.getLogger('gm.emr')
28 _log.info(__version__)
29
30 try: _
31 except NameError: _ = lambda x:x
32 #============================================================
33 # diagnostic certainty classification
34 #============================================================
35 __diagnostic_certainty_classification_map = None
36
38
39 global __diagnostic_certainty_classification_map
40
41 if __diagnostic_certainty_classification_map is None:
42 __diagnostic_certainty_classification_map = {
43 None: u'',
44 u'A': _(u'A: Sign'),
45 u'B': _(u'B: Cluster of signs'),
46 u'C': _(u'C: Syndromic diagnosis'),
47 u'D': _(u'D: Scientific diagnosis')
48 }
49
50 try:
51 return __diagnostic_certainty_classification_map[classification]
52 except KeyError:
53 return _(u'<%s>: unknown diagnostic certainty classification') % classification
54 #============================================================
55 # Health Issues API
56 #============================================================
57 laterality2str = {
58 None: u'?',
59 u'na': u'',
60 u'sd': _('bilateral'),
61 u'ds': _('bilateral'),
62 u's': _('left'),
63 u'd': _('right')
64 }
65
66 #============================================================
68 """Represents one health issue."""
69
70 _cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s"
71 _cmds_store_payload = [
72 u"""update clin.health_issue set
73 description = %(description)s,
74 summary = gm.nullify_empty_string(%(summary)s),
75 age_noted = %(age_noted)s,
76 laterality = gm.nullify_empty_string(%(laterality)s),
77 grouping = gm.nullify_empty_string(%(grouping)s),
78 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
79 is_active = %(is_active)s,
80 clinically_relevant = %(clinically_relevant)s,
81 is_confidential = %(is_confidential)s,
82 is_cause_of_death = %(is_cause_of_death)s
83 where
84 pk = %(pk_health_issue)s and
85 xmin = %(xmin_health_issue)s""",
86 u"select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
87 ]
88 _updatable_fields = [
89 'description',
90 'summary',
91 'grouping',
92 'age_noted',
93 'laterality',
94 'is_active',
95 'clinically_relevant',
96 'is_confidential',
97 'is_cause_of_death',
98 'diagnostic_certainty_classification'
99 ]
100 #--------------------------------------------------------
101 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
102 pk = aPK_obj
103
104 if (pk is not None) or (row is not None):
105 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
106 return
107
108 if patient is None:
109 cmd = u"""select *, xmin_health_issue from clin.v_health_issues
110 where
111 description = %(desc)s
112 and
113 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
114 else:
115 cmd = u"""select *, xmin_health_issue from clin.v_health_issues
116 where
117 description = %(desc)s
118 and
119 pk_patient = %(pat)s"""
120
121 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
122 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
123
124 if len(rows) == 0:
125 raise gmExceptions.NoSuchBusinessObjectError, 'no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient)
126
127 pk = rows[0][0]
128 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
129
130 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
131 #--------------------------------------------------------
132 # external API
133 #--------------------------------------------------------
135 """Method for issue renaming.
136
137 @param description
138 - the new descriptive name for the issue
139 @type description
140 - a string instance
141 """
142 # sanity check
143 if not type(description) in [str, unicode] or description.strip() == '':
144 _log.error('<description> must be a non-empty string')
145 return False
146 # update the issue description
147 old_description = self._payload[self._idx['description']]
148 self._payload[self._idx['description']] = description.strip()
149 self._is_modified = True
150 successful, data = self.save_payload()
151 if not successful:
152 _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
153 self._payload[self._idx['description']] = old_description
154 return False
155 return True
156 #--------------------------------------------------------
158 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
159 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
160 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
161 #--------------------------------------------------------
163 """ttl in days"""
164 open_episode = self.get_open_episode()
165 if open_episode is None:
166 return True
167 latest = open_episode.latest_access_date
168 ttl = datetime.timedelta(ttl)
169 now = datetime.datetime.now(tz=latest.tzinfo)
170 if (latest + ttl) > now:
171 return False
172 open_episode['episode_open'] = False
173 success, data = open_episode.save_payload()
174 if success:
175 return True
176 return False # should be an exception
177 #--------------------------------------------------------
179 open_episode = self.get_open_episode()
180 open_episode['episode_open'] = False
181 success, data = open_episode.save_payload()
182 if success:
183 return True
184 return False
185 #--------------------------------------------------------
187 cmd = u"select exists (select 1 from clin.episode where fk_health_issue = %s and is_open is True)"
188 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
189 return rows[0][0]
190 #--------------------------------------------------------
192 cmd = u"select pk from clin.episode where fk_health_issue = %s and is_open is True"
193 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
194 if len(rows) == 0:
195 return None
196 return cEpisode(aPK_obj=rows[0][0])
197 #--------------------------------------------------------
199 if self._payload[self._idx['age_noted']] is None:
200 return u'<???>'
201
202 # since we've already got an interval we are bound to use it,
203 # further transformation will only introduce more errors,
204 # later we can improve this deeper inside
205 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
206 #--------------------------------------------------------
208 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
209 cmd = u"INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
210 args = {
211 'item': self._payload[self._idx['pk_health_issue']],
212 'code': pk_code
213 }
214 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
215 return True
216 #--------------------------------------------------------
218 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
219 cmd = u"DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
220 args = {
221 'item': self._payload[self._idx['pk_health_issue']],
222 'code': pk_code
223 }
224 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
225 return True
226 #--------------------------------------------------------
228 rows = gmClinNarrative.get_as_journal (
229 issues = (self.pk_obj,),
230 order_by = u'pk_episode, pk_encounter, clin_when, scr, src_table'
231 )
232
233 if len(rows) == 0:
234 return u''
235
236 left_margin = u' ' * left_margin
237
238 lines = []
239 lines.append(_('Clinical data generated during encounters under this health issue:'))
240
241 prev_epi = None
242 for row in rows:
243 if row['pk_episode'] != prev_epi:
244 lines.append(u'')
245 prev_epi = row['pk_episode']
246
247 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding())
248 top_row = u'%s%s %s (%s) %s' % (
249 gmTools.u_box_top_left_arc,
250 gmTools.u_box_horiz_single,
251 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']],
252 when,
253 gmTools.u_box_horiz_single * 5
254 )
255 soap = gmTools.wrap (
256 text = row['narrative'],
257 width = 60,
258 initial_indent = u' ',
259 subsequent_indent = u' ' + left_margin
260 )
261 row_ver = u''
262 if row['row_version'] > 0:
263 row_ver = u'v%s: ' % row['row_version']
264 bottom_row = u'%s%s %s, %s%s %s' % (
265 u' ' * 40,
266 gmTools.u_box_horiz_light_heavy,
267 row['modified_by'],
268 row_ver,
269 row['date_modified'],
270 gmTools.u_box_horiz_heavy_light
271 )
272
273 lines.append(top_row)
274 lines.append(soap)
275 lines.append(bottom_row)
276
277 eol_w_margin = u'\n%s' % left_margin
278 return left_margin + eol_w_margin.join(lines) + u'\n'
279 #--------------------------------------------------------
280 - def format (self, left_margin=0, patient=None,
281 with_summary=True,
282 with_codes=True,
283 with_episodes=True,
284 with_encounters=True,
285 with_medications=True,
286 with_hospital_stays=True,
287 with_procedures=True,
288 with_family_history=True,
289 with_documents=True,
290 with_tests=True,
291 with_vaccinations=True
292 ):
293
294 if patient.ID != self._payload[self._idx['pk_patient']]:
295 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % (
296 patient.ID,
297 self._payload[self._idx['pk_health_issue']],
298 self._payload[self._idx['pk_patient']]
299 )
300 raise ValueError(msg)
301
302 lines = []
303
304 lines.append(_('Health Issue %s%s%s%s [#%s]') % (
305 u'\u00BB',
306 self._payload[self._idx['description']],
307 u'\u00AB',
308 gmTools.coalesce (
309 initial = self.laterality_description,
310 instead = u'',
311 template_initial = u' (%s)',
312 none_equivalents = [None, u'', u'?']
313 ),
314 self._payload[self._idx['pk_health_issue']]
315 ))
316
317 if self._payload[self._idx['is_confidential']]:
318 lines.append('')
319 lines.append(_(' ***** CONFIDENTIAL *****'))
320 lines.append('')
321
322 if self._payload[self._idx['is_cause_of_death']]:
323 lines.append('')
324 lines.append(_(' contributed to death of patient'))
325 lines.append('')
326
327 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
328 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % (
329 enc['l10n_type'],
330 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
331 enc['last_affirmed_original_tz'].strftime('%H:%M'),
332 self._payload[self._idx['pk_encounter']]
333 ))
334
335 if self._payload[self._idx['age_noted']] is not None:
336 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable())
337
338 lines.append(u' ' + _('Status') + u': %s, %s%s' % (
339 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')),
340 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')),
341 gmTools.coalesce (
342 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
343 instead = u'',
344 template_initial = u', %s',
345 none_equivalents = [None, u'']
346 )
347 ))
348
349 if with_summary:
350 if self._payload[self._idx['summary']] is not None:
351 lines.append(u' %s:' % _('Synopsis'))
352 lines.append(gmTools.wrap (
353 text = self._payload[self._idx['summary']],
354 width = 60,
355 initial_indent = u' ',
356 subsequent_indent = u' '
357 ))
358
359 # codes ?
360 if with_codes:
361 codes = self.generic_codes
362 if len(codes) > 0:
363 lines.append(u'')
364 for c in codes:
365 lines.append(u' %s: %s (%s - %s)' % (
366 c['code'],
367 c['term'],
368 c['name_short'],
369 c['version']
370 ))
371 del codes
372
373 lines.append(u'')
374
375 emr = patient.get_emr()
376
377 # episodes
378 if with_episodes:
379 epis = emr.get_episodes(issues = [self._payload[self._idx['pk_health_issue']]])
380 if epis is None:
381 lines.append(_('Error retrieving episodes for this health issue.'))
382 elif len(epis) == 0:
383 lines.append(_('There are no episodes for this health issue.'))
384 else:
385 lines.append (
386 _('Episodes: %s (most recent: %s%s%s)') % (
387 len(epis),
388 gmTools.u_left_double_angle_quote,
389 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'],
390 gmTools.u_right_double_angle_quote
391 )
392 )
393 for epi in epis:
394 lines.append(u' \u00BB%s\u00AB (%s)' % (
395 epi['description'],
396 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed'))
397 ))
398 lines.append('')
399
400 # encounters
401 if with_encounters:
402 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
403 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
404
405 if first_encounter is None or last_encounter is None:
406 lines.append(_('No encounters found for this health issue.'))
407 else:
408 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]])
409 lines.append(_('Encounters: %s (%s - %s):') % (
410 len(encs),
411 first_encounter['started_original_tz'].strftime('%m/%Y'),
412 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y')
413 ))
414 lines.append(_(' Most recent: %s - %s') % (
415 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
416 last_encounter['last_affirmed_original_tz'].strftime('%H:%M')
417 ))
418
419 # medications
420 if with_medications:
421 meds = emr.get_current_substance_intake (
422 issues = [ self._payload[self._idx['pk_health_issue']] ],
423 order_by = u'is_currently_active, started, substance'
424 )
425 if len(meds) > 0:
426 lines.append(u'')
427 lines.append(_('Active medications: %s') % len(meds))
428 for m in meds:
429 lines.append(m.format(left_margin = (left_margin + 1)))
430 del meds
431
432 # hospitalizations
433 if with_hospital_stays:
434 stays = emr.get_hospital_stays (
435 issues = [ self._payload[self._idx['pk_health_issue']] ]
436 )
437 if len(stays) > 0:
438 lines.append(u'')
439 lines.append(_('Hospitalizations: %s') % len(stays))
440 for s in stays:
441 lines.append(s.format(left_margin = (left_margin + 1)))
442 del stays
443
444 # procedures
445 if with_procedures:
446 procs = emr.get_performed_procedures (
447 issues = [ self._payload[self._idx['pk_health_issue']] ]
448 )
449 if len(procs) > 0:
450 lines.append(u'')
451 lines.append(_('Procedures performed: %s') % len(procs))
452 for p in procs:
453 lines.append(p.format(left_margin = (left_margin + 1)))
454 del procs
455
456 # family history
457 if with_family_history:
458 fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ])
459 if len(fhx) > 0:
460 lines.append(u'')
461 lines.append(_('Family History: %s') % len(fhx))
462 for f in fhx:
463 lines.append(f.format (
464 left_margin = (left_margin + 1),
465 include_episode = True,
466 include_comment = True,
467 include_codes = False
468 ))
469 del fhx
470
471 epis = self.get_episodes()
472 if len(epis) > 0:
473 epi_pks = [ e['pk_episode'] for e in epis ]
474
475 # documents
476 if with_documents:
477 doc_folder = patient.get_document_folder()
478 docs = doc_folder.get_documents(episodes = epi_pks)
479 if len(docs) > 0:
480 lines.append(u'')
481 lines.append(_('Documents: %s') % len(docs))
482 del docs
483
484 # test results
485 if with_tests:
486 tests = emr.get_test_results_by_date(episodes = epi_pks)
487 if len(tests) > 0:
488 lines.append(u'')
489 lines.append(_('Measurements and Results: %s') % len(tests))
490 del tests
491
492 # vaccinations
493 if with_vaccinations:
494 vaccs = emr.get_vaccinations(episodes = epi_pks, order_by = u'date_given, vaccine')
495 if len(vaccs) > 0:
496 lines.append(u'')
497 lines.append(_('Vaccinations:'))
498 for vacc in vaccs:
499 lines.extend(vacc.format(with_reaction = True))
500 del vaccs
501
502 del epis
503
504 left_margin = u' ' * left_margin
505 eol_w_margin = u'\n%s' % left_margin
506 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n')
507 return left_margin + eol_w_margin.join(lines) + u'\n'
508 #--------------------------------------------------------
509 # properties
510 #--------------------------------------------------------
511 episodes = property(get_episodes, lambda x:x)
512 #--------------------------------------------------------
513 open_episode = property(get_open_episode, lambda x:x)
514 #--------------------------------------------------------
515 has_open_episode = property(has_open_episode, lambda x:x)
516 #--------------------------------------------------------
518 cmd = u"""SELECT pk_episode FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY started_first limit 1"""
519 args = {'issue': self.pk_obj}
520 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
521 if len(rows) == 0:
522 return None
523 return cEpisode(aPK_obj = rows[0][0])
524
525 first_episode = property(_get_first_episode, lambda x:x)
526 #--------------------------------------------------------
528 cmd = u"""SELECT
529 coalesce (
530 (SELECT pk FROM clin.episode WHERE fk_health_issue = %(issue)s AND is_open IS TRUE),
531 (SELECT pk_episode AS pk FROM clin.v_pat_episodes WHERE pk_health_issue = %(issue)s ORDER BY last_affirmed DESC limit 1)
532 )"""
533 args = {'issue': self.pk_obj}
534 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
535 if len(rows) == 0:
536 return None
537 if rows[0][0] is None:
538 return None
539 return cEpisode(aPK_obj = rows[0][0])
540
541 latest_episode = property(_get_latest_episode, lambda x:x)
542 #--------------------------------------------------------
543 # Steffi suggested we divide into safe and assumed start dates
545 """This returns the date when we can assume to safely
546 KNOW the health issue existed (because
547 the provider said so)."""
548 args = {
549 'enc': self._payload[self._idx['pk_encounter']],
550 'pk': self._payload[self._idx['pk_health_issue']]
551 }
552 cmd = u"""
553 SELECT COALESCE (
554 -- this one must override all:
555 -- .age_noted if not null and DOB is known
556 (CASE
557 WHEN c_hi.age_noted IS NULL
558 THEN NULL::timestamp with time zone
559 WHEN
560 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
561 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
562 )) IS NULL
563 THEN NULL::timestamp with time zone
564 ELSE
565 c_hi.age_noted + (
566 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
567 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
568 )
569 )
570 END),
571
572 -- start of encounter in which created, earliest = explicitely set
573 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
574 c_hi.fk_encounter
575 --SELECT fk_encounter FROM clin.health_issue WHERE clin.health_issue.pk = %(pk)s
576 ))
577 )
578 FROM clin.health_issue c_hi
579 WHERE c_hi.pk = %(pk)s"""
580 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
581 return rows[0][0]
582
583 safe_start_date = property(_get_safe_start_date, lambda x:x)
584 #--------------------------------------------------------
586 args = {'pk': self._payload[self._idx['pk_health_issue']]}
587 cmd = u"""
588 SELECT MIN(earliest) FROM (
589 -- last modification, earliest = when created in/changed to the current state
590 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
591
592 UNION ALL
593 -- last modification of encounter in which created, earliest = initial creation of that encounter
594 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
595 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
596 ))
597
598 UNION ALL
599 -- earliest explicit .clin_when of clinical items linked to this health_issue
600 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
601
602 UNION ALL
603 -- earliest modification time of clinical items linked to this health issue
604 -- this CAN be used since if an item is linked to a health issue it can be
605 -- assumed the health issue (should have) existed at the time of creation
606 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
607
608 UNION ALL
609 -- earliest start of encounters of clinical items linked to this episode
610 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
611 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
612 ))
613
614 -- here we should be looking at
615 -- .best_guess_start_date of all episodes linked to this encounter
616
617 ) AS candidates"""
618
619 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
620 return rows[0][0]
621
622 possible_start_date = property(_get_possible_start_date)
623 #--------------------------------------------------------
625 if self._payload[self._idx['is_active']]:
626 return gmDateTime.pydt_now_here()
627 if self.has_open_episode:
628 return gmDateTime.pydt_now_here()
629 return self.latest_access_date
630
631 end_date = property(_get_end_date)
632 #--------------------------------------------------------
634 args = {
635 'enc': self._payload[self._idx['pk_encounter']],
636 'pk': self._payload[self._idx['pk_health_issue']]
637 }
638 cmd = u"""
639 SELECT
640 MAX(latest)
641 FROM (
642 -- last modification, latest = when last changed to the current state
643 -- DO NOT USE: database upgrades may change this field
644 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
645
646 --UNION ALL
647 -- last modification of encounter in which created, latest = initial creation of that encounter
648 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
649 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
650 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
651 -- )
652 --)
653
654 --UNION ALL
655 -- end of encounter in which created, latest = explicitely set
656 -- DO NOT USE: we can retrospectively create issues which
657 -- DO NOT USE: are long since finished
658 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
659 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
660 -- )
661 --)
662
663 UNION ALL
664 -- latest end of encounters of clinical items linked to this issue
665 (SELECT
666 MAX(last_affirmed) AS latest
667 FROM clin.encounter
668 WHERE pk IN (
669 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
670 )
671 )
672
673 UNION ALL
674 -- latest explicit .clin_when of clinical items linked to this issue
675 (SELECT
676 MAX(clin_when) AS latest
677 FROM clin.v_pat_items
678 WHERE pk_health_issue = %(pk)s
679 )
680
681 -- latest modification time of clinical items linked to this issue
682 -- this CAN be used since if an item is linked to an issue it can be
683 -- assumed the issue (should have) existed at the time of modification
684 -- DO NOT USE, because typo fixes should not extend the issue
685 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
686
687 ) AS candidates"""
688 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
689 return rows[0][0]
690
691 latest_access_date = property(_get_latest_access_date)
692 #--------------------------------------------------------
694 try:
695 return laterality2str[self._payload[self._idx['laterality']]]
696 except KeyError:
697 return u'<???>'
698
699 laterality_description = property(_get_laterality_description, lambda x:x)
700 #--------------------------------------------------------
702 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
703
704 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
705 #--------------------------------------------------------
707 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
708 return []
709
710 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
711 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
712 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
713 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
714
716 queries = []
717 # remove all codes
718 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
719 queries.append ({
720 'cmd': u'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
721 'args': {
722 'issue': self._payload[self._idx['pk_health_issue']],
723 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
724 }
725 })
726 # add new codes
727 for pk_code in pk_codes:
728 queries.append ({
729 'cmd': u'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
730 'args': {
731 'issue': self._payload[self._idx['pk_health_issue']],
732 'pk_code': pk_code
733 }
734 })
735 if len(queries) == 0:
736 return
737 # run it all in one transaction
738 rows, idx = gmPG2.run_rw_queries(queries = queries)
739 return
740
741 generic_codes = property(_get_generic_codes, _set_generic_codes)
742 #============================================================
744 """Creates a new health issue for a given patient.
745
746 description - health issue name
747 """
748 try:
749 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
750 return h_issue
751 except gmExceptions.NoSuchBusinessObjectError:
752 pass
753
754 queries = []
755 cmd = u"insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
756 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
757
758 cmd = u"select currval('clin.health_issue_pk_seq')"
759 queries.append({'cmd': cmd})
760
761 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
762 h_issue = cHealthIssue(aPK_obj = rows[0][0])
763
764 return h_issue
765 #-----------------------------------------------------------
767 if isinstance(health_issue, cHealthIssue):
768 pk = health_issue['pk_health_issue']
769 else:
770 pk = int(health_issue)
771
772 try:
773 gmPG2.run_rw_queries(queries = [{'cmd': u'delete from clin.health_issue where pk=%(pk)s', 'args': {'pk': pk}}])
774 except gmPG2.dbapi.IntegrityError:
775 # should be parsing pgcode/and or error message
776 _log.exception('cannot delete health issue')
777 raise gmExceptions.DatabaseObjectInUseError('cannot delete health issue, it is in use')
778 #------------------------------------------------------------
779 # use as dummy for unassociated episodes
781 issue = {
782 'pk_health_issue': None,
783 'description': _('Unattributed episodes'),
784 'age_noted': None,
785 'laterality': u'na',
786 'is_active': True,
787 'clinically_relevant': True,
788 'is_confidential': None,
789 'is_cause_of_death': False,
790 'is_dummy': True,
791 'grouping': None
792 }
793 return issue
794 #-----------------------------------------------------------
796 return cProblem (
797 aPK_obj = {
798 'pk_patient': health_issue['pk_patient'],
799 'pk_health_issue': health_issue['pk_health_issue'],
800 'pk_episode': None
801 },
802 try_potential_problems = allow_irrelevant
803 )
804 #============================================================
805 # episodes API
806 #============================================================
808 """Represents one clinical episode.
809 """
810 _cmd_fetch_payload = u"select * from clin.v_pat_episodes where pk_episode=%s"
811 _cmds_store_payload = [
812 u"""update clin.episode set
813 fk_health_issue = %(pk_health_issue)s,
814 is_open = %(episode_open)s::boolean,
815 description = %(description)s,
816 summary = gm.nullify_empty_string(%(summary)s),
817 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
818 where
819 pk = %(pk_episode)s and
820 xmin = %(xmin_episode)s""",
821 u"""select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
822 ]
823 _updatable_fields = [
824 'pk_health_issue',
825 'episode_open',
826 'description',
827 'summary',
828 'diagnostic_certainty_classification'
829 ]
830 #--------------------------------------------------------
831 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None):
832 pk = aPK_obj
833 if pk is None and row is None:
834
835 where_parts = [u'description = %(desc)s']
836
837 if id_patient is not None:
838 where_parts.append(u'pk_patient = %(pat)s')
839
840 if health_issue is not None:
841 where_parts.append(u'pk_health_issue = %(issue)s')
842
843 if encounter is not None:
844 where_parts.append(u'pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)')
845
846 args = {
847 'pat': id_patient,
848 'issue': health_issue,
849 'enc': encounter,
850 'desc': name
851 }
852
853 cmd = u"select * from clin.v_pat_episodes where %s" % u' and '.join(where_parts)
854
855 rows, idx = gmPG2.run_ro_queries(
856 queries = [{'cmd': cmd, 'args': args}],
857 get_col_idx=True
858 )
859
860 if len(rows) == 0:
861 raise gmExceptions.NoSuchBusinessObjectError, 'no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter)
862
863 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
864 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
865
866 else:
867 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
868 #--------------------------------------------------------
869 # external API
870 #--------------------------------------------------------
872 """Get earliest and latest access to this episode.
873
874 Returns a tuple(earliest, latest).
875 """
876 return (self.best_guess_start_date, self.latest_access_date)
877 #--------------------------------------------------------
880 #--------------------------------------------------------
882 return gmClinNarrative.get_narrative (
883 soap_cats = soap_cats,
884 encounters = encounters,
885 episodes = [self.pk_obj],
886 order_by = order_by
887 )
888 #--------------------------------------------------------
890 """Method for episode editing, that is, episode renaming.
891
892 @param description
893 - the new descriptive name for the encounter
894 @type description
895 - a string instance
896 """
897 # sanity check
898 if description.strip() == '':
899 _log.error('<description> must be a non-empty string instance')
900 return False
901 # update the episode description
902 old_description = self._payload[self._idx['description']]
903 self._payload[self._idx['description']] = description.strip()
904 self._is_modified = True
905 successful, data = self.save_payload()
906 if not successful:
907 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
908 self._payload[self._idx['description']] = old_description
909 return False
910 return True
911 #--------------------------------------------------------
913 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
914
915 if pk_code in self._payload[self._idx['pk_generic_codes']]:
916 return
917
918 cmd = u"""
919 INSERT INTO clin.lnk_code2episode
920 (fk_item, fk_generic_code)
921 SELECT
922 %(item)s,
923 %(code)s
924 WHERE NOT EXISTS (
925 SELECT 1 FROM clin.lnk_code2episode
926 WHERE
927 fk_item = %(item)s
928 AND
929 fk_generic_code = %(code)s
930 )"""
931 args = {
932 'item': self._payload[self._idx['pk_episode']],
933 'code': pk_code
934 }
935 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
936 return
937 #--------------------------------------------------------
939 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
940 cmd = u"DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
941 args = {
942 'item': self._payload[self._idx['pk_episode']],
943 'code': pk_code
944 }
945 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
946 return True
947 #--------------------------------------------------------
949 rows = gmClinNarrative.get_as_journal (
950 episodes = (self.pk_obj,),
951 order_by = u'pk_encounter, clin_when, scr, src_table'
952 #order_by = u'pk_encounter, scr, clin_when, src_table'
953 )
954
955 if len(rows) == 0:
956 return u''
957
958 lines = []
959
960 lines.append(_('Clinical data generated during encounters within this episode:'))
961
962 left_margin = u' ' * left_margin
963
964 prev_enc = None
965 for row in rows:
966 if row['pk_encounter'] != prev_enc:
967 lines.append(u'')
968 prev_enc = row['pk_encounter']
969
970 when = row['clin_when'].strftime(date_format).decode(gmI18N.get_encoding())
971 top_row = u'%s%s %s (%s) %s' % (
972 gmTools.u_box_top_left_arc,
973 gmTools.u_box_horiz_single,
974 gmClinNarrative.soap_cat2l10n_str[row['real_soap_cat']],
975 when,
976 gmTools.u_box_horiz_single * 5
977 )
978 soap = gmTools.wrap (
979 text = row['narrative'],
980 width = 60,
981 initial_indent = u' ',
982 subsequent_indent = u' ' + left_margin
983 )
984 row_ver = u''
985 if row['row_version'] > 0:
986 row_ver = u'v%s: ' % row['row_version']
987 bottom_row = u'%s%s %s, %s%s %s' % (
988 u' ' * 40,
989 gmTools.u_box_horiz_light_heavy,
990 row['modified_by'],
991 row_ver,
992 row['date_modified'],
993 gmTools.u_box_horiz_heavy_light
994 )
995
996 lines.append(top_row)
997 lines.append(soap)
998 lines.append(bottom_row)
999
1000 eol_w_margin = u'\n%s' % left_margin
1001 return left_margin + eol_w_margin.join(lines) + u'\n'
1002 #--------------------------------------------------------
1003 - def format(self, left_margin=0, patient=None,
1004 with_summary=True,
1005 with_codes=True,
1006 with_encounters=True,
1007 with_documents=True,
1008 with_hospital_stays=True,
1009 with_procedures=True,
1010 with_family_history=True,
1011 with_tests=True,
1012 with_vaccinations=True,
1013 with_health_issue=False
1014 ):
1015
1016 if patient.ID != self._payload[self._idx['pk_patient']]:
1017 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % (
1018 patient.ID,
1019 self._payload[self._idx['pk_episode']],
1020 self._payload[self._idx['pk_patient']]
1021 )
1022 raise ValueError(msg)
1023
1024 lines = []
1025
1026 # episode details
1027 lines.append (_('Episode %s%s%s [#%s]') % (
1028 gmTools.u_left_double_angle_quote,
1029 self._payload[self._idx['description']],
1030 gmTools.u_right_double_angle_quote,
1031 self._payload[self._idx['pk_episode']]
1032 ))
1033
1034 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
1035 lines.append (u' ' + _('Created during encounter: %s (%s - %s) [#%s]') % (
1036 enc['l10n_type'],
1037 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1038 enc['last_affirmed_original_tz'].strftime('%H:%M'),
1039 self._payload[self._idx['pk_encounter']]
1040 ))
1041
1042 emr = patient.get_emr()
1043 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]])
1044 first_encounter = None
1045 last_encounter = None
1046 if (encs is not None) and (len(encs) > 0):
1047 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']])
1048 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']])
1049 if self._payload[self._idx['episode_open']]:
1050 end = gmDateTime.pydt_now_here()
1051 end_str = gmTools.u_ellipsis
1052 else:
1053 end = last_encounter['last_affirmed']
1054 end_str = last_encounter['last_affirmed'].strftime('%m/%Y')
1055 age = gmDateTime.format_interval_medically(end - first_encounter['started'])
1056 lines.append(_(' Duration: %s (%s - %s)') % (
1057 age,
1058 first_encounter['started'].strftime('%m/%Y'),
1059 end_str
1060 ))
1061
1062 lines.append(u' ' + _('Status') + u': %s%s' % (
1063 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')),
1064 gmTools.coalesce (
1065 initial = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
1066 instead = u'',
1067 template_initial = u', %s',
1068 none_equivalents = [None, u'']
1069 )
1070 ))
1071
1072 if with_health_issue:
1073 lines.append(u' ' + _('Health issue') + u': %s' % gmTools.coalesce (
1074 self._payload[self._idx['health_issue']],
1075 _('none associated')
1076 ))
1077
1078 if with_summary:
1079 if self._payload[self._idx['summary']] is not None:
1080 lines.append(u' %s:' % _('Synopsis'))
1081 lines.append(gmTools.wrap (
1082 text = self._payload[self._idx['summary']],
1083 width = 60,
1084 initial_indent = u' ',
1085 subsequent_indent = u' '
1086 )
1087 )
1088
1089 # codes
1090 if with_codes:
1091 codes = self.generic_codes
1092 if len(codes) > 0:
1093 lines.append(u'')
1094 for c in codes:
1095 lines.append(u' %s: %s (%s - %s)' % (
1096 c['code'],
1097 c['term'],
1098 c['name_short'],
1099 c['version']
1100 ))
1101 del codes
1102
1103 lines.append(u'')
1104
1105 # encounters
1106 if with_encounters:
1107 if encs is None:
1108 lines.append(_('Error retrieving encounters for this health issue.'))
1109 elif len(encs) == 0:
1110 #lines.append(_('There are no encounters for this issue.'))
1111 pass
1112 else:
1113 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M'))
1114
1115 if len(encs) < 4:
1116 line = _('%s encounter(s) (%s - %s):')
1117 else:
1118 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):')
1119 lines.append(line % (
1120 len(encs),
1121 first_encounter['started'].strftime('%m/%Y'),
1122 last_encounter['last_affirmed'].strftime('%m/%Y')
1123 ))
1124
1125 lines.append(u' %s - %s (%s):%s' % (
1126 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1127 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'),
1128 first_encounter['l10n_type'],
1129 gmTools.coalesce (
1130 first_encounter['assessment_of_encounter'],
1131 gmTools.coalesce (
1132 first_encounter['reason_for_encounter'],
1133 u'',
1134 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE'))
1135 ),
1136 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE'))
1137 )
1138 ))
1139
1140 if len(encs) > 4:
1141 lines.append(_(' ... %s skipped ...') % (len(encs) - 4))
1142
1143 for enc in encs[1:][-3:]:
1144 lines.append(u' %s - %s (%s):%s' % (
1145 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1146 enc['last_affirmed_original_tz'].strftime('%H:%M'),
1147 enc['l10n_type'],
1148 gmTools.coalesce (
1149 enc['assessment_of_encounter'],
1150 gmTools.coalesce (
1151 enc['reason_for_encounter'],
1152 u'',
1153 u' \u00BB%s\u00AB' + (u' (%s)' % _('RFE'))
1154 ),
1155 u' \u00BB%s\u00AB' + (u' (%s)' % _('AOE'))
1156 )
1157 ))
1158 del encs
1159
1160 # spell out last encounter
1161 if last_encounter is not None:
1162 lines.append('')
1163 lines.append(_('Progress notes in most recent encounter:'))
1164 lines.extend(last_encounter.format_soap (
1165 episodes = [ self._payload[self._idx['pk_episode']] ],
1166 left_margin = left_margin,
1167 soap_cats = 'soapu',
1168 emr = emr
1169 ))
1170
1171 # documents
1172 if with_documents:
1173 doc_folder = patient.get_document_folder()
1174 docs = doc_folder.get_documents (
1175 episodes = [ self._payload[self._idx['pk_episode']] ]
1176 )
1177 if len(docs) > 0:
1178 lines.append('')
1179 lines.append(_('Documents: %s') % len(docs))
1180 for d in docs:
1181 lines.append(u' %s %s:%s%s' % (
1182 d['clin_when'].strftime('%Y-%m-%d'),
1183 d['l10n_type'],
1184 gmTools.coalesce(d['comment'], u'', u' "%s"'),
1185 gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
1186 ))
1187 del docs
1188
1189 # hospitalizations
1190 if with_hospital_stays:
1191 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ])
1192 if len(stays) > 0:
1193 lines.append('')
1194 lines.append(_('Hospitalizations: %s') % len(stays))
1195 for s in stays:
1196 lines.append(s.format(left_margin = (left_margin + 1)))
1197 del stays
1198
1199 # procedures
1200 if with_procedures:
1201 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ])
1202 if len(procs) > 0:
1203 lines.append(u'')
1204 lines.append(_('Procedures performed: %s') % len(procs))
1205 for p in procs:
1206 lines.append(p.format (
1207 left_margin = (left_margin + 1),
1208 include_episode = False,
1209 include_codes = True
1210 ))
1211 del procs
1212
1213 # family history
1214 if with_family_history:
1215 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ])
1216 if len(fhx) > 0:
1217 lines.append(u'')
1218 lines.append(_('Family History: %s') % len(fhx))
1219 for f in fhx:
1220 lines.append(f.format (
1221 left_margin = (left_margin + 1),
1222 include_episode = False,
1223 include_comment = True,
1224 include_codes = True
1225 ))
1226 del fhx
1227
1228 # test results
1229 if with_tests:
1230 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ])
1231 if len(tests) > 0:
1232 lines.append('')
1233 lines.append(_('Measurements and Results:'))
1234 for t in tests:
1235 lines.append(t.format (
1236 with_review = False,
1237 with_ranges = False,
1238 with_evaluation = False,
1239 with_episode = False,
1240 with_type_details = False,
1241 date_format = '%Y %b %d'
1242 ))
1243 del tests
1244
1245 # vaccinations
1246 if with_vaccinations:
1247 vaccs = emr.get_vaccinations (
1248 episodes = [ self._payload[self._idx['pk_episode']] ],
1249 order_by = u'date_given DESC, vaccine'
1250 )
1251 if len(vaccs) > 0:
1252 lines.append(u'')
1253 lines.append(_('Vaccinations:'))
1254 for vacc in vaccs:
1255 lines.extend(vacc.format (
1256 with_indications = True,
1257 with_comment = True,
1258 with_reaction = True,
1259 date_format = '%Y-%m-%d'
1260 ))
1261 del vaccs
1262
1263 left_margin = u' ' * left_margin
1264 eol_w_margin = u'\n%s' % left_margin
1265 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n')
1266 return left_margin + eol_w_margin.join(lines) + u'\n'
1267 #--------------------------------------------------------
1268 # properties
1269 #--------------------------------------------------------
1271 cmd = u"""
1272 SELECT
1273 MIN(earliest)
1274 FROM (
1275 -- last modification, earliest = when created in/changed to the current state
1276 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1277
1278 UNION ALL
1279
1280 -- last modification of encounter in which created, earliest = initial creation of that encounter
1281 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1282 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1283 )
1284 )
1285 UNION ALL
1286
1287 -- start of encounter in which created, earliest = explicitely set
1288 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1289 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1290 )
1291 )
1292 UNION ALL
1293
1294 -- earliest start of encounters of clinical items linked to this episode
1295 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1296 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1297 )
1298 )
1299 UNION ALL
1300
1301 -- earliest explicit .clin_when of clinical items linked to this episode
1302 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1303
1304 UNION ALL
1305
1306 -- earliest modification time of clinical items linked to this episode
1307 -- this CAN be used since if an item is linked to an episode it can be
1308 -- assumed the episode (should have) existed at the time of creation
1309 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1310
1311 -- not sure about this one:
1312 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1313
1314 ) AS candidates"""
1315 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1316 return rows[0][0]
1317
1318 best_guess_start_date = property(_get_best_guess_start_date)
1319 #--------------------------------------------------------
1321 cmd = u"""
1322 SELECT
1323 MAX(latest)
1324 FROM (
1325 -- last modification, latest = when last changed to the current state
1326 (SELECT c_epi.modified_when AS latest, 'clin.episode.modified_when'::text AS candidate FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1327
1328 UNION ALL
1329
1330 -- last modification of encounter in which created, latest = initial creation of that encounter
1331 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1332 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1333 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1334 -- )
1335 --)
1336
1337 -- end of encounter in which created, latest = explicitely set
1338 -- DO NOT USE: we can retrospectively create episodes which
1339 -- DO NOT USE: are long since finished
1340 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1341 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1342 -- )
1343 --)
1344
1345 -- latest end of encounters of clinical items linked to this episode
1346 (SELECT
1347 MAX(last_affirmed) AS latest,
1348 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1349 FROM clin.encounter
1350 WHERE pk IN (
1351 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1352 )
1353 )
1354 UNION ALL
1355
1356 -- latest explicit .clin_when of clinical items linked to this episode
1357 (SELECT
1358 MAX(clin_when) AS latest,
1359 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1360 FROM clin.clin_root_item
1361 WHERE fk_episode = %(pk)s
1362 )
1363
1364 -- latest modification time of clinical items linked to this episode
1365 -- this CAN be used since if an item is linked to an episode it can be
1366 -- assumed the episode (should have) existed at the time of creation
1367 -- DO NOT USE, because typo fixes should not extend the episode
1368 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1369
1370 -- not sure about this one:
1371 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1372
1373 ) AS candidates"""
1374 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1375 #_log.debug('last episode access: %s (%s)', rows[0][0], rows[0][1])
1376 return rows[0][0]
1377
1378 latest_access_date = property(_get_latest_access_date)
1379 #--------------------------------------------------------
1381 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1382
1383 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1384 #--------------------------------------------------------
1386 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1387 return []
1388
1389 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
1390 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1391 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1392 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1393
1395 queries = []
1396 # remove all codes
1397 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1398 queries.append ({
1399 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1400 'args': {
1401 'epi': self._payload[self._idx['pk_episode']],
1402 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1403 }
1404 })
1405 # add new codes
1406 for pk_code in pk_codes:
1407 queries.append ({
1408 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1409 'args': {
1410 'epi': self._payload[self._idx['pk_episode']],
1411 'pk_code': pk_code
1412 }
1413 })
1414 if len(queries) == 0:
1415 return
1416 # run it all in one transaction
1417 rows, idx = gmPG2.run_rw_queries(queries = queries)
1418 return
1419
1420 generic_codes = property(_get_generic_codes, _set_generic_codes)
1421 #--------------------------------------------------------
1423 cmd = u"""SELECT EXISTS (
1424 SELECT 1 FROM clin.clin_narrative
1425 WHERE
1426 fk_episode = %(epi)s
1427 AND
1428 fk_encounter IN (
1429 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1430 )
1431 )"""
1432 args = {
1433 u'pat': self._payload[self._idx['pk_patient']],
1434 u'epi': self._payload[self._idx['pk_episode']]
1435 }
1436 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1437 return rows[0][0]
1438
1439 has_narrative = property(_get_has_narrative, lambda x:x)
1440 #============================================================
1441 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
1442 """Creates a new episode for a given patient's health issue.
1443
1444 pk_health_issue - given health issue PK
1445 episode_name - name of episode
1446 """
1447 if not allow_dupes:
1448 try:
1449 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter)
1450 if episode['episode_open'] != is_open:
1451 episode['episode_open'] = is_open
1452 episode.save_payload()
1453 return episode
1454 except gmExceptions.ConstructorError:
1455 pass
1456
1457 queries = []
1458 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)"
1459 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1460 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"})
1461 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True)
1462
1463 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1464 return episode
1465 #-----------------------------------------------------------
1467 if isinstance(episode, cEpisode):
1468 pk = episode['pk_episode']
1469 else:
1470 pk = int(episode)
1471
1472 cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s'
1473
1474 try:
1475 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1476 except gmPG2.dbapi.IntegrityError:
1477 # should be parsing pgcode/and or error message
1478 _log.exception('cannot delete episode, it is in use')
1479 return False
1480
1481 return True
1482 #-----------------------------------------------------------
1484 return cProblem (
1485 aPK_obj = {
1486 'pk_patient': episode['pk_patient'],
1487 'pk_episode': episode['pk_episode'],
1488 'pk_health_issue': episode['pk_health_issue']
1489 },
1490 try_potential_problems = allow_closed
1491 )
1492 #============================================================
1493 # encounter API
1494 #============================================================
1496 """Represents one encounter."""
1497
1498 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s"
1499 _cmds_store_payload = [
1500 u"""UPDATE clin.encounter SET
1501 started = %(started)s,
1502 last_affirmed = %(last_affirmed)s,
1503 fk_location = %(pk_location)s,
1504 fk_type = %(pk_type)s,
1505 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
1506 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
1507 WHERE
1508 pk = %(pk_encounter)s AND
1509 xmin = %(xmin_encounter)s
1510 """,
1511 # need to return all fields so we can survive in-place upgrades
1512 u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s"""
1513 ]
1514 _updatable_fields = [
1515 'started',
1516 'last_affirmed',
1517 'pk_location',
1518 'pk_type',
1519 'reason_for_encounter',
1520 'assessment_of_encounter'
1521 ]
1522 #--------------------------------------------------------
1524 """Set the encounter as the active one.
1525
1526 "Setting active" means making sure the encounter
1527 row has the youngest "last_affirmed" timestamp of
1528 all encounter rows for this patient.
1529 """
1530 self['last_affirmed'] = gmDateTime.pydt_now_here()
1531 self.save()
1532 #--------------------------------------------------------
1534 """
1535 Moves every element currently linked to the current encounter
1536 and the source_episode onto target_episode.
1537
1538 @param source_episode The episode the elements are currently linked to.
1539 @type target_episode A cEpisode intance.
1540 @param target_episode The episode the elements will be relinked to.
1541 @type target_episode A cEpisode intance.
1542 """
1543 if source_episode['pk_episode'] == target_episode['pk_episode']:
1544 return True
1545
1546 queries = []
1547 cmd = u"""
1548 UPDATE clin.clin_root_item
1549 SET fk_episode = %(trg)s
1550 WHERE
1551 fk_encounter = %(enc)s AND
1552 fk_episode = %(src)s
1553 """
1554 rows, idx = gmPG2.run_rw_queries(queries = [{
1555 'cmd': cmd,
1556 'args': {
1557 'trg': target_episode['pk_episode'],
1558 'enc': self.pk_obj,
1559 'src': source_episode['pk_episode']
1560 }
1561 }])
1562 self.refetch_payload()
1563 return True
1564 #--------------------------------------------------------
1566
1567 relevant_fields = [
1568 'pk_location',
1569 'pk_type',
1570 'pk_patient',
1571 'reason_for_encounter',
1572 'assessment_of_encounter'
1573 ]
1574 for field in relevant_fields:
1575 if self._payload[self._idx[field]] != another_object[field]:
1576 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1577 return False
1578
1579 relevant_fields = [
1580 'started',
1581 'last_affirmed',
1582 ]
1583 for field in relevant_fields:
1584 if self._payload[self._idx[field]] is None:
1585 if another_object[field] is None:
1586 continue
1587 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1588 return False
1589
1590 if another_object[field] is None:
1591 return False
1592
1593 # compares at minute granularity
1594 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'):
1595 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1596 return False
1597
1598 # compare codes
1599 # 1) RFE
1600 if another_object['pk_generic_codes_rfe'] is None:
1601 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
1602 return False
1603 if another_object['pk_generic_codes_rfe'] is not None:
1604 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
1605 return False
1606 if (
1607 (another_object['pk_generic_codes_rfe'] is None)
1608 and
1609 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
1610 ) is False:
1611 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
1612 return False
1613 # 2) AOE
1614 if another_object['pk_generic_codes_aoe'] is None:
1615 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
1616 return False
1617 if another_object['pk_generic_codes_aoe'] is not None:
1618 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
1619 return False
1620 if (
1621 (another_object['pk_generic_codes_aoe'] is None)
1622 and
1623 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
1624 ) is False:
1625 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
1626 return False
1627
1628 return True
1629 #--------------------------------------------------------
1631 cmd = u"""
1632 select exists (
1633 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
1634 union all
1635 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1636 )"""
1637 args = {
1638 'pat': self._payload[self._idx['pk_patient']],
1639 'enc': self.pk_obj
1640 }
1641 rows, idx = gmPG2.run_ro_queries (
1642 queries = [{
1643 'cmd': cmd,
1644 'args': args
1645 }]
1646 )
1647 return rows[0][0]
1648 #--------------------------------------------------------
1650 cmd = u"""
1651 select exists (
1652 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
1653 )"""
1654 args = {
1655 'pat': self._payload[self._idx['pk_patient']],
1656 'enc': self.pk_obj
1657 }
1658 rows, idx = gmPG2.run_ro_queries (
1659 queries = [{
1660 'cmd': cmd,
1661 'args': args
1662 }]
1663 )
1664 return rows[0][0]
1665 #--------------------------------------------------------
1667 """soap_cats: <space> = admin category"""
1668
1669 if soap_cats is None:
1670 soap_cats = u'soap '
1671 else:
1672 soap_cats = soap_cats.lower()
1673
1674 cats = []
1675 for cat in soap_cats:
1676 if cat in u'soapu':
1677 cats.append(cat)
1678 continue
1679 if cat == u' ':
1680 cats.append(None)
1681
1682 cmd = u"""
1683 SELECT EXISTS (
1684 SELECT 1 FROM clin.clin_narrative
1685 WHERE
1686 fk_encounter = %(enc)s
1687 AND
1688 soap_cat IN %(cats)s
1689 LIMIT 1
1690 )
1691 """
1692 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
1693 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
1694 return rows[0][0]
1695 #--------------------------------------------------------
1697 cmd = u"""
1698 select exists (
1699 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1700 )"""
1701 args = {
1702 'pat': self._payload[self._idx['pk_patient']],
1703 'enc': self.pk_obj
1704 }
1705 rows, idx = gmPG2.run_ro_queries (
1706 queries = [{
1707 'cmd': cmd,
1708 'args': args
1709 }]
1710 )
1711 return rows[0][0]
1712 #--------------------------------------------------------
1714
1715 if soap_cat is not None:
1716 soap_cat = soap_cat.lower()
1717
1718 if episode is None:
1719 epi_part = u'fk_episode is null'
1720 else:
1721 epi_part = u'fk_episode = %(epi)s'
1722
1723 cmd = u"""
1724 select narrative
1725 from clin.clin_narrative
1726 where
1727 fk_encounter = %%(enc)s
1728 and
1729 soap_cat = %%(cat)s
1730 and
1731 %s
1732 order by clin_when desc
1733 limit 1
1734 """ % epi_part
1735
1736 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
1737
1738 rows, idx = gmPG2.run_ro_queries (
1739 queries = [{
1740 'cmd': cmd,
1741 'args': args
1742 }]
1743 )
1744 if len(rows) == 0:
1745 return None
1746
1747 return rows[0][0]
1748 #--------------------------------------------------------
1750 cmd = u"""
1751 SELECT * FROM clin.v_pat_episodes
1752 WHERE pk_episode IN (
1753 SELECT DISTINCT fk_episode
1754 FROM clin.clin_root_item
1755 WHERE fk_encounter = %%(enc)s
1756
1757 UNION
1758
1759 SELECT DISTINCT fk_episode
1760 FROM blobs.doc_med
1761 WHERE fk_encounter = %%(enc)s
1762 ) %s"""
1763 args = {'enc': self.pk_obj}
1764 if exclude is not None:
1765 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s'
1766 args['excluded'] = tuple(exclude)
1767 else:
1768 cmd = cmd % u''
1769
1770 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1771
1772 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1773
1774 episodes = property(get_episodes, lambda x:x)
1775 #--------------------------------------------------------
1777 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1778 if field == u'rfe':
1779 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1780 elif field == u'aoe':
1781 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1782 else:
1783 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1784 args = {
1785 'item': self._payload[self._idx['pk_encounter']],
1786 'code': pk_code
1787 }
1788 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1789 return True
1790 #--------------------------------------------------------
1792 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1793 if field == u'rfe':
1794 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1795 elif field == u'aoe':
1796 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1797 else:
1798 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1799 args = {
1800 'item': self._payload[self._idx['pk_encounter']],
1801 'code': pk_code
1802 }
1803 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1804 return True
1805 #--------------------------------------------------------
1806 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
1807
1808 lines = []
1809 for soap_cat in soap_cats:
1810 soap_cat_narratives = emr.get_clin_narrative (
1811 episodes = episodes,
1812 issues = issues,
1813 encounters = [self._payload[self._idx['pk_encounter']]],
1814 soap_cats = [soap_cat]
1815 )
1816 if soap_cat_narratives is None:
1817 continue
1818 if len(soap_cat_narratives) == 0:
1819 continue
1820
1821 lines.append(u'%s%s %s %s' % (
1822 gmTools.u_box_top_left_arc,
1823 gmTools.u_box_horiz_single,
1824 gmClinNarrative.soap_cat2l10n_str[soap_cat],
1825 gmTools.u_box_horiz_single * 5
1826 ))
1827 for soap_entry in soap_cat_narratives:
1828 txt = gmTools.wrap (
1829 text = soap_entry['narrative'],
1830 width = 75,
1831 initial_indent = u'',
1832 subsequent_indent = (u' ' * left_margin)
1833 )
1834 lines.append(txt)
1835 when = gmDateTime.pydt_strftime (
1836 soap_entry['date'],
1837 format = '%Y-%m-%d %H:%M',
1838 accuracy = gmDateTime.acc_minutes
1839 )
1840 txt = u'%s%s %.8s, %s %s' % (
1841 u' ' * 40,
1842 gmTools.u_box_horiz_light_heavy,
1843 soap_entry['provider'],
1844 when,
1845 gmTools.u_box_horiz_heavy_light
1846 )
1847 lines.append(txt)
1848 lines.append('')
1849
1850 return lines
1851 #--------------------------------------------------------
1853
1854 nothing2format = (
1855 (self._payload[self._idx['reason_for_encounter']] is None)
1856 and
1857 (self._payload[self._idx['assessment_of_encounter']] is None)
1858 and
1859 (self.has_soap_narrative(soap_cats = u'soapu') is False)
1860 )
1861 if nothing2format:
1862 return u''
1863
1864 if date_format is None:
1865 date_format = '%A, %b %d %Y'
1866
1867 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % (
1868 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]),
1869 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()),
1870 self._payload[self._idx['started']].strftime('%H:%M'),
1871 self._payload[self._idx['last_affirmed']].strftime('%H:%M')
1872 )
1873 tex += u'\\hline \\tabularnewline \n'
1874
1875 for epi in self.get_episodes():
1876 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order)
1877 if len(soaps) == 0:
1878 continue
1879 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
1880 gmTools.tex_escape_string(_('Problem')),
1881 gmTools.tex_escape_string(epi['description']),
1882 gmTools.coalesce (
1883 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']),
1884 instead = u'',
1885 template_initial = u' {\\footnotesize [%s]}',
1886 none_equivalents = [None, u'']
1887 )
1888 )
1889 if epi['pk_health_issue'] is not None:
1890 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
1891 gmTools.tex_escape_string(_('Health issue')),
1892 gmTools.tex_escape_string(epi['health_issue']),
1893 gmTools.coalesce (
1894 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']),
1895 instead = u'',
1896 template_initial = u' {\\footnotesize [%s]}',
1897 none_equivalents = [None, u'']
1898 )
1899 )
1900 for soap in soaps:
1901 tex += u'{\\small %s} & %s \\tabularnewline \n' % (
1902 gmClinNarrative.soap_cat2l10n[soap['soap_cat']],
1903 gmTools.tex_escape_string(soap['narrative'].strip(u'\n'))
1904 )
1905 tex += u' & \\tabularnewline \n'
1906
1907 if self._payload[self._idx['reason_for_encounter']] is not None:
1908 tex += u'%s & %s \\tabularnewline \n' % (
1909 gmTools.tex_escape_string(_('RFE')),
1910 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']])
1911 )
1912 if self._payload[self._idx['assessment_of_encounter']] is not None:
1913 tex += u'%s & %s \\tabularnewline \n' % (
1914 gmTools.tex_escape_string(_('AOE')),
1915 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']])
1916 )
1917
1918 tex += u'\\hline \\tabularnewline \n'
1919 tex += u' & \\tabularnewline \n'
1920
1921 return tex
1922 #--------------------------------------------------------
1924 lines = []
1925
1926 if fancy_header:
1927 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % (
1928 u' ' * left_margin,
1929 self._payload[self._idx['l10n_type']],
1930 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'),
1931 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
1932 self._payload[self._idx['source_time_zone']],
1933 gmTools.coalesce (
1934 self._payload[self._idx['assessment_of_encounter']],
1935 u'',
1936 u' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
1937 ),
1938 self._payload[self._idx['pk_encounter']]
1939 ))
1940
1941 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % (
1942 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'),
1943 self._payload[self._idx['last_affirmed']].strftime('%H:%M'),
1944 gmDateTime.current_local_iso_numeric_timezone_string,
1945 gmTools.bool2subst (
1946 gmDateTime.dst_currently_in_effect,
1947 gmDateTime.py_dst_timezone_name,
1948 gmDateTime.py_timezone_name
1949 ),
1950 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'')
1951 ))
1952
1953 if self._payload[self._idx['reason_for_encounter']] is not None:
1954 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
1955 codes = self.generic_codes_rfe
1956 for c in codes:
1957 lines.append(u' %s: %s (%s - %s)' % (
1958 c['code'],
1959 c['term'],
1960 c['name_short'],
1961 c['version']
1962 ))
1963 if len(codes) > 0:
1964 lines.append(u'')
1965
1966 if self._payload[self._idx['assessment_of_encounter']] is not None:
1967 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
1968 codes = self.generic_codes_aoe
1969 for c in codes:
1970 lines.append(u' %s: %s (%s - %s)' % (
1971 c['code'],
1972 c['term'],
1973 c['name_short'],
1974 c['version']
1975 ))
1976 if len(codes) > 0:
1977 lines.append(u'')
1978 del codes
1979 return lines
1980
1981 now = gmDateTime.pydt_now_here()
1982 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'):
1983 start = u'%s %s' % (
1984 _('today'),
1985 self._payload[self._idx['started_original_tz']].strftime('%H:%M')
1986 )
1987 else:
1988 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M')
1989 lines.append(u'%s%s: %s - %s%s' % (
1990 u' ' * left_margin,
1991 self._payload[self._idx['l10n_type']],
1992 start,
1993 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
1994 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB')
1995 ))
1996 if with_rfe_aoe:
1997 if self._payload[self._idx['reason_for_encounter']] is not None:
1998 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
1999 codes = self.generic_codes_rfe
2000 for c in codes:
2001 lines.append(u' %s: %s (%s - %s)' % (
2002 c['code'],
2003 c['term'],
2004 c['name_short'],
2005 c['version']
2006 ))
2007 if len(codes) > 0:
2008 lines.append(u'')
2009 if self._payload[self._idx['assessment_of_encounter']] is not None:
2010 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
2011 codes = self.generic_codes_aoe
2012 if len(codes) > 0:
2013 lines.append(u'')
2014 for c in codes:
2015 lines.append(u' %s: %s (%s - %s)' % (
2016 c['code'],
2017 c['term'],
2018 c['name_short'],
2019 c['version']
2020 ))
2021 if len(codes) > 0:
2022 lines.append(u'')
2023 del codes
2024
2025 return lines
2026 #--------------------------------------------------------
2027 - def format_by_episode(self, episodes=None, issues=None, left_margin=0, patient=None, with_soap=False, with_tests=True, with_docs=True, with_vaccinations=True, with_family_history=True):
2028
2029 lines = []
2030 emr = patient.emr
2031 if episodes is None:
2032 episodes = [ e['pk_episode'] for e in self.episodes ]
2033
2034 for pk in episodes:
2035 epi = cEpisode(aPK_obj = pk)
2036 lines.append(_('\nEpisode %s%s%s%s:') % (
2037 gmTools.u_left_double_angle_quote,
2038 epi['description'],
2039 gmTools.u_right_double_angle_quote,
2040 gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
2041 ))
2042
2043 # soap
2044 if with_soap:
2045 if patient.ID != self._payload[self._idx['pk_patient']]:
2046 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2047 patient.ID,
2048 self._payload[self._idx['pk_encounter']],
2049 self._payload[self._idx['pk_patient']]
2050 )
2051 raise ValueError(msg)
2052
2053 lines.extend(self.format_soap (
2054 episodes = [pk],
2055 left_margin = left_margin,
2056 soap_cats = 'soapu',
2057 emr = emr,
2058 issues = issues
2059 ))
2060
2061 # test results
2062 if with_tests:
2063 tests = emr.get_test_results_by_date (
2064 episodes = [pk],
2065 encounter = self._payload[self._idx['pk_encounter']]
2066 )
2067 if len(tests) > 0:
2068 lines.append('')
2069 lines.append(_('Measurements and Results:'))
2070
2071 for t in tests:
2072 lines.append(t.format())
2073
2074 del tests
2075
2076 # vaccinations
2077 if with_vaccinations:
2078 vaccs = emr.get_vaccinations (
2079 episodes = [pk],
2080 encounters = [ self._payload[self._idx['pk_encounter']] ],
2081 order_by = u'date_given DESC, vaccine'
2082 )
2083 if len(vaccs) > 0:
2084 lines.append(u'')
2085 lines.append(_('Vaccinations:'))
2086 for vacc in vaccs:
2087 lines.extend(vacc.format (
2088 with_indications = True,
2089 with_comment = True,
2090 with_reaction = True,
2091 date_format = '%Y-%m-%d'
2092 ))
2093 del vaccs
2094
2095 # family history
2096 if with_family_history:
2097 fhx = emr.get_family_history(episodes = [pk])
2098 if len(fhx) > 0:
2099 lines.append(u'')
2100 lines.append(_('Family History: %s') % len(fhx))
2101 for f in fhx:
2102 lines.append(f.format (
2103 left_margin = (left_margin + 1),
2104 include_episode = False,
2105 include_comment = True
2106 ))
2107 del fhx
2108
2109 # documents
2110 if with_docs:
2111 doc_folder = patient.get_document_folder()
2112 docs = doc_folder.get_documents (
2113 episodes = [pk],
2114 encounter = self._payload[self._idx['pk_encounter']]
2115 )
2116 if len(docs) > 0:
2117 lines.append(u'')
2118 lines.append(_('Documents:'))
2119 for d in docs:
2120 lines.append(u' %s %s:%s%s' % (
2121 d['clin_when'].strftime('%Y-%m-%d'),
2122 d['l10n_type'],
2123 gmTools.coalesce(d['comment'], u'', u' "%s"'),
2124 gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
2125 ))
2126
2127 del docs
2128
2129 return lines
2130 #--------------------------------------------------------
2131 - def format(self, episodes=None, with_soap=False, left_margin=0, patient=None, issues=None, with_docs=True, with_tests=True, fancy_header=True, with_vaccinations=True, with_co_encountlet_hints=False, with_rfe_aoe=False, with_family_history=True, by_episode=False):
2132 """Format an encounter.
2133
2134 with_co_encountlet_hints:
2135 - whether to include which *other* episodes were discussed during this encounter
2136 - (only makes sense if episodes != None)
2137 """
2138 lines = self.format_header (
2139 fancy_header = fancy_header,
2140 left_margin = left_margin,
2141 with_rfe_aoe = with_rfe_aoe
2142 )
2143
2144 if by_episode:
2145 lines.extend(self.format_by_episode (
2146 episodes = episodes,
2147 issues = issues,
2148 left_margin = left_margin,
2149 patient = patient,
2150 with_soap = with_soap,
2151 with_tests = with_tests,
2152 with_docs = with_docs,
2153 with_vaccinations = with_vaccinations,
2154 with_family_history = with_family_history
2155 ))
2156
2157 else:
2158 if with_soap:
2159 lines.append(u'')
2160
2161 if patient.ID != self._payload[self._idx['pk_patient']]:
2162 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2163 patient.ID,
2164 self._payload[self._idx['pk_encounter']],
2165 self._payload[self._idx['pk_patient']]
2166 )
2167 raise ValueError(msg)
2168
2169 emr = patient.get_emr()
2170
2171 lines.extend(self.format_soap (
2172 episodes = episodes,
2173 left_margin = left_margin,
2174 soap_cats = 'soapu',
2175 emr = emr,
2176 issues = issues
2177 ))
2178
2179 # # family history
2180 # if with_family_history:
2181 # if episodes is not None:
2182 # fhx = emr.get_family_history(episodes = episodes)
2183 # if len(fhx) > 0:
2184 # lines.append(u'')
2185 # lines.append(_('Family History: %s') % len(fhx))
2186 # for f in fhx:
2187 # lines.append(f.format (
2188 # left_margin = (left_margin + 1),
2189 # include_episode = False,
2190 # include_comment = True
2191 # ))
2192 # del fhx
2193
2194 # test results
2195 if with_tests:
2196 emr = patient.get_emr()
2197 tests = emr.get_test_results_by_date (
2198 episodes = episodes,
2199 encounter = self._payload[self._idx['pk_encounter']]
2200 )
2201 if len(tests) > 0:
2202 lines.append('')
2203 lines.append(_('Measurements and Results:'))
2204
2205 for t in tests:
2206 lines.append(t.format())
2207
2208 del tests
2209
2210 # vaccinations
2211 if with_vaccinations:
2212 emr = patient.get_emr()
2213 vaccs = emr.get_vaccinations (
2214 episodes = episodes,
2215 encounters = [ self._payload[self._idx['pk_encounter']] ],
2216 order_by = u'date_given DESC, vaccine'
2217 )
2218
2219 if len(vaccs) > 0:
2220 lines.append(u'')
2221 lines.append(_('Vaccinations:'))
2222
2223 for vacc in vaccs:
2224 lines.extend(vacc.format (
2225 with_indications = True,
2226 with_comment = True,
2227 with_reaction = True,
2228 date_format = '%Y-%m-%d'
2229 ))
2230 del vaccs
2231
2232 # documents
2233 if with_docs:
2234 doc_folder = patient.get_document_folder()
2235 docs = doc_folder.get_documents (
2236 episodes = episodes,
2237 encounter = self._payload[self._idx['pk_encounter']]
2238 )
2239
2240 if len(docs) > 0:
2241 lines.append(u'')
2242 lines.append(_('Documents:'))
2243
2244 for d in docs:
2245 lines.append(u' %s %s:%s%s' % (
2246 d['clin_when'].strftime('%Y-%m-%d'),
2247 d['l10n_type'],
2248 gmTools.coalesce(d['comment'], u'', u' "%s"'),
2249 gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
2250 ))
2251
2252 del docs
2253
2254 # co-encountlets
2255 if with_co_encountlet_hints:
2256 if episodes is not None:
2257 other_epis = self.get_episodes(exclude = episodes)
2258 if len(other_epis) > 0:
2259 lines.append(u'')
2260 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis))
2261 for epi in other_epis:
2262 lines.append(u' %s%s%s%s' % (
2263 gmTools.u_left_double_angle_quote,
2264 epi['description'],
2265 gmTools.u_right_double_angle_quote,
2266 gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
2267 ))
2268
2269 eol_w_margin = u'\n%s' % (u' ' * left_margin)
2270 return u'%s\n' % eol_w_margin.join(lines)
2271 #--------------------------------------------------------
2272 # properties
2273 #--------------------------------------------------------
2275 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2276 return []
2277
2278 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2279 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2280 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2281 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2282
2284 queries = []
2285 # remove all codes
2286 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2287 queries.append ({
2288 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2289 'args': {
2290 'enc': self._payload[self._idx['pk_encounter']],
2291 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2292 }
2293 })
2294 # add new codes
2295 for pk_code in pk_codes:
2296 queries.append ({
2297 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2298 'args': {
2299 'enc': self._payload[self._idx['pk_encounter']],
2300 'pk_code': pk_code
2301 }
2302 })
2303 if len(queries) == 0:
2304 return
2305 # run it all in one transaction
2306 rows, idx = gmPG2.run_rw_queries(queries = queries)
2307 self.refetch_payload()
2308 return
2309
2310 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2311 #--------------------------------------------------------
2313 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2314 return []
2315
2316 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2317 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2318 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2319 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2320
2322 queries = []
2323 # remove all codes
2324 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2325 queries.append ({
2326 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2327 'args': {
2328 'enc': self._payload[self._idx['pk_encounter']],
2329 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2330 }
2331 })
2332 # add new codes
2333 for pk_code in pk_codes:
2334 queries.append ({
2335 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2336 'args': {
2337 'enc': self._payload[self._idx['pk_encounter']],
2338 'pk_code': pk_code
2339 }
2340 })
2341 if len(queries) == 0:
2342 return
2343 # run it all in one transaction
2344 rows, idx = gmPG2.run_rw_queries(queries = queries)
2345 self.refetch_payload()
2346 return
2347
2348 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2349 #-----------------------------------------------------------
2351 """Creates a new encounter for a patient.
2352
2353 fk_patient - patient PK
2354 fk_location - encounter location
2355 enc_type - type of encounter
2356
2357 FIXME: we don't deal with location yet
2358 """
2359 if enc_type is None:
2360 enc_type = u'in surgery'
2361 # insert new encounter
2362 queries = []
2363 try:
2364 enc_type = int(enc_type)
2365 cmd = u"""
2366 INSERT INTO clin.encounter (
2367 fk_patient, fk_location, fk_type
2368 ) VALUES (
2369 %(pat)s,
2370 -1,
2371 %(typ)s
2372 ) RETURNING pk"""
2373 except ValueError:
2374 enc_type = enc_type
2375 cmd = u"""
2376 insert into clin.encounter (
2377 fk_patient, fk_location, fk_type
2378 ) values (
2379 %(pat)s,
2380 -1,
2381 coalesce (
2382 (select pk from clin.encounter_type where description = %(typ)s),
2383 -- pick the first available
2384 (select pk from clin.encounter_type limit 1)
2385 )
2386 ) RETURNING pk"""
2387 args = {'pat': fk_patient, 'typ': enc_type}
2388 queries.append({'cmd': cmd, 'args': args})
2389 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
2390 encounter = cEncounter(aPK_obj = rows[0]['pk'])
2391
2392 return encounter
2393 #-----------------------------------------------------------
2395
2396 rows, idx = gmPG2.run_rw_queries(
2397 queries = [{
2398 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
2399 'args': {'desc': description, 'l10n_desc': l10n_description}
2400 }],
2401 return_data = True
2402 )
2403
2404 success = rows[0][0]
2405 if not success:
2406 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
2407
2408 return {'description': description, 'l10n_description': l10n_description}
2409 #-----------------------------------------------------------
2411 """This will attempt to create a NEW encounter type."""
2412
2413 # need a system name, so derive one if necessary
2414 if description is None:
2415 description = l10n_description
2416
2417 args = {
2418 'desc': description,
2419 'l10n_desc': l10n_description
2420 }
2421
2422 _log.debug('creating encounter type: %s, %s', description, l10n_description)
2423
2424 # does it exist already ?
2425 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s"
2426 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2427
2428 # yes
2429 if len(rows) > 0:
2430 # both system and l10n name are the same so all is well
2431 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
2432 _log.info('encounter type [%s] already exists with the proper translation')
2433 return {'description': description, 'l10n_description': l10n_description}
2434
2435 # or maybe there just wasn't a translation to
2436 # the current language for this type yet ?
2437 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
2438 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2439
2440 # there was, so fail
2441 if rows[0][0]:
2442 _log.error('encounter type [%s] already exists but with another translation')
2443 return None
2444
2445 # else set it
2446 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
2447 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2448 return {'description': description, 'l10n_description': l10n_description}
2449
2450 # no
2451 queries = [
2452 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
2453 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
2454 ]
2455 rows, idx = gmPG2.run_rw_queries(queries = queries)
2456
2457 return {'description': description, 'l10n_description': l10n_description}
2458 #-----------------------------------------------------------
2460 cmd = u"""
2461 SELECT
2462 _(description) AS l10n_description,
2463 description
2464 FROM
2465 clin.encounter_type
2466 ORDER BY
2467 l10n_description
2468 """
2469 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
2470 return rows
2471 #-----------------------------------------------------------
2473 cmd = u"SELECT * from clin.encounter_type where description = %s"
2474 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}])
2475 return rows
2476 #-----------------------------------------------------------
2478 cmd = u"delete from clin.encounter_type where description = %(desc)s"
2479 args = {'desc': description}
2480 try:
2481 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2482 except gmPG2.dbapi.IntegrityError, e:
2483 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
2484 return False
2485 raise
2486
2487 return True
2488 #============================================================
2490 """Represents one problem.
2491
2492 problems are the aggregation of
2493 .clinically_relevant=True issues and
2494 .is_open=True episodes
2495 """
2496 _cmd_fetch_payload = u'' # will get programmatically defined in __init__
2497 _cmds_store_payload = [u"select 1"]
2498 _updatable_fields = []
2499
2500 #--------------------------------------------------------
2502 """Initialize.
2503
2504 aPK_obj must contain the keys
2505 pk_patient
2506 pk_episode
2507 pk_health_issue
2508 """
2509 if aPK_obj is None:
2510 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj)
2511
2512 # As problems are rows from a view of different emr struct items,
2513 # the PK can't be a single field and, as some of the values of the
2514 # composed PK may be None, they must be queried using 'is null',
2515 # so we must programmatically construct the SQL query
2516 where_parts = []
2517 pk = {}
2518 for col_name in aPK_obj.keys():
2519 val = aPK_obj[col_name]
2520 if val is None:
2521 where_parts.append('%s IS NULL' % col_name)
2522 else:
2523 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
2524 pk[col_name] = val
2525
2526 # try to instantiate from true problem view
2527 cProblem._cmd_fetch_payload = u"""
2528 SELECT *, False as is_potential_problem
2529 FROM clin.v_problem_list
2530 WHERE %s""" % u' AND '.join(where_parts)
2531
2532 try:
2533 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2534 return
2535 except gmExceptions.ConstructorError:
2536 _log.exception('actual problem not found, trying "potential" problems')
2537 if try_potential_problems is False:
2538 raise
2539
2540 # try to instantiate from potential-problems view
2541 cProblem._cmd_fetch_payload = u"""
2542 SELECT *, True as is_potential_problem
2543 FROM clin.v_potential_problem_list
2544 WHERE %s""" % u' AND '.join(where_parts)
2545 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2546 #--------------------------------------------------------
2548 """
2549 Retrieve the cEpisode instance equivalent to this problem.
2550 The problem's type attribute must be 'episode'
2551 """
2552 if self._payload[self._idx['type']] != 'episode':
2553 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2554 return None
2555 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
2556 #--------------------------------------------------------
2558 """
2559 Retrieve the cHealthIssue instance equivalent to this problem.
2560 The problem's type attribute must be 'issue'
2561 """
2562 if self._payload[self._idx['type']] != 'issue':
2563 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2564 return None
2565 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
2566 #--------------------------------------------------------
2568
2569 if self._payload[self._idx['type']] == u'issue':
2570 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ]
2571 #xxxxxxxxxxxxx
2572
2573 emr = patient.get_emr()
2574
2575 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID)
2576 return doc_folder.get_visual_progress_notes (
2577 health_issue = self._payload[self._idx['pk_health_issue']],
2578 episode = self._payload[self._idx['pk_episode']]
2579 )
2580 #--------------------------------------------------------
2581 # properties
2582 #--------------------------------------------------------
2583 # doubles as 'diagnostic_certainty_description' getter:
2585 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
2586
2587 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
2588 #--------------------------------------------------------
2590 if self._payload[self._idx['type']] == u'issue':
2591 cmd = u"""
2592 SELECT * FROM clin.v_linked_codes WHERE
2593 item_table = 'clin.lnk_code2h_issue'::regclass
2594 AND
2595 pk_item = %(item)s
2596 """
2597 args = {'item': self._payload[self._idx['pk_health_issue']]}
2598 else:
2599 cmd = u"""
2600 SELECT * FROM clin.v_linked_codes WHERE
2601 item_table = 'clin.lnk_code2episode'::regclass
2602 AND
2603 pk_item = %(item)s
2604 """
2605 args = {'item': self._payload[self._idx['pk_episode']]}
2606
2607 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2608 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2609
2610 generic_codes = property(_get_generic_codes, lambda x:x)
2611 #-----------------------------------------------------------
2613 """Retrieve the cEpisode instance equivalent to the given problem.
2614
2615 The problem's type attribute must be 'episode'
2616
2617 @param problem: The problem to retrieve its related episode for
2618 @type problem: A gmEMRStructItems.cProblem instance
2619 """
2620 if isinstance(problem, cEpisode):
2621 return problem
2622
2623 exc = TypeError('cannot convert [%s] to episode' % problem)
2624
2625 if not isinstance(problem, cProblem):
2626 raise exc
2627
2628 if problem['type'] != 'episode':
2629 raise exc
2630
2631 return cEpisode(aPK_obj = problem['pk_episode'])
2632 #-----------------------------------------------------------
2634 """Retrieve the cIssue instance equivalent to the given problem.
2635
2636 The problem's type attribute must be 'issue'.
2637
2638 @param problem: The problem to retrieve the corresponding issue for
2639 @type problem: A gmEMRStructItems.cProblem instance
2640 """
2641 if isinstance(problem, cHealthIssue):
2642 return problem
2643
2644 exc = TypeError('cannot convert [%s] to health issue' % problem)
2645
2646 if not isinstance(problem, cProblem):
2647 raise exc
2648
2649 if problem['type'] != 'issue':
2650 raise exc
2651
2652 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2653 #-----------------------------------------------------------
2655 """Transform given problem into either episode or health issue instance.
2656 """
2657 if isinstance(problem, (cEpisode, cHealthIssue)):
2658 return problem
2659
2660 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
2661
2662 if not isinstance(problem, cProblem):
2663 _log.debug(u'%s' % problem)
2664 raise exc
2665
2666 if problem['type'] == 'episode':
2667 return cEpisode(aPK_obj = problem['pk_episode'])
2668
2669 if problem['type'] == 'issue':
2670 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2671
2672 raise exc
2673 #============================================================
2675
2676 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s"
2677 _cmds_store_payload = [
2678 u"""update clin.hospital_stay set
2679 clin_when = %(admission)s,
2680 discharge = %(discharge)s,
2681 narrative = gm.nullify_empty_string(%(hospital)s),
2682 fk_episode = %(pk_episode)s,
2683 fk_encounter = %(pk_encounter)s
2684 where
2685 pk = %(pk_hospital_stay)s and
2686 xmin = %(xmin_hospital_stay)s""",
2687 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s"""
2688 ]
2689 _updatable_fields = [
2690 'admission',
2691 'discharge',
2692 'hospital',
2693 'pk_episode',
2694 'pk_encounter'
2695 ]
2696 #-------------------------------------------------------
2698
2699 if self._payload[self._idx['discharge']] is not None:
2700 discharge = u' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d')
2701 else:
2702 discharge = u''
2703
2704 line = u'%s%s%s%s: %s%s%s' % (
2705 u' ' * left_margin,
2706 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'),
2707 discharge,
2708 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'),
2709 gmTools.u_left_double_angle_quote,
2710 self._payload[self._idx['episode']],
2711 gmTools.u_right_double_angle_quote
2712 )
2713
2714 return line
2715 #-----------------------------------------------------------
2717 queries = [{
2718 # this assumes non-overarching stays
2719 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1',
2720 'args': {'pat': patient}
2721 }]
2722 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2723 if len(rows) == 0:
2724 return None
2725 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
2726 #-----------------------------------------------------------
2728 args = {'pat': patient}
2729 if ongoing_only:
2730 cmd = u"""
2731 SELECT *
2732 FROM clin.v_pat_hospital_stays
2733 WHERE
2734 pk_patient = %(pat)s
2735 AND
2736 discharge is NULL
2737 ORDER BY admission"""
2738 else:
2739 cmd = u"""
2740 SELECT *
2741 FROM clin.v_pat_hospital_stays
2742 WHERE pk_patient = %(pat)s
2743 ORDER BY admission"""
2744
2745 queries = [{'cmd': cmd, 'args': args}]
2746 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2747
2748 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
2749 #-----------------------------------------------------------
2751
2752 queries = [{
2753 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk',
2754 'args': {'enc': encounter, 'epi': episode}
2755 }]
2756 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
2757
2758 return cHospitalStay(aPK_obj = rows[0][0])
2759 #-----------------------------------------------------------
2761 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
2762 args = {'pk': stay}
2763 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2764 return True
2765 #============================================================
2767
2768 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s"
2769 _cmds_store_payload = [
2770 u"""UPDATE clin.procedure SET
2771 soap_cat = 'p',
2772 clin_when = %(clin_when)s,
2773 clin_end = %(clin_end)s,
2774 is_ongoing = %(is_ongoing)s,
2775 clin_where = NULLIF (
2776 COALESCE (
2777 %(pk_hospital_stay)s::TEXT,
2778 gm.nullify_empty_string(%(clin_where)s)
2779 ),
2780 %(pk_hospital_stay)s::TEXT
2781 ),
2782 narrative = gm.nullify_empty_string(%(performed_procedure)s),
2783 fk_hospital_stay = %(pk_hospital_stay)s,
2784 fk_episode = %(pk_episode)s,
2785 fk_encounter = %(pk_encounter)s
2786 WHERE
2787 pk = %(pk_procedure)s AND
2788 xmin = %(xmin_procedure)s
2789 RETURNING xmin as xmin_procedure"""
2790 ]
2791 _updatable_fields = [
2792 'clin_when',
2793 'clin_end',
2794 'is_ongoing',
2795 'clin_where',
2796 'performed_procedure',
2797 'pk_hospital_stay',
2798 'pk_episode',
2799 'pk_encounter'
2800 ]
2801 #-------------------------------------------------------
2803
2804 if (attribute == 'pk_hospital_stay') and (value is not None):
2805 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None)
2806
2807 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''):
2808 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None)
2809
2810 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
2811 #-------------------------------------------------------
2813
2814 if self._payload[self._idx['is_ongoing']]:
2815 end = _(' (ongoing)')
2816 else:
2817 end = self._payload[self._idx['clin_end']]
2818 if end is None:
2819 end = u''
2820 else:
2821 end = u' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d')
2822
2823 line = u'%s%s%s (%s): %s' % (
2824 (u' ' * left_margin),
2825 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'),
2826 end,
2827 self._payload[self._idx['clin_where']],
2828 self._payload[self._idx['performed_procedure']]
2829 )
2830 if include_episode:
2831 line = u'%s (%s)' % (line, self._payload[self._idx['episode']])
2832
2833 if include_codes:
2834 codes = self.generic_codes
2835 if len(codes) > 0:
2836 line += u'\n'
2837 for c in codes:
2838 line += u'%s %s: %s (%s - %s)\n' % (
2839 (u' ' * left_margin),
2840 c['code'],
2841 c['term'],
2842 c['name_short'],
2843 c['version']
2844 )
2845 del codes
2846
2847 return line
2848 #--------------------------------------------------------
2850 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2851 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)"
2852 args = {
2853 'issue': self._payload[self._idx['pk_procedure']],
2854 'code': pk_code
2855 }
2856 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2857 return True
2858 #--------------------------------------------------------
2860 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2861 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s"
2862 args = {
2863 'issue': self._payload[self._idx['pk_procedure']],
2864 'code': pk_code
2865 }
2866 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2867 return True
2868 #--------------------------------------------------------
2869 # properties
2870 #--------------------------------------------------------
2872 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
2873 return []
2874
2875 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2876 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
2877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2878 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2879
2881 queries = []
2882 # remove all codes
2883 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
2884 queries.append ({
2885 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s',
2886 'args': {
2887 'proc': self._payload[self._idx['pk_procedure']],
2888 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
2889 }
2890 })
2891 # add new codes
2892 for pk_code in pk_codes:
2893 queries.append ({
2894 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)',
2895 'args': {
2896 'proc': self._payload[self._idx['pk_procedure']],
2897 'pk_code': pk_code
2898 }
2899 })
2900 if len(queries) == 0:
2901 return
2902 # run it all in one transaction
2903 rows, idx = gmPG2.run_rw_queries(queries = queries)
2904 return
2905
2906 generic_codes = property(_get_generic_codes, _set_generic_codes)
2907 #-----------------------------------------------------------
2909
2910 queries = [
2911 {
2912 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when',
2913 'args': {'pat': patient}
2914 }
2915 ]
2916
2917 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2918
2919 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
2920 #-----------------------------------------------------------
2922 queries = [
2923 {
2924 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1',
2925 'args': {'pat': patient}
2926 }
2927 ]
2928 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2929 if len(rows) == 0:
2930 return None
2931 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
2932 #-----------------------------------------------------------
2933 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
2934
2935 queries = [{
2936 'cmd': u"""
2937 INSERT INTO clin.procedure (
2938 fk_encounter,
2939 fk_episode,
2940 soap_cat,
2941 clin_where,
2942 fk_hospital_stay,
2943 narrative
2944 ) VALUES (
2945 %(enc)s,
2946 %(epi)s,
2947 'p',
2948 gm.nullify_empty_string(%(loc)s),
2949 %(stay)s,
2950 gm.nullify_empty_string(%(proc)s)
2951 )
2952 RETURNING pk""",
2953 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure}
2954 }]
2955
2956 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
2957
2958 return cPerformedProcedure(aPK_obj = rows[0][0])
2959 #-----------------------------------------------------------
2961 cmd = u'delete from clin.procedure where pk = %(pk)s'
2962 args = {'pk': procedure}
2963 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2964 return True
2965 #============================================================
2966 # main - unit testing
2967 #------------------------------------------------------------
2968 if __name__ == '__main__':
2969
2970 if len(sys.argv) < 2:
2971 sys.exit()
2972
2973 if sys.argv[1] != 'test':
2974 sys.exit()
2975
2976 #--------------------------------------------------------
2977 # define tests
2978 #--------------------------------------------------------
2980 print "\nProblem test"
2981 print "------------"
2982 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
2983 print prob
2984 fields = prob.get_fields()
2985 for field in fields:
2986 print field, ':', prob[field]
2987 print '\nupdatable:', prob.get_updatable_fields()
2988 epi = prob.get_as_episode()
2989 print '\nas episode:'
2990 if epi is not None:
2991 for field in epi.get_fields():
2992 print ' .%s : %s' % (field, epi[field])
2993 #--------------------------------------------------------
2995 print "\nhealth issue test"
2996 print "-----------------"
2997 h_issue = cHealthIssue(aPK_obj=2)
2998 print h_issue
2999 print h_issue.latest_access_date
3000 print h_issue.end_date
3001 # fields = h_issue.get_fields()
3002 # for field in fields:
3003 # print field, ':', h_issue[field]
3004 # print "has open episode:", h_issue.has_open_episode()
3005 # print "open episode:", h_issue.get_open_episode()
3006 # print "updateable:", h_issue.get_updatable_fields()
3007 # h_issue.close_expired_episode(ttl=7300)
3008 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis')
3009 # print h_issue
3010 # print h_issue.format_as_journal()
3011 #--------------------------------------------------------
3013 print "\nepisode test"
3014 print "------------"
3015 episode = cEpisode(aPK_obj=1)
3016 print episode
3017 fields = episode.get_fields()
3018 for field in fields:
3019 print field, ':', episode[field]
3020 print "updatable:", episode.get_updatable_fields()
3021 raw_input('ENTER to continue')
3022
3023 old_description = episode['description']
3024 old_enc = cEncounter(aPK_obj = 1)
3025
3026 desc = '1-%s' % episode['description']
3027 print "==> renaming to", desc
3028 successful = episode.rename (
3029 description = desc
3030 )
3031 if not successful:
3032 print "error"
3033 else:
3034 print "success"
3035 for field in fields:
3036 print field, ':', episode[field]
3037
3038 print "episode range:", episode.get_access_range()
3039
3040 raw_input('ENTER to continue')
3041
3042 #--------------------------------------------------------
3044 print "\nencounter test"
3045 print "--------------"
3046 encounter = cEncounter(aPK_obj=1)
3047 print encounter
3048 fields = encounter.get_fields()
3049 for field in fields:
3050 print field, ':', encounter[field]
3051 print "updatable:", encounter.get_updatable_fields()
3052 #--------------------------------------------------------
3054 encounter = cEncounter(aPK_obj=1)
3055 print encounter
3056 print ""
3057 print encounter.format_latex()
3058 #--------------------------------------------------------
3060 procs = get_performed_procedures(patient = 12)
3061 for proc in procs:
3062 print proc.format(left_margin=2)
3063 #--------------------------------------------------------
3065 stay = create_hospital_stay(encounter = 1, episode = 2)
3066 stay['hospital'] = u'Starfleet Galaxy General Hospital'
3067 stay.save_payload()
3068 print stay
3069 for s in get_patient_hospital_stays(12):
3070 print s
3071 delete_hospital_stay(stay['pk_hospital_stay'])
3072 stay = create_hospital_stay(encounter = 1, episode = 4)
3073 #--------------------------------------------------------
3075 tests = [None, 'A', 'B', 'C', 'D', 'E']
3076
3077 for t in tests:
3078 print type(t), t
3079 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
3080 #--------------------------------------------------------
3085 #--------------------------------------------------------
3086 # run them
3087 #test_episode()
3088 #test_problem()
3089 #test_encounter()
3090 test_health_issue()
3091 #test_hospital_stay()
3092 #test_performed_procedure()
3093 #test_diagnostic_certainty_classification_map()
3094 #test_encounter2latex()
3095 #test_episode_codes()
3096 #============================================================
3097
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 10 03:57:00 2013 | http://epydoc.sourceforge.net |