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