1
2 """GNUmed clinical patient record.
3
4 This is a clinical record object intended to let a useful
5 client-side API crystallize from actual use in true XP fashion.
6
7 Make sure to call set_func_ask_user() and set_encounter_ttl()
8 early on in your code (before cClinicalRecord.__init__() is
9 called for the first time).
10 """
11
12 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
13 __license__ = "GPL v2 or later"
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 import sys
30 import logging
31
32
33 if __name__ == '__main__':
34 sys.path.insert(0, '../../')
35 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
36 gmI18N.activate_locale()
37 gmI18N.install_domain()
38 gmDateTime.init()
39
40 from Gnumed.pycommon import gmExceptions
41 from Gnumed.pycommon import gmPG2
42 from Gnumed.pycommon import gmDispatcher
43 from Gnumed.pycommon import gmI18N
44 from Gnumed.pycommon import gmCfg
45 from Gnumed.pycommon import gmTools
46 from Gnumed.pycommon import gmDateTime
47
48 from Gnumed.business import gmAllergy
49 from Gnumed.business import gmPathLab
50 from Gnumed.business import gmLOINC
51 from Gnumed.business import gmClinNarrative
52 from Gnumed.business import gmEMRStructItems
53 from Gnumed.business import gmMedication
54 from Gnumed.business import gmVaccination
55 from Gnumed.business import gmFamilyHistory
56 from Gnumed.business.gmDemographicRecord import get_occupations
57
58
59 _log = logging.getLogger('gm.emr')
60
61 _me = None
62 _here = None
63
64
65
66 _func_ask_user = None
67
69 if not callable(a_func):
70 _log.error('[%] not callable, not setting _func_ask_user', a_func)
71 return False
72
73 _log.debug('setting _func_ask_user to [%s]', a_func)
74
75 global _func_ask_user
76 _func_ask_user = a_func
77
78
80
81 _clin_root_item_children_union_query = None
82
140
143
145 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
146 return True
147
148
149
154
190
193
195 try:
196 del self.__db_cache['health issues']
197 except KeyError:
198 pass
199 return 1
200
202
203
204
205
206 return 1
207
209 _log.debug('DB: clin_root_item modification')
210
211
212
213 - def get_family_history(self, episodes=None, issues=None, encounters=None):
214 fhx = gmFamilyHistory.get_family_history (
215 order_by = u'l10n_relation, condition',
216 patient = self.pk_patient
217 )
218
219 if episodes is not None:
220 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
221
222 if issues is not None:
223 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
224
225 if encounters is not None:
226 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
227
228 return fhx
229
230 - def add_family_history(self, episode=None, condition=None, relation=None):
231 return gmFamilyHistory.create_family_history (
232 encounter = self.current_encounter['pk_encounter'],
233 episode = episode,
234 condition = condition,
235 relation = relation
236 )
237
238
239
251
252 performed_procedures = property(get_performed_procedures, lambda x:x)
253
256
265
266
267
275
276 hospital_stays = property(get_hospital_stays, lambda x:x)
277
280
286
288 args = {'pat': self.pk_patient, 'range': cover_period}
289 where_parts = [u'pk_patient = %(pat)s']
290 if cover_period is not None:
291 where_parts.append(u'discharge > (now() - %(range)s)')
292
293 cmd = u"""
294 SELECT hospital, count(1) AS frequency
295 FROM clin.v_pat_hospital_stays
296 WHERE
297 %s
298 GROUP BY hospital
299 ORDER BY frequency DESC
300 """ % u' AND '.join(where_parts)
301
302 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
303 return rows
304
305
306
307 - def add_notes(self, notes=None, episode=None, encounter=None):
323
338
339 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
340 """Get SOAP notes pertinent to this encounter.
341
342 since
343 - initial date for narrative items
344 until
345 - final date for narrative items
346 encounters
347 - list of encounters whose narrative are to be retrieved
348 episodes
349 - list of episodes whose narrative are to be retrieved
350 issues
351 - list of health issues whose narrative are to be retrieved
352 soap_cats
353 - list of SOAP categories of the narrative to be retrieved
354 """
355 where_parts = [u'pk_patient = %(pat)s']
356 args = {u'pat': self.pk_patient}
357
358 if issues is not None:
359 where_parts.append(u'pk_health_issue IN %(issues)s')
360 args['issues'] = tuple(issues)
361
362 if episodes is not None:
363 where_parts.append(u'pk_episode IN %(epis)s')
364 args['epis'] = tuple(episodes)
365
366 if encounters is not None:
367 where_parts.append(u'pk_encounter IN %(encs)s')
368 args['encs'] = tuple(encounters)
369
370 if soap_cats is not None:
371 where_parts.append(u'soap_cat IN %(cats)s')
372 soap_cats = list(soap_cats)
373 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
374 if None in soap_cats:
375 args['cats'].append(None)
376 args['cats'] = tuple(args['cats'])
377
378 cmd = u"""
379 SELECT
380 c_vpn.*,
381 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank
382 FROM clin.v_pat_narrative c_vpn
383 WHERE %s
384 ORDER BY date, soap_rank
385 """ % u' AND '.join(where_parts)
386
387 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
388
389 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
390
391 if since is not None:
392 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
393
394 if until is not None:
395 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
396
397 if providers is not None:
398 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
399
400 return filtered_narrative
401
402 - def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
403 return gmClinNarrative.get_as_journal (
404 patient = self.pk_patient,
405 since = since,
406 until = until,
407 encounters = encounters,
408 episodes = episodes,
409 issues = issues,
410 soap_cats = soap_cats,
411 providers = providers,
412 order_by = order_by,
413 time_range = time_range
414 )
415
417
418 search_term = search_term.strip()
419 if search_term == '':
420 return []
421
422 cmd = u"""
423 SELECT
424 *,
425 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
426 as episode,
427 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
428 as health_issue,
429 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
430 as encounter_started,
431 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
432 as encounter_ended,
433 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
434 as encounter_type
435 from clin.v_narrative4search vn4s
436 WHERE
437 pk_patient = %(pat)s and
438 vn4s.narrative ~ %(term)s
439 order by
440 encounter_started
441 """
442 rows, idx = gmPG2.run_ro_queries(queries = [
443 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
444 ])
445 return rows
446
448
449
450
451
452
453
454 try:
455 return self.__db_cache['text dump old']
456 except KeyError:
457 pass
458
459 fields = [
460 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
461 'modified_by',
462 'clin_when',
463 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
464 'pk_item',
465 'pk_encounter',
466 'pk_episode',
467 'pk_health_issue',
468 'src_table'
469 ]
470 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields)
471 ro_conn = self._conn_pool.GetConnection('historica')
472 curs = ro_conn.cursor()
473 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
474 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
475 curs.close()
476 return None
477 rows = curs.fetchall()
478 view_col_idx = gmPG2.get_col_indices(curs)
479
480
481 items_by_table = {}
482 for item in rows:
483 src_table = item[view_col_idx['src_table']]
484 pk_item = item[view_col_idx['pk_item']]
485 if not items_by_table.has_key(src_table):
486 items_by_table[src_table] = {}
487 items_by_table[src_table][pk_item] = item
488
489
490 issues = self.get_health_issues()
491 issue_map = {}
492 for issue in issues:
493 issue_map[issue['pk']] = issue['description']
494 episodes = self.get_episodes()
495 episode_map = {}
496 for episode in episodes:
497 episode_map[episode['pk_episode']] = episode['description']
498 emr_data = {}
499
500 for src_table in items_by_table.keys():
501 item_ids = items_by_table[src_table].keys()
502
503
504 if len(item_ids) == 0:
505 _log.info('no items in table [%s] ?!?' % src_table)
506 continue
507 elif len(item_ids) == 1:
508 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
509 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
510 _log.error('cannot load items from table [%s]' % src_table)
511
512 continue
513 elif len(item_ids) > 1:
514 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
515 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
516 _log.error('cannot load items from table [%s]' % src_table)
517
518 continue
519 rows = curs.fetchall()
520 table_col_idx = gmPG.get_col_indices(curs)
521
522 for row in rows:
523
524 pk_item = row[table_col_idx['pk_item']]
525 view_row = items_by_table[src_table][pk_item]
526 age = view_row[view_col_idx['age']]
527
528 try:
529 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
530 except:
531 episode_name = view_row[view_col_idx['pk_episode']]
532 try:
533 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
534 except:
535 issue_name = view_row[view_col_idx['pk_health_issue']]
536
537 if not emr_data.has_key(age):
538 emr_data[age] = []
539
540 emr_data[age].append(
541 _('%s: encounter (%s)') % (
542 view_row[view_col_idx['clin_when']],
543 view_row[view_col_idx['pk_encounter']]
544 )
545 )
546 emr_data[age].append(_('health issue: %s') % issue_name)
547 emr_data[age].append(_('episode : %s') % episode_name)
548
549
550
551 cols2ignore = [
552 'pk_audit', 'row_version', 'modified_when', 'modified_by',
553 'pk_item', 'id', 'fk_encounter', 'fk_episode'
554 ]
555 col_data = []
556 for col_name in table_col_idx.keys():
557 if col_name in cols2ignore:
558 continue
559 emr_data[age].append("=> %s:" % col_name)
560 emr_data[age].append(row[table_col_idx[col_name]])
561 emr_data[age].append("----------------------------------------------------")
562 emr_data[age].append("-- %s from table %s" % (
563 view_row[view_col_idx['modified_string']],
564 src_table
565 ))
566 emr_data[age].append("-- written %s by %s" % (
567 view_row[view_col_idx['modified_when']],
568 view_row[view_col_idx['modified_by']]
569 ))
570 emr_data[age].append("----------------------------------------------------")
571 curs.close()
572 self._conn_pool.ReleaseConnection('historica')
573 return emr_data
574
575 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
576
577
578
579
580
581
582 try:
583 return self.__db_cache['text dump']
584 except KeyError:
585 pass
586
587
588 fields = [
589 'age',
590 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
591 'modified_by',
592 'clin_when',
593 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
594 'pk_item',
595 'pk_encounter',
596 'pk_episode',
597 'pk_health_issue',
598 'src_table'
599 ]
600 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
601
602 where_snippets = []
603 params = {}
604 where_snippets.append('pk_patient=%(pat_id)s')
605 params['pat_id'] = self.pk_patient
606 if not since is None:
607 where_snippets.append('clin_when >= %(since)s')
608 params['since'] = since
609 if not until is None:
610 where_snippets.append('clin_when <= %(until)s')
611 params['until'] = until
612
613
614
615 if not encounters is None and len(encounters) > 0:
616 params['enc'] = encounters
617 if len(encounters) > 1:
618 where_snippets.append('fk_encounter in %(enc)s')
619 else:
620 where_snippets.append('fk_encounter=%(enc)s')
621
622 if not episodes is None and len(episodes) > 0:
623 params['epi'] = episodes
624 if len(episodes) > 1:
625 where_snippets.append('fk_episode in %(epi)s')
626 else:
627 where_snippets.append('fk_episode=%(epi)s')
628
629 if not issues is None and len(issues) > 0:
630 params['issue'] = issues
631 if len(issues) > 1:
632 where_snippets.append('fk_health_issue in %(issue)s')
633 else:
634 where_snippets.append('fk_health_issue=%(issue)s')
635
636 where_clause = ' and '.join(where_snippets)
637 order_by = 'order by src_table, age'
638 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
639
640 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
641 if rows is None:
642 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
643 return None
644
645
646
647
648 items_by_table = {}
649 for item in rows:
650 src_table = item[view_col_idx['src_table']]
651 pk_item = item[view_col_idx['pk_item']]
652 if not items_by_table.has_key(src_table):
653 items_by_table[src_table] = {}
654 items_by_table[src_table][pk_item] = item
655
656
657 issues = self.get_health_issues()
658 issue_map = {}
659 for issue in issues:
660 issue_map[issue['pk_health_issue']] = issue['description']
661 episodes = self.get_episodes()
662 episode_map = {}
663 for episode in episodes:
664 episode_map[episode['pk_episode']] = episode['description']
665 emr_data = {}
666
667 ro_conn = self._conn_pool.GetConnection('historica')
668 curs = ro_conn.cursor()
669 for src_table in items_by_table.keys():
670 item_ids = items_by_table[src_table].keys()
671
672
673 if len(item_ids) == 0:
674 _log.info('no items in table [%s] ?!?' % src_table)
675 continue
676 elif len(item_ids) == 1:
677 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
678 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
679 _log.error('cannot load items from table [%s]' % src_table)
680
681 continue
682 elif len(item_ids) > 1:
683 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
684 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
685 _log.error('cannot load items from table [%s]' % src_table)
686
687 continue
688 rows = curs.fetchall()
689 table_col_idx = gmPG.get_col_indices(curs)
690
691 for row in rows:
692
693 pk_item = row[table_col_idx['pk_item']]
694 view_row = items_by_table[src_table][pk_item]
695 age = view_row[view_col_idx['age']]
696
697 try:
698 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
699 except:
700 episode_name = view_row[view_col_idx['pk_episode']]
701 try:
702 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
703 except:
704 issue_name = view_row[view_col_idx['pk_health_issue']]
705
706 if not emr_data.has_key(age):
707 emr_data[age] = []
708
709 emr_data[age].append(
710 _('%s: encounter (%s)') % (
711 view_row[view_col_idx['clin_when']],
712 view_row[view_col_idx['pk_encounter']]
713 )
714 )
715 emr_data[age].append(_('health issue: %s') % issue_name)
716 emr_data[age].append(_('episode : %s') % episode_name)
717
718
719
720 cols2ignore = [
721 'pk_audit', 'row_version', 'modified_when', 'modified_by',
722 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
723 ]
724 col_data = []
725 for col_name in table_col_idx.keys():
726 if col_name in cols2ignore:
727 continue
728 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
729 emr_data[age].append("----------------------------------------------------")
730 emr_data[age].append("-- %s from table %s" % (
731 view_row[view_col_idx['modified_string']],
732 src_table
733 ))
734 emr_data[age].append("-- written %s by %s" % (
735 view_row[view_col_idx['modified_when']],
736 view_row[view_col_idx['modified_by']]
737 ))
738 emr_data[age].append("----------------------------------------------------")
739 curs.close()
740 return emr_data
741
743 return self.pk_patient
744
746 union_query = u'\n union all\n'.join ([
747 u"""
748 SELECT ((
749 -- all relevant health issues + active episodes WITH health issue
750 SELECT COUNT(1)
751 FROM clin.v_problem_list
752 WHERE
753 pk_patient = %(pat)s
754 AND
755 pk_health_issue is not null
756 ) + (
757 -- active episodes WITHOUT health issue
758 SELECT COUNT(1)
759 FROM clin.v_problem_list
760 WHERE
761 pk_patient = %(pat)s
762 AND
763 pk_health_issue is null
764 ))""",
765 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
766 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
767 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
768 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
769 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
770 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
771
772 u"""
773 SELECT count(1)
774 from clin.v_pat_substance_intake
775 WHERE
776 pk_patient = %(pat)s
777 and is_currently_active in (null, true)
778 and intake_is_approved_of in (null, true)""",
779 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
780 ])
781
782 rows, idx = gmPG2.run_ro_queries (
783 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
784 get_col_idx = False
785 )
786
787 stats = dict (
788 problems = rows[0][0],
789 encounters = rows[1][0],
790 items = rows[2][0],
791 documents = rows[3][0],
792 results = rows[4][0],
793 stays = rows[5][0],
794 procedures = rows[6][0],
795 active_drugs = rows[7][0],
796 vaccinations = rows[8][0]
797 )
798
799 return stats
800
813
915
936
937
938
939 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
940 """Retrieves patient allergy items.
941
942 remove_sensitivities
943 - retrieve real allergies only, without sensitivities
944 since
945 - initial date for allergy items
946 until
947 - final date for allergy items
948 encounters
949 - list of encounters whose allergies are to be retrieved
950 episodes
951 - list of episodes whose allergies are to be retrieved
952 issues
953 - list of health issues whose allergies are to be retrieved
954 """
955 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
956 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
957 allergies = []
958 for r in rows:
959 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
960
961
962 filtered_allergies = []
963 filtered_allergies.extend(allergies)
964
965 if ID_list is not None:
966 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
967 if len(filtered_allergies) == 0:
968 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
969
970 return None
971 else:
972 return filtered_allergies
973
974 if remove_sensitivities:
975 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
976 if since is not None:
977 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
978 if until is not None:
979 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
980 if issues is not None:
981 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
982 if episodes is not None:
983 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
984 if encounters is not None:
985 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
986
987 return filtered_allergies
988
989 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
990 if encounter_id is None:
991 encounter_id = self.current_encounter['pk_encounter']
992
993 if episode_id is None:
994 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
995 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
996 episode_id = epi['pk_episode']
997
998 new_allergy = gmAllergy.create_allergy (
999 allergene = allergene,
1000 allg_type = allg_type,
1001 encounter_id = encounter_id,
1002 episode_id = episode_id
1003 )
1004
1005 return new_allergy
1006
1008 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1009 args = {'pk_allg': pk_allergy}
1010 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1011
1013 """Cave: only use with one potential allergic agent
1014 otherwise you won't know which of the agents the allergy is to."""
1015
1016
1017 if self.allergy_state is None:
1018 return None
1019
1020
1021 if self.allergy_state == 0:
1022 return False
1023
1024 args = {
1025 'atcs': atcs,
1026 'inns': inns,
1027 'brand': brand,
1028 'pat': self.pk_patient
1029 }
1030 allergenes = []
1031 where_parts = []
1032
1033 if len(atcs) == 0:
1034 atcs = None
1035 if atcs is not None:
1036 where_parts.append(u'atc_code in %(atcs)s')
1037 if len(inns) == 0:
1038 inns = None
1039 if inns is not None:
1040 where_parts.append(u'generics in %(inns)s')
1041 allergenes.extend(inns)
1042 if brand is not None:
1043 where_parts.append(u'substance = %(brand)s')
1044 allergenes.append(brand)
1045
1046 if len(allergenes) != 0:
1047 where_parts.append(u'allergene in %(allgs)s')
1048 args['allgs'] = tuple(allergenes)
1049
1050 cmd = u"""
1051 SELECT * FROM clin.v_pat_allergies
1052 WHERE
1053 pk_patient = %%(pat)s
1054 AND ( %s )""" % u' OR '.join(where_parts)
1055
1056 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1057
1058 if len(rows) == 0:
1059 return False
1060
1061 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1062
1072
1075
1076 allergy_state = property(_get_allergy_state, _set_allergy_state)
1077
1078
1079
1080 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1081 """Fetches from backend patient episodes.
1082
1083 id_list - Episodes' PKs list
1084 issues - Health issues' PKs list to filter episodes by
1085 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1086 """
1087 if (unlinked_only is True) and (issues is not None):
1088 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1089
1090 if order_by is None:
1091 order_by = u''
1092 else:
1093 order_by = u'ORDER BY %s' % order_by
1094
1095 args = {
1096 'pat': self.pk_patient,
1097 'open': open_status
1098 }
1099 where_parts = [u'pk_patient = %(pat)s']
1100
1101 if open_status is not None:
1102 where_parts.append(u'episode_open IS %(open)s')
1103
1104 if unlinked_only:
1105 where_parts.append(u'pk_health_issue is NULL')
1106
1107 if issues is not None:
1108 where_parts.append(u'pk_health_issue IN %(issues)s')
1109 args['issues'] = tuple(issues)
1110
1111 if id_list is not None:
1112 where_parts.append(u'pk_episode IN %(epis)s')
1113 args['epis'] = tuple(id_list)
1114
1115 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1116 u' AND '.join(where_parts),
1117 order_by
1118 )
1119 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1120
1121 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1122
1123 episodes = property(get_episodes, lambda x:x)
1124
1126 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1127
1128 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1129
1131 cmd = u"""SELECT distinct pk_episode
1132 from clin.v_pat_items
1133 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1134 args = {
1135 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1136 'pat': self.pk_patient
1137 }
1138 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1139 if len(rows) == 0:
1140 return []
1141 epis = []
1142 for row in rows:
1143 epis.append(row[0])
1144 return self.get_episodes(id_list=epis)
1145
1146 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1147 """Add episode 'episode_name' for a patient's health issue.
1148
1149 - silently returns if episode already exists
1150 """
1151 episode = gmEMRStructItems.create_episode (
1152 pk_health_issue = pk_health_issue,
1153 episode_name = episode_name,
1154 is_open = is_open,
1155 encounter = self.current_encounter['pk_encounter']
1156 )
1157 return episode
1158
1160
1161
1162 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1163
1164 cmd = u"""
1165 SELECT pk
1166 from clin.episode
1167 WHERE pk = (
1168 SELECT distinct on(pk_episode) pk_episode
1169 from clin.v_pat_items
1170 WHERE
1171 pk_patient = %%(pat)s
1172 and
1173 modified_when = (
1174 SELECT max(vpi.modified_when)
1175 from clin.v_pat_items vpi
1176 WHERE vpi.pk_patient = %%(pat)s
1177 )
1178 %s
1179 -- guard against several episodes created at the same moment of time
1180 limit 1
1181 )""" % issue_where
1182 rows, idx = gmPG2.run_ro_queries(queries = [
1183 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1184 ])
1185 if len(rows) != 0:
1186 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1187
1188
1189
1190 cmd = u"""
1191 SELECT vpe0.pk_episode
1192 from
1193 clin.v_pat_episodes vpe0
1194 WHERE
1195 vpe0.pk_patient = %%(pat)s
1196 and
1197 vpe0.episode_modified_when = (
1198 SELECT max(vpe1.episode_modified_when)
1199 from clin.v_pat_episodes vpe1
1200 WHERE vpe1.pk_episode = vpe0.pk_episode
1201 )
1202 %s""" % issue_where
1203 rows, idx = gmPG2.run_ro_queries(queries = [
1204 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1205 ])
1206 if len(rows) != 0:
1207 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1208
1209 return None
1210
1213
1214
1215
1216 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1217 """Retrieve a patient's problems.
1218
1219 "Problems" are the UNION of:
1220
1221 - issues which are .clinically_relevant
1222 - episodes which are .is_open
1223
1224 Therefore, both an issue and the open episode
1225 thereof can each be listed as a problem.
1226
1227 include_closed_episodes/include_irrelevant_issues will
1228 include those -- which departs from the definition of
1229 the problem list being "active" items only ...
1230
1231 episodes - episodes' PKs to filter problems by
1232 issues - health issues' PKs to filter problems by
1233 """
1234
1235
1236 args = {'pat': self.pk_patient}
1237
1238 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1239 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1240
1241
1242 problems = []
1243 for row in rows:
1244 pk_args = {
1245 u'pk_patient': self.pk_patient,
1246 u'pk_health_issue': row['pk_health_issue'],
1247 u'pk_episode': row['pk_episode']
1248 }
1249 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1250
1251
1252 other_rows = []
1253 if include_closed_episodes:
1254 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1255 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1256 other_rows.extend(rows)
1257
1258 if include_irrelevant_issues:
1259 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1260 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1261 other_rows.extend(rows)
1262
1263 if len(other_rows) > 0:
1264 for row in other_rows:
1265 pk_args = {
1266 u'pk_patient': self.pk_patient,
1267 u'pk_health_issue': row['pk_health_issue'],
1268 u'pk_episode': row['pk_episode']
1269 }
1270 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1271
1272
1273 if (episodes is None) and (issues is None):
1274 return problems
1275
1276
1277 if issues is not None:
1278 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1279 if episodes is not None:
1280 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1281
1282 return problems
1283
1286
1289
1292
1293
1294
1296
1297 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1298 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1299 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1300
1301 if id_list is None:
1302 return issues
1303
1304 if len(id_list) == 0:
1305 raise ValueError('id_list to filter by is empty, most likely a programming error')
1306
1307 filtered_issues = []
1308 for issue in issues:
1309 if issue['pk_health_issue'] in id_list:
1310 filtered_issues.append(issue)
1311
1312 return filtered_issues
1313
1314 health_issues = property(get_health_issues, lambda x:x)
1315
1323
1326
1327
1328
1330
1331 where_parts = [u'pk_patient = %(pat)s']
1332
1333 if not include_inactive:
1334 where_parts.append(u'is_currently_active in (true, null)')
1335
1336 if not include_unapproved:
1337 where_parts.append(u'intake_is_approved_of in (true, null)')
1338
1339 if order_by is None:
1340 order_by = u''
1341 else:
1342 order_by = u'order by %s' % order_by
1343
1344 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1345 u'\nand '.join(where_parts),
1346 order_by
1347 )
1348
1349 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1350
1351 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1352
1353 if episodes is not None:
1354 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1355
1356 if issues is not None:
1357 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1358
1359 return meds
1360
1361 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1369
1376
1377
1378
1386
1388 """Returns latest given vaccination for each vaccinated indication.
1389
1390 as a dict {'l10n_indication': cVaccination instance}
1391
1392 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1393 """
1394
1395 args = {'pat': self.pk_patient}
1396 where_parts = [u'pk_patient = %(pat)s']
1397
1398 if (episodes is not None) and (len(episodes) > 0):
1399 where_parts.append(u'pk_episode IN %(epis)s')
1400 args['epis'] = tuple(episodes)
1401
1402 if (issues is not None) and (len(issues) > 0):
1403 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1404 args['issues'] = tuple(issues)
1405
1406 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1407 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1408
1409
1410 if len(rows) == 0:
1411 return {}
1412
1413 vpks = [ ind['pk_vaccination'] for ind in rows ]
1414 vinds = [ ind['l10n_indication'] for ind in rows ]
1415 ind_counts = [ ind['indication_count'] for ind in rows ]
1416
1417
1418 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1419 args = {'pks': tuple(vpks)}
1420 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1421
1422 vaccs = {}
1423 for idx in range(len(vpks)):
1424 pk = vpks[idx]
1425 ind_count = ind_counts[idx]
1426 for r in rows:
1427 if r['pk_vaccination'] == pk:
1428 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1429
1430 return vaccs
1431
1432 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1433
1434 args = {'pat': self.pk_patient}
1435 where_parts = [u'pk_patient = %(pat)s']
1436
1437 if order_by is None:
1438 order_by = u''
1439 else:
1440 order_by = u'ORDER BY %s' % order_by
1441
1442 if (episodes is not None) and (len(episodes) > 0):
1443 where_parts.append(u'pk_episode IN %(epis)s')
1444 args['epis'] = tuple(episodes)
1445
1446 if (issues is not None) and (len(issues) > 0):
1447 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1448 args['issues'] = tuple(issues)
1449
1450 if (encounters is not None) and (len(encounters) > 0):
1451 where_parts.append(u'pk_encounter IN %(encs)s')
1452 args['encs'] = tuple(encounters)
1453
1454 cmd = u'%s %s' % (
1455 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1456 order_by
1457 )
1458 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1459 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1460
1461 return vaccs
1462
1463 vaccinations = property(get_vaccinations, lambda x:x)
1464
1465
1466
1468 """Retrieves vaccination regimes the patient is on.
1469
1470 optional:
1471 * ID - PK of the vaccination regime
1472 * indications - indications we want to retrieve vaccination
1473 regimes for, must be primary language, not l10n_indication
1474 """
1475
1476 try:
1477 self.__db_cache['vaccinations']['scheduled regimes']
1478 except KeyError:
1479
1480 self.__db_cache['vaccinations']['scheduled regimes'] = []
1481 cmd = """SELECT distinct on(pk_course) pk_course
1482 FROM clin.v_vaccs_scheduled4pat
1483 WHERE pk_patient=%s"""
1484 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1485 if rows is None:
1486 _log.error('cannot retrieve scheduled vaccination courses')
1487 del self.__db_cache['vaccinations']['scheduled regimes']
1488 return None
1489
1490 for row in rows:
1491 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1492
1493
1494 filtered_regimes = []
1495 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1496 if ID is not None:
1497 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1498 if len(filtered_regimes) == 0:
1499 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1500 return []
1501 else:
1502 return filtered_regimes[0]
1503 if indications is not None:
1504 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1505
1506 return filtered_regimes
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1535 """Retrieves list of vaccinations the patient has received.
1536
1537 optional:
1538 * ID - PK of a vaccination
1539 * indications - indications we want to retrieve vaccination
1540 items for, must be primary language, not l10n_indication
1541 * since - initial date for allergy items
1542 * until - final date for allergy items
1543 * encounters - list of encounters whose allergies are to be retrieved
1544 * episodes - list of episodes whose allergies are to be retrieved
1545 * issues - list of health issues whose allergies are to be retrieved
1546 """
1547 try:
1548 self.__db_cache['vaccinations']['vaccinated']
1549 except KeyError:
1550 self.__db_cache['vaccinations']['vaccinated'] = []
1551
1552 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1553 WHERE pk_patient=%s
1554 order by indication, date"""
1555 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1556 if rows is None:
1557 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1558 del self.__db_cache['vaccinations']['vaccinated']
1559 return None
1560
1561 vaccs_by_ind = {}
1562 for row in rows:
1563 vacc_row = {
1564 'pk_field': 'pk_vaccination',
1565 'idx': idx,
1566 'data': row
1567 }
1568 vacc = gmVaccination.cVaccination(row=vacc_row)
1569 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1570
1571 try:
1572 vaccs_by_ind[vacc['indication']].append(vacc)
1573 except KeyError:
1574 vaccs_by_ind[vacc['indication']] = [vacc]
1575
1576
1577 for ind in vaccs_by_ind.keys():
1578 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1579 for vacc in vaccs_by_ind[ind]:
1580
1581
1582 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1583 vacc['seq_no'] = seq_no
1584
1585
1586 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1587 continue
1588 if seq_no > vacc_regimes[0]['shots']:
1589 vacc['is_booster'] = True
1590 del vaccs_by_ind
1591
1592
1593 filtered_shots = []
1594 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1595 if ID is not None:
1596 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1597 if len(filtered_shots) == 0:
1598 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1599 return None
1600 else:
1601 return filtered_shots[0]
1602 if since is not None:
1603 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1604 if until is not None:
1605 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1606 if issues is not None:
1607 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1608 if episodes is not None:
1609 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1610 if encounters is not None:
1611 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1612 if indications is not None:
1613 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1614 return filtered_shots
1615
1617 """Retrieves vaccinations scheduled for a regime a patient is on.
1618
1619 The regime is referenced by its indication (not l10n)
1620
1621 * indications - List of indications (not l10n) of regimes we want scheduled
1622 vaccinations to be fetched for
1623 """
1624 try:
1625 self.__db_cache['vaccinations']['scheduled']
1626 except KeyError:
1627 self.__db_cache['vaccinations']['scheduled'] = []
1628 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1629 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1630 if rows is None:
1631 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1632 del self.__db_cache['vaccinations']['scheduled']
1633 return None
1634
1635 for row in rows:
1636 vacc_row = {
1637 'pk_field': 'pk_vacc_def',
1638 'idx': idx,
1639 'data': row
1640 }
1641 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1642
1643
1644 if indications is None:
1645 return self.__db_cache['vaccinations']['scheduled']
1646 filtered_shots = []
1647 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1648 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1649 return filtered_shots
1650
1652 try:
1653 self.__db_cache['vaccinations']['missing']
1654 except KeyError:
1655 self.__db_cache['vaccinations']['missing'] = {}
1656
1657 self.__db_cache['vaccinations']['missing']['due'] = []
1658
1659 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1660 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1661 if rows is None:
1662 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1663 return None
1664 pk_args = {'pat_id': self.pk_patient}
1665 if rows is not None:
1666 for row in rows:
1667 pk_args['indication'] = row[0]
1668 pk_args['seq_no'] = row[1]
1669 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1670
1671
1672 self.__db_cache['vaccinations']['missing']['boosters'] = []
1673
1674 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1675 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1676 if rows is None:
1677 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1678 return None
1679 pk_args = {'pat_id': self.pk_patient}
1680 if rows is not None:
1681 for row in rows:
1682 pk_args['indication'] = row[0]
1683 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1684
1685
1686 if indications is None:
1687 return self.__db_cache['vaccinations']['missing']
1688 if len(indications) == 0:
1689 return self.__db_cache['vaccinations']['missing']
1690
1691 filtered_shots = {
1692 'due': [],
1693 'boosters': []
1694 }
1695 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1696 if due_shot['indication'] in indications:
1697 filtered_shots['due'].append(due_shot)
1698 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1699 if due_shot['indication'] in indications:
1700 filtered_shots['boosters'].append(due_shot)
1701 return filtered_shots
1702
1703
1704
1706 return self.__encounter
1707
1709
1710
1711 if self.__encounter is None:
1712 _log.debug('first setting of active encounter in this clinical record instance')
1713 else:
1714 _log.debug('switching of active encounter')
1715
1716 if self.__encounter.is_modified():
1717 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1718 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1719
1720
1721 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1722 now = gmDateTime.pydt_now_here()
1723 if now > encounter['started']:
1724 encounter['last_affirmed'] = now
1725 encounter.save()
1726 self.__encounter = encounter
1727 gmDispatcher.send(u'current_encounter_switched')
1728
1729 return True
1730
1731 current_encounter = property(_get_current_encounter, _set_current_encounter)
1732 active_encounter = property(_get_current_encounter, _set_current_encounter)
1733
1735
1736
1737 if self.__activate_very_recent_encounter():
1738 return True
1739
1740
1741 if self.__activate_fairly_recent_encounter():
1742 return True
1743
1744
1745 self.start_new_encounter()
1746 return True
1747
1749 """Try to attach to a "very recent" encounter if there is one.
1750
1751 returns:
1752 False: no "very recent" encounter, create new one
1753 True: success
1754 """
1755 cfg_db = gmCfg.cCfgSQL()
1756 min_ttl = cfg_db.get2 (
1757 option = u'encounter.minimum_ttl',
1758 workplace = _here.active_workplace,
1759 bias = u'user',
1760 default = u'1 hour 30 minutes'
1761 )
1762 cmd = u"""
1763 SELECT pk_encounter
1764 FROM clin.v_most_recent_encounters
1765 WHERE
1766 pk_patient = %s
1767 and
1768 last_affirmed > (now() - %s::interval)
1769 ORDER BY
1770 last_affirmed DESC"""
1771 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1772
1773 if len(enc_rows) == 0:
1774 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1775 return False
1776
1777 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1778 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1779 return True
1780
1782 """Try to attach to a "fairly recent" encounter if there is one.
1783
1784 returns:
1785 False: no "fairly recent" encounter, create new one
1786 True: success
1787 """
1788 if _func_ask_user is None:
1789 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1790 return False
1791
1792 cfg_db = gmCfg.cCfgSQL()
1793 min_ttl = cfg_db.get2 (
1794 option = u'encounter.minimum_ttl',
1795 workplace = _here.active_workplace,
1796 bias = u'user',
1797 default = u'1 hour 30 minutes'
1798 )
1799 max_ttl = cfg_db.get2 (
1800 option = u'encounter.maximum_ttl',
1801 workplace = _here.active_workplace,
1802 bias = u'user',
1803 default = u'6 hours'
1804 )
1805 cmd = u"""
1806 SELECT pk_encounter
1807 FROM clin.v_most_recent_encounters
1808 WHERE
1809 pk_patient=%s
1810 AND
1811 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1812 ORDER BY
1813 last_affirmed DESC"""
1814 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1815
1816 if len(enc_rows) == 0:
1817 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1818 return False
1819 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1820
1821 cmd = u"""
1822 SELECT title, firstnames, lastnames, gender, dob
1823 FROM dem.v_basic_person WHERE pk_identity=%s"""
1824 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1825 pat = pats[0]
1826 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1827 gmTools.coalesce(pat[0], u'')[:5],
1828 pat[1][:15],
1829 pat[2][:15],
1830 pat[3],
1831 pat[4].strftime('%Y %b %d'),
1832 self.pk_patient
1833 )
1834 enc = gmI18N.get_encoding()
1835 msg = _(
1836 '%s\n'
1837 '\n'
1838 "This patient's chart was worked on only recently:\n"
1839 '\n'
1840 ' %s %s - %s (%s)\n'
1841 '\n'
1842 ' Request: %s\n'
1843 ' Outcome: %s\n'
1844 '\n'
1845 'Do you want to continue that consultation\n'
1846 'or do you want to start a new one ?\n'
1847 ) % (
1848 pat_str,
1849 encounter['started'].strftime('%Y %b %d').decode(enc),
1850 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1851 encounter['l10n_type'],
1852 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1853 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1854 )
1855 attach = False
1856 try:
1857 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1858 except:
1859 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1860 return False
1861 if not attach:
1862 return False
1863
1864
1865 self.current_encounter = encounter
1866
1867 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1868 return True
1869
1881
1882 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1883 """Retrieves patient's encounters.
1884
1885 id_list - PKs of encounters to fetch
1886 since - initial date for encounter items, DateTime instance
1887 until - final date for encounter items, DateTime instance
1888 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1889 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1890 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
1891
1892 NOTE: if you specify *both* issues and episodes
1893 you will get the *aggregate* of all encounters even
1894 if the episodes all belong to the health issues listed.
1895 IOW, the issues broaden the episode list rather than
1896 the episode list narrowing the episodes-from-issues
1897 list.
1898 Rationale: If it was the other way round it would be
1899 redundant to specify the list of issues at all.
1900 """
1901 where_parts = [u'c_vpe.pk_patient = %(pat)s']
1902 args = {'pat': self.pk_patient}
1903
1904 if skip_empty:
1905 where_parts.append(u"""NOT (
1906 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
1907 AND
1908 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
1909 AND
1910 NOT EXISTS (
1911 SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
1912 UNION ALL
1913 SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
1914 ))""")
1915
1916 if since is not None:
1917 where_parts.append(u'c_vpe.started >= %(start)s')
1918 args['start'] = since
1919
1920 if until is not None:
1921 where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
1922 args['end'] = since
1923
1924 cmd = u"""
1925 SELECT *
1926 FROM clin.v_pat_encounters c_vpe
1927 WHERE
1928 %s
1929 ORDER BY started
1930 """ % u' AND '.join(where_parts)
1931 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1932 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
1933
1934
1935 filtered_encounters = []
1936 filtered_encounters.extend(encounters)
1937
1938 if id_list is not None:
1939 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1940
1941 if (issues is not None) and (len(issues) > 0):
1942 issues = tuple(issues)
1943
1944
1945 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1947 epi_ids = map(lambda x:x[0], rows)
1948 if episodes is None:
1949 episodes = []
1950 episodes.extend(epi_ids)
1951
1952 if (episodes is not None) and (len(episodes) > 0):
1953 episodes = tuple(episodes)
1954
1955
1956 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1957 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1958 enc_ids = map(lambda x:x[0], rows)
1959 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1960
1961 return filtered_encounters
1962
1964 """Retrieves first encounter for a particular issue and/or episode.
1965
1966 issue_id - First encounter associated health issue
1967 episode - First encounter associated episode
1968 """
1969
1970 if issue_id is None:
1971 issues = None
1972 else:
1973 issues = [issue_id]
1974
1975 if episode_id is None:
1976 episodes = None
1977 else:
1978 episodes = [episode_id]
1979
1980 encounters = self.get_encounters(issues=issues, episodes=episodes)
1981 if len(encounters) == 0:
1982 return None
1983
1984
1985 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1986 return encounters[0]
1987
1989 args = {'pat': self.pk_patient}
1990 cmd = u"""
1991 SELECT MIN(earliest) FROM (
1992 (
1993 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
1994
1995 ) UNION ALL (
1996
1997 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
1998
1999 ) UNION ALL (
2000
2001 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2002
2003 ) UNION ALL (
2004
2005 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2006
2007 ) UNION ALL (
2008
2009 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2010
2011 ) UNION ALL (
2012
2013 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2014
2015 ) UNION ALL (
2016
2017 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2018
2019 )
2020 ) AS candidates"""
2021 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2022 return rows[0][0]
2023
2024 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2025
2027 """Retrieves last encounter for a concrete issue and/or episode
2028
2029 issue_id - Last encounter associated health issue
2030 episode_id - Last encounter associated episode
2031 """
2032
2033
2034 if issue_id is None:
2035 issues = None
2036 else:
2037 issues = [issue_id]
2038
2039 if episode_id is None:
2040 episodes = None
2041 else:
2042 episodes = [episode_id]
2043
2044 encounters = self.get_encounters(issues=issues, episodes=episodes)
2045 if len(encounters) == 0:
2046 return None
2047
2048
2049 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
2050 return encounters[-1]
2051
2052 last_encounter = property(get_last_encounter, lambda x:x)
2053
2055 args = {'pat': self.pk_patient, 'range': cover_period}
2056 where_parts = [u'pk_patient = %(pat)s']
2057 if cover_period is not None:
2058 where_parts.append(u'last_affirmed > now() - %(range)s')
2059
2060 cmd = u"""
2061 SELECT l10n_type, count(1) AS frequency
2062 FROM clin.v_pat_encounters
2063 WHERE
2064 %s
2065 GROUP BY l10n_type
2066 ORDER BY frequency DESC
2067 """ % u' AND '.join(where_parts)
2068 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2069 return rows
2070
2072
2073 args = {'pat': self.pk_patient}
2074
2075 if (issue_id is None) and (episode_id is None):
2076
2077 cmd = u"""
2078 SELECT * FROM clin.v_pat_encounters
2079 WHERE pk_patient = %(pat)s
2080 ORDER BY started DESC
2081 LIMIT 2
2082 """
2083 else:
2084 where_parts = []
2085
2086 if issue_id is not None:
2087 where_parts.append(u'pk_health_issue = %(issue)s')
2088 args['issue'] = issue_id
2089
2090 if episode_id is not None:
2091 where_parts.append(u'pk_episode = %(epi)s')
2092 args['epi'] = episode_id
2093
2094 cmd = u"""
2095 SELECT *
2096 FROM clin.v_pat_encounters
2097 WHERE
2098 pk_patient = %%(pat)s
2099 AND
2100 pk_encounter IN (
2101 SELECT distinct pk_encounter
2102 FROM clin.v_pat_narrative
2103 WHERE
2104 %s
2105 )
2106 ORDER BY started DESC
2107 LIMIT 2
2108 """ % u' AND '.join(where_parts)
2109
2110 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2111
2112 if len(rows) == 0:
2113 return None
2114
2115
2116 if len(rows) == 1:
2117
2118 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2119
2120 return None
2121
2122 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2123
2124
2125 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2126 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2127
2128 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2129
2131 cfg_db = gmCfg.cCfgSQL()
2132 ttl = cfg_db.get2 (
2133 option = u'encounter.ttl_if_empty',
2134 workplace = _here.active_workplace,
2135 bias = u'user',
2136 default = u'1 week'
2137 )
2138
2139
2140 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2141 args = {'pat': self.pk_patient, 'ttl': ttl}
2142 try:
2143 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2144 except:
2145 _log.exception('error deleting empty encounters')
2146
2147 return True
2148
2149
2150
2158
2167
2169 if order_by is None:
2170 order_by = u''
2171 else:
2172 order_by = u'ORDER BY %s' % order_by
2173 cmd = u"""
2174 SELECT * FROM clin.v_test_results
2175 WHERE
2176 pk_patient = %%(pat)s
2177 AND
2178 reviewed IS FALSE
2179 %s""" % order_by
2180 args = {'pat': self.pk_patient}
2181 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2182 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2183
2184
2186 """Retrieve data about test types for which this patient has results."""
2187
2188 cmd = u"""
2189 SELECT * FROM (
2190 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2191 FROM clin.v_test_results
2192 WHERE pk_patient = %(pat)s
2193 ) AS foo
2194 ORDER BY clin_when desc, unified_name
2195 """
2196 args = {'pat': self.pk_patient}
2197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2198 return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
2199
2201 """Get the dates for which we have results."""
2202 where_parts = [u'pk_patient = %(pat)s']
2203 args = {'pat': self.pk_patient}
2204
2205 if tests is not None:
2206 where_parts.append(u'pk_test_type IN %(tests)s')
2207 args['tests'] = tuple(tests)
2208
2209 cmd = u"""
2210 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2211 FROM clin.v_test_results
2212 WHERE %s
2213 ORDER BY cwhen %s
2214 """ % (
2215 u' AND '.join(where_parts),
2216 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2217 )
2218 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2219 return rows
2220
2221 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2228
2230
2231 where_parts = [u'pk_patient = %(pat)s']
2232 args = {'pat': self.pk_patient}
2233
2234 if tests is not None:
2235 where_parts.append(u'pk_test_type IN %(tests)s')
2236 args['tests'] = tuple(tests)
2237
2238 if encounter is not None:
2239 where_parts.append(u'pk_encounter = %(enc)s')
2240 args['enc'] = encounter
2241
2242 if episodes is not None:
2243 where_parts.append(u'pk_episode IN %(epis)s')
2244 args['epis'] = tuple(episodes)
2245
2246 cmd = u"""
2247 SELECT * FROM clin.v_test_results
2248 WHERE %s
2249 ORDER BY clin_when %s, pk_episode, unified_name
2250 """ % (
2251 u' AND '.join(where_parts),
2252 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2253 )
2254 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2255
2256 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2257
2258 return tests
2259
2260 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2261
2262 try:
2263 epi = int(episode)
2264 except:
2265 epi = episode['pk_episode']
2266
2267 try:
2268 type = int(type)
2269 except:
2270 type = type['pk_test_type']
2271
2272 if intended_reviewer is None:
2273 intended_reviewer = _me['pk_staff']
2274
2275 tr = gmPathLab.create_test_result (
2276 encounter = self.current_encounter['pk_encounter'],
2277 episode = epi,
2278 type = type,
2279 intended_reviewer = intended_reviewer,
2280 val_num = val_num,
2281 val_alpha = val_alpha,
2282 unit = unit
2283 )
2284
2285 return tr
2286
2287
2288
2289
2294
2295 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2309
2310
2311
2312
2313 if __name__ == "__main__":
2314
2315 if len(sys.argv) == 1:
2316 sys.exit()
2317
2318 if sys.argv[1] != 'test':
2319 sys.exit()
2320
2321 from Gnumed.pycommon import gmLog2
2322
2336
2343
2350
2352 emr = cClinicalRecord(aPKey=12)
2353 rows, idx = emr.get_measurements_by_date()
2354 print "test results:"
2355 for row in rows:
2356 print row
2357
2364
2369
2371 emr = cClinicalRecord(aPKey=12)
2372
2373 probs = emr.get_problems()
2374 print "normal probs (%s):" % len(probs)
2375 for p in probs:
2376 print u'%s (%s)' % (p['problem'], p['type'])
2377
2378 probs = emr.get_problems(include_closed_episodes=True)
2379 print "probs + closed episodes (%s):" % len(probs)
2380 for p in probs:
2381 print u'%s (%s)' % (p['problem'], p['type'])
2382
2383 probs = emr.get_problems(include_irrelevant_issues=True)
2384 print "probs + issues (%s):" % len(probs)
2385 for p in probs:
2386 print u'%s (%s)' % (p['problem'], p['type'])
2387
2388 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2389 print "probs + issues + epis (%s):" % len(probs)
2390 for p in probs:
2391 print u'%s (%s)' % (p['problem'], p['type'])
2392
2394 emr = cClinicalRecord(aPKey=12)
2395 tr = emr.add_test_result (
2396 episode = 1,
2397 intended_reviewer = 1,
2398 type = 1,
2399 val_num = 75,
2400 val_alpha = u'somewhat obese',
2401 unit = u'kg'
2402 )
2403 print tr
2404
2408
2413
2418
2422
2424 emr = cClinicalRecord(aPKey = 12)
2425 for journal_line in emr.get_as_journal():
2426
2427 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2428 print ""
2429
2433
2438
2439
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463 test_format_as_journal()
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511