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