| 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.extend(t.format (
1236 with_review = False,
1237 with_comments = False,
1238 date_format = '%Y-%m-%d'
1239 ))
1240 del tests
1241
1242 # vaccinations
1243 if with_vaccinations:
1244 vaccs = emr.get_vaccinations (
1245 episodes = [ self._payload[self._idx['pk_episode']] ],
1246 order_by = u'date_given DESC, vaccine'
1247 )
1248 if len(vaccs) > 0:
1249 lines.append(u'')
1250 lines.append(_('Vaccinations:'))
1251 for vacc in vaccs:
1252 lines.extend(vacc.format (
1253 with_indications = True,
1254 with_comment = True,
1255 with_reaction = True,
1256 date_format = '%Y-%m-%d'
1257 ))
1258 del vaccs
1259
1260 left_margin = u' ' * left_margin
1261 eol_w_margin = u'\n%s' % left_margin
1262 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = u'\n')
1263 return left_margin + eol_w_margin.join(lines) + u'\n'
1264 #--------------------------------------------------------
1265 # properties
1266 #--------------------------------------------------------
1268 cmd = u"""
1269 SELECT
1270 MIN(earliest)
1271 FROM (
1272 -- last modification, earliest = when created in/changed to the current state
1273 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1274
1275 UNION ALL
1276
1277 -- last modification of encounter in which created, earliest = initial creation of that encounter
1278 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1279 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1280 )
1281 )
1282 UNION ALL
1283
1284 -- start of encounter in which created, earliest = explicitely set
1285 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1286 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1287 )
1288 )
1289 UNION ALL
1290
1291 -- earliest start of encounters of clinical items linked to this episode
1292 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1293 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1294 )
1295 )
1296 UNION ALL
1297
1298 -- earliest explicit .clin_when of clinical items linked to this episode
1299 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1300
1301 UNION ALL
1302
1303 -- earliest modification time of clinical items linked to this episode
1304 -- this CAN be used since if an item is linked to an episode it can be
1305 -- assumed the episode (should have) existed at the time of creation
1306 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1307
1308 -- not sure about this one:
1309 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1310
1311 ) AS candidates"""
1312 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1313 return rows[0][0]
1314
1315 best_guess_start_date = property(_get_best_guess_start_date)
1316 #--------------------------------------------------------
1318 cmd = u"""
1319 SELECT
1320 MAX(latest)
1321 FROM (
1322 -- last modification, latest = when last changed to the current state
1323 (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)
1324
1325 UNION ALL
1326
1327 -- last modification of encounter in which created, latest = initial creation of that encounter
1328 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1329 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1330 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1331 -- )
1332 --)
1333
1334 -- end of encounter in which created, latest = explicitely set
1335 -- DO NOT USE: we can retrospectively create episodes which
1336 -- DO NOT USE: are long since finished
1337 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1338 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1339 -- )
1340 --)
1341
1342 -- latest end of encounters of clinical items linked to this episode
1343 (SELECT
1344 MAX(last_affirmed) AS latest,
1345 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1346 FROM clin.encounter
1347 WHERE pk IN (
1348 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1349 )
1350 )
1351 UNION ALL
1352
1353 -- latest explicit .clin_when of clinical items linked to this episode
1354 (SELECT
1355 MAX(clin_when) AS latest,
1356 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1357 FROM clin.clin_root_item
1358 WHERE fk_episode = %(pk)s
1359 )
1360
1361 -- latest modification time of clinical items linked to this episode
1362 -- this CAN be used since if an item is linked to an episode it can be
1363 -- assumed the episode (should have) existed at the time of creation
1364 -- DO NOT USE, because typo fixes should not extend the episode
1365 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1366
1367 -- not sure about this one:
1368 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1369
1370 ) AS candidates"""
1371 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1372 #_log.debug('last episode access: %s (%s)', rows[0][0], rows[0][1])
1373 return rows[0][0]
1374
1375 latest_access_date = property(_get_latest_access_date)
1376 #--------------------------------------------------------
1378 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1379
1380 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1381 #--------------------------------------------------------
1383 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1384 return []
1385
1386 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
1387 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1388 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1389 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1390
1392 queries = []
1393 # remove all codes
1394 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1395 queries.append ({
1396 'cmd': u'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1397 'args': {
1398 'epi': self._payload[self._idx['pk_episode']],
1399 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1400 }
1401 })
1402 # add new codes
1403 for pk_code in pk_codes:
1404 queries.append ({
1405 'cmd': u'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1406 'args': {
1407 'epi': self._payload[self._idx['pk_episode']],
1408 'pk_code': pk_code
1409 }
1410 })
1411 if len(queries) == 0:
1412 return
1413 # run it all in one transaction
1414 rows, idx = gmPG2.run_rw_queries(queries = queries)
1415 return
1416
1417 generic_codes = property(_get_generic_codes, _set_generic_codes)
1418 #--------------------------------------------------------
1420 cmd = u"""SELECT EXISTS (
1421 SELECT 1 FROM clin.clin_narrative
1422 WHERE
1423 fk_episode = %(epi)s
1424 AND
1425 fk_encounter IN (
1426 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1427 )
1428 )"""
1429 args = {
1430 u'pat': self._payload[self._idx['pk_patient']],
1431 u'epi': self._payload[self._idx['pk_episode']]
1432 }
1433 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1434 return rows[0][0]
1435
1436 has_narrative = property(_get_has_narrative, lambda x:x)
1437 #============================================================
1438 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None):
1439 """Creates a new episode for a given patient's health issue.
1440
1441 pk_health_issue - given health issue PK
1442 episode_name - name of episode
1443 """
1444 if not allow_dupes:
1445 try:
1446 episode = cEpisode(name=episode_name, health_issue=pk_health_issue, encounter = encounter)
1447 if episode['episode_open'] != is_open:
1448 episode['episode_open'] = is_open
1449 episode.save_payload()
1450 return episode
1451 except gmExceptions.ConstructorError:
1452 pass
1453
1454 queries = []
1455 cmd = u"insert into clin.episode (fk_health_issue, description, is_open, fk_encounter) values (%s, %s, %s::boolean, %s)"
1456 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1457 queries.append({'cmd': cEpisode._cmd_fetch_payload % u"currval('clin.episode_pk_seq')"})
1458 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data=True, get_col_idx=True)
1459
1460 episode = cEpisode(row={'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1461 return episode
1462 #-----------------------------------------------------------
1464 if isinstance(episode, cEpisode):
1465 pk = episode['pk_episode']
1466 else:
1467 pk = int(episode)
1468
1469 cmd = u'DELETE FROM clin.episode WHERE pk = %(pk)s'
1470
1471 try:
1472 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1473 except gmPG2.dbapi.IntegrityError:
1474 # should be parsing pgcode/and or error message
1475 _log.exception('cannot delete episode, it is in use')
1476 return False
1477
1478 return True
1479 #-----------------------------------------------------------
1481 return cProblem (
1482 aPK_obj = {
1483 'pk_patient': episode['pk_patient'],
1484 'pk_episode': episode['pk_episode'],
1485 'pk_health_issue': episode['pk_health_issue']
1486 },
1487 try_potential_problems = allow_closed
1488 )
1489 #============================================================
1490 # encounter API
1491 #============================================================
1493 """Represents one encounter."""
1494
1495 _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s"
1496 _cmds_store_payload = [
1497 u"""UPDATE clin.encounter SET
1498 started = %(started)s,
1499 last_affirmed = %(last_affirmed)s,
1500 fk_location = %(pk_location)s,
1501 fk_type = %(pk_type)s,
1502 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
1503 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
1504 WHERE
1505 pk = %(pk_encounter)s AND
1506 xmin = %(xmin_encounter)s
1507 """,
1508 # need to return all fields so we can survive in-place upgrades
1509 u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s"""
1510 ]
1511 _updatable_fields = [
1512 'started',
1513 'last_affirmed',
1514 'pk_location',
1515 'pk_type',
1516 'reason_for_encounter',
1517 'assessment_of_encounter'
1518 ]
1519 #--------------------------------------------------------
1521 """Set the encounter as the active one.
1522
1523 "Setting active" means making sure the encounter
1524 row has the youngest "last_affirmed" timestamp of
1525 all encounter rows for this patient.
1526 """
1527 self['last_affirmed'] = gmDateTime.pydt_now_here()
1528 self.save()
1529 #--------------------------------------------------------
1531 """
1532 Moves every element currently linked to the current encounter
1533 and the source_episode onto target_episode.
1534
1535 @param source_episode The episode the elements are currently linked to.
1536 @type target_episode A cEpisode intance.
1537 @param target_episode The episode the elements will be relinked to.
1538 @type target_episode A cEpisode intance.
1539 """
1540 if source_episode['pk_episode'] == target_episode['pk_episode']:
1541 return True
1542
1543 queries = []
1544 cmd = u"""
1545 UPDATE clin.clin_root_item
1546 SET fk_episode = %(trg)s
1547 WHERE
1548 fk_encounter = %(enc)s AND
1549 fk_episode = %(src)s
1550 """
1551 rows, idx = gmPG2.run_rw_queries(queries = [{
1552 'cmd': cmd,
1553 'args': {
1554 'trg': target_episode['pk_episode'],
1555 'enc': self.pk_obj,
1556 'src': source_episode['pk_episode']
1557 }
1558 }])
1559 self.refetch_payload()
1560 return True
1561 #--------------------------------------------------------
1563
1564 relevant_fields = [
1565 'pk_location',
1566 'pk_type',
1567 'pk_patient',
1568 'reason_for_encounter',
1569 'assessment_of_encounter'
1570 ]
1571 for field in relevant_fields:
1572 if self._payload[self._idx[field]] != another_object[field]:
1573 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1574 return False
1575
1576 relevant_fields = [
1577 'started',
1578 'last_affirmed',
1579 ]
1580 for field in relevant_fields:
1581 if self._payload[self._idx[field]] is None:
1582 if another_object[field] is None:
1583 continue
1584 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1585 return False
1586
1587 if another_object[field] is None:
1588 return False
1589
1590 # compares at minute granularity
1591 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M') != another_object[field].strftime('%Y-%m-%d %H:%M'):
1592 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
1593 return False
1594
1595 # compare codes
1596 # 1) RFE
1597 if another_object['pk_generic_codes_rfe'] is None:
1598 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
1599 return False
1600 if another_object['pk_generic_codes_rfe'] is not None:
1601 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
1602 return False
1603 if (
1604 (another_object['pk_generic_codes_rfe'] is None)
1605 and
1606 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
1607 ) is False:
1608 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
1609 return False
1610 # 2) AOE
1611 if another_object['pk_generic_codes_aoe'] is None:
1612 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
1613 return False
1614 if another_object['pk_generic_codes_aoe'] is not None:
1615 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
1616 return False
1617 if (
1618 (another_object['pk_generic_codes_aoe'] is None)
1619 and
1620 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
1621 ) is False:
1622 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
1623 return False
1624
1625 return True
1626 #--------------------------------------------------------
1628 cmd = u"""
1629 select exists (
1630 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
1631 union all
1632 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1633 )"""
1634 args = {
1635 'pat': self._payload[self._idx['pk_patient']],
1636 'enc': self.pk_obj
1637 }
1638 rows, idx = gmPG2.run_ro_queries (
1639 queries = [{
1640 'cmd': cmd,
1641 'args': args
1642 }]
1643 )
1644 return rows[0][0]
1645 #--------------------------------------------------------
1647 cmd = u"""
1648 select exists (
1649 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
1650 )"""
1651 args = {
1652 'pat': self._payload[self._idx['pk_patient']],
1653 'enc': self.pk_obj
1654 }
1655 rows, idx = gmPG2.run_ro_queries (
1656 queries = [{
1657 'cmd': cmd,
1658 'args': args
1659 }]
1660 )
1661 return rows[0][0]
1662 #--------------------------------------------------------
1664 """soap_cats: <space> = admin category"""
1665
1666 if soap_cats is None:
1667 soap_cats = u'soap '
1668 else:
1669 soap_cats = soap_cats.lower()
1670
1671 cats = []
1672 for cat in soap_cats:
1673 if cat in u'soapu':
1674 cats.append(cat)
1675 continue
1676 if cat == u' ':
1677 cats.append(None)
1678
1679 cmd = u"""
1680 SELECT EXISTS (
1681 SELECT 1 FROM clin.clin_narrative
1682 WHERE
1683 fk_encounter = %(enc)s
1684 AND
1685 soap_cat IN %(cats)s
1686 LIMIT 1
1687 )
1688 """
1689 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
1690 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
1691 return rows[0][0]
1692 #--------------------------------------------------------
1694 cmd = u"""
1695 select exists (
1696 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
1697 )"""
1698 args = {
1699 'pat': self._payload[self._idx['pk_patient']],
1700 'enc': self.pk_obj
1701 }
1702 rows, idx = gmPG2.run_ro_queries (
1703 queries = [{
1704 'cmd': cmd,
1705 'args': args
1706 }]
1707 )
1708 return rows[0][0]
1709 #--------------------------------------------------------
1711
1712 if soap_cat is not None:
1713 soap_cat = soap_cat.lower()
1714
1715 if episode is None:
1716 epi_part = u'fk_episode is null'
1717 else:
1718 epi_part = u'fk_episode = %(epi)s'
1719
1720 cmd = u"""
1721 select narrative
1722 from clin.clin_narrative
1723 where
1724 fk_encounter = %%(enc)s
1725 and
1726 soap_cat = %%(cat)s
1727 and
1728 %s
1729 order by clin_when desc
1730 limit 1
1731 """ % epi_part
1732
1733 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
1734
1735 rows, idx = gmPG2.run_ro_queries (
1736 queries = [{
1737 'cmd': cmd,
1738 'args': args
1739 }]
1740 )
1741 if len(rows) == 0:
1742 return None
1743
1744 return rows[0][0]
1745 #--------------------------------------------------------
1747 cmd = u"""
1748 SELECT * FROM clin.v_pat_episodes
1749 WHERE pk_episode IN (
1750 SELECT DISTINCT fk_episode
1751 FROM clin.clin_root_item
1752 WHERE fk_encounter = %%(enc)s
1753
1754 UNION
1755
1756 SELECT DISTINCT fk_episode
1757 FROM blobs.doc_med
1758 WHERE fk_encounter = %%(enc)s
1759 ) %s"""
1760 args = {'enc': self.pk_obj}
1761 if exclude is not None:
1762 cmd = cmd % u'AND pk_episode NOT IN %(excluded)s'
1763 args['excluded'] = tuple(exclude)
1764 else:
1765 cmd = cmd % u''
1766
1767 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1768
1769 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1770
1771 episodes = property(get_episodes, lambda x:x)
1772 #--------------------------------------------------------
1774 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1775 if field == u'rfe':
1776 cmd = u"INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1777 elif field == u'aoe':
1778 cmd = u"INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
1779 else:
1780 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1781 args = {
1782 'item': self._payload[self._idx['pk_encounter']],
1783 'code': pk_code
1784 }
1785 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1786 return True
1787 #--------------------------------------------------------
1789 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1790 if field == u'rfe':
1791 cmd = u"DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1792 elif field == u'aoe':
1793 cmd = u"DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1794 else:
1795 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
1796 args = {
1797 'item': self._payload[self._idx['pk_encounter']],
1798 'code': pk_code
1799 }
1800 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1801 return True
1802 #--------------------------------------------------------
1803 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
1804
1805 lines = []
1806 for soap_cat in soap_cats:
1807 soap_cat_narratives = emr.get_clin_narrative (
1808 episodes = episodes,
1809 issues = issues,
1810 encounters = [self._payload[self._idx['pk_encounter']]],
1811 soap_cats = [soap_cat]
1812 )
1813 if soap_cat_narratives is None:
1814 continue
1815 if len(soap_cat_narratives) == 0:
1816 continue
1817
1818 lines.append(u'%s%s %s %s' % (
1819 gmTools.u_box_top_left_arc,
1820 gmTools.u_box_horiz_single,
1821 gmClinNarrative.soap_cat2l10n_str[soap_cat],
1822 gmTools.u_box_horiz_single * 5
1823 ))
1824 for soap_entry in soap_cat_narratives:
1825 txt = gmTools.wrap (
1826 text = soap_entry['narrative'],
1827 width = 75,
1828 initial_indent = u'',
1829 subsequent_indent = (u' ' * left_margin)
1830 )
1831 lines.append(txt)
1832 when = gmDateTime.pydt_strftime (
1833 soap_entry['date'],
1834 format = '%Y-%m-%d %H:%M',
1835 accuracy = gmDateTime.acc_minutes
1836 )
1837 txt = u'%s%s %.8s, %s %s' % (
1838 u' ' * 40,
1839 gmTools.u_box_horiz_light_heavy,
1840 soap_entry['provider'],
1841 when,
1842 gmTools.u_box_horiz_heavy_light
1843 )
1844 lines.append(txt)
1845 lines.append('')
1846
1847 return lines
1848 #--------------------------------------------------------
1850
1851 nothing2format = (
1852 (self._payload[self._idx['reason_for_encounter']] is None)
1853 and
1854 (self._payload[self._idx['assessment_of_encounter']] is None)
1855 and
1856 (self.has_soap_narrative(soap_cats = u'soapu') is False)
1857 )
1858 if nothing2format:
1859 return u''
1860
1861 if date_format is None:
1862 date_format = '%A, %B %d %Y'
1863
1864 tex = u'\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % (
1865 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]),
1866 self._payload[self._idx['started']].strftime(date_format).decode(gmI18N.get_encoding()),
1867 self._payload[self._idx['started']].strftime('%H:%M'),
1868 self._payload[self._idx['last_affirmed']].strftime('%H:%M')
1869 )
1870 tex += u'\\hline \\tabularnewline \n'
1871
1872 for epi in self.get_episodes():
1873 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order)
1874 if len(soaps) == 0:
1875 continue
1876 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
1877 gmTools.tex_escape_string(_('Problem')),
1878 gmTools.tex_escape_string(epi['description']),
1879 gmTools.coalesce (
1880 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']),
1881 instead = u'',
1882 template_initial = u' {\\footnotesize [%s]}',
1883 none_equivalents = [None, u'']
1884 )
1885 )
1886 if epi['pk_health_issue'] is not None:
1887 tex += u'\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
1888 gmTools.tex_escape_string(_('Health issue')),
1889 gmTools.tex_escape_string(epi['health_issue']),
1890 gmTools.coalesce (
1891 initial = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']),
1892 instead = u'',
1893 template_initial = u' {\\footnotesize [%s]}',
1894 none_equivalents = [None, u'']
1895 )
1896 )
1897 for soap in soaps:
1898 tex += u'{\\small %s} & %s \\tabularnewline \n' % (
1899 gmClinNarrative.soap_cat2l10n[soap['soap_cat']],
1900 gmTools.tex_escape_string(soap['narrative'].strip(u'\n'))
1901 )
1902 tex += u' & \\tabularnewline \n'
1903
1904 if self._payload[self._idx['reason_for_encounter']] is not None:
1905 tex += u'%s & %s \\tabularnewline \n' % (
1906 gmTools.tex_escape_string(_('RFE')),
1907 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']])
1908 )
1909 if self._payload[self._idx['assessment_of_encounter']] is not None:
1910 tex += u'%s & %s \\tabularnewline \n' % (
1911 gmTools.tex_escape_string(_('AOE')),
1912 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']])
1913 )
1914
1915 tex += u'\\hline \\tabularnewline \n'
1916 tex += u' & \\tabularnewline \n'
1917
1918 return tex
1919 #--------------------------------------------------------
1921 lines = []
1922
1923 if fancy_header:
1924 lines.append(u'%s%s: %s - %s (@%s)%s [#%s]' % (
1925 u' ' * left_margin,
1926 self._payload[self._idx['l10n_type']],
1927 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'),
1928 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
1929 self._payload[self._idx['source_time_zone']],
1930 gmTools.coalesce (
1931 self._payload[self._idx['assessment_of_encounter']],
1932 u'',
1933 u' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
1934 ),
1935 self._payload[self._idx['pk_encounter']]
1936 ))
1937
1938 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % (
1939 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'),
1940 self._payload[self._idx['last_affirmed']].strftime('%H:%M'),
1941 gmDateTime.current_local_iso_numeric_timezone_string,
1942 gmTools.bool2subst (
1943 gmDateTime.dst_currently_in_effect,
1944 gmDateTime.py_dst_timezone_name,
1945 gmDateTime.py_timezone_name
1946 ),
1947 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, u' - ' + _('daylight savings time in effect'), u'')
1948 ))
1949
1950 if self._payload[self._idx['reason_for_encounter']] is not None:
1951 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
1952 codes = self.generic_codes_rfe
1953 for c in codes:
1954 lines.append(u' %s: %s (%s - %s)' % (
1955 c['code'],
1956 c['term'],
1957 c['name_short'],
1958 c['version']
1959 ))
1960 if len(codes) > 0:
1961 lines.append(u'')
1962
1963 if self._payload[self._idx['assessment_of_encounter']] is not None:
1964 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
1965 codes = self.generic_codes_aoe
1966 for c in codes:
1967 lines.append(u' %s: %s (%s - %s)' % (
1968 c['code'],
1969 c['term'],
1970 c['name_short'],
1971 c['version']
1972 ))
1973 if len(codes) > 0:
1974 lines.append(u'')
1975 del codes
1976 return lines
1977
1978 now = gmDateTime.pydt_now_here()
1979 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'):
1980 start = u'%s %s' % (
1981 _('today'),
1982 self._payload[self._idx['started_original_tz']].strftime('%H:%M')
1983 )
1984 else:
1985 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M')
1986 lines.append(u'%s%s: %s - %s%s' % (
1987 u' ' * left_margin,
1988 self._payload[self._idx['l10n_type']],
1989 start,
1990 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
1991 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], u'', u' \u00BB%s\u00AB')
1992 ))
1993 if with_rfe_aoe:
1994 if self._payload[self._idx['reason_for_encounter']] is not None:
1995 lines.append(u'%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
1996 codes = self.generic_codes_rfe
1997 for c in codes:
1998 lines.append(u' %s: %s (%s - %s)' % (
1999 c['code'],
2000 c['term'],
2001 c['name_short'],
2002 c['version']
2003 ))
2004 if len(codes) > 0:
2005 lines.append(u'')
2006 if self._payload[self._idx['assessment_of_encounter']] is not None:
2007 lines.append(u'%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
2008 codes = self.generic_codes_aoe
2009 if len(codes) > 0:
2010 lines.append(u'')
2011 for c in codes:
2012 lines.append(u' %s: %s (%s - %s)' % (
2013 c['code'],
2014 c['term'],
2015 c['name_short'],
2016 c['version']
2017 ))
2018 if len(codes) > 0:
2019 lines.append(u'')
2020 del codes
2021
2022 return lines
2023 #--------------------------------------------------------
2024 - 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):
2025
2026 lines = []
2027 emr = patient.emr
2028 if episodes is None:
2029 episodes = [ e['pk_episode'] for e in self.episodes ]
2030
2031 for pk in episodes:
2032 epi = cEpisode(aPK_obj = pk)
2033 lines.append(_('\nEpisode %s%s%s%s:') % (
2034 gmTools.u_left_double_angle_quote,
2035 epi['description'],
2036 gmTools.u_right_double_angle_quote,
2037 gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
2038 ))
2039
2040 # soap
2041 if with_soap:
2042 if patient.ID != self._payload[self._idx['pk_patient']]:
2043 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2044 patient.ID,
2045 self._payload[self._idx['pk_encounter']],
2046 self._payload[self._idx['pk_patient']]
2047 )
2048 raise ValueError(msg)
2049
2050 lines.extend(self.format_soap (
2051 episodes = [pk],
2052 left_margin = left_margin,
2053 soap_cats = 'soapu',
2054 emr = emr,
2055 issues = issues
2056 ))
2057
2058 # test results
2059 if with_tests:
2060 tests = emr.get_test_results_by_date (
2061 episodes = [pk],
2062 encounter = self._payload[self._idx['pk_encounter']]
2063 )
2064 if len(tests) > 0:
2065 lines.append('')
2066 lines.append(_('Measurements and Results:'))
2067
2068 for t in tests:
2069 lines.extend(t.format())
2070
2071 del tests
2072
2073 # vaccinations
2074 if with_vaccinations:
2075 vaccs = emr.get_vaccinations (
2076 episodes = [pk],
2077 encounters = [ self._payload[self._idx['pk_encounter']] ],
2078 order_by = u'date_given DESC, vaccine'
2079 )
2080 if len(vaccs) > 0:
2081 lines.append(u'')
2082 lines.append(_('Vaccinations:'))
2083 for vacc in vaccs:
2084 lines.extend(vacc.format (
2085 with_indications = True,
2086 with_comment = True,
2087 with_reaction = True,
2088 date_format = '%Y-%m-%d'
2089 ))
2090 del vaccs
2091
2092 # family history
2093 if with_family_history:
2094 fhx = emr.get_family_history(episodes = [pk])
2095 if len(fhx) > 0:
2096 lines.append(u'')
2097 lines.append(_('Family History: %s') % len(fhx))
2098 for f in fhx:
2099 lines.append(f.format (
2100 left_margin = (left_margin + 1),
2101 include_episode = False,
2102 include_comment = True
2103 ))
2104 del fhx
2105
2106 # documents
2107 if with_docs:
2108 doc_folder = patient.get_document_folder()
2109 docs = doc_folder.get_documents (
2110 episodes = [pk],
2111 encounter = self._payload[self._idx['pk_encounter']]
2112 )
2113 if len(docs) > 0:
2114 lines.append(u'')
2115 lines.append(_('Documents:'))
2116 for d in docs:
2117 lines.append(u' %s %s:%s%s' % (
2118 d['clin_when'].strftime('%Y-%m-%d'),
2119 d['l10n_type'],
2120 gmTools.coalesce(d['comment'], u'', u' "%s"'),
2121 gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
2122 ))
2123
2124 del docs
2125
2126 return lines
2127 #--------------------------------------------------------
2128 - 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):
2129 """Format an encounter.
2130
2131 with_co_encountlet_hints:
2132 - whether to include which *other* episodes were discussed during this encounter
2133 - (only makes sense if episodes != None)
2134 """
2135 lines = self.format_header (
2136 fancy_header = fancy_header,
2137 left_margin = left_margin,
2138 with_rfe_aoe = with_rfe_aoe
2139 )
2140
2141 if by_episode:
2142 lines.extend(self.format_by_episode (
2143 episodes = episodes,
2144 issues = issues,
2145 left_margin = left_margin,
2146 patient = patient,
2147 with_soap = with_soap,
2148 with_tests = with_tests,
2149 with_docs = with_docs,
2150 with_vaccinations = with_vaccinations,
2151 with_family_history = with_family_history
2152 ))
2153
2154 else:
2155 if with_soap:
2156 lines.append(u'')
2157
2158 if patient.ID != self._payload[self._idx['pk_patient']]:
2159 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2160 patient.ID,
2161 self._payload[self._idx['pk_encounter']],
2162 self._payload[self._idx['pk_patient']]
2163 )
2164 raise ValueError(msg)
2165
2166 emr = patient.get_emr()
2167
2168 lines.extend(self.format_soap (
2169 episodes = episodes,
2170 left_margin = left_margin,
2171 soap_cats = 'soapu',
2172 emr = emr,
2173 issues = issues
2174 ))
2175
2176 # # family history
2177 # if with_family_history:
2178 # if episodes is not None:
2179 # fhx = emr.get_family_history(episodes = episodes)
2180 # if len(fhx) > 0:
2181 # lines.append(u'')
2182 # lines.append(_('Family History: %s') % len(fhx))
2183 # for f in fhx:
2184 # lines.append(f.format (
2185 # left_margin = (left_margin + 1),
2186 # include_episode = False,
2187 # include_comment = True
2188 # ))
2189 # del fhx
2190
2191 # test results
2192 if with_tests:
2193 emr = patient.get_emr()
2194 tests = emr.get_test_results_by_date (
2195 episodes = episodes,
2196 encounter = self._payload[self._idx['pk_encounter']]
2197 )
2198 if len(tests) > 0:
2199 lines.append('')
2200 lines.append(_('Measurements and Results:'))
2201
2202 for t in tests:
2203 lines.extend(t.format())
2204
2205 del tests
2206
2207 # vaccinations
2208 if with_vaccinations:
2209 emr = patient.get_emr()
2210 vaccs = emr.get_vaccinations (
2211 episodes = episodes,
2212 encounters = [ self._payload[self._idx['pk_encounter']] ],
2213 order_by = u'date_given DESC, vaccine'
2214 )
2215
2216 if len(vaccs) > 0:
2217 lines.append(u'')
2218 lines.append(_('Vaccinations:'))
2219
2220 for vacc in vaccs:
2221 lines.extend(vacc.format (
2222 with_indications = True,
2223 with_comment = True,
2224 with_reaction = True,
2225 date_format = '%Y-%m-%d'
2226 ))
2227 del vaccs
2228
2229 # documents
2230 if with_docs:
2231 doc_folder = patient.get_document_folder()
2232 docs = doc_folder.get_documents (
2233 episodes = episodes,
2234 encounter = self._payload[self._idx['pk_encounter']]
2235 )
2236
2237 if len(docs) > 0:
2238 lines.append(u'')
2239 lines.append(_('Documents:'))
2240
2241 for d in docs:
2242 lines.append(u' %s %s:%s%s' % (
2243 d['clin_when'].strftime('%Y-%m-%d'),
2244 d['l10n_type'],
2245 gmTools.coalesce(d['comment'], u'', u' "%s"'),
2246 gmTools.coalesce(d['ext_ref'], u'', u' (%s)')
2247 ))
2248
2249 del docs
2250
2251 # co-encountlets
2252 if with_co_encountlet_hints:
2253 if episodes is not None:
2254 other_epis = self.get_episodes(exclude = episodes)
2255 if len(other_epis) > 0:
2256 lines.append(u'')
2257 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis))
2258 for epi in other_epis:
2259 lines.append(u' %s%s%s%s' % (
2260 gmTools.u_left_double_angle_quote,
2261 epi['description'],
2262 gmTools.u_right_double_angle_quote,
2263 gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
2264 ))
2265
2266 eol_w_margin = u'\n%s' % (u' ' * left_margin)
2267 return u'%s\n' % eol_w_margin.join(lines)
2268 #--------------------------------------------------------
2269 # properties
2270 #--------------------------------------------------------
2272 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2273 return []
2274
2275 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2276 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2277 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2278 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2279
2281 queries = []
2282 # remove all codes
2283 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2284 queries.append ({
2285 'cmd': u'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2286 'args': {
2287 'enc': self._payload[self._idx['pk_encounter']],
2288 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2289 }
2290 })
2291 # add new codes
2292 for pk_code in pk_codes:
2293 queries.append ({
2294 'cmd': u'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2295 'args': {
2296 'enc': self._payload[self._idx['pk_encounter']],
2297 'pk_code': pk_code
2298 }
2299 })
2300 if len(queries) == 0:
2301 return
2302 # run it all in one transaction
2303 rows, idx = gmPG2.run_rw_queries(queries = queries)
2304 self.refetch_payload()
2305 return
2306
2307 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2308 #--------------------------------------------------------
2310 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2311 return []
2312
2313 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2314 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2315 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2316 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2317
2319 queries = []
2320 # remove all codes
2321 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2322 queries.append ({
2323 'cmd': u'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2324 'args': {
2325 'enc': self._payload[self._idx['pk_encounter']],
2326 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2327 }
2328 })
2329 # add new codes
2330 for pk_code in pk_codes:
2331 queries.append ({
2332 'cmd': u'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2333 'args': {
2334 'enc': self._payload[self._idx['pk_encounter']],
2335 'pk_code': pk_code
2336 }
2337 })
2338 if len(queries) == 0:
2339 return
2340 # run it all in one transaction
2341 rows, idx = gmPG2.run_rw_queries(queries = queries)
2342 self.refetch_payload()
2343 return
2344
2345 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2346 #-----------------------------------------------------------
2348 """Creates a new encounter for a patient.
2349
2350 fk_patient - patient PK
2351 fk_location - encounter location
2352 enc_type - type of encounter
2353
2354 FIXME: we don't deal with location yet
2355 """
2356 if enc_type is None:
2357 enc_type = u'in surgery'
2358 # insert new encounter
2359 queries = []
2360 try:
2361 enc_type = int(enc_type)
2362 cmd = u"""
2363 INSERT INTO clin.encounter (
2364 fk_patient, fk_location, fk_type
2365 ) VALUES (
2366 %(pat)s,
2367 -1,
2368 %(typ)s
2369 ) RETURNING pk"""
2370 except ValueError:
2371 enc_type = enc_type
2372 cmd = u"""
2373 insert into clin.encounter (
2374 fk_patient, fk_location, fk_type
2375 ) values (
2376 %(pat)s,
2377 -1,
2378 coalesce (
2379 (select pk from clin.encounter_type where description = %(typ)s),
2380 -- pick the first available
2381 (select pk from clin.encounter_type limit 1)
2382 )
2383 ) RETURNING pk"""
2384 args = {'pat': fk_patient, 'typ': enc_type}
2385 queries.append({'cmd': cmd, 'args': args})
2386 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
2387 encounter = cEncounter(aPK_obj = rows[0]['pk'])
2388
2389 return encounter
2390 #-----------------------------------------------------------
2392
2393 rows, idx = gmPG2.run_rw_queries(
2394 queries = [{
2395 'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
2396 'args': {'desc': description, 'l10n_desc': l10n_description}
2397 }],
2398 return_data = True
2399 )
2400
2401 success = rows[0][0]
2402 if not success:
2403 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
2404
2405 return {'description': description, 'l10n_description': l10n_description}
2406 #-----------------------------------------------------------
2408 """This will attempt to create a NEW encounter type."""
2409
2410 # need a system name, so derive one if necessary
2411 if description is None:
2412 description = l10n_description
2413
2414 args = {
2415 'desc': description,
2416 'l10n_desc': l10n_description
2417 }
2418
2419 _log.debug('creating encounter type: %s, %s', description, l10n_description)
2420
2421 # does it exist already ?
2422 cmd = u"select description, _(description) from clin.encounter_type where description = %(desc)s"
2423 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2424
2425 # yes
2426 if len(rows) > 0:
2427 # both system and l10n name are the same so all is well
2428 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
2429 _log.info('encounter type [%s] already exists with the proper translation')
2430 return {'description': description, 'l10n_description': l10n_description}
2431
2432 # or maybe there just wasn't a translation to
2433 # the current language for this type yet ?
2434 cmd = u"select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
2435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
2436
2437 # there was, so fail
2438 if rows[0][0]:
2439 _log.error('encounter type [%s] already exists but with another translation')
2440 return None
2441
2442 # else set it
2443 cmd = u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
2444 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2445 return {'description': description, 'l10n_description': l10n_description}
2446
2447 # no
2448 queries = [
2449 {'cmd': u"insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
2450 {'cmd': u"select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
2451 ]
2452 rows, idx = gmPG2.run_rw_queries(queries = queries)
2453
2454 return {'description': description, 'l10n_description': l10n_description}
2455 #-----------------------------------------------------------
2457 cmd = u"""
2458 SELECT
2459 _(description) AS l10n_description,
2460 description
2461 FROM
2462 clin.encounter_type
2463 ORDER BY
2464 l10n_description
2465 """
2466 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
2467 return rows
2468 #-----------------------------------------------------------
2470 cmd = u"SELECT * from clin.encounter_type where description = %s"
2471 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}])
2472 return rows
2473 #-----------------------------------------------------------
2475 cmd = u"delete from clin.encounter_type where description = %(desc)s"
2476 args = {'desc': description}
2477 try:
2478 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2479 except gmPG2.dbapi.IntegrityError, e:
2480 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
2481 return False
2482 raise
2483
2484 return True
2485 #============================================================
2487 """Represents one problem.
2488
2489 problems are the aggregation of
2490 .clinically_relevant=True issues and
2491 .is_open=True episodes
2492 """
2493 _cmd_fetch_payload = u'' # will get programmatically defined in __init__
2494 _cmds_store_payload = [u"select 1"]
2495 _updatable_fields = []
2496
2497 #--------------------------------------------------------
2499 """Initialize.
2500
2501 aPK_obj must contain the keys
2502 pk_patient
2503 pk_episode
2504 pk_health_issue
2505 """
2506 if aPK_obj is None:
2507 raise gmExceptions.ConstructorError, 'cannot instatiate cProblem for PK: [%s]' % (aPK_obj)
2508
2509 # As problems are rows from a view of different emr struct items,
2510 # the PK can't be a single field and, as some of the values of the
2511 # composed PK may be None, they must be queried using 'is null',
2512 # so we must programmatically construct the SQL query
2513 where_parts = []
2514 pk = {}
2515 for col_name in aPK_obj.keys():
2516 val = aPK_obj[col_name]
2517 if val is None:
2518 where_parts.append('%s IS NULL' % col_name)
2519 else:
2520 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
2521 pk[col_name] = val
2522
2523 # try to instantiate from true problem view
2524 cProblem._cmd_fetch_payload = u"""
2525 SELECT *, False as is_potential_problem
2526 FROM clin.v_problem_list
2527 WHERE %s""" % u' AND '.join(where_parts)
2528
2529 try:
2530 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2531 return
2532 except gmExceptions.ConstructorError:
2533 _log.exception('actual problem not found, trying "potential" problems')
2534 if try_potential_problems is False:
2535 raise
2536
2537 # try to instantiate from potential-problems view
2538 cProblem._cmd_fetch_payload = u"""
2539 SELECT *, True as is_potential_problem
2540 FROM clin.v_potential_problem_list
2541 WHERE %s""" % u' AND '.join(where_parts)
2542 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
2543 #--------------------------------------------------------
2545 """
2546 Retrieve the cEpisode instance equivalent to this problem.
2547 The problem's type attribute must be 'episode'
2548 """
2549 if self._payload[self._idx['type']] != 'episode':
2550 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2551 return None
2552 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
2553 #--------------------------------------------------------
2555 """
2556 Retrieve the cHealthIssue instance equivalent to this problem.
2557 The problem's type attribute must be 'issue'
2558 """
2559 if self._payload[self._idx['type']] != 'issue':
2560 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
2561 return None
2562 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
2563 #--------------------------------------------------------
2565
2566 if self._payload[self._idx['type']] == u'issue':
2567 episodes = [ cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode ]
2568 #xxxxxxxxxxxxx
2569
2570 emr = patient.get_emr()
2571
2572 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID)
2573 return doc_folder.get_visual_progress_notes (
2574 health_issue = self._payload[self._idx['pk_health_issue']],
2575 episode = self._payload[self._idx['pk_episode']]
2576 )
2577 #--------------------------------------------------------
2578 # properties
2579 #--------------------------------------------------------
2580 # doubles as 'diagnostic_certainty_description' getter:
2582 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
2583
2584 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
2585 #--------------------------------------------------------
2587 if self._payload[self._idx['type']] == u'issue':
2588 cmd = u"""
2589 SELECT * FROM clin.v_linked_codes WHERE
2590 item_table = 'clin.lnk_code2h_issue'::regclass
2591 AND
2592 pk_item = %(item)s
2593 """
2594 args = {'item': self._payload[self._idx['pk_health_issue']]}
2595 else:
2596 cmd = u"""
2597 SELECT * FROM clin.v_linked_codes WHERE
2598 item_table = 'clin.lnk_code2episode'::regclass
2599 AND
2600 pk_item = %(item)s
2601 """
2602 args = {'item': self._payload[self._idx['pk_episode']]}
2603
2604 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2605 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2606
2607 generic_codes = property(_get_generic_codes, lambda x:x)
2608 #-----------------------------------------------------------
2610 """Retrieve the cEpisode instance equivalent to the given problem.
2611
2612 The problem's type attribute must be 'episode'
2613
2614 @param problem: The problem to retrieve its related episode for
2615 @type problem: A gmEMRStructItems.cProblem instance
2616 """
2617 if isinstance(problem, cEpisode):
2618 return problem
2619
2620 exc = TypeError('cannot convert [%s] to episode' % problem)
2621
2622 if not isinstance(problem, cProblem):
2623 raise exc
2624
2625 if problem['type'] != 'episode':
2626 raise exc
2627
2628 return cEpisode(aPK_obj = problem['pk_episode'])
2629 #-----------------------------------------------------------
2631 """Retrieve the cIssue instance equivalent to the given problem.
2632
2633 The problem's type attribute must be 'issue'.
2634
2635 @param problem: The problem to retrieve the corresponding issue for
2636 @type problem: A gmEMRStructItems.cProblem instance
2637 """
2638 if isinstance(problem, cHealthIssue):
2639 return problem
2640
2641 exc = TypeError('cannot convert [%s] to health issue' % problem)
2642
2643 if not isinstance(problem, cProblem):
2644 raise exc
2645
2646 if problem['type'] != 'issue':
2647 raise exc
2648
2649 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2650 #-----------------------------------------------------------
2652 """Transform given problem into either episode or health issue instance.
2653 """
2654 if isinstance(problem, (cEpisode, cHealthIssue)):
2655 return problem
2656
2657 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
2658
2659 if not isinstance(problem, cProblem):
2660 _log.debug(u'%s' % problem)
2661 raise exc
2662
2663 if problem['type'] == 'episode':
2664 return cEpisode(aPK_obj = problem['pk_episode'])
2665
2666 if problem['type'] == 'issue':
2667 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
2668
2669 raise exc
2670 #============================================================
2672
2673 _cmd_fetch_payload = u"select * from clin.v_pat_hospital_stays where pk_hospital_stay = %s"
2674 _cmds_store_payload = [
2675 u"""update clin.hospital_stay set
2676 clin_when = %(admission)s,
2677 discharge = %(discharge)s,
2678 narrative = gm.nullify_empty_string(%(hospital)s),
2679 fk_episode = %(pk_episode)s,
2680 fk_encounter = %(pk_encounter)s
2681 where
2682 pk = %(pk_hospital_stay)s and
2683 xmin = %(xmin_hospital_stay)s""",
2684 u"""select xmin_hospital_stay from clin.v_pat_hospital_stays where pk_hospital_stay = %(pk_hospital_stay)s"""
2685 ]
2686 _updatable_fields = [
2687 'admission',
2688 'discharge',
2689 'hospital',
2690 'pk_episode',
2691 'pk_encounter'
2692 ]
2693 #-------------------------------------------------------
2695
2696 if self._payload[self._idx['discharge']] is not None:
2697 discharge = u' - %s' % self._payload[self._idx['discharge']].strftime('%Y %b %d').decode(gmI18N.get_encoding())
2698 else:
2699 discharge = u''
2700
2701 line = u'%s%s%s%s: %s%s%s' % (
2702 u' ' * left_margin,
2703 self._payload[self._idx['admission']].strftime('%Y %b %d').decode(gmI18N.get_encoding()),
2704 discharge,
2705 gmTools.coalesce(self._payload[self._idx['hospital']], u'', u' (%s)'),
2706 gmTools.u_left_double_angle_quote,
2707 self._payload[self._idx['episode']],
2708 gmTools.u_right_double_angle_quote
2709 )
2710
2711 return line
2712 #-----------------------------------------------------------
2714 queries = [{
2715 # this assumes non-overarching stays
2716 'cmd': u'SELECT * FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1',
2717 'args': {'pat': patient}
2718 }]
2719 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2720 if len(rows) == 0:
2721 return None
2722 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
2723 #-----------------------------------------------------------
2725 args = {'pat': patient}
2726 if ongoing_only:
2727 cmd = u"""
2728 SELECT *
2729 FROM clin.v_pat_hospital_stays
2730 WHERE
2731 pk_patient = %(pat)s
2732 AND
2733 discharge is NULL
2734 ORDER BY admission"""
2735 else:
2736 cmd = u"""
2737 SELECT *
2738 FROM clin.v_pat_hospital_stays
2739 WHERE pk_patient = %(pat)s
2740 ORDER BY admission"""
2741
2742 queries = [{'cmd': cmd, 'args': args}]
2743 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2744
2745 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
2746 #-----------------------------------------------------------
2748
2749 queries = [{
2750 'cmd': u'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode) VALUES (%(enc)s, %(epi)s) RETURNING pk',
2751 'args': {'enc': encounter, 'epi': episode}
2752 }]
2753 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
2754
2755 return cHospitalStay(aPK_obj = rows[0][0])
2756 #-----------------------------------------------------------
2758 cmd = u'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
2759 args = {'pk': stay}
2760 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2761 return True
2762 #============================================================
2764
2765 _cmd_fetch_payload = u"select * from clin.v_pat_procedures where pk_procedure = %s"
2766 _cmds_store_payload = [
2767 u"""UPDATE clin.procedure SET
2768 soap_cat = 'p',
2769 clin_when = %(clin_when)s,
2770 clin_end = %(clin_end)s,
2771 is_ongoing = %(is_ongoing)s,
2772 clin_where = NULLIF (
2773 COALESCE (
2774 %(pk_hospital_stay)s::TEXT,
2775 gm.nullify_empty_string(%(clin_where)s)
2776 ),
2777 %(pk_hospital_stay)s::TEXT
2778 ),
2779 narrative = gm.nullify_empty_string(%(performed_procedure)s),
2780 fk_hospital_stay = %(pk_hospital_stay)s,
2781 fk_episode = %(pk_episode)s,
2782 fk_encounter = %(pk_encounter)s
2783 WHERE
2784 pk = %(pk_procedure)s AND
2785 xmin = %(xmin_procedure)s
2786 RETURNING xmin as xmin_procedure"""
2787 ]
2788 _updatable_fields = [
2789 'clin_when',
2790 'clin_end',
2791 'is_ongoing',
2792 'clin_where',
2793 'performed_procedure',
2794 'pk_hospital_stay',
2795 'pk_episode',
2796 'pk_encounter'
2797 ]
2798 #-------------------------------------------------------
2800
2801 if (attribute == 'pk_hospital_stay') and (value is not None):
2802 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'clin_where', None)
2803
2804 if (attribute == 'clin_where') and (value is not None) and (value.strip() != u''):
2805 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None)
2806
2807 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
2808 #-------------------------------------------------------
2810
2811 if self._payload[self._idx['is_ongoing']]:
2812 end = _(' (ongoing)')
2813 else:
2814 end = self._payload[self._idx['clin_end']]
2815 if end is None:
2816 end = u''
2817 else:
2818 end = u' - %s' % end.strftime('%Y %b %d').decode(gmI18N.get_encoding())
2819
2820 line = u'%s%s%s (%s): %s' % (
2821 (u' ' * left_margin),
2822 self._payload[self._idx['clin_when']].strftime('%Y %b %d').decode(gmI18N.get_encoding()),
2823 end,
2824 self._payload[self._idx['clin_where']],
2825 self._payload[self._idx['performed_procedure']]
2826 )
2827 if include_episode:
2828 line = u'%s (%s)' % (line, self._payload[self._idx['episode']])
2829
2830 if include_codes:
2831 codes = self.generic_codes
2832 if len(codes) > 0:
2833 line += u'\n'
2834 for c in codes:
2835 line += u'%s %s: %s (%s - %s)\n' % (
2836 (u' ' * left_margin),
2837 c['code'],
2838 c['term'],
2839 c['name_short'],
2840 c['version']
2841 )
2842 del codes
2843
2844 return line
2845 #--------------------------------------------------------
2847 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2848 cmd = u"INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)"
2849 args = {
2850 'issue': self._payload[self._idx['pk_procedure']],
2851 'code': pk_code
2852 }
2853 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2854 return True
2855 #--------------------------------------------------------
2857 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2858 cmd = u"DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s"
2859 args = {
2860 'issue': self._payload[self._idx['pk_procedure']],
2861 'code': pk_code
2862 }
2863 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2864 return True
2865 #--------------------------------------------------------
2866 # properties
2867 #--------------------------------------------------------
2869 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
2870 return []
2871
2872 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s'
2873 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
2874 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2875 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2876
2878 queries = []
2879 # remove all codes
2880 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
2881 queries.append ({
2882 'cmd': u'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s',
2883 'args': {
2884 'proc': self._payload[self._idx['pk_procedure']],
2885 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
2886 }
2887 })
2888 # add new codes
2889 for pk_code in pk_codes:
2890 queries.append ({
2891 'cmd': u'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)',
2892 'args': {
2893 'proc': self._payload[self._idx['pk_procedure']],
2894 'pk_code': pk_code
2895 }
2896 })
2897 if len(queries) == 0:
2898 return
2899 # run it all in one transaction
2900 rows, idx = gmPG2.run_rw_queries(queries = queries)
2901 return
2902
2903 generic_codes = property(_get_generic_codes, _set_generic_codes)
2904 #-----------------------------------------------------------
2906
2907 queries = [
2908 {
2909 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when',
2910 'args': {'pat': patient}
2911 }
2912 ]
2913
2914 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2915
2916 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
2917 #-----------------------------------------------------------
2919 queries = [
2920 {
2921 'cmd': u'select * from clin.v_pat_procedures where pk_patient = %(pat)s order by clin_when DESC LIMIT 1',
2922 'args': {'pat': patient}
2923 }
2924 ]
2925 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
2926 if len(rows) == 0:
2927 return None
2928 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
2929 #-----------------------------------------------------------
2930 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
2931
2932 queries = [{
2933 'cmd': u"""
2934 INSERT INTO clin.procedure (
2935 fk_encounter,
2936 fk_episode,
2937 soap_cat,
2938 clin_where,
2939 fk_hospital_stay,
2940 narrative
2941 ) VALUES (
2942 %(enc)s,
2943 %(epi)s,
2944 'p',
2945 gm.nullify_empty_string(%(loc)s),
2946 %(stay)s,
2947 gm.nullify_empty_string(%(proc)s)
2948 )
2949 RETURNING pk""",
2950 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure}
2951 }]
2952
2953 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
2954
2955 return cPerformedProcedure(aPK_obj = rows[0][0])
2956 #-----------------------------------------------------------
2958 cmd = u'delete from clin.procedure where pk = %(pk)s'
2959 args = {'pk': procedure}
2960 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2961 return True
2962 #============================================================
2963 # main - unit testing
2964 #------------------------------------------------------------
2965 if __name__ == '__main__':
2966
2967 if len(sys.argv) < 2:
2968 sys.exit()
2969
2970 if sys.argv[1] != 'test':
2971 sys.exit()
2972
2973 #--------------------------------------------------------
2974 # define tests
2975 #--------------------------------------------------------
2977 print "\nProblem test"
2978 print "------------"
2979 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
2980 print prob
2981 fields = prob.get_fields()
2982 for field in fields:
2983 print field, ':', prob[field]
2984 print '\nupdatable:', prob.get_updatable_fields()
2985 epi = prob.get_as_episode()
2986 print '\nas episode:'
2987 if epi is not None:
2988 for field in epi.get_fields():
2989 print ' .%s : %s' % (field, epi[field])
2990 #--------------------------------------------------------
2992 print "\nhealth issue test"
2993 print "-----------------"
2994 h_issue = cHealthIssue(aPK_obj=2)
2995 print h_issue
2996 print h_issue.latest_access_date
2997 print h_issue.end_date
2998 # fields = h_issue.get_fields()
2999 # for field in fields:
3000 # print field, ':', h_issue[field]
3001 # print "has open episode:", h_issue.has_open_episode()
3002 # print "open episode:", h_issue.get_open_episode()
3003 # print "updateable:", h_issue.get_updatable_fields()
3004 # h_issue.close_expired_episode(ttl=7300)
3005 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis')
3006 # print h_issue
3007 # print h_issue.format_as_journal()
3008 #--------------------------------------------------------
3010 print "\nepisode test"
3011 print "------------"
3012 episode = cEpisode(aPK_obj=1)
3013 print episode
3014 fields = episode.get_fields()
3015 for field in fields:
3016 print field, ':', episode[field]
3017 print "updatable:", episode.get_updatable_fields()
3018 raw_input('ENTER to continue')
3019
3020 old_description = episode['description']
3021 old_enc = cEncounter(aPK_obj = 1)
3022
3023 desc = '1-%s' % episode['description']
3024 print "==> renaming to", desc
3025 successful = episode.rename (
3026 description = desc
3027 )
3028 if not successful:
3029 print "error"
3030 else:
3031 print "success"
3032 for field in fields:
3033 print field, ':', episode[field]
3034
3035 print "episode range:", episode.get_access_range()
3036
3037 raw_input('ENTER to continue')
3038
3039 #--------------------------------------------------------
3041 print "\nencounter test"
3042 print "--------------"
3043 encounter = cEncounter(aPK_obj=1)
3044 print encounter
3045 fields = encounter.get_fields()
3046 for field in fields:
3047 print field, ':', encounter[field]
3048 print "updatable:", encounter.get_updatable_fields()
3049 #--------------------------------------------------------
3051 encounter = cEncounter(aPK_obj=1)
3052 print encounter
3053 print ""
3054 print encounter.format_latex()
3055 #--------------------------------------------------------
3057 procs = get_performed_procedures(patient = 12)
3058 for proc in procs:
3059 print proc.format(left_margin=2)
3060 #--------------------------------------------------------
3062 stay = create_hospital_stay(encounter = 1, episode = 2)
3063 stay['hospital'] = u'Starfleet Galaxy General Hospital'
3064 stay.save_payload()
3065 print stay
3066 for s in get_patient_hospital_stays(12):
3067 print s
3068 delete_hospital_stay(stay['pk_hospital_stay'])
3069 stay = create_hospital_stay(encounter = 1, episode = 4)
3070 #--------------------------------------------------------
3072 tests = [None, 'A', 'B', 'C', 'D', 'E']
3073
3074 for t in tests:
3075 print type(t), t
3076 print type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t)
3077 #--------------------------------------------------------
3082 #--------------------------------------------------------
3083 # run them
3084 #test_episode()
3085 #test_problem()
3086 #test_encounter()
3087 test_health_issue()
3088 #test_hospital_stay()
3089 #test_performed_procedure()
3090 #test_diagnostic_certainty_classification_map()
3091 #test_encounter2latex()
3092 #test_episode_codes()
3093 #============================================================
3094
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Feb 1 03:56:36 2013 | http://epydoc.sourceforge.net |