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