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