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