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