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