| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """GNUmed health related business object.
3
4 license: GPL v2 or later
5 """
6 #============================================================
7 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, <karsten.hilbert@gmx.net>"
8
9 import sys
10 import datetime
11 import logging
12 import io
13 import os
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmPG2
19 from Gnumed.pycommon import gmI18N
20 from Gnumed.pycommon import gmTools
21 from Gnumed.pycommon import gmDateTime
22 from Gnumed.pycommon import gmBusinessDBObject
23 from Gnumed.pycommon import gmNull
24 from Gnumed.pycommon import gmExceptions
25 from Gnumed.pycommon import gmMatchProvider
26
27 from Gnumed.business import gmClinNarrative
28 from Gnumed.business import gmSoapDefs
29 from Gnumed.business import gmCoding
30 from Gnumed.business import gmPraxis
31 from Gnumed.business import gmOrganization
32 from Gnumed.business import gmExternalCare
33 from Gnumed.business import gmDocuments
34
35
36 _log = logging.getLogger('gm.emr')
37
38
39 if __name__ == '__main__':
40 gmI18N.activate_locale()
41 gmI18N.install_domain('gnumed')
42
43 #============================================================
44 # diagnostic certainty classification
45 #============================================================
46 __diagnostic_certainty_classification_map = None
47
49
50 global __diagnostic_certainty_classification_map
51
52 if __diagnostic_certainty_classification_map is None:
53 __diagnostic_certainty_classification_map = {
54 None: '',
55 'A': _('A: Sign'),
56 'B': _('B: Cluster of signs'),
57 'C': _('C: Syndromic diagnosis'),
58 'D': _('D: Scientific diagnosis')
59 }
60
61 try:
62 return __diagnostic_certainty_classification_map[classification]
63 except KeyError:
64 return _('<%s>: unknown diagnostic certainty classification') % classification
65
66 #============================================================
67 # Health Issues API
68 #============================================================
69 laterality2str = {
70 None: '?',
71 'na': '',
72 'sd': _('bilateral'),
73 'ds': _('bilateral'),
74 's': _('left'),
75 'd': _('right')
76 }
77
78 #============================================================
80 """Represents one health issue."""
81
82 #_cmd_fetch_payload = u"select *, xmin_health_issue from clin.v_health_issues where pk_health_issue=%s"
83 _cmd_fetch_payload = "select * from clin.v_health_issues where pk_health_issue = %s"
84 _cmds_store_payload = [
85 """update clin.health_issue set
86 description = %(description)s,
87 summary = gm.nullify_empty_string(%(summary)s),
88 age_noted = %(age_noted)s,
89 laterality = gm.nullify_empty_string(%(laterality)s),
90 grouping = gm.nullify_empty_string(%(grouping)s),
91 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s),
92 is_active = %(is_active)s,
93 clinically_relevant = %(clinically_relevant)s,
94 is_confidential = %(is_confidential)s,
95 is_cause_of_death = %(is_cause_of_death)s
96 WHERE
97 pk = %(pk_health_issue)s
98 AND
99 xmin = %(xmin_health_issue)s""",
100 "select xmin as xmin_health_issue from clin.health_issue where pk = %(pk_health_issue)s"
101 ]
102 _updatable_fields = [
103 'description',
104 'summary',
105 'grouping',
106 'age_noted',
107 'laterality',
108 'is_active',
109 'clinically_relevant',
110 'is_confidential',
111 'is_cause_of_death',
112 'diagnostic_certainty_classification'
113 ]
114
115 #--------------------------------------------------------
116 - def __init__(self, aPK_obj=None, encounter=None, name='xxxDEFAULTxxx', patient=None, row=None):
117 pk = aPK_obj
118
119 if (pk is not None) or (row is not None):
120 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row)
121 return
122
123 if patient is None:
124 cmd = """select *, xmin_health_issue from clin.v_health_issues
125 where
126 description = %(desc)s
127 and
128 pk_patient = (select fk_patient from clin.encounter where pk = %(enc)s)"""
129 else:
130 cmd = """select *, xmin_health_issue from clin.v_health_issues
131 where
132 description = %(desc)s
133 and
134 pk_patient = %(pat)s"""
135
136 queries = [{'cmd': cmd, 'args': {'enc': encounter, 'desc': name, 'pat': patient}}]
137 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
138
139 if len(rows) == 0:
140 raise gmExceptions.NoSuchBusinessObjectError('no health issue for [enc:%s::desc:%s::pat:%s]' % (encounter, name, patient))
141
142 pk = rows[0][0]
143 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_health_issue'}
144
145 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
146
147 #--------------------------------------------------------
148 # external API
149 #--------------------------------------------------------
151 """Method for issue renaming.
152
153 @param description
154 - the new descriptive name for the issue
155 @type description
156 - a string instance
157 """
158 # sanity check
159 if not type(description) in [str, str] or description.strip() == '':
160 _log.error('<description> must be a non-empty string')
161 return False
162 # update the issue description
163 old_description = self._payload[self._idx['description']]
164 self._payload[self._idx['description']] = description.strip()
165 self._is_modified = True
166 successful, data = self.save_payload()
167 if not successful:
168 _log.error('cannot rename health issue [%s] with [%s]' % (self, description))
169 self._payload[self._idx['description']] = old_description
170 return False
171 return True
172
173 #--------------------------------------------------------
175 cmd = "SELECT * FROM clin.v_pat_episodes WHERE pk_health_issue = %(pk)s"
176 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = True)
177 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
178
179 #--------------------------------------------------------
181 """ttl in days"""
182 open_episode = self.open_episode
183 if open_episode is None:
184 return True
185 #clinical_end = open_episode.best_guess_clinical_end_date
186 clinical_end = open_episode.latest_access_date # :-/
187 ttl = datetime.timedelta(ttl)
188 now = datetime.datetime.now(tz = clinical_end.tzinfo)
189 if (clinical_end + ttl) > now:
190 return False
191 open_episode['episode_open'] = False
192 success, data = open_episode.save_payload()
193 if success:
194 return True
195 return False # should be an exception
196
197 #--------------------------------------------------------
199 open_episode = self.get_open_episode()
200 open_episode['episode_open'] = False
201 success, data = open_episode.save_payload()
202 if success:
203 return True
204 return False
205
206 #--------------------------------------------------------
209
210 #--------------------------------------------------------
212 cmd = "select pk from clin.episode where fk_health_issue = %s and is_open IS True LIMIT 1"
213 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_obj]}])
214 if len(rows) == 0:
215 return None
216 return cEpisode(aPK_obj=rows[0][0])
217
218 #--------------------------------------------------------
220 if self._payload[self._idx['age_noted']] is None:
221 return '<???>'
222
223 # since we've already got an interval we are bound to use it,
224 # further transformation will only introduce more errors,
225 # later we can improve this deeper inside
226 return gmDateTime.format_interval_medically(self._payload[self._idx['age_noted']])
227
228 #--------------------------------------------------------
230 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
231 cmd = "INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
232 args = {
233 'item': self._payload[self._idx['pk_health_issue']],
234 'code': pk_code
235 }
236 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
237 return True
238
239 #--------------------------------------------------------
241 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
242 cmd = "DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
243 args = {
244 'item': self._payload[self._idx['pk_health_issue']],
245 'code': pk_code
246 }
247 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
248 return True
249
250 #--------------------------------------------------------
252 rows = gmClinNarrative.get_as_journal (
253 issues = (self.pk_obj,),
254 order_by = 'pk_episode, pk_encounter, clin_when, scr, src_table'
255 )
256
257 if len(rows) == 0:
258 return ''
259
260 left_margin = ' ' * left_margin
261
262 lines = []
263 lines.append(_('Clinical data generated during encounters under this health issue:'))
264
265 prev_epi = None
266 for row in rows:
267 if row['pk_episode'] != prev_epi:
268 lines.append('')
269 prev_epi = row['pk_episode']
270
271 when = gmDateTime.pydt_strftime(row['clin_when'], date_format)
272 top_row = '%s%s %s (%s) %s' % (
273 gmTools.u_box_top_left_arc,
274 gmTools.u_box_horiz_single,
275 gmSoapDefs.soap_cat2l10n_str[row['real_soap_cat']],
276 when,
277 gmTools.u_box_horiz_single * 5
278 )
279 soap = gmTools.wrap (
280 text = row['narrative'],
281 width = 60,
282 initial_indent = ' ',
283 subsequent_indent = ' ' + left_margin
284 )
285 row_ver = ''
286 if row['row_version'] > 0:
287 row_ver = 'v%s: ' % row['row_version']
288 bottom_row = '%s%s %s, %s%s %s' % (
289 ' ' * 40,
290 gmTools.u_box_horiz_light_heavy,
291 row['modified_by'],
292 row_ver,
293 gmDateTime.pydt_strftime(row['modified_when'], date_format),
294 gmTools.u_box_horiz_heavy_light
295 )
296
297 lines.append(top_row)
298 lines.append(soap)
299 lines.append(bottom_row)
300
301 eol_w_margin = '\n%s' % left_margin
302 return left_margin + eol_w_margin.join(lines) + '\n'
303
304 #--------------------------------------------------------
305 - def format (self, left_margin=0, patient=None,
306 with_summary=True,
307 with_codes=True,
308 with_episodes=True,
309 with_encounters=True,
310 with_medications=True,
311 with_hospital_stays=True,
312 with_procedures=True,
313 with_family_history=True,
314 with_documents=True,
315 with_tests=True,
316 with_vaccinations=True,
317 with_external_care=True
318 ):
319
320 lines = []
321
322 lines.append(_('Health Issue %s%s%s%s [#%s]') % (
323 '\u00BB',
324 self._payload[self._idx['description']],
325 '\u00AB',
326 gmTools.coalesce (
327 value2test = self.laterality_description,
328 return_instead = '',
329 template4value = ' (%s)',
330 none_equivalents = [None, '', '?']
331 ),
332 self._payload[self._idx['pk_health_issue']]
333 ))
334
335 if self._payload[self._idx['is_confidential']]:
336 lines.append('')
337 lines.append(_(' ***** CONFIDENTIAL *****'))
338 lines.append('')
339
340 if self._payload[self._idx['is_cause_of_death']]:
341 lines.append('')
342 lines.append(_(' contributed to death of patient'))
343 lines.append('')
344
345 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
346 lines.append (_(' Created during encounter: %s (%s - %s) [#%s]') % (
347 enc['l10n_type'],
348 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
349 enc['last_affirmed_original_tz'].strftime('%H:%M'),
350 self._payload[self._idx['pk_encounter']]
351 ))
352
353 if self._payload[self._idx['age_noted']] is not None:
354 lines.append(_(' Noted at age: %s') % self.age_noted_human_readable())
355
356 lines.append(' ' + _('Status') + ': %s, %s%s' % (
357 gmTools.bool2subst(self._payload[self._idx['is_active']], _('active'), _('inactive')),
358 gmTools.bool2subst(self._payload[self._idx['clinically_relevant']], _('clinically relevant'), _('not clinically relevant')),
359 gmTools.coalesce (
360 value2test = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
361 return_instead = '',
362 template4value = ', %s',
363 none_equivalents = [None, '']
364 )
365 ))
366
367 if with_summary:
368 if self._payload[self._idx['summary']] is not None:
369 lines.append(' %s:' % _('Synopsis'))
370 lines.append(gmTools.wrap (
371 text = self._payload[self._idx['summary']],
372 width = 60,
373 initial_indent = ' ',
374 subsequent_indent = ' '
375 ))
376
377 # codes ?
378 if with_codes:
379 codes = self.generic_codes
380 if len(codes) > 0:
381 lines.append('')
382 for c in codes:
383 lines.append(' %s: %s (%s - %s)' % (
384 c['code'],
385 c['term'],
386 c['name_short'],
387 c['version']
388 ))
389 del codes
390
391 lines.append('')
392
393 # patient/emr dependant
394 if patient is not None:
395 if patient.ID != self._payload[self._idx['pk_patient']]:
396 msg = '<patient>.ID = %s but health issue %s belongs to patient %s' % (
397 patient.ID,
398 self._payload[self._idx['pk_health_issue']],
399 self._payload[self._idx['pk_patient']]
400 )
401 raise ValueError(msg)
402 emr = patient.emr
403
404 # episodes
405 if with_episodes:
406 epis = self.get_episodes()
407 if epis is None:
408 lines.append(_('Error retrieving episodes for this health issue.'))
409 elif len(epis) == 0:
410 lines.append(_('There are no episodes for this health issue.'))
411 else:
412 lines.append (
413 _('Episodes: %s (most recent: %s%s%s)') % (
414 len(epis),
415 gmTools.u_left_double_angle_quote,
416 emr.get_most_recent_episode(issue = self._payload[self._idx['pk_health_issue']])['description'],
417 gmTools.u_right_double_angle_quote
418 )
419 )
420 for epi in epis:
421 lines.append(' \u00BB%s\u00AB (%s)' % (
422 epi['description'],
423 gmTools.bool2subst(epi['episode_open'], _('ongoing'), _('closed'))
424 ))
425 lines.append('')
426
427 # encounters
428 if with_encounters:
429 first_encounter = emr.get_first_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
430 last_encounter = emr.get_last_encounter(issue_id = self._payload[self._idx['pk_health_issue']])
431
432 if first_encounter is None or last_encounter is None:
433 lines.append(_('No encounters found for this health issue.'))
434 else:
435 encs = emr.get_encounters(issues = [self._payload[self._idx['pk_health_issue']]])
436 lines.append(_('Encounters: %s (%s - %s):') % (
437 len(encs),
438 first_encounter['started_original_tz'].strftime('%m/%Y'),
439 last_encounter['last_affirmed_original_tz'].strftime('%m/%Y')
440 ))
441 lines.append(_(' Most recent: %s - %s') % (
442 last_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
443 last_encounter['last_affirmed_original_tz'].strftime('%H:%M')
444 ))
445
446 # medications
447 if with_medications:
448 meds = emr.get_current_medications (
449 issues = [ self._payload[self._idx['pk_health_issue']] ],
450 order_by = 'is_currently_active DESC, started, substance'
451 )
452 if len(meds) > 0:
453 lines.append('')
454 lines.append(_('Medications and Substances'))
455 for m in meds:
456 lines.append(m.format(left_margin = (left_margin + 1)))
457 del meds
458
459 # hospitalizations
460 if with_hospital_stays:
461 stays = emr.get_hospital_stays (
462 issues = [ self._payload[self._idx['pk_health_issue']] ]
463 )
464 if len(stays) > 0:
465 lines.append('')
466 lines.append(_('Hospitalizations: %s') % len(stays))
467 for s in stays:
468 lines.append(s.format(left_margin = (left_margin + 1)))
469 del stays
470
471 # procedures
472 if with_procedures:
473 procs = emr.get_performed_procedures (
474 issues = [ self._payload[self._idx['pk_health_issue']] ]
475 )
476 if len(procs) > 0:
477 lines.append('')
478 lines.append(_('Procedures performed: %s') % len(procs))
479 for p in procs:
480 lines.append(p.format(left_margin = (left_margin + 1)))
481 del procs
482
483 # family history
484 if with_family_history:
485 fhx = emr.get_family_history(issues = [ self._payload[self._idx['pk_health_issue']] ])
486 if len(fhx) > 0:
487 lines.append('')
488 lines.append(_('Family History: %s') % len(fhx))
489 for f in fhx:
490 lines.append(f.format (
491 left_margin = (left_margin + 1),
492 include_episode = True,
493 include_comment = True,
494 include_codes = False
495 ))
496 del fhx
497
498 epis = self.get_episodes()
499 if len(epis) > 0:
500 epi_pks = [ e['pk_episode'] for e in epis ]
501
502 # documents
503 if with_documents:
504 doc_folder = patient.get_document_folder()
505 docs = doc_folder.get_documents(pk_episodes = epi_pks)
506 if len(docs) > 0:
507 lines.append('')
508 lines.append(_('Documents: %s') % len(docs))
509 del docs
510
511 # test results
512 if with_tests:
513 tests = emr.get_test_results_by_date(episodes = epi_pks)
514 if len(tests) > 0:
515 lines.append('')
516 lines.append(_('Measurements and Results: %s') % len(tests))
517 del tests
518
519 # vaccinations
520 if with_vaccinations:
521 vaccs = emr.get_vaccinations(episodes = epi_pks, order_by = 'date_given, vaccine')
522 if len(vaccs) > 0:
523 lines.append('')
524 lines.append(_('Vaccinations:'))
525 for vacc in vaccs:
526 lines.extend(vacc.format(with_reaction = True))
527 del vaccs
528
529 del epis
530
531 if with_external_care:
532 care = self._get_external_care(order_by = 'organization, unit, provider')
533 if len(care) > 0:
534 lines.append('')
535 lines.append(_('External care:'))
536 for item in care:
537 lines.append(' %s%s@%s%s' % (
538 gmTools.coalesce(item['provider'], '', '%s: '),
539 item['unit'],
540 item['organization'],
541 gmTools.coalesce(item['comment'], '', ' (%s)')
542 ))
543
544 left_margin = ' ' * left_margin
545 eol_w_margin = '\n%s' % left_margin
546 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = '\n')
547 return left_margin + eol_w_margin.join(lines) + '\n'
548 #--------------------------------------------------------
549 # properties
550 #--------------------------------------------------------
552 return gmExternalCare.get_external_care_items(pk_health_issue = self.pk_obj, order_by = order_by)
553
554 external_care = property(_get_external_care, lambda x:x)
555
556 #--------------------------------------------------------
557 episodes = property(get_episodes, lambda x:x)
558
559 open_episode = property(get_open_episode, lambda x:x)
560
561 has_open_episode = property(has_open_episode, lambda x:x)
562
563 #--------------------------------------------------------
565
566 args = {'pk_issue': self.pk_obj}
567
568 cmd = """SELECT
569 earliest, pk_episode
570 FROM (
571 -- .modified_when of all episodes of this issue,
572 -- earliest-possible thereof = when created,
573 -- should actually go all the way back into audit.log_episode
574 (SELECT
575 c_epi.modified_when AS earliest,
576 c_epi.pk AS pk_episode
577 FROM clin.episode c_epi
578 WHERE c_epi.fk_health_issue = %(pk_issue)s
579 )
580 UNION ALL
581
582 -- last modification of encounter in which episodes of this issue were created,
583 -- earliest-possible thereof = initial creation of that encounter
584 (SELECT
585 c_enc.modified_when AS earliest,
586 c_epi.pk AS pk_episode
587 FROM
588 clin.episode c_epi
589 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
590 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
591 WHERE c_hi.pk = %(pk_issue)s
592 )
593 UNION ALL
594
595 -- start of encounter in which episodes of this issue were created,
596 -- earliest-possible thereof = set by user
597 (SELECT
598 c_enc.started AS earliest,
599 c_epi.pk AS pk_episode
600 FROM
601 clin.episode c_epi
602 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
603 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
604 WHERE c_hi.pk = %(pk_issue)s
605 )
606 UNION ALL
607
608 -- start of encounters of clinical items linked to episodes of this issue,
609 -- earliest-possible thereof = explicitely set by user
610 (SELECT
611 c_enc.started AS earliest,
612 c_epi.pk AS pk_episode
613 FROM
614 clin.clin_root_item c_cri
615 INNER JOIN clin.encounter c_enc ON (c_cri.fk_encounter = c_enc.pk)
616 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
617 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
618 WHERE c_hi.pk = %(pk_issue)s
619 )
620 UNION ALL
621
622 -- .clin_when of clinical items linked to episodes of this issue,
623 -- earliest-possible thereof = explicitely set by user
624 (SELECT
625 c_cri.clin_when AS earliest,
626 c_epi.pk AS pk_episode
627 FROM
628 clin.clin_root_item c_cri
629 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
630 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
631 WHERE c_hi.pk = %(pk_issue)s
632 )
633 UNION ALL
634
635 -- earliest modification time of clinical items linked to episodes of this issue
636 -- this CAN be used since if an item is linked to an episode it can be
637 -- assumed the episode (should have) existed at the time of creation
638 (SELECT
639 c_cri.modified_when AS earliest,
640 c_epi.pk AS pk_episode
641 FROM
642 clin.clin_root_item c_cri
643 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
644 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
645 WHERE c_hi.pk = %(pk_issue)s
646 )
647 UNION ALL
648
649 -- there may not be items, but there may still be documents ...
650 (SELECT
651 b_dm.clin_when AS earliest,
652 c_epi.pk AS pk_episode
653 FROM
654 blobs.doc_med b_dm
655 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
656 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
657 WHERE c_hi.pk = %(pk_issue)s
658 )
659 ) AS candidates
660 ORDER BY earliest NULLS LAST
661 LIMIT 1"""
662 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
663 if len(rows) == 0:
664 return None
665 return cEpisode(aPK_obj = rows[0]['pk_episode'])
666
667 first_episode = property(_get_first_episode, lambda x:x)
668
669 #--------------------------------------------------------
671
672 # explicit always wins:
673 if self._payload[self._idx['has_open_episode']]:
674 return self.open_episode
675
676 args = {'pk_issue': self.pk_obj}
677
678 # cheap query first: any episodes at all ?
679 cmd = "SELECT 1 FROM clin.episode WHERE fk_health_issue = %(pk_issue)s"
680 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
681 if len(rows) == 0:
682 return None
683
684 cmd = """SELECT
685 latest, pk_episode
686 FROM (
687 -- .clin_when of clinical items linked to episodes of this issue,
688 -- latest-possible thereof = explicitely set by user
689 (SELECT
690 c_cri.clin_when AS latest,
691 c_epi.pk AS pk_episode,
692 1 AS rank
693 FROM
694 clin.clin_root_item c_cri
695 INNER JOIN clin.episode c_epi ON (c_cri.fk_episode = c_epi.pk)
696 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
697 WHERE c_hi.pk = %(pk_issue)s
698 )
699 UNION ALL
700
701 -- .clin_when of documents linked to episodes of this issue
702 (SELECT
703 b_dm.clin_when AS latest,
704 c_epi.pk AS pk_episode,
705 1 AS rank
706 FROM
707 blobs.doc_med b_dm
708 INNER JOIN clin.episode c_epi ON (b_dm.fk_episode = c_epi.pk)
709 INNER JOIN clin.health_issue c_hi ON (c_epi.fk_health_issue = c_hi.pk)
710 WHERE c_hi.pk = %(pk_issue)s
711 )
712 UNION ALL
713
714 -- last_affirmed of encounter in which episodes of this issue were created,
715 -- earliest-possible thereof = set by user
716 (SELECT
717 c_enc.last_affirmed AS latest,
718 c_epi.pk AS pk_episode,
719 2 AS rank
720 FROM
721 clin.episode c_epi
722 INNER JOIN clin.encounter c_enc ON (c_enc.pk = c_epi.fk_encounter)
723 INNER JOIN clin.health_issue c_hi ON (c_hi.pk = c_epi.fk_health_issue)
724 WHERE c_hi.pk = %(pk_issue)s
725 )
726
727 ) AS candidates
728 WHERE
729 -- weed out NULL rows due to episodes w/o clinical items and w/o documents
730 latest IS NOT NULL
731 ORDER BY
732 rank,
733 latest DESC
734 LIMIT 1
735 """
736 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
737 if len(rows) == 0:
738 # there were no episodes for this issue
739 return None
740 return cEpisode(aPK_obj = rows[0]['pk_episode'])
741
742 latest_episode = property(_get_latest_episode, lambda x:x)
743
744 #--------------------------------------------------------
745 # Steffi suggested we divide into safe and assumed (= possible) start dates
747 """This returns the date when we can assume to safely KNOW
748 the health issue existed (because the provider said so)."""
749
750 args = {
751 'enc': self._payload[self._idx['pk_encounter']],
752 'pk': self._payload[self._idx['pk_health_issue']]
753 }
754 cmd = """SELECT COALESCE (
755 -- this one must override all:
756 -- .age_noted if not null and DOB is known
757 (CASE
758 WHEN c_hi.age_noted IS NULL
759 THEN NULL::timestamp with time zone
760 WHEN
761 (SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
762 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
763 )) IS NULL
764 THEN NULL::timestamp with time zone
765 ELSE
766 c_hi.age_noted + (
767 SELECT d_i.dob FROM dem.identity d_i WHERE d_i.pk = (
768 SELECT c_enc.fk_patient FROM clin.encounter c_enc WHERE c_enc.pk = %(enc)s
769 )
770 )
771 END),
772
773 -- look at best_guess_clinical_start_date of all linked episodes
774
775 -- start of encounter in which created, earliest = explicitely set
776 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = c_hi.fk_encounter)
777 )
778 FROM clin.health_issue c_hi
779 WHERE c_hi.pk = %(pk)s"""
780 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
781 start = rows[0][0]
782 # leads to a loop:
783 #end = self.clinical_end_date
784 #if start > end:
785 # return end
786 return start
787
788 safe_start_date = property(_get_safe_start_date, lambda x:x)
789
790 #--------------------------------------------------------
792 args = {'pk': self._payload[self._idx['pk_health_issue']]}
793 cmd = """
794 SELECT MIN(earliest) FROM (
795 -- last modification, earliest = when created in/changed to the current state
796 (SELECT modified_when AS earliest FROM clin.health_issue WHERE pk = %(pk)s)
797
798 UNION ALL
799 -- last modification of encounter in which created, earliest = initial creation of that encounter
800 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
801 SELECT c_hi.fk_encounter FROM clin.health_issue c_hi WHERE c_hi.pk = %(pk)s
802 ))
803
804 UNION ALL
805 -- earliest explicit .clin_when of clinical items linked to this health_issue
806 (SELECT MIN(c_vpi.clin_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
807
808 UNION ALL
809 -- earliest modification time of clinical items linked to this health issue
810 -- this CAN be used since if an item is linked to a health issue it can be
811 -- assumed the health issue (should have) existed at the time of creation
812 (SELECT MIN(c_vpi.modified_when) AS earliest FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s)
813
814 UNION ALL
815 -- earliest start of encounters of clinical items linked to this episode
816 (SELECT MIN(c_enc.started) AS earliest FROM clin.encounter c_enc WHERE c_enc.pk IN (
817 SELECT c_vpi.pk_encounter FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_health_issue = %(pk)s
818 ))
819
820 -- here we should be looking at
821 -- .best_guess_clinical_start_date of all episodes linked to this encounter
822
823 ) AS candidates"""
824
825 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
826 return rows[0][0]
827
828 possible_start_date = property(_get_possible_start_date)
829
830 #--------------------------------------------------------
832 if self._payload[self._idx['is_active']]:
833 return None
834 if self._payload[self._idx['has_open_episode']]:
835 return None
836 latest_episode = self.latest_episode
837 if latest_episode is not None:
838 return latest_episode.best_guess_clinical_end_date
839 # apparently, there are no episodes for this issue
840 # and the issue is not active either
841 # so, we simply do not know, the safest assumption is:
842 return self.safe_start_date
843
844 clinical_end_date = property(_get_clinical_end_date)
845
846 #--------------------------------------------------------
848 args = {
849 'enc': self._payload[self._idx['pk_encounter']],
850 'pk': self._payload[self._idx['pk_health_issue']]
851 }
852 cmd = """
853 SELECT
854 MAX(latest)
855 FROM (
856 -- last modification, latest = when last changed to the current state
857 -- DO NOT USE: database upgrades may change this field
858 (SELECT modified_when AS latest FROM clin.health_issue WHERE pk = %(pk)s)
859
860 --UNION ALL
861 -- last modification of encounter in which created, latest = initial creation of that encounter
862 -- DO NOT USE: just because one corrects a typo does not mean the issue took any longer
863 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
864 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
865 -- )
866 --)
867
868 --UNION ALL
869 -- end of encounter in which created, latest = explicitely set
870 -- DO NOT USE: we can retrospectively create issues which
871 -- DO NOT USE: are long since finished
872 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
873 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
874 -- )
875 --)
876
877 UNION ALL
878 -- latest end of encounters of clinical items linked to this issue
879 (SELECT
880 MAX(last_affirmed) AS latest
881 FROM clin.encounter
882 WHERE pk IN (
883 SELECT pk_encounter FROM clin.v_pat_items WHERE pk_health_issue = %(pk)s
884 )
885 )
886
887 UNION ALL
888 -- latest explicit .clin_when of clinical items linked to this issue
889 (SELECT
890 MAX(clin_when) AS latest
891 FROM clin.v_pat_items
892 WHERE pk_health_issue = %(pk)s
893 )
894
895 -- latest modification time of clinical items linked to this issue
896 -- this CAN be used since if an item is linked to an issue it can be
897 -- assumed the issue (should have) existed at the time of modification
898 -- DO NOT USE, because typo fixes should not extend the issue
899 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
900
901 ) AS candidates"""
902 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
903 return rows[0][0]
904
905 latest_access_date = property(_get_latest_access_date)
906
907 #--------------------------------------------------------
909 try:
910 return laterality2str[self._payload[self._idx['laterality']]]
911 except KeyError:
912 return '<?>'
913
914 laterality_description = property(_get_laterality_description, lambda x:x)
915
916 #--------------------------------------------------------
918 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
919
920 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
921
922 #--------------------------------------------------------
924 cmd = """SELECT
925 'NONE (live row)'::text as audit__action_applied,
926 NULL AS audit__action_when,
927 NULL AS audit__action_by,
928 pk_audit,
929 row_version,
930 modified_when,
931 modified_by,
932 pk,
933 description,
934 laterality,
935 age_noted,
936 is_active,
937 clinically_relevant,
938 is_confidential,
939 is_cause_of_death,
940 fk_encounter,
941 grouping,
942 diagnostic_certainty_classification,
943 summary
944 FROM clin.health_issue
945 WHERE pk = %(pk_health_issue)s
946 UNION ALL (
947 SELECT
948 audit_action as audit__action_applied,
949 audit_when as audit__action_when,
950 audit_by as audit__action_by,
951 pk_audit,
952 orig_version as row_version,
953 orig_when as modified_when,
954 orig_by as modified_by,
955 pk,
956 description,
957 laterality,
958 age_noted,
959 is_active,
960 clinically_relevant,
961 is_confidential,
962 is_cause_of_death,
963 fk_encounter,
964 grouping,
965 diagnostic_certainty_classification,
966 summary
967 FROM audit.log_health_issue
968 WHERE pk = %(pk_health_issue)s
969 )
970 ORDER BY row_version DESC
971 """
972 args = {'pk_health_issue': self.pk_obj}
973 title = _('Health issue: %s%s%s') % (
974 gmTools.u_left_double_angle_quote,
975 self._payload[self._idx['description']],
976 gmTools.u_right_double_angle_quote
977 )
978 return '\n'.join(self._get_revision_history(cmd, args, title))
979
980 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
981 #--------------------------------------------------------
983 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
984 return []
985
986 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
987 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
988 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
989 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
990
992 queries = []
993 # remove all codes
994 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
995 queries.append ({
996 'cmd': 'DELETE FROM clin.lnk_code2h_issue WHERE fk_item = %(issue)s AND fk_generic_code IN %(codes)s',
997 'args': {
998 'issue': self._payload[self._idx['pk_health_issue']],
999 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1000 }
1001 })
1002 # add new codes
1003 for pk_code in pk_codes:
1004 queries.append ({
1005 'cmd': 'INSERT INTO clin.lnk_code2h_issue (fk_item, fk_generic_code) VALUES (%(issue)s, %(pk_code)s)',
1006 'args': {
1007 'issue': self._payload[self._idx['pk_health_issue']],
1008 'pk_code': pk_code
1009 }
1010 })
1011 if len(queries) == 0:
1012 return
1013 # run it all in one transaction
1014 rows, idx = gmPG2.run_rw_queries(queries = queries)
1015 return
1016
1017 generic_codes = property(_get_generic_codes, _set_generic_codes)
1018
1019 #============================================================
1021 """Creates a new health issue for a given patient.
1022
1023 description - health issue name
1024 """
1025 try:
1026 h_issue = cHealthIssue(name = description, encounter = encounter, patient = patient)
1027 return h_issue
1028 except gmExceptions.NoSuchBusinessObjectError:
1029 pass
1030
1031 queries = []
1032 cmd = "insert into clin.health_issue (description, fk_encounter) values (%(desc)s, %(enc)s)"
1033 queries.append({'cmd': cmd, 'args': {'desc': description, 'enc': encounter}})
1034
1035 cmd = "select currval('clin.health_issue_pk_seq')"
1036 queries.append({'cmd': cmd})
1037
1038 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
1039 h_issue = cHealthIssue(aPK_obj = rows[0][0])
1040
1041 return h_issue
1042
1043 #-----------------------------------------------------------
1045 if isinstance(health_issue, cHealthIssue):
1046 args = {'pk': health_issue['pk_health_issue']}
1047 else:
1048 args = {'pk': int(health_issue)}
1049 try:
1050 gmPG2.run_rw_queries(queries = [{'cmd': 'DELETE FROM clin.health_issue WHERE pk = %(pk)s', 'args': args}])
1051 except gmPG2.dbapi.IntegrityError:
1052 # should be parsing pgcode/and or error message
1053 _log.exception('cannot delete health issue')
1054 return False
1055
1056 return True
1057
1058 #------------------------------------------------------------
1059 # use as dummy for unassociated episodes
1061 issue = {
1062 'pk_health_issue': None,
1063 'description': _('Unattributed episodes'),
1064 'age_noted': None,
1065 'laterality': 'na',
1066 'is_active': True,
1067 'clinically_relevant': True,
1068 'is_confidential': None,
1069 'is_cause_of_death': False,
1070 'is_dummy': True,
1071 'grouping': None
1072 }
1073 return issue
1074
1075 #-----------------------------------------------------------
1077 return cProblem (
1078 aPK_obj = {
1079 'pk_patient': health_issue['pk_patient'],
1080 'pk_health_issue': health_issue['pk_health_issue'],
1081 'pk_episode': None
1082 },
1083 try_potential_problems = allow_irrelevant
1084 )
1085
1086 #============================================================
1087 # episodes API
1088 #============================================================
1090 """Represents one clinical episode.
1091 """
1092 _cmd_fetch_payload = "select * from clin.v_pat_episodes where pk_episode=%s"
1093 _cmds_store_payload = [
1094 """update clin.episode set
1095 fk_health_issue = %(pk_health_issue)s,
1096 is_open = %(episode_open)s::boolean,
1097 description = %(description)s,
1098 summary = gm.nullify_empty_string(%(summary)s),
1099 diagnostic_certainty_classification = gm.nullify_empty_string(%(diagnostic_certainty_classification)s)
1100 where
1101 pk = %(pk_episode)s and
1102 xmin = %(xmin_episode)s""",
1103 """select xmin_episode from clin.v_pat_episodes where pk_episode = %(pk_episode)s"""
1104 ]
1105 _updatable_fields = [
1106 'pk_health_issue',
1107 'episode_open',
1108 'description',
1109 'summary',
1110 'diagnostic_certainty_classification'
1111 ]
1112 #--------------------------------------------------------
1113 - def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx', health_issue=None, row=None, encounter=None, link_obj=None):
1114 pk = aPK_obj
1115 if pk is None and row is None:
1116
1117 where_parts = ['description = %(desc)s']
1118
1119 if id_patient is not None:
1120 where_parts.append('pk_patient = %(pat)s')
1121
1122 if health_issue is not None:
1123 where_parts.append('pk_health_issue = %(issue)s')
1124
1125 if encounter is not None:
1126 where_parts.append('pk_patient = (SELECT fk_patient FROM clin.encounter WHERE pk = %(enc)s)')
1127
1128 args = {
1129 'pat': id_patient,
1130 'issue': health_issue,
1131 'enc': encounter,
1132 'desc': name
1133 }
1134
1135 cmd = 'SELECT * FROM clin.v_pat_episodes WHERE %s' % ' AND '.join(where_parts)
1136
1137 rows, idx = gmPG2.run_ro_queries (
1138 link_obj = link_obj,
1139 queries = [{'cmd': cmd, 'args': args}],
1140 get_col_idx=True
1141 )
1142
1143 if len(rows) == 0:
1144 raise gmExceptions.NoSuchBusinessObjectError('no episode for [%s:%s:%s:%s]' % (id_patient, name, health_issue, encounter))
1145
1146 r = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_episode'}
1147 gmBusinessDBObject.cBusinessDBObject.__init__(self, row=r)
1148
1149 else:
1150 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk, row=row, link_obj = link_obj)
1151
1152 #--------------------------------------------------------
1153 # external API
1154 #--------------------------------------------------------
1157
1158 #--------------------------------------------------------
1160 return gmClinNarrative.get_narrative (
1161 soap_cats = soap_cats,
1162 encounters = encounters,
1163 episodes = [self.pk_obj],
1164 order_by = order_by
1165 )
1166
1167 #--------------------------------------------------------
1169 """Method for episode editing, that is, episode renaming.
1170
1171 @param description
1172 - the new descriptive name for the encounter
1173 @type description
1174 - a string instance
1175 """
1176 # sanity check
1177 if description.strip() == '':
1178 _log.error('<description> must be a non-empty string instance')
1179 return False
1180 # update the episode description
1181 old_description = self._payload[self._idx['description']]
1182 self._payload[self._idx['description']] = description.strip()
1183 self._is_modified = True
1184 successful, data = self.save_payload()
1185 if not successful:
1186 _log.error('cannot rename episode [%s] to [%s]' % (self, description))
1187 self._payload[self._idx['description']] = old_description
1188 return False
1189 return True
1190
1191 #--------------------------------------------------------
1193 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1194
1195 if pk_code in self._payload[self._idx['pk_generic_codes']]:
1196 return
1197
1198 cmd = """
1199 INSERT INTO clin.lnk_code2episode
1200 (fk_item, fk_generic_code)
1201 SELECT
1202 %(item)s,
1203 %(code)s
1204 WHERE NOT EXISTS (
1205 SELECT 1 FROM clin.lnk_code2episode
1206 WHERE
1207 fk_item = %(item)s
1208 AND
1209 fk_generic_code = %(code)s
1210 )"""
1211 args = {
1212 'item': self._payload[self._idx['pk_episode']],
1213 'code': pk_code
1214 }
1215 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1216 return
1217
1218 #--------------------------------------------------------
1220 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
1221 cmd = "DELETE FROM clin.lnk_code2episode WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
1222 args = {
1223 'item': self._payload[self._idx['pk_episode']],
1224 'code': pk_code
1225 }
1226 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1227 return True
1228
1229 #--------------------------------------------------------
1231 rows = gmClinNarrative.get_as_journal (
1232 episodes = (self.pk_obj,),
1233 order_by = 'pk_encounter, clin_when, scr, src_table'
1234 #order_by = u'pk_encounter, scr, clin_when, src_table'
1235 )
1236
1237 if len(rows) == 0:
1238 return ''
1239
1240 lines = []
1241
1242 lines.append(_('Clinical data generated during encounters within this episode:'))
1243
1244 left_margin = ' ' * left_margin
1245
1246 prev_enc = None
1247 for row in rows:
1248 if row['pk_encounter'] != prev_enc:
1249 lines.append('')
1250 prev_enc = row['pk_encounter']
1251
1252 when = row['clin_when'].strftime(date_format)
1253 top_row = '%s%s %s (%s) %s' % (
1254 gmTools.u_box_top_left_arc,
1255 gmTools.u_box_horiz_single,
1256 gmSoapDefs.soap_cat2l10n_str[row['real_soap_cat']],
1257 when,
1258 gmTools.u_box_horiz_single * 5
1259 )
1260 soap = gmTools.wrap (
1261 text = row['narrative'],
1262 width = 60,
1263 initial_indent = ' ',
1264 subsequent_indent = ' ' + left_margin
1265 )
1266 row_ver = ''
1267 if row['row_version'] > 0:
1268 row_ver = 'v%s: ' % row['row_version']
1269 bottom_row = '%s%s %s, %s%s %s' % (
1270 ' ' * 40,
1271 gmTools.u_box_horiz_light_heavy,
1272 row['modified_by'],
1273 row_ver,
1274 gmDateTime.pydt_strftime(row['modified_when'], date_format),
1275 gmTools.u_box_horiz_heavy_light
1276 )
1277
1278 lines.append(top_row)
1279 lines.append(soap)
1280 lines.append(bottom_row)
1281
1282 eol_w_margin = '\n%s' % left_margin
1283 return left_margin + eol_w_margin.join(lines) + '\n'
1284
1285 #--------------------------------------------------------
1287 if patient is None:
1288 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson
1289 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID:
1290 patient = gmCurrentPatient()
1291 else:
1292 patient = cPerson(self._payload[self._idx['pk_patient']])
1293
1294 return self.format (
1295 patient = patient,
1296 with_summary = True,
1297 with_codes = True,
1298 with_encounters = True,
1299 with_documents = True,
1300 with_hospital_stays = True,
1301 with_procedures = True,
1302 with_family_history = True,
1303 with_tests = False, # does not inform on the episode itself
1304 with_vaccinations = True,
1305 with_health_issue = True,
1306 return_list = True
1307 )
1308
1309 #--------------------------------------------------------
1310 - def format(self, left_margin=0, patient=None,
1311 with_summary=True,
1312 with_codes=True,
1313 with_encounters=True,
1314 with_documents=True,
1315 with_hospital_stays=True,
1316 with_procedures=True,
1317 with_family_history=True,
1318 with_tests=True,
1319 with_vaccinations=True,
1320 with_health_issue=False,
1321 return_list=False
1322 ):
1323
1324 if patient is not None:
1325 if patient.ID != self._payload[self._idx['pk_patient']]:
1326 msg = '<patient>.ID = %s but episode %s belongs to patient %s' % (
1327 patient.ID,
1328 self._payload[self._idx['pk_episode']],
1329 self._payload[self._idx['pk_patient']]
1330 )
1331 raise ValueError(msg)
1332 emr = patient.emr
1333 else:
1334 with_encounters = False
1335 with_documents = False
1336 with_hospital_stays = False
1337 with_procedures = False
1338 with_family_history = False
1339 with_tests = False
1340 with_vaccinations = False
1341
1342 lines = []
1343
1344 # episode details
1345 lines.append (_('Episode %s%s%s [#%s]') % (
1346 gmTools.u_left_double_angle_quote,
1347 self._payload[self._idx['description']],
1348 gmTools.u_right_double_angle_quote,
1349 self._payload[self._idx['pk_episode']]
1350 ))
1351
1352 enc = cEncounter(aPK_obj = self._payload[self._idx['pk_encounter']])
1353 lines.append (' ' + _('Created during encounter: %s (%s - %s) [#%s]') % (
1354 enc['l10n_type'],
1355 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1356 enc['last_affirmed_original_tz'].strftime('%H:%M'),
1357 self._payload[self._idx['pk_encounter']]
1358 ))
1359
1360 if patient is not None:
1361 range_str, range_str_verb, duration_str = self.formatted_clinical_duration
1362 lines.append(_(' Duration: %s (%s)') % (duration_str, range_str_verb))
1363
1364 lines.append(' ' + _('Status') + ': %s%s' % (
1365 gmTools.bool2subst(self._payload[self._idx['episode_open']], _('active'), _('finished')),
1366 gmTools.coalesce (
1367 value2test = diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']]),
1368 return_instead = '',
1369 template4value = ', %s',
1370 none_equivalents = [None, '']
1371 )
1372 ))
1373
1374 if with_health_issue:
1375 lines.append(' ' + _('Health issue') + ': %s' % gmTools.coalesce (
1376 self._payload[self._idx['health_issue']],
1377 _('none associated')
1378 ))
1379
1380 if with_summary:
1381 if self._payload[self._idx['summary']] is not None:
1382 lines.append(' %s:' % _('Synopsis'))
1383 lines.append(gmTools.wrap (
1384 text = self._payload[self._idx['summary']],
1385 width = 60,
1386 initial_indent = ' ',
1387 subsequent_indent = ' '
1388 )
1389 )
1390
1391 # codes
1392 if with_codes:
1393 codes = self.generic_codes
1394 if len(codes) > 0:
1395 lines.append('')
1396 for c in codes:
1397 lines.append(' %s: %s (%s - %s)' % (
1398 c['code'],
1399 c['term'],
1400 c['name_short'],
1401 c['version']
1402 ))
1403 del codes
1404
1405 lines.append('')
1406
1407 # encounters
1408 if with_encounters:
1409 encs = emr.get_encounters(episodes = [self._payload[self._idx['pk_episode']]])
1410 if encs is None:
1411 lines.append(_('Error retrieving encounters for this episode.'))
1412 elif len(encs) == 0:
1413 #lines.append(_('There are no encounters for this issue.'))
1414 pass
1415 else:
1416 first_encounter = emr.get_first_encounter(episode_id = self._payload[self._idx['pk_episode']])
1417 last_encounter = emr.get_last_encounter(episode_id = self._payload[self._idx['pk_episode']])
1418 lines.append(_('Last worked on: %s\n') % last_encounter['last_affirmed_original_tz'].strftime('%Y-%m-%d %H:%M'))
1419 if len(encs) < 4:
1420 line = _('%s encounter(s) (%s - %s):')
1421 else:
1422 line = _('1st and (up to 3) most recent (of %s) encounters (%s - %s):')
1423 lines.append(line % (
1424 len(encs),
1425 first_encounter['started'].strftime('%m/%Y'),
1426 last_encounter['last_affirmed'].strftime('%m/%Y')
1427 ))
1428 lines.append(' %s - %s (%s):%s' % (
1429 first_encounter['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1430 first_encounter['last_affirmed_original_tz'].strftime('%H:%M'),
1431 first_encounter['l10n_type'],
1432 gmTools.coalesce (
1433 first_encounter['assessment_of_encounter'],
1434 gmTools.coalesce (
1435 first_encounter['reason_for_encounter'],
1436 '',
1437 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE'))
1438 ),
1439 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE'))
1440 )
1441 ))
1442 if len(encs) > 4:
1443 lines.append(_(' %s %s skipped %s') % (
1444 gmTools.u_ellipsis,
1445 (len(encs) - 4),
1446 gmTools.u_ellipsis
1447 ))
1448 for enc in encs[1:][-3:]:
1449 lines.append(' %s - %s (%s):%s' % (
1450 enc['started_original_tz'].strftime('%Y-%m-%d %H:%M'),
1451 enc['last_affirmed_original_tz'].strftime('%H:%M'),
1452 enc['l10n_type'],
1453 gmTools.coalesce (
1454 enc['assessment_of_encounter'],
1455 gmTools.coalesce (
1456 enc['reason_for_encounter'],
1457 '',
1458 ' \u00BB%s\u00AB' + (' (%s)' % _('RFE'))
1459 ),
1460 ' \u00BB%s\u00AB' + (' (%s)' % _('AOE'))
1461 )
1462 ))
1463 del encs
1464 # spell out last encounter
1465 if last_encounter is not None:
1466 lines.append('')
1467 lines.append(_('Progress notes in most recent encounter:'))
1468 lines.extend(last_encounter.format_soap (
1469 episodes = [ self._payload[self._idx['pk_episode']] ],
1470 left_margin = left_margin,
1471 soap_cats = 'soapu',
1472 emr = emr
1473 ))
1474
1475 # documents
1476 if with_documents:
1477 doc_folder = patient.get_document_folder()
1478 docs = doc_folder.get_documents (
1479 pk_episodes = [ self._payload[self._idx['pk_episode']] ]
1480 )
1481 if len(docs) > 0:
1482 lines.append('')
1483 lines.append(_('Documents: %s') % len(docs))
1484 for d in docs:
1485 lines.append(' ' + d.format(single_line = True))
1486 del docs
1487
1488 # hospitalizations
1489 if with_hospital_stays:
1490 stays = emr.get_hospital_stays(episodes = [ self._payload[self._idx['pk_episode']] ])
1491 if len(stays) > 0:
1492 lines.append('')
1493 lines.append(_('Hospitalizations: %s') % len(stays))
1494 for s in stays:
1495 lines.append(s.format(left_margin = (left_margin + 1)))
1496 del stays
1497
1498 # procedures
1499 if with_procedures:
1500 procs = emr.get_performed_procedures(episodes = [ self._payload[self._idx['pk_episode']] ])
1501 if len(procs) > 0:
1502 lines.append('')
1503 lines.append(_('Procedures performed: %s') % len(procs))
1504 for p in procs:
1505 lines.append(p.format (
1506 left_margin = (left_margin + 1),
1507 include_episode = False,
1508 include_codes = True
1509 ))
1510 del procs
1511
1512 # family history
1513 if with_family_history:
1514 fhx = emr.get_family_history(episodes = [ self._payload[self._idx['pk_episode']] ])
1515 if len(fhx) > 0:
1516 lines.append('')
1517 lines.append(_('Family History: %s') % len(fhx))
1518 for f in fhx:
1519 lines.append(f.format (
1520 left_margin = (left_margin + 1),
1521 include_episode = False,
1522 include_comment = True,
1523 include_codes = True
1524 ))
1525 del fhx
1526
1527 # test results
1528 if with_tests:
1529 tests = emr.get_test_results_by_date(episodes = [ self._payload[self._idx['pk_episode']] ])
1530 if len(tests) > 0:
1531 lines.append('')
1532 lines.append(_('Measurements and Results:'))
1533 for t in tests:
1534 lines.append(' ' + t.format_concisely(date_format = '%Y %b %d', with_notes = True))
1535 del tests
1536
1537 # vaccinations
1538 if with_vaccinations:
1539 vaccs = emr.get_vaccinations (
1540 episodes = [ self._payload[self._idx['pk_episode']] ],
1541 order_by = 'date_given DESC, vaccine'
1542 )
1543 if len(vaccs) > 0:
1544 lines.append('')
1545 lines.append(_('Vaccinations:'))
1546 for vacc in vaccs:
1547 lines.extend(vacc.format (
1548 with_indications = True,
1549 with_comment = True,
1550 with_reaction = True,
1551 date_format = '%Y-%m-%d'
1552 ))
1553 del vaccs
1554
1555 lines = gmTools.strip_trailing_empty_lines(lines = lines, eol = '\n')
1556 if return_list:
1557 return lines
1558
1559 left_margin = ' ' * left_margin
1560 eol_w_margin = '\n%s' % left_margin
1561 return left_margin + eol_w_margin.join(lines) + '\n'
1562
1563 #--------------------------------------------------------
1564 # properties
1565 #--------------------------------------------------------
1567 return get_best_guess_clinical_start_date_for_episode(pk_episode = self.pk_obj)
1568
1569 best_guess_clinical_start_date = property(_get_best_guess_clinical_start_date)
1570
1571 #--------------------------------------------------------
1573 return get_best_guess_clinical_end_date_for_episode(self.pk_obj)
1574
1575 best_guess_clinical_end_date = property(_get_best_guess_clinical_end_date)
1576
1577 #--------------------------------------------------------
1579 return format_clinical_duration_of_episode (
1580 start = get_best_guess_clinical_start_date_for_episode(self.pk_obj),
1581 end = get_best_guess_clinical_end_date_for_episode(self.pk_obj)
1582 )
1583
1584 formatted_clinical_duration = property(_get_formatted_clinical_duration)
1585
1586 #--------------------------------------------------------
1588 cmd = """SELECT MAX(latest) FROM (
1589 -- last modification, latest = when last changed to the current state
1590 (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)
1591
1592 UNION ALL
1593
1594 -- last modification of encounter in which created, latest = initial creation of that encounter
1595 -- DO NOT USE: just because one corrects a typo does not mean the episode took longer
1596 --(SELECT c_enc.modified_when AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1597 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1598 --))
1599
1600 -- end of encounter in which created, latest = explicitely set
1601 -- DO NOT USE: we can retrospectively create episodes which
1602 -- DO NOT USE: are long since finished
1603 --(SELECT c_enc.last_affirmed AS latest FROM clin.encounter c_enc WHERE c_enc.pk = (
1604 -- SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1605 --))
1606
1607 -- latest end of encounters of clinical items linked to this episode
1608 (SELECT
1609 MAX(last_affirmed) AS latest,
1610 'clin.episode.pk = clin.clin_root_item,fk_episode -> .fk_encounter.last_affirmed'::text AS candidate
1611 FROM clin.encounter
1612 WHERE pk IN (
1613 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1614 ))
1615 UNION ALL
1616
1617 -- latest explicit .clin_when of clinical items linked to this episode
1618 (SELECT
1619 MAX(clin_when) AS latest,
1620 'clin.episode.pk = clin.clin_root_item,fk_episode -> .clin_when'::text AS candidate
1621 FROM clin.clin_root_item
1622 WHERE fk_episode = %(pk)s
1623 )
1624
1625 -- latest modification time of clinical items linked to this episode
1626 -- this CAN be used since if an item is linked to an episode it can be
1627 -- assumed the episode (should have) existed at the time of creation
1628 -- DO NOT USE, because typo fixes should not extend the episode
1629 --(SELECT MIN(modified_when) AS latest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1630
1631 -- not sure about this one:
1632 -- .pk -> clin.clin_root_item.fk_encounter.modified_when
1633
1634 ) AS candidates"""
1635 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}])
1636 return rows[0][0]
1637
1638 latest_access_date = property(_get_latest_access_date)
1639
1640 #--------------------------------------------------------
1642 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
1643
1644 diagnostic_certainty_description = property(_get_diagnostic_certainty_description, lambda x:x)
1645
1646 #--------------------------------------------------------
1648 cmd = """SELECT
1649 'NONE (live row)'::text as audit__action_applied,
1650 NULL AS audit__action_when,
1651 NULL AS audit__action_by,
1652 pk_audit,
1653 row_version,
1654 modified_when,
1655 modified_by,
1656 pk, fk_health_issue, description, is_open, fk_encounter,
1657 diagnostic_certainty_classification,
1658 summary
1659 FROM clin.episode
1660 WHERE pk = %(pk_episode)s
1661 UNION ALL (
1662 SELECT
1663 audit_action as audit__action_applied,
1664 audit_when as audit__action_when,
1665 audit_by as audit__action_by,
1666 pk_audit,
1667 orig_version as row_version,
1668 orig_when as modified_when,
1669 orig_by as modified_by,
1670 pk, fk_health_issue, description, is_open, fk_encounter,
1671 diagnostic_certainty_classification,
1672 summary
1673 FROM audit.log_episode
1674 WHERE pk = %(pk_episode)s
1675 )
1676 ORDER BY row_version DESC
1677 """
1678 args = {'pk_episode': self.pk_obj}
1679 title = _('Episode: %s%s%s') % (
1680 gmTools.u_left_double_angle_quote,
1681 self._payload[self._idx['description']],
1682 gmTools.u_right_double_angle_quote
1683 )
1684 return '\n'.join(self._get_revision_history(cmd, args, title))
1685
1686 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
1687
1688 #--------------------------------------------------------
1690 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
1691 return []
1692
1693 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
1694 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
1695 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1696 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
1697
1699 queries = []
1700 # remove all codes
1701 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
1702 queries.append ({
1703 'cmd': 'DELETE FROM clin.lnk_code2episode WHERE fk_item = %(epi)s AND fk_generic_code IN %(codes)s',
1704 'args': {
1705 'epi': self._payload[self._idx['pk_episode']],
1706 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
1707 }
1708 })
1709 # add new codes
1710 for pk_code in pk_codes:
1711 queries.append ({
1712 'cmd': 'INSERT INTO clin.lnk_code2episode (fk_item, fk_generic_code) VALUES (%(epi)s, %(pk_code)s)',
1713 'args': {
1714 'epi': self._payload[self._idx['pk_episode']],
1715 'pk_code': pk_code
1716 }
1717 })
1718 if len(queries) == 0:
1719 return
1720 # run it all in one transaction
1721 rows, idx = gmPG2.run_rw_queries(queries = queries)
1722 return
1723
1724 generic_codes = property(_get_generic_codes, _set_generic_codes)
1725
1726 #--------------------------------------------------------
1728 cmd = """SELECT EXISTS (
1729 SELECT 1 FROM clin.clin_narrative
1730 WHERE
1731 fk_episode = %(epi)s
1732 AND
1733 fk_encounter IN (
1734 SELECT pk FROM clin.encounter WHERE fk_patient = %(pat)s
1735 )
1736 )"""
1737 args = {
1738 'pat': self._payload[self._idx['pk_patient']],
1739 'epi': self._payload[self._idx['pk_episode']]
1740 }
1741 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1742 return rows[0][0]
1743
1744 has_narrative = property(_get_has_narrative, lambda x:x)
1745
1746 #--------------------------------------------------------
1748 if self._payload[self._idx['pk_health_issue']] is None:
1749 return None
1750 return cHealthIssue(self._payload[self._idx['pk_health_issue']])
1751
1752 health_issue = property(_get_health_issue)
1753
1754 #============================================================
1755 -def create_episode(pk_health_issue=None, episode_name=None, is_open=False, allow_dupes=False, encounter=None, link_obj=None):
1756 """Creates a new episode for a given patient's health issue.
1757
1758 pk_health_issue - given health issue PK
1759 episode_name - name of episode
1760 """
1761 if not allow_dupes:
1762 try:
1763 episode = cEpisode(name = episode_name, health_issue = pk_health_issue, encounter = encounter, link_obj = link_obj)
1764 if episode['episode_open'] != is_open:
1765 episode['episode_open'] = is_open
1766 episode.save_payload()
1767 return episode
1768 except gmExceptions.ConstructorError:
1769 pass
1770
1771 queries = []
1772 cmd = "INSERT INTO clin.episode (fk_health_issue, description, is_open, fk_encounter) VALUES (%s, %s, %s::boolean, %s)"
1773 queries.append({'cmd': cmd, 'args': [pk_health_issue, episode_name, is_open, encounter]})
1774 queries.append({'cmd': cEpisode._cmd_fetch_payload % "currval('clin.episode_pk_seq')"})
1775 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = queries, return_data=True, get_col_idx=True)
1776
1777 episode = cEpisode(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_episode'})
1778 return episode
1779
1780 #-----------------------------------------------------------
1782 if isinstance(episode, cEpisode):
1783 pk = episode['pk_episode']
1784 else:
1785 pk = int(episode)
1786
1787 cmd = 'DELETE FROM clin.episode WHERE pk = %(pk)s'
1788
1789 try:
1790 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': {'pk': pk}}])
1791 except gmPG2.dbapi.IntegrityError:
1792 # should be parsing pgcode/and or error message
1793 _log.exception('cannot delete episode, it is in use')
1794 return False
1795
1796 return True
1797
1798 #-----------------------------------------------------------
1800 return cProblem (
1801 aPK_obj = {
1802 'pk_patient': episode['pk_patient'],
1803 'pk_episode': episode['pk_episode'],
1804 'pk_health_issue': episode['pk_health_issue']
1805 },
1806 try_potential_problems = allow_closed
1807 )
1808
1809 #-----------------------------------------------------------
1810 _SQL_best_guess_clinical_start_date_for_episode = """
1811 SELECT MIN(earliest) FROM (
1812 -- modified_when of episode,
1813 -- earliest possible thereof = when created,
1814 -- should actually go all the way back into audit.log_episode
1815 (SELECT c_epi.modified_when AS earliest FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s)
1816
1817 UNION ALL
1818
1819 -- last modification of encounter in which created,
1820 -- earliest-possible thereof = initial creation of that encounter
1821 (SELECT c_enc.modified_when AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1822 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1823 ))
1824 UNION ALL
1825
1826 -- start of encounter in which created,
1827 -- earliest-possible thereof = explicitely set by user
1828 (SELECT c_enc.started AS earliest FROM clin.encounter c_enc WHERE c_enc.pk = (
1829 SELECT fk_encounter FROM clin.episode WHERE pk = %(pk)s
1830 ))
1831 UNION ALL
1832
1833 -- start of encounters of clinical items linked to this episode,
1834 -- earliest-possible thereof = explicitely set by user
1835 (SELECT MIN(started) AS earliest FROM clin.encounter WHERE pk IN (
1836 SELECT fk_encounter FROM clin.clin_root_item WHERE fk_episode = %(pk)s
1837 ))
1838 UNION ALL
1839
1840 -- .clin_when of clinical items linked to this episode,
1841 -- earliest-possible thereof = explicitely set by user
1842 (SELECT MIN(clin_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1843
1844 UNION ALL
1845
1846 -- earliest modification time of clinical items linked to this episode
1847 -- this CAN be used since if an item is linked to an episode it can be
1848 -- assumed the episode (should have) existed at the time of creation
1849 (SELECT MIN(modified_when) AS earliest FROM clin.clin_root_item WHERE fk_episode = %(pk)s)
1850
1851 UNION ALL
1852
1853 -- there may not be items, but there may still be documents ...
1854 (SELECT MIN(clin_when) AS earliest FROM blobs.doc_med WHERE fk_episode = %(pk)s)
1855 ) AS candidates
1856 """
1857
1859 assert (pk_episode is not None), '<pk_episode> must not be None'
1860 query = {
1861 'cmd': _SQL_best_guess_clinical_start_date_for_episode,
1862 'args': {'pk': pk_episode}
1863 }
1864 rows, idx = gmPG2.run_ro_queries(queries = [query])
1865 return rows[0][0]
1866
1867 #-----------------------------------------------------------
1868 _SQL_best_guess_clinical_end_date_for_episode = """
1869 SELECT
1870 CASE WHEN
1871 -- if open episode ...
1872 (SELECT is_open FROM clin.episode WHERE pk = %(pk)s)
1873 THEN
1874 -- ... no end date
1875 NULL::timestamp with time zone
1876 ELSE (
1877 SELECT COALESCE (
1878 (SELECT
1879 latest --, source_type
1880 FROM (
1881 -- latest explicit .clin_when of clinical items linked to this episode
1882 (SELECT
1883 MAX(clin_when) AS latest,
1884 'clin.episode.pk = clin.clin_root_item.fk_episode -> .clin_when'::text AS source_type
1885 FROM clin.clin_root_item
1886 WHERE fk_episode = %(pk)s
1887 )
1888 UNION ALL
1889 -- latest explicit .clin_when of documents linked to this episode
1890 (SELECT
1891 MAX(clin_when) AS latest,
1892 'clin.episode.pk = blobs.doc_med.fk_episode -> .clin_when'::text AS source_type
1893 FROM blobs.doc_med
1894 WHERE fk_episode = %(pk)s
1895 )
1896 ) AS candidates
1897 ORDER BY latest DESC NULLS LAST
1898 LIMIT 1
1899 ),
1900 -- last ditch, always exists, only use when no clinical items or documents linked:
1901 -- last modification, latest = when last changed to the current state
1902 (SELECT c_epi.modified_when AS latest --, 'clin.episode.modified_when'::text AS source_type
1903 FROM clin.episode c_epi WHERE c_epi.pk = %(pk)s
1904 )
1905 )
1906 )
1907 END
1908 """
1909
1911 assert (pk_episode is not None), '<pk_episode> must not be None'
1912 query = {
1913 'cmd': _SQL_best_guess_clinical_end_date_for_episode,
1914 'args': {'pk': pk_episode}
1915 }
1916 rows, idx = gmPG2.run_ro_queries(queries = [query], get_col_idx = False)
1917 return rows[0][0]
1918
1919 #-----------------------------------------------------------
1921 assert (start is not None), '<start> must not be None'
1922
1923 if end is None:
1924 start_end_str = '%s-%s' % (
1925 gmDateTime.pydt_strftime(start, "%b'%y"),
1926 gmTools.u_ellipsis
1927 )
1928 start_end_str_long = '%s - %s' % (
1929 gmDateTime.pydt_strftime(start, '%b %d %Y'),
1930 gmTools.u_ellipsis
1931 )
1932 duration_str = _('%s so far') % gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - start)
1933 return (start_end_str, start_end_str_long, duration_str)
1934
1935 duration_str = gmDateTime.format_interval_medically(end - start)
1936 # year different:
1937 if end.year != start.year:
1938 start_end_str = '%s-%s' % (
1939 gmDateTime.pydt_strftime(start, "%b'%y"),
1940 gmDateTime.pydt_strftime(end, "%b'%y")
1941 )
1942 start_end_str_long = '%s - %s' % (
1943 gmDateTime.pydt_strftime(start, '%b %d %Y'),
1944 gmDateTime.pydt_strftime(end, '%b %d %Y')
1945 )
1946 return (start_end_str, start_end_str_long, duration_str)
1947 # same year:
1948 if end.month != start.month:
1949 start_end_str = '%s-%s' % (
1950 gmDateTime.pydt_strftime(start, '%b'),
1951 gmDateTime.pydt_strftime(end, "%b'%y")
1952 )
1953 start_end_str_long = '%s - %s' % (
1954 gmDateTime.pydt_strftime(start, '%b %d'),
1955 gmDateTime.pydt_strftime(end, '%b %d %Y')
1956 )
1957 return (start_end_str, start_end_str_long, duration_str)
1958
1959 # same year and same month
1960 start_end_str = gmDateTime.pydt_strftime(start, "%b'%y")
1961 start_end_str_long = gmDateTime.pydt_strftime(start, '%b %d %Y')
1962 return (start_end_str, start_end_str_long, duration_str)
1963
1964 #============================================================
1966
1967 _SQL_episode_start = _SQL_best_guess_clinical_start_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1968 _SQL_episode_end = _SQL_best_guess_clinical_end_date_for_episode % {'pk': 'c_vpe.pk_episode'}
1969
1970 _SQL_open_episodes = """
1971 SELECT
1972 c_vpe.pk_episode,
1973 c_vpe.description
1974 AS episode,
1975 c_vpe.health_issue,
1976 1 AS rank,
1977 (%s) AS episode_start,
1978 NULL::timestamp with time zone AS episode_end
1979 FROM
1980 clin.v_pat_episodes c_vpe
1981 WHERE
1982 c_vpe.episode_open IS TRUE
1983 AND
1984 c_vpe.description %%(fragment_condition)s
1985 %%(ctxt_pat)s
1986 """ % _SQL_episode_start
1987
1988 _SQL_closed_episodes = """
1989 SELECT
1990 c_vpe.pk_episode,
1991 c_vpe.description
1992 AS episode,
1993 c_vpe.health_issue,
1994 2 AS rank,
1995 (%s) AS episode_start,
1996 (%s) AS episode_end
1997 FROM
1998 clin.v_pat_episodes c_vpe
1999 WHERE
2000 c_vpe.episode_open IS FALSE
2001 AND
2002 c_vpe.description %%(fragment_condition)s
2003 %%(ctxt_pat)s
2004 """ % (
2005 _SQL_episode_start,
2006 _SQL_episode_end
2007 )
2008
2009 #--------------------------------------------------------
2011 query = """
2012 (
2013 %s
2014 ) UNION ALL (
2015 %s
2016 )
2017 ORDER BY rank, episode
2018 LIMIT 30""" % (
2019 cEpisodeMatchProvider._SQL_open_episodes,
2020 cEpisodeMatchProvider._SQL_closed_episodes
2021 )
2022 ctxt = {'ctxt_pat': {'where_part': 'AND pk_patient = %(pat)s', 'placeholder': 'pat'}}
2023 super().__init__(queries = [query], context = ctxt)
2024
2025 #--------------------------------------------------------
2027 matches = []
2028 for row in rows:
2029 match = {
2030 'weight': 0,
2031 'data': row['pk_episode']
2032 }
2033 label = '%s (%s)%s' % (
2034 row['episode'],
2035 format_clinical_duration_of_episode (
2036 start = row['episode_start'],
2037 end = row['episode_end']
2038 )[0],
2039 gmTools.coalesce (
2040 row['health_issue'],
2041 '',
2042 ' - %s'
2043 )
2044 )
2045 match['list_label'] = label
2046 match['field_label'] = label
2047 matches.append(match)
2048
2049 return matches
2050
2051 #============================================================
2052 # encounter API
2053 #============================================================
2054 SQL_get_encounters = "SELECT * FROM clin.v_pat_encounters WHERE %s"
2055
2057 """Represents one encounter."""
2058
2059 _cmd_fetch_payload = SQL_get_encounters % 'pk_encounter = %s'
2060 _cmds_store_payload = [
2061 """UPDATE clin.encounter SET
2062 started = %(started)s,
2063 last_affirmed = %(last_affirmed)s,
2064 fk_location = %(pk_org_unit)s,
2065 fk_type = %(pk_type)s,
2066 reason_for_encounter = gm.nullify_empty_string(%(reason_for_encounter)s),
2067 assessment_of_encounter = gm.nullify_empty_string(%(assessment_of_encounter)s)
2068 WHERE
2069 pk = %(pk_encounter)s AND
2070 xmin = %(xmin_encounter)s
2071 """,
2072 # need to return all fields so we can survive in-place upgrades
2073 "SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
2074 ]
2075 _updatable_fields = [
2076 'started',
2077 'last_affirmed',
2078 'pk_org_unit',
2079 'pk_type',
2080 'reason_for_encounter',
2081 'assessment_of_encounter'
2082 ]
2083 #--------------------------------------------------------
2085 """Set the encounter as the active one.
2086
2087 "Setting active" means making sure the encounter
2088 row has the youngest "last_affirmed" timestamp of
2089 all encounter rows for this patient.
2090 """
2091 self['last_affirmed'] = gmDateTime.pydt_now_here()
2092 self.save()
2093 #--------------------------------------------------------
2095 return lock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2096 #--------------------------------------------------------
2098 return unlock_encounter(self.pk_obj, exclusive = exclusive, link_obj = link_obj)
2099 #--------------------------------------------------------
2101 """
2102 Moves every element currently linked to the current encounter
2103 and the source_episode onto target_episode.
2104
2105 @param source_episode The episode the elements are currently linked to.
2106 @type target_episode A cEpisode intance.
2107 @param target_episode The episode the elements will be relinked to.
2108 @type target_episode A cEpisode intance.
2109 """
2110 if source_episode['pk_episode'] == target_episode['pk_episode']:
2111 return True
2112
2113 queries = []
2114 cmd = """
2115 UPDATE clin.clin_root_item
2116 SET fk_episode = %(trg)s
2117 WHERE
2118 fk_encounter = %(enc)s AND
2119 fk_episode = %(src)s
2120 """
2121 rows, idx = gmPG2.run_rw_queries(queries = [{
2122 'cmd': cmd,
2123 'args': {
2124 'trg': target_episode['pk_episode'],
2125 'enc': self.pk_obj,
2126 'src': source_episode['pk_episode']
2127 }
2128 }])
2129 self.refetch_payload()
2130 return True
2131
2132 #--------------------------------------------------------
2134 if pk_target_encounter == self.pk_obj:
2135 return True
2136 cmd = "SELECT clin.transfer_all_encounter_data(%(src)s, %(trg)s)"
2137 args = {
2138 'src': self.pk_obj,
2139 'trg': pk_target_encounter
2140 }
2141 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2142 return True
2143
2144 # conn = gmPG2.get_connection()
2145 # curs = conn.cursor()
2146 # curs.callproc('clin.get_hints_for_patient', [pk_identity])
2147 # rows = curs.fetchall()
2148 # idx = gmPG2.get_col_indices(curs)
2149 # curs.close()
2150 # conn.rollback()
2151
2152 #--------------------------------------------------------
2154
2155 relevant_fields = [
2156 'pk_org_unit',
2157 'pk_type',
2158 'pk_patient',
2159 'reason_for_encounter',
2160 'assessment_of_encounter'
2161 ]
2162 for field in relevant_fields:
2163 if self._payload[self._idx[field]] != another_object[field]:
2164 _log.debug('mismatch on [%s]: "%s" vs. "%s"', field, self._payload[self._idx[field]], another_object[field])
2165 return False
2166
2167 relevant_fields = [
2168 'started',
2169 'last_affirmed',
2170 ]
2171 for field in relevant_fields:
2172 if self._payload[self._idx[field]] is None:
2173 if another_object[field] is None:
2174 continue
2175 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2176 return False
2177
2178 if another_object[field] is None:
2179 return False
2180
2181 # compares at seconds granularity
2182 if self._payload[self._idx[field]].strftime('%Y-%m-%d %H:%M:%S') != another_object[field].strftime('%Y-%m-%d %H:%M:%S'):
2183 _log.debug('mismatch on [%s]: here="%s", other="%s"', field, self._payload[self._idx[field]], another_object[field])
2184 return False
2185
2186 # compare codes
2187 # 1) RFE
2188 if another_object['pk_generic_codes_rfe'] is None:
2189 if self._payload[self._idx['pk_generic_codes_rfe']] is not None:
2190 return False
2191 if another_object['pk_generic_codes_rfe'] is not None:
2192 if self._payload[self._idx['pk_generic_codes_rfe']] is None:
2193 return False
2194 if (
2195 (another_object['pk_generic_codes_rfe'] is None)
2196 and
2197 (self._payload[self._idx['pk_generic_codes_rfe']] is None)
2198 ) is False:
2199 if set(another_object['pk_generic_codes_rfe']) != set(self._payload[self._idx['pk_generic_codes_rfe']]):
2200 return False
2201 # 2) AOE
2202 if another_object['pk_generic_codes_aoe'] is None:
2203 if self._payload[self._idx['pk_generic_codes_aoe']] is not None:
2204 return False
2205 if another_object['pk_generic_codes_aoe'] is not None:
2206 if self._payload[self._idx['pk_generic_codes_aoe']] is None:
2207 return False
2208 if (
2209 (another_object['pk_generic_codes_aoe'] is None)
2210 and
2211 (self._payload[self._idx['pk_generic_codes_aoe']] is None)
2212 ) is False:
2213 if set(another_object['pk_generic_codes_aoe']) != set(self._payload[self._idx['pk_generic_codes_aoe']]):
2214 return False
2215
2216 return True
2217 #--------------------------------------------------------
2219 cmd = """
2220 select exists (
2221 select 1 from clin.v_pat_items where pk_patient = %(pat)s and pk_encounter = %(enc)s
2222 union all
2223 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2224 )"""
2225 args = {
2226 'pat': self._payload[self._idx['pk_patient']],
2227 'enc': self.pk_obj
2228 }
2229 rows, idx = gmPG2.run_ro_queries (
2230 queries = [{
2231 'cmd': cmd,
2232 'args': args
2233 }]
2234 )
2235 return rows[0][0]
2236
2237 #--------------------------------------------------------
2239 cmd = """
2240 select exists (
2241 select 1 from clin.v_pat_items where pk_patient=%(pat)s and pk_encounter=%(enc)s
2242 )"""
2243 args = {
2244 'pat': self._payload[self._idx['pk_patient']],
2245 'enc': self.pk_obj
2246 }
2247 rows, idx = gmPG2.run_ro_queries (
2248 queries = [{
2249 'cmd': cmd,
2250 'args': args
2251 }]
2252 )
2253 return rows[0][0]
2254 #--------------------------------------------------------
2256 """soap_cats: <space> = admin category"""
2257
2258 if soap_cats is None:
2259 soap_cats = 'soap '
2260 else:
2261 soap_cats = soap_cats.lower()
2262
2263 cats = []
2264 for cat in soap_cats:
2265 if cat in 'soapu':
2266 cats.append(cat)
2267 continue
2268 if cat == ' ':
2269 cats.append(None)
2270
2271 cmd = """
2272 SELECT EXISTS (
2273 SELECT 1 FROM clin.clin_narrative
2274 WHERE
2275 fk_encounter = %(enc)s
2276 AND
2277 soap_cat IN %(cats)s
2278 LIMIT 1
2279 )
2280 """
2281 args = {'enc': self._payload[self._idx['pk_encounter']], 'cats': tuple(cats)}
2282 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd,'args': args}])
2283 return rows[0][0]
2284 #--------------------------------------------------------
2286 cmd = """
2287 select exists (
2288 select 1 from blobs.v_doc_med where pk_patient = %(pat)s and pk_encounter = %(enc)s
2289 )"""
2290 args = {
2291 'pat': self._payload[self._idx['pk_patient']],
2292 'enc': self.pk_obj
2293 }
2294 rows, idx = gmPG2.run_ro_queries (
2295 queries = [{
2296 'cmd': cmd,
2297 'args': args
2298 }]
2299 )
2300 return rows[0][0]
2301 #--------------------------------------------------------
2303
2304 if soap_cat is not None:
2305 soap_cat = soap_cat.lower()
2306
2307 if episode is None:
2308 epi_part = 'fk_episode is null'
2309 else:
2310 epi_part = 'fk_episode = %(epi)s'
2311
2312 cmd = """
2313 select narrative
2314 from clin.clin_narrative
2315 where
2316 fk_encounter = %%(enc)s
2317 and
2318 soap_cat = %%(cat)s
2319 and
2320 %s
2321 order by clin_when desc
2322 limit 1
2323 """ % epi_part
2324
2325 args = {'enc': self.pk_obj, 'cat': soap_cat, 'epi': episode}
2326
2327 rows, idx = gmPG2.run_ro_queries (
2328 queries = [{
2329 'cmd': cmd,
2330 'args': args
2331 }]
2332 )
2333 if len(rows) == 0:
2334 return None
2335
2336 return rows[0][0]
2337 #--------------------------------------------------------
2339 cmd = """
2340 SELECT * FROM clin.v_pat_episodes
2341 WHERE pk_episode IN (
2342 SELECT DISTINCT fk_episode
2343 FROM clin.clin_root_item
2344 WHERE fk_encounter = %%(enc)s
2345
2346 UNION
2347
2348 SELECT DISTINCT fk_episode
2349 FROM blobs.doc_med
2350 WHERE fk_encounter = %%(enc)s
2351 ) %s"""
2352 args = {'enc': self.pk_obj}
2353 if exclude is not None:
2354 cmd = cmd % 'AND pk_episode NOT IN %(excluded)s'
2355 args['excluded'] = tuple(exclude)
2356 else:
2357 cmd = cmd % ''
2358
2359 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2360
2361 return [ cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
2362
2363 episodes = property(get_episodes, lambda x:x)
2364 #--------------------------------------------------------
2366 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2367 if field == 'rfe':
2368 cmd = "INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2369 elif field == 'aoe':
2370 cmd = "INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) values (%(item)s, %(code)s)"
2371 else:
2372 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2373 args = {
2374 'item': self._payload[self._idx['pk_encounter']],
2375 'code': pk_code
2376 }
2377 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2378 return True
2379 #--------------------------------------------------------
2381 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
2382 if field == 'rfe':
2383 cmd = "DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2384 elif field == 'aoe':
2385 cmd = "DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(item)s AND fk_generic_code = %(code)s"
2386 else:
2387 raise ValueError('<field> must be one of "rfe" or "aoe", not "%s"', field)
2388 args = {
2389 'item': self._payload[self._idx['pk_encounter']],
2390 'code': pk_code
2391 }
2392 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2393 return True
2394
2395 #--------------------------------------------------------
2396 # data formatting
2397 #--------------------------------------------------------
2398 - def format_soap(self, episodes=None, left_margin=0, soap_cats='soapu', emr=None, issues=None):
2399
2400 lines = []
2401 for soap_cat in gmSoapDefs.soap_cats2list(soap_cats):
2402 soap_cat_narratives = emr.get_clin_narrative (
2403 episodes = episodes,
2404 issues = issues,
2405 encounters = [self._payload[self._idx['pk_encounter']]],
2406 soap_cats = [soap_cat]
2407 )
2408 if soap_cat_narratives is None:
2409 continue
2410 if len(soap_cat_narratives) == 0:
2411 continue
2412
2413 lines.append('%s%s %s %s' % (
2414 gmTools.u_box_top_left_arc,
2415 gmTools.u_box_horiz_single,
2416 gmSoapDefs.soap_cat2l10n_str[soap_cat],
2417 gmTools.u_box_horiz_single * 5
2418 ))
2419 for soap_entry in soap_cat_narratives:
2420 txt = gmTools.wrap (
2421 text = soap_entry['narrative'],
2422 width = 75,
2423 initial_indent = '',
2424 subsequent_indent = (' ' * left_margin)
2425 )
2426 lines.append(txt)
2427 when = gmDateTime.pydt_strftime (
2428 soap_entry['date'],
2429 format = '%Y-%m-%d %H:%M',
2430 accuracy = gmDateTime.acc_minutes
2431 )
2432 txt = '%s%s %.8s, %s %s' % (
2433 ' ' * 40,
2434 gmTools.u_box_horiz_light_heavy,
2435 soap_entry['modified_by'],
2436 when,
2437 gmTools.u_box_horiz_heavy_light
2438 )
2439 lines.append(txt)
2440 lines.append('')
2441
2442 return lines
2443
2444 #--------------------------------------------------------
2446
2447 nothing2format = (
2448 (self._payload[self._idx['reason_for_encounter']] is None)
2449 and
2450 (self._payload[self._idx['assessment_of_encounter']] is None)
2451 and
2452 (self.has_soap_narrative(soap_cats = 'soapu') is False)
2453 )
2454 if nothing2format:
2455 return ''
2456
2457 if date_format is None:
2458 date_format = '%A, %b %d %Y'
2459
2460 tex = '% -------------------------------------------------------------\n'
2461 tex += '% much recommended: \\usepackage(tabu)\n'
2462 tex += '% much recommended: \\usepackage(longtable)\n'
2463 tex += '% best wrapped in: "\\begin{longtabu} to \\textwidth {lX[,L]}"\n'
2464 tex += '% -------------------------------------------------------------\n'
2465 tex += '\\hline \n'
2466 tex += '\\multicolumn{2}{l}{%s: %s ({\\footnotesize %s - %s})} \\tabularnewline \n' % (
2467 gmTools.tex_escape_string(self._payload[self._idx['l10n_type']]),
2468 gmTools.tex_escape_string (
2469 gmDateTime.pydt_strftime (
2470 self._payload[self._idx['started']],
2471 date_format,
2472 accuracy = gmDateTime.acc_days
2473 )
2474 ),
2475 gmTools.tex_escape_string (
2476 gmDateTime.pydt_strftime (
2477 self._payload[self._idx['started']],
2478 '%H:%M',
2479 accuracy = gmDateTime.acc_minutes
2480 )
2481 ),
2482 gmTools.tex_escape_string (
2483 gmDateTime.pydt_strftime (
2484 self._payload[self._idx['last_affirmed']],
2485 '%H:%M',
2486 accuracy = gmDateTime.acc_minutes
2487 )
2488 )
2489 )
2490 tex += '\\hline \n'
2491
2492 if self._payload[self._idx['reason_for_encounter']] is not None:
2493 tex += '%s & %s \\tabularnewline \n' % (
2494 gmTools.tex_escape_string(_('RFE')),
2495 gmTools.tex_escape_string(self._payload[self._idx['reason_for_encounter']])
2496 )
2497 if self._payload[self._idx['assessment_of_encounter']] is not None:
2498 tex += '%s & %s \\tabularnewline \n' % (
2499 gmTools.tex_escape_string(_('AOE')),
2500 gmTools.tex_escape_string(self._payload[self._idx['assessment_of_encounter']])
2501 )
2502
2503 for epi in self.get_episodes():
2504 soaps = epi.get_narrative(soap_cats = soap_cats, encounters = [self.pk_obj], order_by = soap_order)
2505 if len(soaps) == 0:
2506 continue
2507 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
2508 gmTools.tex_escape_string(_('Problem')),
2509 gmTools.tex_escape_string(epi['description']),
2510 gmTools.tex_escape_string (
2511 gmTools.coalesce (
2512 value2test = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification']),
2513 return_instead = '',
2514 template4value = ' {\\footnotesize [%s]}',
2515 none_equivalents = [None, '']
2516 )
2517 )
2518 )
2519 if epi['pk_health_issue'] is not None:
2520 tex += '\\multicolumn{2}{l}{\\emph{%s: %s%s}} \\tabularnewline \n' % (
2521 gmTools.tex_escape_string(_('Health issue')),
2522 gmTools.tex_escape_string(epi['health_issue']),
2523 gmTools.tex_escape_string (
2524 gmTools.coalesce (
2525 value2test = diagnostic_certainty_classification2str(epi['diagnostic_certainty_classification_issue']),
2526 return_instead = '',
2527 template4value = ' {\\footnotesize [%s]}',
2528 none_equivalents = [None, '']
2529 )
2530 )
2531 )
2532 for soap in soaps:
2533 tex += '{\\small %s} & {\\small %s} \\tabularnewline \n' % (
2534 gmTools.tex_escape_string(gmSoapDefs.soap_cat2l10n[soap['soap_cat']]),
2535 gmTools.tex_escape_string(soap['narrative'], replace_eol = True)
2536 )
2537 tex += ' & \\tabularnewline \n'
2538
2539 return tex
2540
2541 #--------------------------------------------------------
2543 lines = []
2544
2545 lines.append('%s%s: %s - %s (@%s)%s [#%s]' % (
2546 ' ' * left_margin,
2547 self._payload[self._idx['l10n_type']],
2548 self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M'),
2549 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
2550 self._payload[self._idx['source_time_zone']],
2551 gmTools.coalesce (
2552 self._payload[self._idx['assessment_of_encounter']],
2553 '',
2554 ' %s%%s%s' % (gmTools.u_left_double_angle_quote, gmTools.u_right_double_angle_quote)
2555 ),
2556 self._payload[self._idx['pk_encounter']]
2557 ))
2558
2559 lines.append(_(' your time: %s - %s (@%s = %s%s)\n') % (
2560 self._payload[self._idx['started']].strftime('%Y-%m-%d %H:%M'),
2561 self._payload[self._idx['last_affirmed']].strftime('%H:%M'),
2562 gmDateTime.current_local_iso_numeric_timezone_string,
2563 gmTools.bool2subst (
2564 gmDateTime.dst_currently_in_effect,
2565 gmDateTime.py_dst_timezone_name,
2566 gmDateTime.py_timezone_name
2567 ),
2568 gmTools.bool2subst(gmDateTime.dst_currently_in_effect, ' - ' + _('daylight savings time in effect'), '')
2569 ))
2570
2571 if self._payload[self._idx['praxis_branch']] is not None:
2572 lines.append(_('Location: %s (%s)') % (self._payload[self._idx['praxis_branch']], self._payload[self._idx['praxis']]))
2573
2574 if self._payload[self._idx['reason_for_encounter']] is not None:
2575 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
2576 codes = self.generic_codes_rfe
2577 for c in codes:
2578 lines.append(' %s: %s (%s - %s)' % (
2579 c['code'],
2580 c['term'],
2581 c['name_short'],
2582 c['version']
2583 ))
2584 if len(codes) > 0:
2585 lines.append('')
2586
2587 if self._payload[self._idx['assessment_of_encounter']] is not None:
2588 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
2589 codes = self.generic_codes_aoe
2590 for c in codes:
2591 lines.append(' %s: %s (%s - %s)' % (
2592 c['code'],
2593 c['term'],
2594 c['name_short'],
2595 c['version']
2596 ))
2597 if len(codes) > 0:
2598 lines.append('')
2599 del codes
2600 return lines
2601
2602 #--------------------------------------------------------
2604 lines = []
2605
2606 if fancy_header:
2607 return self.__format_header_fancy(left_margin = left_margin)
2608
2609 now = gmDateTime.pydt_now_here()
2610 if now.strftime('%Y-%m-%d') == self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d'):
2611 start = '%s %s' % (
2612 _('today'),
2613 self._payload[self._idx['started_original_tz']].strftime('%H:%M')
2614 )
2615 else:
2616 start = self._payload[self._idx['started_original_tz']].strftime('%Y-%m-%d %H:%M')
2617 lines.append('%s%s: %s - %s%s%s' % (
2618 ' ' * left_margin,
2619 self._payload[self._idx['l10n_type']],
2620 start,
2621 self._payload[self._idx['last_affirmed_original_tz']].strftime('%H:%M'),
2622 gmTools.coalesce(self._payload[self._idx['assessment_of_encounter']], '', ' \u00BB%s\u00AB'),
2623 gmTools.coalesce(self._payload[self._idx['praxis_branch']], '', ' @%s')
2624 ))
2625 if with_rfe_aoe:
2626 if self._payload[self._idx['reason_for_encounter']] is not None:
2627 lines.append('%s: %s' % (_('RFE'), self._payload[self._idx['reason_for_encounter']]))
2628 codes = self.generic_codes_rfe
2629 for c in codes:
2630 lines.append(' %s: %s (%s - %s)' % (
2631 c['code'],
2632 c['term'],
2633 c['name_short'],
2634 c['version']
2635 ))
2636 if len(codes) > 0:
2637 lines.append('')
2638 if self._payload[self._idx['assessment_of_encounter']] is not None:
2639 lines.append('%s: %s' % (_('AOE'), self._payload[self._idx['assessment_of_encounter']]))
2640 codes = self.generic_codes_aoe
2641 if len(codes) > 0:
2642 lines.append('')
2643 for c in codes:
2644 lines.append(' %s: %s (%s - %s)' % (
2645 c['code'],
2646 c['term'],
2647 c['name_short'],
2648 c['version']
2649 ))
2650 if len(codes) > 0:
2651 lines.append('')
2652 del codes
2653
2654 return lines
2655
2656 #--------------------------------------------------------
2657 - 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):
2658
2659 if patient is not None:
2660 emr = patient.emr
2661
2662 lines = []
2663 if episodes is None:
2664 episodes = [ e['pk_episode'] for e in self.episodes ]
2665
2666 for pk in episodes:
2667 epi = cEpisode(aPK_obj = pk)
2668 lines.append(_('\nEpisode %s%s%s%s:') % (
2669 gmTools.u_left_double_angle_quote,
2670 epi['description'],
2671 gmTools.u_right_double_angle_quote,
2672 gmTools.coalesce(epi['health_issue'], '', ' (%s)')
2673 ))
2674
2675 # soap
2676 if with_soap:
2677 if patient.ID != self._payload[self._idx['pk_patient']]:
2678 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2679 patient.ID,
2680 self._payload[self._idx['pk_encounter']],
2681 self._payload[self._idx['pk_patient']]
2682 )
2683 raise ValueError(msg)
2684 lines.extend(self.format_soap (
2685 episodes = [pk],
2686 left_margin = left_margin,
2687 soap_cats = None, # meaning: all
2688 emr = emr,
2689 issues = issues
2690 ))
2691
2692 # test results
2693 if with_tests:
2694 tests = emr.get_test_results_by_date (
2695 episodes = [pk],
2696 encounter = self._payload[self._idx['pk_encounter']]
2697 )
2698 if len(tests) > 0:
2699 lines.append('')
2700 lines.append(_('Measurements and Results:'))
2701
2702 for t in tests:
2703 lines.append(t.format())
2704
2705 del tests
2706
2707 # vaccinations
2708 if with_vaccinations:
2709 vaccs = emr.get_vaccinations (
2710 episodes = [pk],
2711 encounters = [ self._payload[self._idx['pk_encounter']] ],
2712 order_by = 'date_given DESC, vaccine'
2713 )
2714 if len(vaccs) > 0:
2715 lines.append('')
2716 lines.append(_('Vaccinations:'))
2717 for vacc in vaccs:
2718 lines.extend(vacc.format (
2719 with_indications = True,
2720 with_comment = True,
2721 with_reaction = True,
2722 date_format = '%Y-%m-%d'
2723 ))
2724 del vaccs
2725
2726 # family history
2727 if with_family_history:
2728 fhx = emr.get_family_history(episodes = [pk])
2729 if len(fhx) > 0:
2730 lines.append('')
2731 lines.append(_('Family History: %s') % len(fhx))
2732 for f in fhx:
2733 lines.append(f.format (
2734 left_margin = (left_margin + 1),
2735 include_episode = False,
2736 include_comment = True
2737 ))
2738 del fhx
2739
2740 # documents
2741 if with_docs:
2742 doc_folder = patient.get_document_folder()
2743 docs = doc_folder.get_documents (
2744 pk_episodes = [pk],
2745 encounter = self._payload[self._idx['pk_encounter']]
2746 )
2747 if len(docs) > 0:
2748 lines.append('')
2749 lines.append(_('Documents:'))
2750 for d in docs:
2751 lines.append(' ' + d.format(single_line = True))
2752 del docs
2753
2754 return lines
2755
2756 #--------------------------------------------------------
2758 if patient is None:
2759 from Gnumed.business.gmPerson import gmCurrentPatient, cPerson
2760 if self._payload[self._idx['pk_patient']] == gmCurrentPatient().ID:
2761 patient = gmCurrentPatient()
2762 else:
2763 patient = cPerson(self._payload[self._idx['pk_patient']])
2764
2765 return self.format (
2766 patient = patient,
2767 fancy_header = True,
2768 with_rfe_aoe = True,
2769 with_soap = True,
2770 with_docs = True,
2771 with_tests = False,
2772 with_vaccinations = True,
2773 with_co_encountlet_hints = True,
2774 with_family_history = True,
2775 by_episode = False,
2776 return_list = True
2777 )
2778
2779 #--------------------------------------------------------
2780 - 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, return_list=False):
2781 """Format an encounter.
2782
2783 with_co_encountlet_hints:
2784 - whether to include which *other* episodes were discussed during this encounter
2785 - (only makes sense if episodes != None)
2786 """
2787 lines = self.format_header (
2788 fancy_header = fancy_header,
2789 left_margin = left_margin,
2790 with_rfe_aoe = with_rfe_aoe
2791 )
2792
2793 if patient is None:
2794 _log.debug('no patient, cannot load patient related data')
2795 with_soap = False
2796 with_tests = False
2797 with_vaccinations = False
2798 with_docs = False
2799
2800 if by_episode:
2801 lines.extend(self.format_by_episode (
2802 episodes = episodes,
2803 issues = issues,
2804 left_margin = left_margin,
2805 patient = patient,
2806 with_soap = with_soap,
2807 with_tests = with_tests,
2808 with_docs = with_docs,
2809 with_vaccinations = with_vaccinations,
2810 with_family_history = with_family_history
2811 ))
2812 else:
2813 if with_soap:
2814 lines.append('')
2815 if patient.ID != self._payload[self._idx['pk_patient']]:
2816 msg = '<patient>.ID = %s but encounter %s belongs to patient %s' % (
2817 patient.ID,
2818 self._payload[self._idx['pk_encounter']],
2819 self._payload[self._idx['pk_patient']]
2820 )
2821 raise ValueError(msg)
2822 emr = patient.emr
2823 lines.extend(self.format_soap (
2824 episodes = episodes,
2825 left_margin = left_margin,
2826 soap_cats = None, # meaning: all
2827 emr = emr,
2828 issues = issues
2829 ))
2830
2831 # # family history
2832 # if with_family_history:
2833 # if episodes is not None:
2834 # fhx = emr.get_family_history(episodes = episodes)
2835 # if len(fhx) > 0:
2836 # lines.append(u'')
2837 # lines.append(_('Family History: %s') % len(fhx))
2838 # for f in fhx:
2839 # lines.append(f.format (
2840 # left_margin = (left_margin + 1),
2841 # include_episode = False,
2842 # include_comment = True
2843 # ))
2844 # del fhx
2845
2846 # test results
2847 if with_tests:
2848 emr = patient.emr
2849 tests = emr.get_test_results_by_date (
2850 episodes = episodes,
2851 encounter = self._payload[self._idx['pk_encounter']]
2852 )
2853 if len(tests) > 0:
2854 lines.append('')
2855 lines.append(_('Measurements and Results:'))
2856 for t in tests:
2857 lines.append(t.format())
2858 del tests
2859
2860 # vaccinations
2861 if with_vaccinations:
2862 emr = patient.emr
2863 vaccs = emr.get_vaccinations (
2864 episodes = episodes,
2865 encounters = [ self._payload[self._idx['pk_encounter']] ],
2866 order_by = 'date_given DESC, vaccine'
2867 )
2868 if len(vaccs) > 0:
2869 lines.append('')
2870 lines.append(_('Vaccinations:'))
2871 for vacc in vaccs:
2872 lines.extend(vacc.format (
2873 with_indications = True,
2874 with_comment = True,
2875 with_reaction = True,
2876 date_format = '%Y-%m-%d'
2877 ))
2878 del vaccs
2879
2880 # documents
2881 if with_docs:
2882 doc_folder = patient.get_document_folder()
2883 docs = doc_folder.get_documents (
2884 pk_episodes = episodes,
2885 encounter = self._payload[self._idx['pk_encounter']]
2886 )
2887 if len(docs) > 0:
2888 lines.append('')
2889 lines.append(_('Documents:'))
2890 for d in docs:
2891 lines.append(' ' + d.format(single_line = True))
2892 del docs
2893
2894 # co-encountlets
2895 if with_co_encountlet_hints:
2896 if episodes is not None:
2897 other_epis = self.get_episodes(exclude = episodes)
2898 if len(other_epis) > 0:
2899 lines.append('')
2900 lines.append(_('%s other episodes touched upon during this encounter:') % len(other_epis))
2901 for epi in other_epis:
2902 lines.append(' %s%s%s%s' % (
2903 gmTools.u_left_double_angle_quote,
2904 epi['description'],
2905 gmTools.u_right_double_angle_quote,
2906 gmTools.coalesce(epi['health_issue'], '', ' (%s)')
2907 ))
2908
2909 if return_list:
2910 return lines
2911
2912 eol_w_margin = '\n%s' % (' ' * left_margin)
2913 return '%s\n' % eol_w_margin.join(lines)
2914
2915 #--------------------------------------------------------
2916 # properties
2917 #--------------------------------------------------------
2919 if len(self._payload[self._idx['pk_generic_codes_rfe']]) == 0:
2920 return []
2921
2922 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2923 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_rfe']])}
2924 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2925 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2926
2928 queries = []
2929 # remove all codes
2930 if len(self._payload[self._idx['pk_generic_codes_rfe']]) > 0:
2931 queries.append ({
2932 'cmd': 'DELETE FROM clin.lnk_code2rfe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2933 'args': {
2934 'enc': self._payload[self._idx['pk_encounter']],
2935 'codes': tuple(self._payload[self._idx['pk_generic_codes_rfe']])
2936 }
2937 })
2938 # add new codes
2939 for pk_code in pk_codes:
2940 queries.append ({
2941 'cmd': 'INSERT INTO clin.lnk_code2rfe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2942 'args': {
2943 'enc': self._payload[self._idx['pk_encounter']],
2944 'pk_code': pk_code
2945 }
2946 })
2947 if len(queries) == 0:
2948 return
2949 # run it all in one transaction
2950 rows, idx = gmPG2.run_rw_queries(queries = queries)
2951 self.refetch_payload()
2952 return
2953
2954 generic_codes_rfe = property(_get_generic_codes_rfe, _set_generic_codes_rfe)
2955 #--------------------------------------------------------
2957 if len(self._payload[self._idx['pk_generic_codes_aoe']]) == 0:
2958 return []
2959
2960 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
2961 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes_aoe']])}
2962 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2963 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
2964
2966 queries = []
2967 # remove all codes
2968 if len(self._payload[self._idx['pk_generic_codes_aoe']]) > 0:
2969 queries.append ({
2970 'cmd': 'DELETE FROM clin.lnk_code2aoe WHERE fk_item = %(enc)s AND fk_generic_code IN %(codes)s',
2971 'args': {
2972 'enc': self._payload[self._idx['pk_encounter']],
2973 'codes': tuple(self._payload[self._idx['pk_generic_codes_aoe']])
2974 }
2975 })
2976 # add new codes
2977 for pk_code in pk_codes:
2978 queries.append ({
2979 'cmd': 'INSERT INTO clin.lnk_code2aoe (fk_item, fk_generic_code) VALUES (%(enc)s, %(pk_code)s)',
2980 'args': {
2981 'enc': self._payload[self._idx['pk_encounter']],
2982 'pk_code': pk_code
2983 }
2984 })
2985 if len(queries) == 0:
2986 return
2987 # run it all in one transaction
2988 rows, idx = gmPG2.run_rw_queries(queries = queries)
2989 self.refetch_payload()
2990 return
2991
2992 generic_codes_aoe = property(_get_generic_codes_aoe, _set_generic_codes_aoe)
2993 #--------------------------------------------------------
2995 if self._payload[self._idx['pk_org_unit']] is None:
2996 return None
2997 return gmPraxis.get_praxis_branch_by_org_unit(pk_org_unit = self._payload[self._idx['pk_org_unit']])
2998
2999 praxis_branch = property(_get_praxis_branch, lambda x:x)
3000 #--------------------------------------------------------
3002 if self._payload[self._idx['pk_org_unit']] is None:
3003 return None
3004 return gmOrganization.cOrgUnit(aPK_obj = self._payload[self._idx['pk_org_unit']])
3005
3006 org_unit = property(_get_org_unit, lambda x:x)
3007 #--------------------------------------------------------
3009 cmd = """SELECT
3010 'NONE (live row)'::text as audit__action_applied,
3011 NULL AS audit__action_when,
3012 NULL AS audit__action_by,
3013 pk_audit,
3014 row_version,
3015 modified_when,
3016 modified_by,
3017 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3018 FROM clin.encounter
3019 WHERE pk = %(pk_encounter)s
3020 UNION ALL (
3021 SELECT
3022 audit_action as audit__action_applied,
3023 audit_when as audit__action_when,
3024 audit_by as audit__action_by,
3025 pk_audit,
3026 orig_version as row_version,
3027 orig_when as modified_when,
3028 orig_by as modified_by,
3029 pk, fk_patient, fk_type, fk_location, source_time_zone, reason_for_encounter, assessment_of_encounter, started, last_affirmed
3030 FROM audit.log_encounter
3031 WHERE pk = %(pk_encounter)s
3032 )
3033 ORDER BY row_version DESC
3034 """
3035 args = {'pk_encounter': self._payload[self._idx['pk_encounter']]}
3036 title = _('Encounter: %s%s%s') % (
3037 gmTools.u_left_double_angle_quote,
3038 self._payload[self._idx['l10n_type']],
3039 gmTools.u_right_double_angle_quote
3040 )
3041 return '\n'.join(self._get_revision_history(cmd, args, title))
3042
3043 formatted_revision_history = property(_get_formatted_revision_history, lambda x:x)
3044
3045 #-----------------------------------------------------------
3047 """Creates a new encounter for a patient.
3048
3049 fk_patient - patient PK
3050 enc_type - type of encounter
3051 """
3052 if enc_type is None:
3053 enc_type = 'in surgery'
3054 # insert new encounter
3055 queries = []
3056 try:
3057 enc_type = int(enc_type)
3058 cmd = """
3059 INSERT INTO clin.encounter (fk_patient, fk_type, fk_location)
3060 VALUES (%(pat)s, %(typ)s, %(prax)s) RETURNING pk"""
3061 except ValueError:
3062 enc_type = enc_type
3063 cmd = """
3064 INSERT INTO clin.encounter (fk_patient, fk_location, fk_type)
3065 VALUES (
3066 %(pat)s,
3067 %(prax)s,
3068 coalesce (
3069 (select pk from clin.encounter_type where description = %(typ)s),
3070 -- pick the first available
3071 (select pk from clin.encounter_type limit 1)
3072 )
3073 ) RETURNING pk"""
3074 praxis = gmPraxis.gmCurrentPraxisBranch()
3075 args = {'pat': fk_patient, 'typ': enc_type, 'prax': praxis['pk_org_unit']}
3076 queries.append({'cmd': cmd, 'args': args})
3077 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = False)
3078 encounter = cEncounter(aPK_obj = rows[0]['pk'])
3079
3080 return encounter
3081
3082 #------------------------------------------------------------
3084 """Used to protect against deletion of active encounter from another client."""
3085 return gmPG2.lock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
3086
3087 #------------------------------------------------------------
3089 return gmPG2.unlock_row(link_obj = link_obj, table = 'clin.encounter', pk = pk_encounter, exclusive = exclusive)
3090
3091 #-----------------------------------------------------------
3093 """Deletes an encounter by PK.
3094
3095 - attempts to obtain an exclusive lock which should
3096 fail if the encounter is the active encounter in
3097 this or any other client
3098 - catches DB exceptions which should mostly be related
3099 to clinical data already having been attached to
3100 the encounter thus making deletion fail
3101 """
3102 conn = gmPG2.get_connection(readonly = False)
3103 if not lock_encounter(pk_encounter, exclusive = True, link_obj = conn):
3104 _log.debug('cannot lock encounter [%s] for deletion, it seems in use', pk_encounter)
3105 return False
3106 cmd = """DELETE FROM clin.encounter WHERE pk = %(enc)s"""
3107 args = {'enc': pk_encounter}
3108 try:
3109 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3110 except gmPG2.PG_ERROR_EXCEPTION:
3111 _log.exception('cannot delete encounter [%s]', pk_encounter)
3112 gmPG2.log_pg_exception_details(exc)
3113 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3114 return False
3115 unlock_encounter(pk_encounter, exclusive = True, link_obj = conn)
3116 return True
3117
3118 #-----------------------------------------------------------
3119 # encounter types handling
3120 #-----------------------------------------------------------
3122
3123 rows, idx = gmPG2.run_rw_queries(
3124 queries = [{
3125 'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)",
3126 'args': {'desc': description, 'l10n_desc': l10n_description}
3127 }],
3128 return_data = True
3129 )
3130
3131 success = rows[0][0]
3132 if not success:
3133 _log.warning('updating encounter type [%s] to [%s] failed', description, l10n_description)
3134
3135 return {'description': description, 'l10n_description': l10n_description}
3136 #-----------------------------------------------------------
3138 """This will attempt to create a NEW encounter type."""
3139
3140 # need a system name, so derive one if necessary
3141 if description is None:
3142 description = l10n_description
3143
3144 args = {
3145 'desc': description,
3146 'l10n_desc': l10n_description
3147 }
3148
3149 _log.debug('creating encounter type: %s, %s', description, l10n_description)
3150
3151 # does it exist already ?
3152 cmd = "select description, _(description) from clin.encounter_type where description = %(desc)s"
3153 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3154
3155 # yes
3156 if len(rows) > 0:
3157 # both system and l10n name are the same so all is well
3158 if (rows[0][0] == description) and (rows[0][1] == l10n_description):
3159 _log.info('encounter type [%s] already exists with the proper translation')
3160 return {'description': description, 'l10n_description': l10n_description}
3161
3162 # or maybe there just wasn't a translation to
3163 # the current language for this type yet ?
3164 cmd = "select exists (select 1 from i18n.translations where orig = %(desc)s and lang = i18n.get_curr_lang())"
3165 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3166
3167 # there was, so fail
3168 if rows[0][0]:
3169 _log.error('encounter type [%s] already exists but with another translation')
3170 return None
3171
3172 # else set it
3173 cmd = "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)"
3174 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3175 return {'description': description, 'l10n_description': l10n_description}
3176
3177 # no
3178 queries = [
3179 {'cmd': "insert into clin.encounter_type (description) values (%(desc)s)", 'args': args},
3180 {'cmd': "select i18n.upd_tx(%(desc)s, %(l10n_desc)s)", 'args': args}
3181 ]
3182 rows, idx = gmPG2.run_rw_queries(queries = queries)
3183
3184 return {'description': description, 'l10n_description': l10n_description}
3185
3186 #-----------------------------------------------------------
3188 cmd = """
3189 SELECT
3190 COUNT(1) AS type_count,
3191 fk_type
3192 FROM clin.encounter
3193 GROUP BY fk_type
3194 ORDER BY type_count DESC
3195 LIMIT 1
3196 """
3197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3198 if len(rows) == 0:
3199 return None
3200 return rows[0]['fk_type']
3201
3202 #-----------------------------------------------------------
3204 cmd = """
3205 SELECT
3206 _(description) AS l10n_description,
3207 description
3208 FROM
3209 clin.encounter_type
3210 ORDER BY
3211 l10n_description
3212 """
3213 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
3214 return rows
3215
3216 #-----------------------------------------------------------
3218 cmd = "SELECT * from clin.encounter_type where description = %s"
3219 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [description]}])
3220 return rows
3221
3222 #-----------------------------------------------------------
3224 cmd = "delete from clin.encounter_type where description = %(desc)s"
3225 args = {'desc': description}
3226 try:
3227 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3228 except gmPG2.dbapi.IntegrityError as e:
3229 if e.pgcode == gmPG2.sql_error_codes.FOREIGN_KEY_VIOLATION:
3230 return False
3231 raise
3232
3233 return True
3234
3235 #============================================================
3237 """Represents one problem.
3238
3239 problems are the aggregation of
3240 .clinically_relevant=True issues and
3241 .is_open=True episodes
3242 """
3243 _cmd_fetch_payload = '' # will get programmatically defined in __init__
3244 _cmds_store_payload = ["select 1"]
3245 _updatable_fields = []
3246
3247 #--------------------------------------------------------
3249 """Initialize.
3250
3251 aPK_obj must contain the keys
3252 pk_patient
3253 pk_episode
3254 pk_health_issue
3255 """
3256 if aPK_obj is None:
3257 raise gmExceptions.ConstructorError('cannot instatiate cProblem for PK: [%s]' % (aPK_obj))
3258
3259 # As problems are rows from a view of different emr struct items,
3260 # the PK can't be a single field and, as some of the values of the
3261 # composed PK may be None, they must be queried using 'is null',
3262 # so we must programmatically construct the SQL query
3263 where_parts = []
3264 pk = {}
3265 for col_name in aPK_obj.keys():
3266 val = aPK_obj[col_name]
3267 if val is None:
3268 where_parts.append('%s IS NULL' % col_name)
3269 else:
3270 where_parts.append('%s = %%(%s)s' % (col_name, col_name))
3271 pk[col_name] = val
3272
3273 # try to instantiate from true problem view
3274 cProblem._cmd_fetch_payload = """
3275 SELECT *, False as is_potential_problem
3276 FROM clin.v_problem_list
3277 WHERE %s""" % ' AND '.join(where_parts)
3278
3279 try:
3280 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3281 return
3282 except gmExceptions.ConstructorError:
3283 _log.exception('actual problem not found, trying "potential" problems')
3284 if try_potential_problems is False:
3285 raise
3286
3287 # try to instantiate from potential-problems view
3288 cProblem._cmd_fetch_payload = """
3289 SELECT *, True as is_potential_problem
3290 FROM clin.v_potential_problem_list
3291 WHERE %s""" % ' AND '.join(where_parts)
3292 gmBusinessDBObject.cBusinessDBObject.__init__(self, aPK_obj=pk)
3293 #--------------------------------------------------------
3295 """
3296 Retrieve the cEpisode instance equivalent to this problem.
3297 The problem's type attribute must be 'episode'
3298 """
3299 if self._payload[self._idx['type']] != 'episode':
3300 _log.error('cannot convert problem [%s] of type [%s] to episode' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3301 return None
3302 return cEpisode(aPK_obj = self._payload[self._idx['pk_episode']])
3303 #--------------------------------------------------------
3305 """
3306 Retrieve the cHealthIssue instance equivalent to this problem.
3307 The problem's type attribute must be 'issue'
3308 """
3309 if self._payload[self._idx['type']] != 'issue':
3310 _log.error('cannot convert problem [%s] of type [%s] to health issue' % (self._payload[self._idx['problem']], self._payload[self._idx['type']]))
3311 return None
3312 return cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']])
3313 #--------------------------------------------------------
3315
3316 if self._payload[self._idx['type']] == 'issue':
3317 latest = cHealthIssue(aPK_obj = self._payload[self._idx['pk_health_issue']]).latest_episode
3318 if latest is None:
3319 return []
3320 episodes = [ latest ]
3321
3322 emr = patient.emr
3323
3324 doc_folder = gmDocuments.cDocumentFolder(aPKey = patient.ID)
3325 return doc_folder.get_visual_progress_notes (
3326 health_issue = self._payload[self._idx['pk_health_issue']],
3327 episode = self._payload[self._idx['pk_episode']]
3328 )
3329
3330 #--------------------------------------------------------
3331 # properties
3332 #--------------------------------------------------------
3333 # doubles as 'diagnostic_certainty_description' getter:
3335 return diagnostic_certainty_classification2str(self._payload[self._idx['diagnostic_certainty_classification']])
3336
3337 diagnostic_certainty_description = property(get_diagnostic_certainty_description, lambda x:x)
3338 #--------------------------------------------------------
3340 if self._payload[self._idx['type']] == 'issue':
3341 cmd = """
3342 SELECT * FROM clin.v_linked_codes WHERE
3343 item_table = 'clin.lnk_code2h_issue'::regclass
3344 AND
3345 pk_item = %(item)s
3346 """
3347 args = {'item': self._payload[self._idx['pk_health_issue']]}
3348 else:
3349 cmd = """
3350 SELECT * FROM clin.v_linked_codes WHERE
3351 item_table = 'clin.lnk_code2episode'::regclass
3352 AND
3353 pk_item = %(item)s
3354 """
3355 args = {'item': self._payload[self._idx['pk_episode']]}
3356
3357 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3358 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3359
3360 generic_codes = property(_get_generic_codes, lambda x:x)
3361 #-----------------------------------------------------------
3363 """Retrieve the cEpisode instance equivalent to the given problem.
3364
3365 The problem's type attribute must be 'episode'
3366
3367 @param problem: The problem to retrieve its related episode for
3368 @type problem: A gmEMRStructItems.cProblem instance
3369 """
3370 if isinstance(problem, cEpisode):
3371 return problem
3372
3373 exc = TypeError('cannot convert [%s] to episode' % problem)
3374
3375 if not isinstance(problem, cProblem):
3376 raise exc
3377
3378 if problem['type'] != 'episode':
3379 raise exc
3380
3381 return cEpisode(aPK_obj = problem['pk_episode'])
3382 #-----------------------------------------------------------
3384 """Retrieve the cIssue instance equivalent to the given problem.
3385
3386 The problem's type attribute must be 'issue'.
3387
3388 @param problem: The problem to retrieve the corresponding issue for
3389 @type problem: A gmEMRStructItems.cProblem instance
3390 """
3391 if isinstance(problem, cHealthIssue):
3392 return problem
3393
3394 exc = TypeError('cannot convert [%s] to health issue' % problem)
3395
3396 if not isinstance(problem, cProblem):
3397 raise exc
3398
3399 if problem['type'] != 'issue':
3400 raise exc
3401
3402 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3403 #-----------------------------------------------------------
3405 """Transform given problem into either episode or health issue instance.
3406 """
3407 if isinstance(problem, (cEpisode, cHealthIssue)):
3408 return problem
3409
3410 exc = TypeError('cannot reclass [%s] instance to either episode or health issue' % type(problem))
3411
3412 if not isinstance(problem, cProblem):
3413 _log.debug('%s' % problem)
3414 raise exc
3415
3416 if problem['type'] == 'episode':
3417 return cEpisode(aPK_obj = problem['pk_episode'])
3418
3419 if problem['type'] == 'issue':
3420 return cHealthIssue(aPK_obj = problem['pk_health_issue'])
3421
3422 raise exc
3423
3424 #============================================================
3425 _SQL_get_hospital_stays = "select * from clin.v_hospital_stays where %s"
3426
3428
3429 _cmd_fetch_payload = _SQL_get_hospital_stays % "pk_hospital_stay = %s"
3430 _cmds_store_payload = [
3431 """UPDATE clin.hospital_stay SET
3432 clin_when = %(admission)s,
3433 discharge = %(discharge)s,
3434 fk_org_unit = %(pk_org_unit)s,
3435 narrative = gm.nullify_empty_string(%(comment)s),
3436 fk_episode = %(pk_episode)s,
3437 fk_encounter = %(pk_encounter)s
3438 WHERE
3439 pk = %(pk_hospital_stay)s
3440 AND
3441 xmin = %(xmin_hospital_stay)s
3442 RETURNING
3443 xmin AS xmin_hospital_stay
3444 """
3445 ]
3446 _updatable_fields = [
3447 'admission',
3448 'discharge',
3449 'pk_org_unit',
3450 'pk_episode',
3451 'pk_encounter',
3452 'comment'
3453 ]
3454
3455 #--------------------------------------------------------
3457 return self.format (
3458 include_procedures = True,
3459 include_docs = True
3460 ).split('\n')
3461
3462 #-------------------------------------------------------
3463 - def format(self, left_margin=0, include_procedures=False, include_docs=False, include_episode=True):
3464
3465 if self._payload[self._idx['discharge']] is not None:
3466 discharge = ' - %s' % gmDateTime.pydt_strftime(self._payload[self._idx['discharge']], '%Y %b %d')
3467 else:
3468 discharge = ''
3469
3470 episode = ''
3471 if include_episode:
3472 episode = ': %s%s%s' % (
3473 gmTools.u_left_double_angle_quote,
3474 self._payload[self._idx['episode']],
3475 gmTools.u_right_double_angle_quote
3476 )
3477
3478 lines = ['%s%s%s (%s@%s)%s' % (
3479 ' ' * left_margin,
3480 gmDateTime.pydt_strftime(self._payload[self._idx['admission']], '%Y %b %d'),
3481 discharge,
3482 self._payload[self._idx['ward']],
3483 self._payload[self._idx['hospital']],
3484 episode
3485 )]
3486
3487 if include_docs:
3488 for doc in self.documents:
3489 lines.append('%s%s: %s\n' % (
3490 ' ' * left_margin,
3491 _('Document'),
3492 doc.format(single_line = True)
3493 ))
3494
3495 return '\n'.join(lines)
3496
3497 #--------------------------------------------------------
3499 return [ gmDocuments.cDocument(aPK_obj = pk_doc) for pk_doc in self._payload[self._idx['pk_documents']] ]
3500
3501 documents = property(_get_documents, lambda x:x)
3502
3503 #-----------------------------------------------------------
3505 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1"
3506 queries = [{
3507 # this assumes non-overarching stays
3508 #'cmd': u'SELECT * FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s ORDER BY admission DESC LIMIT 1',
3509 'cmd': cmd,
3510 'args': {'pat': patient}
3511 }]
3512 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3513 if len(rows) == 0:
3514 return None
3515 return cHospitalStay(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_hospital_stay'})
3516
3517 #-----------------------------------------------------------
3519 args = {'pat': patient}
3520 if ongoing_only:
3521 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s AND discharge is NULL ORDER BY admission"
3522 else:
3523 cmd = _SQL_get_hospital_stays % "pk_patient = %(pat)s ORDER BY admission"
3524
3525 queries = [{'cmd': cmd, 'args': args}]
3526 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3527 if return_pks:
3528 return [ r['pk_hospital_stay'] for r in rows ]
3529 return [ cHospitalStay(row = {'idx': idx, 'data': r, 'pk_field': 'pk_hospital_stay'}) for r in rows ]
3530
3531 #-----------------------------------------------------------
3533
3534 queries = [{
3535 'cmd': 'INSERT INTO clin.hospital_stay (fk_encounter, fk_episode, fk_org_unit) VALUES (%(enc)s, %(epi)s, %(fk_org_unit)s) RETURNING pk',
3536 'args': {'enc': encounter, 'epi': episode, 'fk_org_unit': fk_org_unit}
3537 }]
3538 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3539
3540 return cHospitalStay(aPK_obj = rows[0][0])
3541
3542 #-----------------------------------------------------------
3544 cmd = 'DELETE FROM clin.hospital_stay WHERE pk = %(pk)s'
3545 args = {'pk': stay}
3546 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3547 return True
3548
3549 #============================================================
3550 _SQL_get_procedures = "select * from clin.v_procedures where %s"
3551
3553
3554 _cmd_fetch_payload = _SQL_get_procedures % "pk_procedure = %s"
3555 _cmds_store_payload = [
3556 """UPDATE clin.procedure SET
3557 soap_cat = 'p',
3558 clin_when = %(clin_when)s,
3559 clin_end = %(clin_end)s,
3560 is_ongoing = %(is_ongoing)s,
3561 narrative = gm.nullify_empty_string(%(performed_procedure)s),
3562 fk_hospital_stay = %(pk_hospital_stay)s,
3563 fk_org_unit = (CASE
3564 WHEN %(pk_hospital_stay)s IS NULL THEN %(pk_org_unit)s
3565 ELSE NULL
3566 END)::integer,
3567 fk_episode = %(pk_episode)s,
3568 fk_encounter = %(pk_encounter)s,
3569 fk_doc = %(pk_doc)s,
3570 comment = gm.nullify_empty_string(%(comment)s)
3571 WHERE
3572 pk = %(pk_procedure)s AND
3573 xmin = %(xmin_procedure)s
3574 RETURNING xmin as xmin_procedure"""
3575 ]
3576 _updatable_fields = [
3577 'clin_when',
3578 'clin_end',
3579 'is_ongoing',
3580 'performed_procedure',
3581 'pk_hospital_stay',
3582 'pk_org_unit',
3583 'pk_episode',
3584 'pk_encounter',
3585 'pk_doc',
3586 'comment'
3587 ]
3588 #-------------------------------------------------------
3590
3591 if (attribute == 'pk_hospital_stay') and (value is not None):
3592 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_org_unit', None)
3593
3594 if (attribute == 'pk_org_unit') and (value is not None):
3595 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, 'pk_hospital_stay', None)
3596
3597 gmBusinessDBObject.cBusinessDBObject.__setitem__(self, attribute, value)
3598
3599 #--------------------------------------------------------
3601 return self.format (
3602 left_margin = left_margin,
3603 include_episode = True,
3604 include_codes = True,
3605 include_address = True,
3606 include_comm = True,
3607 include_doc = True
3608 ).split('\n')
3609
3610 #-------------------------------------------------------
3611 - def format(self, left_margin=0, include_episode=True, include_codes=False, include_address=False, include_comm=False, include_doc=False):
3612
3613 if self._payload[self._idx['is_ongoing']]:
3614 end = _(' (ongoing)')
3615 else:
3616 end = self._payload[self._idx['clin_end']]
3617 if end is None:
3618 end = ''
3619 else:
3620 end = ' - %s' % gmDateTime.pydt_strftime(end, '%Y %b %d')
3621
3622 line = '%s%s%s: %s%s [%s @ %s]' % (
3623 (' ' * left_margin),
3624 gmDateTime.pydt_strftime(self._payload[self._idx['clin_when']], '%Y %b %d'),
3625 end,
3626 self._payload[self._idx['performed_procedure']],
3627 gmTools.bool2str(include_episode, ' (%s)' % self._payload[self._idx['episode']], ''),
3628 self._payload[self._idx['unit']],
3629 self._payload[self._idx['organization']]
3630 )
3631
3632 line += gmTools.coalesce(self._payload[self._idx['comment']], '', '\n' + (' ' * left_margin) + _('Comment: ') + '%s')
3633
3634 if include_comm:
3635 for channel in self.org_unit.comm_channels:
3636 line += ('\n%(comm_type)s: %(url)s' % channel)
3637
3638 if include_address:
3639 adr = self.org_unit.address
3640 if adr is not None:
3641 line += '\n'
3642 line += '\n'.join(adr.format(single_line = False, show_type = False))
3643 line += '\n'
3644
3645 if include_doc:
3646 doc = self.doc
3647 if doc is not None:
3648 line += '\n'
3649 line += _('Document') + ': ' + doc.format(single_line = True)
3650 line += '\n'
3651
3652 if include_codes:
3653 codes = self.generic_codes
3654 if len(codes) > 0:
3655 line += '\n'
3656 for c in codes:
3657 line += '%s %s: %s (%s - %s)\n' % (
3658 (' ' * left_margin),
3659 c['code'],
3660 c['term'],
3661 c['name_short'],
3662 c['version']
3663 )
3664 del codes
3665
3666 return line
3667
3668 #--------------------------------------------------------
3670 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
3671 cmd = "INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) values (%(issue)s, %(code)s)"
3672 args = {
3673 'issue': self._payload[self._idx['pk_procedure']],
3674 'code': pk_code
3675 }
3676 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3677 return True
3678
3679 #--------------------------------------------------------
3681 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)"""
3682 cmd = "DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(issue)s AND fk_generic_code = %(code)s"
3683 args = {
3684 'issue': self._payload[self._idx['pk_procedure']],
3685 'code': pk_code
3686 }
3687 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3688 return True
3689
3690 #--------------------------------------------------------
3691 # properties
3692 #--------------------------------------------------------
3694 if self._payload[self._idx['pk_hospital_stay']] is None:
3695 return None
3696 return cHospitalStay(aPK_obj = self._payload[self._idx['pk_hospital_stay']])
3697
3698 hospital_stay = property(_get_stay, lambda x:x)
3699
3700 #--------------------------------------------------------
3703
3704 org_unit = property(_get_org_unit, lambda x:x)
3705
3706 #--------------------------------------------------------
3708 if self._payload[self._idx['pk_doc']] is None:
3709 return None
3710 return gmDocuments.cDocument(aPK_obj = self._payload[self._idx['pk_doc']])
3711
3712 doc = property(_get_doc, lambda x:x)
3713
3714 #--------------------------------------------------------
3716 if len(self._payload[self._idx['pk_generic_codes']]) == 0:
3717 return []
3718
3719 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s'
3720 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])}
3721 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3722 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
3723
3725 queries = []
3726 # remove all codes
3727 if len(self._payload[self._idx['pk_generic_codes']]) > 0:
3728 queries.append ({
3729 'cmd': 'DELETE FROM clin.lnk_code2procedure WHERE fk_item = %(proc)s AND fk_generic_code IN %(codes)s',
3730 'args': {
3731 'proc': self._payload[self._idx['pk_procedure']],
3732 'codes': tuple(self._payload[self._idx['pk_generic_codes']])
3733 }
3734 })
3735 # add new codes
3736 for pk_code in pk_codes:
3737 queries.append ({
3738 'cmd': 'INSERT INTO clin.lnk_code2procedure (fk_item, fk_generic_code) VALUES (%(proc)s, %(pk_code)s)',
3739 'args': {
3740 'proc': self._payload[self._idx['pk_procedure']],
3741 'pk_code': pk_code
3742 }
3743 })
3744 if len(queries) == 0:
3745 return
3746 # run it all in one transaction
3747 rows, idx = gmPG2.run_rw_queries(queries = queries)
3748 return
3749
3750 generic_codes = property(_get_generic_codes, _set_generic_codes)
3751
3752 #-----------------------------------------------------------
3754 queries = [{
3755 'cmd': 'SELECT * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when',
3756 'args': {'pat': patient}
3757 }]
3758 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3759 if return_pks:
3760 return [ r['pk_procedure'] for r in rows ]
3761 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3762
3763 #-----------------------------------------------------------
3765 args = {'pk_doc': pk_document}
3766 cmd = _SQL_get_procedures % 'pk_doc = %(pk_doc)s'
3767 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
3768 if return_pks:
3769 return [ r['pk_procedure'] for r in rows ]
3770 return [ cPerformedProcedure(row = {'idx': idx, 'data': r, 'pk_field': 'pk_procedure'}) for r in rows ]
3771
3772 #-----------------------------------------------------------
3774 queries = [{
3775 'cmd': 'select * FROM clin.v_procedures WHERE pk_patient = %(pat)s ORDER BY clin_when DESC LIMIT 1',
3776 'args': {'pat': patient}
3777 }]
3778 rows, idx = gmPG2.run_ro_queries(queries = queries, get_col_idx = True)
3779 if len(rows) == 0:
3780 return None
3781 return cPerformedProcedure(row = {'idx': idx, 'data': rows[0], 'pk_field': 'pk_procedure'})
3782
3783 #-----------------------------------------------------------
3784 -def create_performed_procedure(encounter=None, episode=None, location=None, hospital_stay=None, procedure=None):
3785
3786 queries = [{
3787 'cmd': """
3788 INSERT INTO clin.procedure (
3789 fk_encounter,
3790 fk_episode,
3791 soap_cat,
3792 fk_org_unit,
3793 fk_hospital_stay,
3794 narrative
3795 ) VALUES (
3796 %(enc)s,
3797 %(epi)s,
3798 'p',
3799 %(loc)s,
3800 %(stay)s,
3801 gm.nullify_empty_string(%(proc)s)
3802 )
3803 RETURNING pk""",
3804 'args': {'enc': encounter, 'epi': episode, 'loc': location, 'stay': hospital_stay, 'proc': procedure}
3805 }]
3806
3807 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True)
3808
3809 return cPerformedProcedure(aPK_obj = rows[0][0])
3810
3811 #-----------------------------------------------------------
3813 cmd = 'delete from clin.procedure where pk = %(pk)s'
3814 args = {'pk': procedure}
3815 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
3816 return True
3817
3818 #============================================================
3820
3821 if filename is None:
3822 filename = gmTools.get_unique_filename(prefix = 'gm-emr_struct-%s-' % patient.subdir_name, suffix = '.txt')
3823
3824 f = io.open(filename, 'w+', encoding = 'utf8')
3825
3826 f.write('patient [%s]\n' % patient['description_gender'])
3827 emr = patient.emr
3828 for issue in emr.health_issues:
3829 f.write('\n')
3830 f.write('\n')
3831 f.write('issue [%s] #%s\n' % (issue['description'], issue['pk_health_issue']))
3832 f.write(' is active : %s\n' % issue['is_active'])
3833 f.write(' has open epi : %s\n' % issue['has_open_episode'])
3834 f.write(' possible start: %s\n' % issue.possible_start_date)
3835 f.write(' safe start : %s\n' % issue.safe_start_date)
3836 end = issue.clinical_end_date
3837 if end is None:
3838 f.write(' end : active and/or open episode\n')
3839 else:
3840 f.write(' end : %s\n' % end)
3841 f.write(' latest access : %s\n' % issue.latest_access_date)
3842 first = issue.first_episode
3843 if first is not None:
3844 first = first['description']
3845 f.write(' 1st episode : %s\n' % first)
3846 last = issue.latest_episode
3847 if last is not None:
3848 last = last['description']
3849 f.write(' latest episode: %s\n' % last)
3850 epis = sorted(issue.get_episodes(), key = lambda e: e.best_guess_clinical_start_date)
3851 for epi in epis:
3852 f.write('\n')
3853 f.write(' episode [%s] #%s\n' % (epi['description'], epi['pk_episode']))
3854 f.write(' is open : %s\n' % epi['episode_open'])
3855 f.write(' best guess start: %s\n' % epi.best_guess_clinical_start_date)
3856 f.write(' best guess end : %s\n' % epi.best_guess_clinical_end_date)
3857 f.write(' latest access : %s\n' % epi.latest_access_date)
3858 f.write(' start 1st enc : %s\n' % epi['started_first'])
3859 f.write(' start last enc : %s\n' % epi['started_last'])
3860 f.write(' end last enc : %s\n' % epi['last_affirmed'])
3861
3862 f.close()
3863 return filename
3864
3865 #============================================================
3866 # tools
3867 #------------------------------------------------------------
3869
3870 aggregate_result = 0
3871
3872 fks_linking2enc = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'encounter', column = 'pk')
3873 tables_linking2enc = set([ r['referencing_table'] for r in fks_linking2enc ])
3874
3875 fks_linking2epi = gmPG2.get_foreign_keys2column(schema = 'clin', table = 'episode', column = 'pk')
3876 tables_linking2epi = [ r['referencing_table'] for r in fks_linking2epi ]
3877
3878 tables_linking2both = tables_linking2enc.intersection(tables_linking2epi)
3879
3880 tables_linking2enc = {}
3881 for fk in fks_linking2enc:
3882 table = fk['referencing_table']
3883 tables_linking2enc[table] = fk
3884
3885 tables_linking2epi = {}
3886 for fk in fks_linking2epi:
3887 table = fk['referencing_table']
3888 tables_linking2epi[table] = fk
3889
3890 for t in tables_linking2both:
3891
3892 table_file_name = 'x-check_enc_epi_xref-%s.log' % t
3893 table_file = io.open(table_file_name, 'w+', encoding = 'utf8')
3894
3895 # get PK column
3896 args = {'table': t}
3897 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': gmPG2.SQL_get_pk_col_def, 'args': args}])
3898 pk_col = rows[0][0]
3899 print("checking table:", t, '- pk col:', pk_col)
3900 print(' =>', table_file_name)
3901 table_file.write('table: %s\n' % t)
3902 table_file.write('PK col: %s\n' % pk_col)
3903
3904 # get PKs
3905 cmd = 'select %s from %s' % (pk_col, t)
3906 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}])
3907 pks = [ r[0] for r in rows ]
3908 for pk in pks:
3909 args = {'pk': pk, 'tbl': t}
3910 enc_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from %s where %s = %%(pk)s)" % (t, pk_col)
3911 epi_cmd = "select fk_patient from clin.encounter where pk = (select fk_encounter from clin.episode where pk = (select fk_episode from %s where %s = %%(pk)s))" % (t, pk_col)
3912 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': enc_cmd, 'args': args}])
3913 epi_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': epi_cmd, 'args': args}])
3914 enc_pat = enc_rows[0][0]
3915 epi_pat = epi_rows[0][0]
3916 args['pat_enc'] = enc_pat
3917 args['pat_epi'] = epi_pat
3918 if epi_pat != enc_pat:
3919 print(' mismatch: row pk=%s, enc pat=%s, epi pat=%s' % (pk, enc_pat, epi_pat))
3920 aggregate_result = -2
3921
3922 table_file.write('--------------------------------------------------------------------------------\n')
3923 table_file.write('mismatch on row with pk: %s\n' % pk)
3924 table_file.write('\n')
3925
3926 table_file.write('journal entry:\n')
3927 cmd = 'SELECT * from clin.v_emr_journal where src_table = %(tbl)s AND src_pk = %(pk)s'
3928 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3929 if len(rows) > 0:
3930 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3931 table_file.write('\n\n')
3932
3933 table_file.write('row data:\n')
3934 cmd = 'SELECT * from %s where %s = %%(pk)s' % (t, pk_col)
3935 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3936 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3937 table_file.write('\n\n')
3938
3939 table_file.write('episode:\n')
3940 cmd = 'SELECT * from clin.v_pat_episodes WHERE pk_episode = (select fk_episode from %s where %s = %%(pk)s)' % (t, pk_col)
3941 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3942 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3943 table_file.write('\n\n')
3944
3945 table_file.write('patient of episode:\n')
3946 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_epi)s'
3947 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3948 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3949 table_file.write('\n\n')
3950
3951 table_file.write('encounter:\n')
3952 cmd = 'SELECT * from clin.v_pat_encounters WHERE pk_encounter = (select fk_encounter from %s where %s = %%(pk)s)' % (t, pk_col)
3953 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3954 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3955 table_file.write('\n\n')
3956
3957 table_file.write('patient of encounter:\n')
3958 cmd = 'SELECT * FROM dem.v_persons WHERE pk_identity = %(pat_enc)s'
3959 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
3960 table_file.write(gmTools.format_dict_like(rows[0], left_margin = 1, tabular = False, value_delimiters = None))
3961 table_file.write('\n')
3962
3963 table_file.write('done\n')
3964 table_file.close()
3965
3966 return aggregate_result
3967
3968 #------------------------------------------------------------
3970 from Gnumed.business import gmPersonSearch
3971 praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0])
3972 pat = gmPersonSearch.ask_for_patient()
3973 while pat is not None:
3974 print('patient:', pat['description_gender'])
3975 fname = os.path.expanduser('~/gnumed/gm-emr_structure-%s.txt' % pat.subdir_name)
3976 print('exported into:', export_emr_structure(patient = pat, filename = fname))
3977 pat = gmPersonSearch.ask_for_patient()
3978
3979 return 0
3980
3981 #============================================================
3982 # main - unit testing
3983 #------------------------------------------------------------
3984 if __name__ == '__main__':
3985
3986 if len(sys.argv) < 2:
3987 sys.exit()
3988
3989 if sys.argv[1] != 'test':
3990 sys.exit()
3991
3992 #--------------------------------------------------------
3993 # define tests
3994 #--------------------------------------------------------
3996 print("\nProblem test")
3997 print("------------")
3998 prob = cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': None})
3999 print(prob)
4000 fields = prob.get_fields()
4001 for field in fields:
4002 print(field, ':', prob[field])
4003 print('\nupdatable:', prob.get_updatable_fields())
4004 epi = prob.get_as_episode()
4005 print('\nas episode:')
4006 if epi is not None:
4007 for field in epi.get_fields():
4008 print(' .%s : %s' % (field, epi[field]))
4009
4010 #--------------------------------------------------------
4012 print("\nhealth issue test")
4013 print("-----------------")
4014 h_issue = cHealthIssue(aPK_obj = 894)
4015 print(h_issue)
4016 print('possible start:', h_issue.possible_start_date)
4017 print('safe start :', h_issue.safe_start_date)
4018 print('end date :', h_issue.clinical_end_date)
4019
4020 #print(h_issue.latest_access_date)
4021 # fields = h_issue.get_fields()
4022 # for field in fields:
4023 # print field, ':', h_issue[field]
4024 # print "has open episode:", h_issue.has_open_episode()
4025 # print "open episode:", h_issue.get_open_episode()
4026 # print "updateable:", h_issue.get_updatable_fields()
4027 # h_issue.close_expired_episode(ttl=7300)
4028 # h_issue = cHealthIssue(encounter = 1, name = u'post appendectomy/peritonitis')
4029 # print h_issue
4030 # print h_issue.format_as_journal()
4031 # print(h_issue.formatted_revision_history)
4032
4033 #--------------------------------------------------------
4035 print("episode test")
4036 for i in range(1,15):
4037 print("------------")
4038 episode = cEpisode(aPK_obj = i)#322) #1674) #1354) #1461) #1299)
4039
4040 print(episode['description'])
4041 print(' start:', episode.best_guess_clinical_start_date)
4042 print(' end :', episode.best_guess_clinical_end_date)
4043 print(' dura :', get_formatted_clinical_duration(pk_episode = i))
4044 return
4045
4046 print(episode)
4047 fields = episode.get_fields()
4048 for field in fields:
4049 print(field, ':', episode[field])
4050 print("updatable:", episode.get_updatable_fields())
4051 input('ENTER to continue')
4052
4053 old_description = episode['description']
4054 old_enc = cEncounter(aPK_obj = 1)
4055
4056 desc = '1-%s' % episode['description']
4057 print("==> renaming to", desc)
4058 successful = episode.rename (
4059 description = desc
4060 )
4061 if not successful:
4062 print("error")
4063 else:
4064 print("success")
4065 for field in fields:
4066 print(field, ':', episode[field])
4067
4068 print(episode.formatted_revision_history)
4069
4070 input('ENTER to continue')
4071
4072 #--------------------------------------------------------
4074 print("\nencounter test")
4075 print("--------------")
4076 encounter = cEncounter(aPK_obj=1)
4077 print(encounter)
4078 fields = encounter.get_fields()
4079 for field in fields:
4080 print(field, ':', encounter[field])
4081 print("updatable:", encounter.get_updatable_fields())
4082 #print encounter.formatted_revision_history
4083 print(encounter.transfer_all_data_to_another_encounter(pk_target_encounter = 2))
4084
4085 #--------------------------------------------------------
4087 encounter = cEncounter(aPK_obj=1)
4088 print(encounter)
4089 print("")
4090 print(encounter.format_latex())
4091 #--------------------------------------------------------
4093 procs = get_performed_procedures(patient = 12)
4094 for proc in procs:
4095 print(proc.format(left_margin=2))
4096 #--------------------------------------------------------
4098 stay = create_hospital_stay(encounter = 1, episode = 2, fk_org_unit = 1)
4099 # stay['hospital'] = u'Starfleet Galaxy General Hospital'
4100 # stay.save_payload()
4101 print(stay)
4102 for s in get_patient_hospital_stays(12):
4103 print(s)
4104 delete_hospital_stay(stay['pk_hospital_stay'])
4105 stay = create_hospital_stay(encounter = 1, episode = 4, fk_org_unit = 1)
4106 #--------------------------------------------------------
4108 tests = [None, 'A', 'B', 'C', 'D', 'E']
4109
4110 for t in tests:
4111 print(type(t), t)
4112 print(type(diagnostic_certainty_classification2str(t)), diagnostic_certainty_classification2str(t))
4113 #--------------------------------------------------------
4118 #--------------------------------------------------------
4122
4123 #--------------------------------------------------------
4125 export_patient_emr_structure()
4126 #praxis = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.get_praxis_branches()[0])
4127 #from Gnumed.business import gmPerson
4128 ## 12 / 20 / 138 / 58 / 20 / 5 / 14
4129 #pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(aPK_obj = 138))
4130 #fname = os.path.expanduser(u'~/gnumed/emr_structure-%s.txt' % pat.subdir_name)
4131 #print export_emr_structure(patient = pat, filename = fname)
4132
4133 #--------------------------------------------------------
4134 # run them
4135 #test_episode()
4136 #test_episode_encounters()
4137 #test_problem()
4138 #test_encounter()
4139 test_health_issue()
4140 #test_hospital_stay()
4141 #test_performed_procedure()
4142 #test_diagnostic_certainty_classification_map()
4143 #test_encounter2latex()
4144 #test_episode_codes()
4145
4146 #test_export_emr_structure()
4147
4148 #============================================================
4149
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Sep 13 01:55:28 2019 | http://epydoc.sourceforge.net |