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