1
2 """GNUmed clinical patient record.
3
4 Make sure to call set_func_ask_user() and set_encounter_ttl() early on in
5 your code (before cClinicalRecord.__init__() is called for the first time).
6 """
7
8 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later"
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import sys
26 import logging
27
28
29 if __name__ == '__main__':
30 sys.path.insert(0, '../../')
31 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
32 gmI18N.activate_locale()
33 gmI18N.install_domain()
34 gmDateTime.init()
35
36 from Gnumed.pycommon import gmExceptions
37 from Gnumed.pycommon import gmPG2
38 from Gnumed.pycommon import gmDispatcher
39 from Gnumed.pycommon import gmI18N
40 from Gnumed.pycommon import gmCfg
41 from Gnumed.pycommon import gmTools
42 from Gnumed.pycommon import gmDateTime
43
44 from Gnumed.business import gmAllergy
45 from Gnumed.business import gmPathLab
46 from Gnumed.business import gmLOINC
47 from Gnumed.business import gmClinNarrative
48 from Gnumed.business import gmEMRStructItems
49 from Gnumed.business import gmMedication
50 from Gnumed.business import gmVaccination
51 from Gnumed.business import gmFamilyHistory
52 from Gnumed.business.gmDemographicRecord import get_occupations
53
54
55 _log = logging.getLogger('gm.emr')
56
57 _me = None
58 _here = None
59
60
61
62 _func_ask_user = None
63
65 if not callable(a_func):
66 _log.error('[%] not callable, not setting _func_ask_user', a_func)
67 return False
68
69 _log.debug('setting _func_ask_user to [%s]', a_func)
70
71 global _func_ask_user
72 _func_ask_user = a_func
73
74
76
77 _clin_root_item_children_union_query = None
78
79 - def __init__(self, aPKey=None, allow_user_interaction=True):
136
139
141 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
142 return True
143
144
145
150
186
189
191 try:
192 del self.__db_cache['health issues']
193 except KeyError:
194 pass
195 return 1
196
198
199
200
201
202 return 1
203
205 _log.debug('DB: clin_root_item modification')
206
207
208
209 - def get_family_history(self, episodes=None, issues=None, encounters=None):
210 fhx = gmFamilyHistory.get_family_history (
211 order_by = u'l10n_relation, condition',
212 patient = self.pk_patient
213 )
214
215 if episodes is not None:
216 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
217
218 if issues is not None:
219 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
220
221 if encounters is not None:
222 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
223
224 return fhx
225
226 - def add_family_history(self, episode=None, condition=None, relation=None):
227 return gmFamilyHistory.create_family_history (
228 encounter = self.current_encounter['pk_encounter'],
229 episode = episode,
230 condition = condition,
231 relation = relation
232 )
233
234
235
247
248 performed_procedures = property(get_performed_procedures, lambda x:x)
249
252
261
262
263
271
272 hospital_stays = property(get_hospital_stays, lambda x:x)
273
276
282
284 args = {'pat': self.pk_patient, 'range': cover_period}
285 where_parts = [u'pk_patient = %(pat)s']
286 if cover_period is not None:
287 where_parts.append(u'discharge > (now() - %(range)s)')
288
289 cmd = u"""
290 SELECT hospital, count(1) AS frequency
291 FROM clin.v_pat_hospital_stays
292 WHERE
293 %s
294 GROUP BY hospital
295 ORDER BY frequency DESC
296 """ % u' AND '.join(where_parts)
297
298 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
299 return rows
300
301
302
303 - def add_notes(self, notes=None, episode=None, encounter=None):
319
336
337 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
338 """Get SOAP notes pertinent to this encounter.
339
340 since
341 - initial date for narrative items
342 until
343 - final date for narrative items
344 encounters
345 - list of encounters whose narrative are to be retrieved
346 episodes
347 - list of episodes whose narrative are to be retrieved
348 issues
349 - list of health issues whose narrative are to be retrieved
350 soap_cats
351 - list of SOAP categories of the narrative to be retrieved
352 """
353 where_parts = [u'pk_patient = %(pat)s']
354 args = {u'pat': self.pk_patient}
355
356 if issues is not None:
357 where_parts.append(u'pk_health_issue IN %(issues)s')
358 args['issues'] = tuple(issues)
359
360 if episodes is not None:
361 where_parts.append(u'pk_episode IN %(epis)s')
362 args['epis'] = tuple(episodes)
363
364 if encounters is not None:
365 where_parts.append(u'pk_encounter IN %(encs)s')
366 args['encs'] = tuple(encounters)
367
368 if soap_cats is not None:
369 where_parts.append(u'soap_cat IN %(cats)s')
370 soap_cats = list(soap_cats)
371 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
372 if None in soap_cats:
373 args['cats'].append(None)
374 args['cats'] = tuple(args['cats'])
375
376 cmd = u"""
377 SELECT
378 c_vpn.*,
379 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank
380 FROM clin.v_pat_narrative c_vpn
381 WHERE %s
382 ORDER BY date, soap_rank
383 """ % u' AND '.join(where_parts)
384
385 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
386
387 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
388
389 if since is not None:
390 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
391
392 if until is not None:
393 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
394
395 if providers is not None:
396 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
397
398 return filtered_narrative
399
400 - 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):
401 return gmClinNarrative.get_as_journal (
402 patient = self.pk_patient,
403 since = since,
404 until = until,
405 encounters = encounters,
406 episodes = episodes,
407 issues = issues,
408 soap_cats = soap_cats,
409 providers = providers,
410 order_by = order_by,
411 time_range = time_range
412 )
413
415
416 search_term = search_term.strip()
417 if search_term == '':
418 return []
419
420 cmd = u"""
421 SELECT
422 *,
423 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
424 as episode,
425 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
426 as health_issue,
427 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
428 as encounter_started,
429 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
430 as encounter_ended,
431 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
432 as encounter_type
433 from clin.v_narrative4search vn4s
434 WHERE
435 pk_patient = %(pat)s and
436 vn4s.narrative ~ %(term)s
437 order by
438 encounter_started
439 """
440 rows, idx = gmPG2.run_ro_queries(queries = [
441 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
442 ])
443 return rows
444
446
447
448
449
450
451
452 try:
453 return self.__db_cache['text dump old']
454 except KeyError:
455 pass
456
457 fields = [
458 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
459 'modified_by',
460 'clin_when',
461 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
462 'pk_item',
463 'pk_encounter',
464 'pk_episode',
465 'pk_health_issue',
466 'src_table'
467 ]
468 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields)
469 ro_conn = self._conn_pool.GetConnection('historica')
470 curs = ro_conn.cursor()
471 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
472 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
473 curs.close()
474 return None
475 rows = curs.fetchall()
476 view_col_idx = gmPG2.get_col_indices(curs)
477
478
479 items_by_table = {}
480 for item in rows:
481 src_table = item[view_col_idx['src_table']]
482 pk_item = item[view_col_idx['pk_item']]
483 if not items_by_table.has_key(src_table):
484 items_by_table[src_table] = {}
485 items_by_table[src_table][pk_item] = item
486
487
488 issues = self.get_health_issues()
489 issue_map = {}
490 for issue in issues:
491 issue_map[issue['pk']] = issue['description']
492 episodes = self.get_episodes()
493 episode_map = {}
494 for episode in episodes:
495 episode_map[episode['pk_episode']] = episode['description']
496 emr_data = {}
497
498 for src_table in items_by_table.keys():
499 item_ids = items_by_table[src_table].keys()
500
501
502 if len(item_ids) == 0:
503 _log.info('no items in table [%s] ?!?' % src_table)
504 continue
505 elif len(item_ids) == 1:
506 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
507 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
508 _log.error('cannot load items from table [%s]' % src_table)
509
510 continue
511 elif len(item_ids) > 1:
512 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
513 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
514 _log.error('cannot load items from table [%s]' % src_table)
515
516 continue
517 rows = curs.fetchall()
518 table_col_idx = gmPG.get_col_indices(curs)
519
520 for row in rows:
521
522 pk_item = row[table_col_idx['pk_item']]
523 view_row = items_by_table[src_table][pk_item]
524 age = view_row[view_col_idx['age']]
525
526 try:
527 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
528 except:
529 episode_name = view_row[view_col_idx['pk_episode']]
530 try:
531 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
532 except:
533 issue_name = view_row[view_col_idx['pk_health_issue']]
534
535 if not emr_data.has_key(age):
536 emr_data[age] = []
537
538 emr_data[age].append(
539 _('%s: encounter (%s)') % (
540 view_row[view_col_idx['clin_when']],
541 view_row[view_col_idx['pk_encounter']]
542 )
543 )
544 emr_data[age].append(_('health issue: %s') % issue_name)
545 emr_data[age].append(_('episode : %s') % episode_name)
546
547
548
549 cols2ignore = [
550 'pk_audit', 'row_version', 'modified_when', 'modified_by',
551 'pk_item', 'id', 'fk_encounter', 'fk_episode'
552 ]
553 col_data = []
554 for col_name in table_col_idx.keys():
555 if col_name in cols2ignore:
556 continue
557 emr_data[age].append("=> %s:" % col_name)
558 emr_data[age].append(row[table_col_idx[col_name]])
559 emr_data[age].append("----------------------------------------------------")
560 emr_data[age].append("-- %s from table %s" % (
561 view_row[view_col_idx['modified_string']],
562 src_table
563 ))
564 emr_data[age].append("-- written %s by %s" % (
565 view_row[view_col_idx['modified_when']],
566 view_row[view_col_idx['modified_by']]
567 ))
568 emr_data[age].append("----------------------------------------------------")
569 curs.close()
570 self._conn_pool.ReleaseConnection('historica')
571 return emr_data
572
573 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
574
575
576
577
578
579
580 try:
581 return self.__db_cache['text dump']
582 except KeyError:
583 pass
584
585
586 fields = [
587 'age',
588 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
589 'modified_by',
590 'clin_when',
591 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
592 'pk_item',
593 'pk_encounter',
594 'pk_episode',
595 'pk_health_issue',
596 'src_table'
597 ]
598 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
599
600 where_snippets = []
601 params = {}
602 where_snippets.append('pk_patient=%(pat_id)s')
603 params['pat_id'] = self.pk_patient
604 if not since is None:
605 where_snippets.append('clin_when >= %(since)s')
606 params['since'] = since
607 if not until is None:
608 where_snippets.append('clin_when <= %(until)s')
609 params['until'] = until
610
611
612
613 if not encounters is None and len(encounters) > 0:
614 params['enc'] = encounters
615 if len(encounters) > 1:
616 where_snippets.append('fk_encounter in %(enc)s')
617 else:
618 where_snippets.append('fk_encounter=%(enc)s')
619
620 if not episodes is None and len(episodes) > 0:
621 params['epi'] = episodes
622 if len(episodes) > 1:
623 where_snippets.append('fk_episode in %(epi)s')
624 else:
625 where_snippets.append('fk_episode=%(epi)s')
626
627 if not issues is None and len(issues) > 0:
628 params['issue'] = issues
629 if len(issues) > 1:
630 where_snippets.append('fk_health_issue in %(issue)s')
631 else:
632 where_snippets.append('fk_health_issue=%(issue)s')
633
634 where_clause = ' and '.join(where_snippets)
635 order_by = 'order by src_table, age'
636 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
637
638 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
639 if rows is None:
640 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
641 return None
642
643
644
645
646 items_by_table = {}
647 for item in rows:
648 src_table = item[view_col_idx['src_table']]
649 pk_item = item[view_col_idx['pk_item']]
650 if not items_by_table.has_key(src_table):
651 items_by_table[src_table] = {}
652 items_by_table[src_table][pk_item] = item
653
654
655 issues = self.get_health_issues()
656 issue_map = {}
657 for issue in issues:
658 issue_map[issue['pk_health_issue']] = issue['description']
659 episodes = self.get_episodes()
660 episode_map = {}
661 for episode in episodes:
662 episode_map[episode['pk_episode']] = episode['description']
663 emr_data = {}
664
665 ro_conn = self._conn_pool.GetConnection('historica')
666 curs = ro_conn.cursor()
667 for src_table in items_by_table.keys():
668 item_ids = items_by_table[src_table].keys()
669
670
671 if len(item_ids) == 0:
672 _log.info('no items in table [%s] ?!?' % src_table)
673 continue
674 elif len(item_ids) == 1:
675 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
676 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
677 _log.error('cannot load items from table [%s]' % src_table)
678
679 continue
680 elif len(item_ids) > 1:
681 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
682 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
683 _log.error('cannot load items from table [%s]' % src_table)
684
685 continue
686 rows = curs.fetchall()
687 table_col_idx = gmPG.get_col_indices(curs)
688
689 for row in rows:
690
691 pk_item = row[table_col_idx['pk_item']]
692 view_row = items_by_table[src_table][pk_item]
693 age = view_row[view_col_idx['age']]
694
695 try:
696 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
697 except:
698 episode_name = view_row[view_col_idx['pk_episode']]
699 try:
700 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
701 except:
702 issue_name = view_row[view_col_idx['pk_health_issue']]
703
704 if not emr_data.has_key(age):
705 emr_data[age] = []
706
707 emr_data[age].append(
708 _('%s: encounter (%s)') % (
709 view_row[view_col_idx['clin_when']],
710 view_row[view_col_idx['pk_encounter']]
711 )
712 )
713 emr_data[age].append(_('health issue: %s') % issue_name)
714 emr_data[age].append(_('episode : %s') % episode_name)
715
716
717
718 cols2ignore = [
719 'pk_audit', 'row_version', 'modified_when', 'modified_by',
720 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
721 ]
722 col_data = []
723 for col_name in table_col_idx.keys():
724 if col_name in cols2ignore:
725 continue
726 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
727 emr_data[age].append("----------------------------------------------------")
728 emr_data[age].append("-- %s from table %s" % (
729 view_row[view_col_idx['modified_string']],
730 src_table
731 ))
732 emr_data[age].append("-- written %s by %s" % (
733 view_row[view_col_idx['modified_when']],
734 view_row[view_col_idx['modified_by']]
735 ))
736 emr_data[age].append("----------------------------------------------------")
737 curs.close()
738 return emr_data
739
741 return self.pk_patient
742
744 union_query = u'\n union all\n'.join ([
745 u"""
746 SELECT ((
747 -- all relevant health issues + active episodes WITH health issue
748 SELECT COUNT(1)
749 FROM clin.v_problem_list
750 WHERE
751 pk_patient = %(pat)s
752 AND
753 pk_health_issue is not null
754 ) + (
755 -- active episodes WITHOUT health issue
756 SELECT COUNT(1)
757 FROM clin.v_problem_list
758 WHERE
759 pk_patient = %(pat)s
760 AND
761 pk_health_issue is null
762 ))""",
763 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
764 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
765 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
766 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
767 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
768 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
769
770 u"""
771 SELECT count(1)
772 from clin.v_substance_intakes
773 WHERE
774 pk_patient = %(pat)s
775 and is_currently_active in (null, true)
776 and intake_is_approved_of in (null, true)""",
777 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
778 ])
779
780 rows, idx = gmPG2.run_ro_queries (
781 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
782 get_col_idx = False
783 )
784
785 stats = dict (
786 problems = rows[0][0],
787 encounters = rows[1][0],
788 items = rows[2][0],
789 documents = rows[3][0],
790 results = rows[4][0],
791 stays = rows[5][0],
792 procedures = rows[6][0],
793 active_drugs = rows[7][0],
794 vaccinations = rows[8][0]
795 )
796
797 return stats
798
811
913
934
935
936
937 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
938 """Retrieves patient allergy items.
939
940 remove_sensitivities
941 - retrieve real allergies only, without sensitivities
942 since
943 - initial date for allergy items
944 until
945 - final date for allergy items
946 encounters
947 - list of encounters whose allergies are to be retrieved
948 episodes
949 - list of episodes whose allergies are to be retrieved
950 issues
951 - list of health issues whose allergies are to be retrieved
952 """
953 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
954 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
955 allergies = []
956 for r in rows:
957 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
958
959
960 filtered_allergies = []
961 filtered_allergies.extend(allergies)
962
963 if ID_list is not None:
964 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
965 if len(filtered_allergies) == 0:
966 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
967
968 return None
969 else:
970 return filtered_allergies
971
972 if remove_sensitivities:
973 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
974 if since is not None:
975 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
976 if until is not None:
977 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
978 if issues is not None:
979 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
980 if episodes is not None:
981 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
982 if encounters is not None:
983 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
984
985 return filtered_allergies
986
987 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
988 if encounter_id is None:
989 encounter_id = self.current_encounter['pk_encounter']
990
991 if episode_id is None:
992 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
993 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
994 episode_id = epi['pk_episode']
995
996 new_allergy = gmAllergy.create_allergy (
997 allergene = allergene,
998 allg_type = allg_type,
999 encounter_id = encounter_id,
1000 episode_id = episode_id
1001 )
1002
1003 return new_allergy
1004
1006 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1007 args = {'pk_allg': pk_allergy}
1008 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1009
1011 """Cave: only use with one potential allergic agent
1012 otherwise you won't know which of the agents the allergy is to."""
1013
1014
1015 if self.allergy_state is None:
1016 return None
1017
1018
1019 if self.allergy_state == 0:
1020 return False
1021
1022 args = {
1023 'atcs': atcs,
1024 'inns': inns,
1025 'brand': brand,
1026 'pat': self.pk_patient
1027 }
1028 allergenes = []
1029 where_parts = []
1030
1031 if len(atcs) == 0:
1032 atcs = None
1033 if atcs is not None:
1034 where_parts.append(u'atc_code in %(atcs)s')
1035 if len(inns) == 0:
1036 inns = None
1037 if inns is not None:
1038 where_parts.append(u'generics in %(inns)s')
1039 allergenes.extend(inns)
1040 if brand is not None:
1041 where_parts.append(u'substance = %(brand)s')
1042 allergenes.append(brand)
1043
1044 if len(allergenes) != 0:
1045 where_parts.append(u'allergene in %(allgs)s')
1046 args['allgs'] = tuple(allergenes)
1047
1048 cmd = u"""
1049 SELECT * FROM clin.v_pat_allergies
1050 WHERE
1051 pk_patient = %%(pat)s
1052 AND ( %s )""" % u' OR '.join(where_parts)
1053
1054 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1055
1056 if len(rows) == 0:
1057 return False
1058
1059 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1060
1070
1073
1074 allergy_state = property(_get_allergy_state, _set_allergy_state)
1075
1076
1077
1078 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1079 """Fetches from backend patient episodes.
1080
1081 id_list - Episodes' PKs list
1082 issues - Health issues' PKs list to filter episodes by
1083 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1084 """
1085 if (unlinked_only is True) and (issues is not None):
1086 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1087
1088 if order_by is None:
1089 order_by = u''
1090 else:
1091 order_by = u'ORDER BY %s' % order_by
1092
1093 args = {
1094 'pat': self.pk_patient,
1095 'open': open_status
1096 }
1097 where_parts = [u'pk_patient = %(pat)s']
1098
1099 if open_status is not None:
1100 where_parts.append(u'episode_open IS %(open)s')
1101
1102 if unlinked_only:
1103 where_parts.append(u'pk_health_issue is NULL')
1104
1105 if issues is not None:
1106 where_parts.append(u'pk_health_issue IN %(issues)s')
1107 args['issues'] = tuple(issues)
1108
1109 if id_list is not None:
1110 where_parts.append(u'pk_episode IN %(epis)s')
1111 args['epis'] = tuple(id_list)
1112
1113 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1114 u' AND '.join(where_parts),
1115 order_by
1116 )
1117 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1118
1119 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1120
1121 episodes = property(get_episodes, lambda x:x)
1122
1124 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1125
1126 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1127
1129 cmd = u"""SELECT distinct pk_episode
1130 from clin.v_pat_items
1131 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1132 args = {
1133 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1134 'pat': self.pk_patient
1135 }
1136 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1137 if len(rows) == 0:
1138 return []
1139 epis = []
1140 for row in rows:
1141 epis.append(row[0])
1142 return self.get_episodes(id_list=epis)
1143
1144 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1145 """Add episode 'episode_name' for a patient's health issue.
1146
1147 - silently returns if episode already exists
1148 """
1149 episode = gmEMRStructItems.create_episode (
1150 pk_health_issue = pk_health_issue,
1151 episode_name = episode_name,
1152 is_open = is_open,
1153 encounter = self.current_encounter['pk_encounter']
1154 )
1155 return episode
1156
1158
1159
1160 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1161
1162 cmd = u"""
1163 SELECT pk
1164 from clin.episode
1165 WHERE pk = (
1166 SELECT distinct on(pk_episode) pk_episode
1167 from clin.v_pat_items
1168 WHERE
1169 pk_patient = %%(pat)s
1170 and
1171 modified_when = (
1172 SELECT max(vpi.modified_when)
1173 from clin.v_pat_items vpi
1174 WHERE vpi.pk_patient = %%(pat)s
1175 )
1176 %s
1177 -- guard against several episodes created at the same moment of time
1178 limit 1
1179 )""" % issue_where
1180 rows, idx = gmPG2.run_ro_queries(queries = [
1181 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1182 ])
1183 if len(rows) != 0:
1184 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1185
1186
1187
1188 cmd = u"""
1189 SELECT vpe0.pk_episode
1190 from
1191 clin.v_pat_episodes vpe0
1192 WHERE
1193 vpe0.pk_patient = %%(pat)s
1194 and
1195 vpe0.episode_modified_when = (
1196 SELECT max(vpe1.episode_modified_when)
1197 from clin.v_pat_episodes vpe1
1198 WHERE vpe1.pk_episode = vpe0.pk_episode
1199 )
1200 %s""" % issue_where
1201 rows, idx = gmPG2.run_ro_queries(queries = [
1202 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1203 ])
1204 if len(rows) != 0:
1205 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1206
1207 return None
1208
1211
1212
1213
1214 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1215 """Retrieve a patient's problems.
1216
1217 "Problems" are the UNION of:
1218
1219 - issues which are .clinically_relevant
1220 - episodes which are .is_open
1221
1222 Therefore, both an issue and the open episode
1223 thereof can each be listed as a problem.
1224
1225 include_closed_episodes/include_irrelevant_issues will
1226 include those -- which departs from the definition of
1227 the problem list being "active" items only ...
1228
1229 episodes - episodes' PKs to filter problems by
1230 issues - health issues' PKs to filter problems by
1231 """
1232
1233
1234 args = {'pat': self.pk_patient}
1235
1236 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1237 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1238
1239
1240 problems = []
1241 for row in rows:
1242 pk_args = {
1243 u'pk_patient': self.pk_patient,
1244 u'pk_health_issue': row['pk_health_issue'],
1245 u'pk_episode': row['pk_episode']
1246 }
1247 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1248
1249
1250 other_rows = []
1251 if include_closed_episodes:
1252 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1253 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1254 other_rows.extend(rows)
1255
1256 if include_irrelevant_issues:
1257 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1258 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1259 other_rows.extend(rows)
1260
1261 if len(other_rows) > 0:
1262 for row in other_rows:
1263 pk_args = {
1264 u'pk_patient': self.pk_patient,
1265 u'pk_health_issue': row['pk_health_issue'],
1266 u'pk_episode': row['pk_episode']
1267 }
1268 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1269
1270
1271 if (episodes is None) and (issues is None):
1272 return problems
1273
1274
1275 if issues is not None:
1276 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1277 if episodes is not None:
1278 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1279
1280 return problems
1281
1284
1287
1290
1291
1292
1294
1295 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1296 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1297 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1298
1299 if id_list is None:
1300 return issues
1301
1302 if len(id_list) == 0:
1303 raise ValueError('id_list to filter by is empty, most likely a programming error')
1304
1305 filtered_issues = []
1306 for issue in issues:
1307 if issue['pk_health_issue'] in id_list:
1308 filtered_issues.append(issue)
1309
1310 return filtered_issues
1311
1312 health_issues = property(get_health_issues, lambda x:x)
1313
1321
1324
1325
1326
1328
1329 where_parts = [u'pk_patient = %(pat)s']
1330 args = {'pat': self.pk_patient}
1331
1332 if not include_inactive:
1333 where_parts.append(u'is_currently_active IN (true, null)')
1334
1335 if not include_unapproved:
1336 where_parts.append(u'intake_is_approved_of IN (true, null)')
1337
1338 if order_by is None:
1339 order_by = u''
1340 else:
1341 order_by = u'ORDER BY %s' % order_by
1342
1343 cmd = u"SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1344 u'\nAND '.join(where_parts),
1345 order_by
1346 )
1347 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1348 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1349
1350 if episodes is not None:
1351 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1352
1353 if issues is not None:
1354 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1355
1356 return meds
1357
1358 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1366
1373
1374
1375
1383
1385 """Returns latest given vaccination for each vaccinated indication.
1386
1387 as a dict {'l10n_indication': cVaccination instance}
1388
1389 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1390 """
1391
1392 args = {'pat': self.pk_patient}
1393 where_parts = [u'pk_patient = %(pat)s']
1394
1395 if (episodes is not None) and (len(episodes) > 0):
1396 where_parts.append(u'pk_episode IN %(epis)s')
1397 args['epis'] = tuple(episodes)
1398
1399 if (issues is not None) and (len(issues) > 0):
1400 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1401 args['issues'] = tuple(issues)
1402
1403 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1404 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1405
1406
1407 if len(rows) == 0:
1408 return {}
1409
1410 vpks = [ ind['pk_vaccination'] for ind in rows ]
1411 vinds = [ ind['l10n_indication'] for ind in rows ]
1412 ind_counts = [ ind['indication_count'] for ind in rows ]
1413
1414
1415 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1416 args = {'pks': tuple(vpks)}
1417 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1418
1419 vaccs = {}
1420 for idx in range(len(vpks)):
1421 pk = vpks[idx]
1422 ind_count = ind_counts[idx]
1423 for r in rows:
1424 if r['pk_vaccination'] == pk:
1425 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1426
1427 return vaccs
1428
1429 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1430
1431 args = {'pat': self.pk_patient}
1432 where_parts = [u'pk_patient = %(pat)s']
1433
1434 if order_by is None:
1435 order_by = u''
1436 else:
1437 order_by = u'ORDER BY %s' % order_by
1438
1439 if (episodes is not None) and (len(episodes) > 0):
1440 where_parts.append(u'pk_episode IN %(epis)s')
1441 args['epis'] = tuple(episodes)
1442
1443 if (issues is not None) and (len(issues) > 0):
1444 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1445 args['issues'] = tuple(issues)
1446
1447 if (encounters is not None) and (len(encounters) > 0):
1448 where_parts.append(u'pk_encounter IN %(encs)s')
1449 args['encs'] = tuple(encounters)
1450
1451 cmd = u'%s %s' % (
1452 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1453 order_by
1454 )
1455 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1456 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1457
1458 return vaccs
1459
1460 vaccinations = property(get_vaccinations, lambda x:x)
1461
1462
1463
1465 """Retrieves vaccination regimes the patient is on.
1466
1467 optional:
1468 * ID - PK of the vaccination regime
1469 * indications - indications we want to retrieve vaccination
1470 regimes for, must be primary language, not l10n_indication
1471 """
1472
1473 try:
1474 self.__db_cache['vaccinations']['scheduled regimes']
1475 except KeyError:
1476
1477 self.__db_cache['vaccinations']['scheduled regimes'] = []
1478 cmd = """SELECT distinct on(pk_course) pk_course
1479 FROM clin.v_vaccs_scheduled4pat
1480 WHERE pk_patient=%s"""
1481 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1482 if rows is None:
1483 _log.error('cannot retrieve scheduled vaccination courses')
1484 del self.__db_cache['vaccinations']['scheduled regimes']
1485 return None
1486
1487 for row in rows:
1488 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1489
1490
1491 filtered_regimes = []
1492 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1493 if ID is not None:
1494 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1495 if len(filtered_regimes) == 0:
1496 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1497 return []
1498 else:
1499 return filtered_regimes[0]
1500 if indications is not None:
1501 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1502
1503 return filtered_regimes
1504
1505
1506
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 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1532 """Retrieves list of vaccinations the patient has received.
1533
1534 optional:
1535 * ID - PK of a vaccination
1536 * indications - indications we want to retrieve vaccination
1537 items for, must be primary language, not l10n_indication
1538 * since - initial date for allergy items
1539 * until - final date for allergy items
1540 * encounters - list of encounters whose allergies are to be retrieved
1541 * episodes - list of episodes whose allergies are to be retrieved
1542 * issues - list of health issues whose allergies are to be retrieved
1543 """
1544 try:
1545 self.__db_cache['vaccinations']['vaccinated']
1546 except KeyError:
1547 self.__db_cache['vaccinations']['vaccinated'] = []
1548
1549 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1550 WHERE pk_patient=%s
1551 order by indication, date"""
1552 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1553 if rows is None:
1554 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1555 del self.__db_cache['vaccinations']['vaccinated']
1556 return None
1557
1558 vaccs_by_ind = {}
1559 for row in rows:
1560 vacc_row = {
1561 'pk_field': 'pk_vaccination',
1562 'idx': idx,
1563 'data': row
1564 }
1565 vacc = gmVaccination.cVaccination(row=vacc_row)
1566 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1567
1568 try:
1569 vaccs_by_ind[vacc['indication']].append(vacc)
1570 except KeyError:
1571 vaccs_by_ind[vacc['indication']] = [vacc]
1572
1573
1574 for ind in vaccs_by_ind.keys():
1575 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1576 for vacc in vaccs_by_ind[ind]:
1577
1578
1579 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1580 vacc['seq_no'] = seq_no
1581
1582
1583 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1584 continue
1585 if seq_no > vacc_regimes[0]['shots']:
1586 vacc['is_booster'] = True
1587 del vaccs_by_ind
1588
1589
1590 filtered_shots = []
1591 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1592 if ID is not None:
1593 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1594 if len(filtered_shots) == 0:
1595 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1596 return None
1597 else:
1598 return filtered_shots[0]
1599 if since is not None:
1600 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1601 if until is not None:
1602 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1603 if issues is not None:
1604 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1605 if episodes is not None:
1606 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1607 if encounters is not None:
1608 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1609 if indications is not None:
1610 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1611 return filtered_shots
1612
1614 """Retrieves vaccinations scheduled for a regime a patient is on.
1615
1616 The regime is referenced by its indication (not l10n)
1617
1618 * indications - List of indications (not l10n) of regimes we want scheduled
1619 vaccinations to be fetched for
1620 """
1621 try:
1622 self.__db_cache['vaccinations']['scheduled']
1623 except KeyError:
1624 self.__db_cache['vaccinations']['scheduled'] = []
1625 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1626 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1627 if rows is None:
1628 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1629 del self.__db_cache['vaccinations']['scheduled']
1630 return None
1631
1632 for row in rows:
1633 vacc_row = {
1634 'pk_field': 'pk_vacc_def',
1635 'idx': idx,
1636 'data': row
1637 }
1638 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1639
1640
1641 if indications is None:
1642 return self.__db_cache['vaccinations']['scheduled']
1643 filtered_shots = []
1644 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1645 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1646 return filtered_shots
1647
1649 try:
1650 self.__db_cache['vaccinations']['missing']
1651 except KeyError:
1652 self.__db_cache['vaccinations']['missing'] = {}
1653
1654 self.__db_cache['vaccinations']['missing']['due'] = []
1655
1656 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1657 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1658 if rows is None:
1659 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1660 return None
1661 pk_args = {'pat_id': self.pk_patient}
1662 if rows is not None:
1663 for row in rows:
1664 pk_args['indication'] = row[0]
1665 pk_args['seq_no'] = row[1]
1666 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1667
1668
1669 self.__db_cache['vaccinations']['missing']['boosters'] = []
1670
1671 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1672 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1673 if rows is None:
1674 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1675 return None
1676 pk_args = {'pat_id': self.pk_patient}
1677 if rows is not None:
1678 for row in rows:
1679 pk_args['indication'] = row[0]
1680 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1681
1682
1683 if indications is None:
1684 return self.__db_cache['vaccinations']['missing']
1685 if len(indications) == 0:
1686 return self.__db_cache['vaccinations']['missing']
1687
1688 filtered_shots = {
1689 'due': [],
1690 'boosters': []
1691 }
1692 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1693 if due_shot['indication'] in indications:
1694 filtered_shots['due'].append(due_shot)
1695 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1696 if due_shot['indication'] in indications:
1697 filtered_shots['boosters'].append(due_shot)
1698 return filtered_shots
1699
1700
1701
1703 return self.__encounter
1704
1706
1707
1708 if self.__encounter is None:
1709 _log.debug('first setting of active encounter in this clinical record instance')
1710 else:
1711 _log.debug('switching of active encounter')
1712
1713 if self.__encounter.is_modified():
1714 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1715 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1716
1717
1718 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1719 now = gmDateTime.pydt_now_here()
1720 if now > encounter['started']:
1721 encounter['last_affirmed'] = now
1722 encounter.save()
1723 self.__encounter = encounter
1724 gmDispatcher.send(u'current_encounter_switched')
1725
1726 return True
1727
1728 current_encounter = property(_get_current_encounter, _set_current_encounter)
1729 active_encounter = property(_get_current_encounter, _set_current_encounter)
1730
1732
1733
1734 if self.__activate_very_recent_encounter():
1735 return True
1736
1737
1738 if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction):
1739 return True
1740
1741
1742 self.start_new_encounter()
1743 return True
1744
1746 """Try to attach to a "very recent" encounter if there is one.
1747
1748 returns:
1749 False: no "very recent" encounter, create new one
1750 True: success
1751 """
1752 cfg_db = gmCfg.cCfgSQL()
1753 min_ttl = cfg_db.get2 (
1754 option = u'encounter.minimum_ttl',
1755 workplace = _here.active_workplace,
1756 bias = u'user',
1757 default = u'1 hour 30 minutes'
1758 )
1759 cmd = u"""
1760 SELECT pk_encounter
1761 FROM clin.v_most_recent_encounters
1762 WHERE
1763 pk_patient = %s
1764 and
1765 last_affirmed > (now() - %s::interval)
1766 ORDER BY
1767 last_affirmed DESC"""
1768 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1769
1770 if len(enc_rows) == 0:
1771 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1772 return False
1773
1774 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1775 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1776 return True
1777
1779 """Try to attach to a "fairly recent" encounter if there is one.
1780
1781 returns:
1782 False: no "fairly recent" encounter, create new one
1783 True: success
1784 """
1785 if _func_ask_user is None:
1786 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1787 return False
1788
1789 if not allow_user_interaction:
1790 _log.exception('user interaction not desired, not looking for fairly recent encounter')
1791 return False
1792
1793 cfg_db = gmCfg.cCfgSQL()
1794 min_ttl = cfg_db.get2 (
1795 option = u'encounter.minimum_ttl',
1796 workplace = _here.active_workplace,
1797 bias = u'user',
1798 default = u'1 hour 30 minutes'
1799 )
1800 max_ttl = cfg_db.get2 (
1801 option = u'encounter.maximum_ttl',
1802 workplace = _here.active_workplace,
1803 bias = u'user',
1804 default = u'6 hours'
1805 )
1806 cmd = u"""
1807 SELECT pk_encounter
1808 FROM clin.v_most_recent_encounters
1809 WHERE
1810 pk_patient=%s
1811 AND
1812 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1813 ORDER BY
1814 last_affirmed DESC"""
1815 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1816
1817 if len(enc_rows) == 0:
1818 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1819 return False
1820
1821 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
1822
1823 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1824
1825 cmd = u"""
1826 SELECT title, firstnames, lastnames, gender, dob
1827 FROM dem.v_basic_person WHERE pk_identity=%s"""
1828 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1829 pat = pats[0]
1830 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1831 gmTools.coalesce(pat[0], u'')[:5],
1832 pat[1][:15],
1833 pat[2][:15],
1834 pat[3],
1835 gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
1836 self.pk_patient
1837 )
1838 msg = _(
1839 '%s\n'
1840 '\n'
1841 "This patient's chart was worked on only recently:\n"
1842 '\n'
1843 ' %s %s - %s (%s)\n'
1844 '\n'
1845 ' Request: %s\n'
1846 ' Outcome: %s\n'
1847 '\n'
1848 'Do you want to continue that consultation\n'
1849 'or do you want to start a new one ?\n'
1850 ) % (
1851 pat_str,
1852 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
1853 gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
1854 encounter['l10n_type'],
1855 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1856 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1857 )
1858 attach = False
1859 try:
1860 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1861 except:
1862 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1863 return False
1864 if not attach:
1865 return False
1866
1867
1868 self.current_encounter = encounter
1869 _log.debug('"fairly recent" encounter re-activated')
1870 return True
1871
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