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, unlinked_only=False):
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 (None) episodes, only open (True) or closed (False) one(s)
1047 """
1048 if (unlinked_only is True) and (issues is not None):
1049 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1050
1051 if order_by is None:
1052 order_by = u''
1053 else:
1054 order_by = u'ORDER BY %s' % order_by
1055
1056 args = {
1057 'pat': self.pk_patient,
1058 'open': open_status
1059 }
1060 where_parts = [u'pk_patient = %(pat)s']
1061
1062 if open_status is not None:
1063 where_parts.append(u'episode_open IS %(open)s')
1064
1065 if unlinked_only:
1066 where_parts.append(u'pk_health_issue is NULL')
1067
1068 if issues is not None:
1069 where_parts.append(u'pk_health_issue IN %(issues)s')
1070 args['issues'] = tuple(issues)
1071
1072 if id_list is not None:
1073 where_parts.append(u'pk_episode IN %(epis)s')
1074 args['epis'] = tuple(id_list)
1075
1076 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1077 u' AND '.join(where_parts),
1078 order_by
1079 )
1080 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1081
1082 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1083
1084 episodes = property(get_episodes, lambda x:x)
1085
1087 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1088
1089 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1090
1092 cmd = u"""SELECT distinct pk_episode
1093 from clin.v_pat_items
1094 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1095 args = {
1096 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1097 'pat': self.pk_patient
1098 }
1099 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1100 if len(rows) == 0:
1101 return []
1102 epis = []
1103 for row in rows:
1104 epis.append(row[0])
1105 return self.get_episodes(id_list=epis)
1106
1107 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1108 """Add episode 'episode_name' for a patient's health issue.
1109
1110 - silently returns if episode already exists
1111 """
1112 episode = gmEMRStructItems.create_episode (
1113 pk_health_issue = pk_health_issue,
1114 episode_name = episode_name,
1115 is_open = is_open,
1116 encounter = self.current_encounter['pk_encounter']
1117 )
1118 return episode
1119
1121
1122
1123 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1124
1125 cmd = u"""
1126 SELECT pk
1127 from clin.episode
1128 WHERE pk = (
1129 SELECT distinct on(pk_episode) pk_episode
1130 from clin.v_pat_items
1131 WHERE
1132 pk_patient = %%(pat)s
1133 and
1134 modified_when = (
1135 SELECT max(vpi.modified_when)
1136 from clin.v_pat_items vpi
1137 WHERE vpi.pk_patient = %%(pat)s
1138 )
1139 %s
1140 -- guard against several episodes created at the same moment of time
1141 limit 1
1142 )""" % issue_where
1143 rows, idx = gmPG2.run_ro_queries(queries = [
1144 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1145 ])
1146 if len(rows) != 0:
1147 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1148
1149
1150
1151 cmd = u"""
1152 SELECT vpe0.pk_episode
1153 from
1154 clin.v_pat_episodes vpe0
1155 WHERE
1156 vpe0.pk_patient = %%(pat)s
1157 and
1158 vpe0.episode_modified_when = (
1159 SELECT max(vpe1.episode_modified_when)
1160 from clin.v_pat_episodes vpe1
1161 WHERE vpe1.pk_episode = vpe0.pk_episode
1162 )
1163 %s""" % issue_where
1164 rows, idx = gmPG2.run_ro_queries(queries = [
1165 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1166 ])
1167 if len(rows) != 0:
1168 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1169
1170 return None
1171
1174
1175
1176
1177 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1178 """Retrieve a patient's problems.
1179
1180 "Problems" are the UNION of:
1181
1182 - issues which are .clinically_relevant
1183 - episodes which are .is_open
1184
1185 Therefore, both an issue and the open episode
1186 thereof can each be listed as a problem.
1187
1188 include_closed_episodes/include_irrelevant_issues will
1189 include those -- which departs from the definition of
1190 the problem list being "active" items only ...
1191
1192 episodes - episodes' PKs to filter problems by
1193 issues - health issues' PKs to filter problems by
1194 """
1195
1196
1197 args = {'pat': self.pk_patient}
1198
1199 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1200 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1201
1202
1203 problems = []
1204 for row in rows:
1205 pk_args = {
1206 u'pk_patient': self.pk_patient,
1207 u'pk_health_issue': row['pk_health_issue'],
1208 u'pk_episode': row['pk_episode']
1209 }
1210 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1211
1212
1213 other_rows = []
1214 if include_closed_episodes:
1215 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1216 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1217 other_rows.extend(rows)
1218
1219 if include_irrelevant_issues:
1220 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1221 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1222 other_rows.extend(rows)
1223
1224 if len(other_rows) > 0:
1225 for row in other_rows:
1226 pk_args = {
1227 u'pk_patient': self.pk_patient,
1228 u'pk_health_issue': row['pk_health_issue'],
1229 u'pk_episode': row['pk_episode']
1230 }
1231 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1232
1233
1234 if (episodes is None) and (issues is None):
1235 return problems
1236
1237
1238 if issues is not None:
1239 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1240 if episodes is not None:
1241 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1242
1243 return problems
1244
1247
1250
1253
1254
1255
1257
1258 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s"
1259 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1260 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1261
1262 if id_list is None:
1263 return issues
1264
1265 if len(id_list) == 0:
1266 raise ValueError('id_list to filter by is empty, most likely a programming error')
1267
1268 filtered_issues = []
1269 for issue in issues:
1270 if issue['pk_health_issue'] in id_list:
1271 filtered_issues.append(issue)
1272
1273 return filtered_issues
1274
1275 health_issues = property(get_health_issues, lambda x:x)
1276
1284
1287
1288
1289
1291
1292 where_parts = [u'pk_patient = %(pat)s']
1293
1294 if not include_inactive:
1295 where_parts.append(u'is_currently_active in (true, null)')
1296
1297 if not include_unapproved:
1298 where_parts.append(u'intake_is_approved_of in (true, null)')
1299
1300 if order_by is None:
1301 order_by = u''
1302 else:
1303 order_by = u'order by %s' % order_by
1304
1305 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1306 u'\nand '.join(where_parts),
1307 order_by
1308 )
1309
1310 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1311
1312 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1313
1314 if episodes is not None:
1315 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1316
1317 if issues is not None:
1318 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1319
1320 return meds
1321
1322 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1330
1337
1338
1339
1347
1349 """Returns latest given vaccination for each vaccinated indication.
1350
1351 as a dict {'l10n_indication': cVaccination instance}
1352
1353 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1354 """
1355
1356 args = {'pat': self.pk_patient}
1357 where_parts = [u'pk_patient = %(pat)s']
1358
1359 if (episodes is not None) and (len(episodes) > 0):
1360 where_parts.append(u'pk_episode IN %(epis)s')
1361 args['epis'] = tuple(episodes)
1362
1363 if (issues is not None) and (len(issues) > 0):
1364 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1365 args['issues'] = tuple(issues)
1366
1367 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1368 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1369
1370
1371 if len(rows) == 0:
1372 return {}
1373
1374 vpks = [ ind['pk_vaccination'] for ind in rows ]
1375 vinds = [ ind['l10n_indication'] for ind in rows ]
1376 ind_counts = [ ind['indication_count'] for ind in rows ]
1377
1378
1379 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1380 args = {'pks': tuple(vpks)}
1381 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1382
1383 vaccs = {}
1384 for idx in range(len(vpks)):
1385 pk = vpks[idx]
1386 ind_count = ind_counts[idx]
1387 for r in rows:
1388 if r['pk_vaccination'] == pk:
1389 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1390
1391 return vaccs
1392
1393 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1394
1395 args = {'pat': self.pk_patient}
1396 where_parts = [u'pk_patient = %(pat)s']
1397
1398 if order_by is None:
1399 order_by = u''
1400 else:
1401 order_by = u'ORDER BY %s' % order_by
1402
1403 if (episodes is not None) and (len(episodes) > 0):
1404 where_parts.append(u'pk_episode IN %(epis)s')
1405 args['epis'] = tuple(episodes)
1406
1407 if (issues is not None) and (len(issues) > 0):
1408 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1409 args['issues'] = tuple(issues)
1410
1411 if (encounters is not None) and (len(encounters) > 0):
1412 where_parts.append(u'pk_encounter IN %(encs)s')
1413 args['encs'] = tuple(encounters)
1414
1415 cmd = u'%s %s' % (
1416 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1417 order_by
1418 )
1419 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1420 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1421
1422 return vaccs
1423
1424 vaccinations = property(get_vaccinations, lambda x:x)
1425
1426
1427
1429 """Retrieves vaccination regimes the patient is on.
1430
1431 optional:
1432 * ID - PK of the vaccination regime
1433 * indications - indications we want to retrieve vaccination
1434 regimes for, must be primary language, not l10n_indication
1435 """
1436
1437 try:
1438 self.__db_cache['vaccinations']['scheduled regimes']
1439 except KeyError:
1440
1441 self.__db_cache['vaccinations']['scheduled regimes'] = []
1442 cmd = """SELECT distinct on(pk_course) pk_course
1443 FROM clin.v_vaccs_scheduled4pat
1444 WHERE pk_patient=%s"""
1445 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1446 if rows is None:
1447 _log.error('cannot retrieve scheduled vaccination courses')
1448 del self.__db_cache['vaccinations']['scheduled regimes']
1449 return None
1450
1451 for row in rows:
1452 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1453
1454
1455 filtered_regimes = []
1456 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1457 if ID is not None:
1458 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1459 if len(filtered_regimes) == 0:
1460 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1461 return []
1462 else:
1463 return filtered_regimes[0]
1464 if indications is not None:
1465 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1466
1467 return filtered_regimes
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1496 """Retrieves list of vaccinations the patient has received.
1497
1498 optional:
1499 * ID - PK of a vaccination
1500 * indications - indications we want to retrieve vaccination
1501 items for, must be primary language, not l10n_indication
1502 * since - initial date for allergy items
1503 * until - final date for allergy items
1504 * encounters - list of encounters whose allergies are to be retrieved
1505 * episodes - list of episodes whose allergies are to be retrieved
1506 * issues - list of health issues whose allergies are to be retrieved
1507 """
1508 try:
1509 self.__db_cache['vaccinations']['vaccinated']
1510 except KeyError:
1511 self.__db_cache['vaccinations']['vaccinated'] = []
1512
1513 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1514 WHERE pk_patient=%s
1515 order by indication, date"""
1516 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1517 if rows is None:
1518 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1519 del self.__db_cache['vaccinations']['vaccinated']
1520 return None
1521
1522 vaccs_by_ind = {}
1523 for row in rows:
1524 vacc_row = {
1525 'pk_field': 'pk_vaccination',
1526 'idx': idx,
1527 'data': row
1528 }
1529 vacc = gmVaccination.cVaccination(row=vacc_row)
1530 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1531
1532 try:
1533 vaccs_by_ind[vacc['indication']].append(vacc)
1534 except KeyError:
1535 vaccs_by_ind[vacc['indication']] = [vacc]
1536
1537
1538 for ind in vaccs_by_ind.keys():
1539 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1540 for vacc in vaccs_by_ind[ind]:
1541
1542
1543 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1544 vacc['seq_no'] = seq_no
1545
1546
1547 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1548 continue
1549 if seq_no > vacc_regimes[0]['shots']:
1550 vacc['is_booster'] = True
1551 del vaccs_by_ind
1552
1553
1554 filtered_shots = []
1555 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1556 if ID is not None:
1557 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1558 if len(filtered_shots) == 0:
1559 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1560 return None
1561 else:
1562 return filtered_shots[0]
1563 if since is not None:
1564 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1565 if until is not None:
1566 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1567 if issues is not None:
1568 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1569 if episodes is not None:
1570 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1571 if encounters is not None:
1572 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1573 if indications is not None:
1574 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1575 return filtered_shots
1576
1578 """Retrieves vaccinations scheduled for a regime a patient is on.
1579
1580 The regime is referenced by its indication (not l10n)
1581
1582 * indications - List of indications (not l10n) of regimes we want scheduled
1583 vaccinations to be fetched for
1584 """
1585 try:
1586 self.__db_cache['vaccinations']['scheduled']
1587 except KeyError:
1588 self.__db_cache['vaccinations']['scheduled'] = []
1589 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1590 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1591 if rows is None:
1592 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1593 del self.__db_cache['vaccinations']['scheduled']
1594 return None
1595
1596 for row in rows:
1597 vacc_row = {
1598 'pk_field': 'pk_vacc_def',
1599 'idx': idx,
1600 'data': row
1601 }
1602 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1603
1604
1605 if indications is None:
1606 return self.__db_cache['vaccinations']['scheduled']
1607 filtered_shots = []
1608 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1609 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1610 return filtered_shots
1611
1613 try:
1614 self.__db_cache['vaccinations']['missing']
1615 except KeyError:
1616 self.__db_cache['vaccinations']['missing'] = {}
1617
1618 self.__db_cache['vaccinations']['missing']['due'] = []
1619
1620 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1621 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1622 if rows is None:
1623 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1624 return None
1625 pk_args = {'pat_id': self.pk_patient}
1626 if rows is not None:
1627 for row in rows:
1628 pk_args['indication'] = row[0]
1629 pk_args['seq_no'] = row[1]
1630 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1631
1632
1633 self.__db_cache['vaccinations']['missing']['boosters'] = []
1634
1635 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1636 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1637 if rows is None:
1638 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1639 return None
1640 pk_args = {'pat_id': self.pk_patient}
1641 if rows is not None:
1642 for row in rows:
1643 pk_args['indication'] = row[0]
1644 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1645
1646
1647 if indications is None:
1648 return self.__db_cache['vaccinations']['missing']
1649 if len(indications) == 0:
1650 return self.__db_cache['vaccinations']['missing']
1651
1652 filtered_shots = {
1653 'due': [],
1654 'boosters': []
1655 }
1656 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1657 if due_shot['indication'] in indications:
1658 filtered_shots['due'].append(due_shot)
1659 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1660 if due_shot['indication'] in indications:
1661 filtered_shots['boosters'].append(due_shot)
1662 return filtered_shots
1663
1664
1665
1667 return self.__encounter
1668
1670
1671
1672 if self.__encounter is None:
1673 _log.debug('first setting of active encounter in this clinical record instance')
1674 else:
1675 _log.debug('switching of active encounter')
1676
1677 if self.__encounter.is_modified():
1678 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1679 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1680
1681
1682 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1683 now = gmDateTime.pydt_now_here()
1684 if now > encounter['started']:
1685 encounter['last_affirmed'] = now
1686 encounter.save()
1687 self.__encounter = encounter
1688 gmDispatcher.send(u'current_encounter_switched')
1689
1690 return True
1691
1692 current_encounter = property(_get_current_encounter, _set_current_encounter)
1693 active_encounter = property(_get_current_encounter, _set_current_encounter)
1694
1696
1697
1698 if self.__activate_very_recent_encounter():
1699 return True
1700
1701
1702 if self.__activate_fairly_recent_encounter():
1703 return True
1704
1705
1706 self.start_new_encounter()
1707 return True
1708
1710 """Try to attach to a "very recent" encounter if there is one.
1711
1712 returns:
1713 False: no "very recent" encounter, create new one
1714 True: success
1715 """
1716 cfg_db = gmCfg.cCfgSQL()
1717 min_ttl = cfg_db.get2 (
1718 option = u'encounter.minimum_ttl',
1719 workplace = _here.active_workplace,
1720 bias = u'user',
1721 default = u'1 hour 30 minutes'
1722 )
1723 cmd = u"""
1724 SELECT pk_encounter
1725 FROM clin.v_most_recent_encounters
1726 WHERE
1727 pk_patient = %s
1728 and
1729 last_affirmed > (now() - %s::interval)
1730 ORDER BY
1731 last_affirmed DESC"""
1732 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1733
1734 if len(enc_rows) == 0:
1735 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1736 return False
1737
1738 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1739 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1740 return True
1741
1743 """Try to attach to a "fairly recent" encounter if there is one.
1744
1745 returns:
1746 False: no "fairly recent" encounter, create new one
1747 True: success
1748 """
1749 if _func_ask_user is None:
1750 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1751 return False
1752
1753 cfg_db = gmCfg.cCfgSQL()
1754 min_ttl = cfg_db.get2 (
1755 option = u'encounter.minimum_ttl',
1756 workplace = _here.active_workplace,
1757 bias = u'user',
1758 default = u'1 hour 30 minutes'
1759 )
1760 max_ttl = cfg_db.get2 (
1761 option = u'encounter.maximum_ttl',
1762 workplace = _here.active_workplace,
1763 bias = u'user',
1764 default = u'6 hours'
1765 )
1766 cmd = u"""
1767 SELECT pk_encounter
1768 FROM clin.v_most_recent_encounters
1769 WHERE
1770 pk_patient=%s
1771 AND
1772 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1773 ORDER BY
1774 last_affirmed DESC"""
1775 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1776
1777 if len(enc_rows) == 0:
1778 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1779 return False
1780 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1781
1782 cmd = u"""
1783 SELECT title, firstnames, lastnames, gender, dob
1784 FROM dem.v_basic_person WHERE pk_identity=%s"""
1785 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1786 pat = pats[0]
1787 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1788 gmTools.coalesce(pat[0], u'')[:5],
1789 pat[1][:15],
1790 pat[2][:15],
1791 pat[3],
1792 pat[4].strftime('%x'),
1793 self.pk_patient
1794 )
1795 enc = gmI18N.get_encoding()
1796 msg = _(
1797 '%s\n'
1798 '\n'
1799 "This patient's chart was worked on only recently:\n"
1800 '\n'
1801 ' %s %s - %s (%s)\n'
1802 '\n'
1803 ' Request: %s\n'
1804 ' Outcome: %s\n'
1805 '\n'
1806 'Do you want to continue that consultation\n'
1807 'or do you want to start a new one ?\n'
1808 ) % (
1809 pat_str,
1810 encounter['started'].strftime('%x').decode(enc),
1811 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1812 encounter['l10n_type'],
1813 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1814 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1815 )
1816 attach = False
1817 try:
1818 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1819 except:
1820 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1821 return False
1822 if not attach:
1823 return False
1824
1825
1826 self.current_encounter = encounter
1827
1828 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1829 return True
1830
1842
1843 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1844 """Retrieves patient's encounters.
1845
1846 id_list - PKs of encounters to fetch
1847 since - initial date for encounter items, DateTime instance
1848 until - final date for encounter items, DateTime instance
1849 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1850 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1851 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
1852
1853 NOTE: if you specify *both* issues and episodes
1854 you will get the *aggregate* of all encounters even
1855 if the episodes all belong to the health issues listed.
1856 IOW, the issues broaden the episode list rather than
1857 the episode list narrowing the episodes-from-issues
1858 list.
1859 Rationale: If it was the other way round it would be
1860 redundant to specify the list of issues at all.
1861 """
1862 where_parts = [u'c_vpe.pk_patient = %(pat)s']
1863 args = {'pat': self.pk_patient}
1864
1865 if skip_empty:
1866 where_parts.append(u"""NOT (
1867 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
1868 AND
1869 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
1870 AND
1871 NOT EXISTS (
1872 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
1873 UNION ALL
1874 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
1875 ))""")
1876
1877 if since is not None:
1878 where_parts.append(u'c_vpe.started >= %(start)s')
1879 args['start'] = since
1880
1881 if until is not None:
1882 where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
1883 args['end'] = since
1884
1885 cmd = u"""
1886 SELECT *
1887 FROM clin.v_pat_encounters c_vpe
1888 WHERE
1889 %s
1890 ORDER BY started
1891 """ % u' AND '.join(where_parts)
1892 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1893 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
1894
1895
1896 filtered_encounters = []
1897 filtered_encounters.extend(encounters)
1898
1899 if id_list is not None:
1900 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1901
1902 if (issues is not None) and (len(issues) > 0):
1903 issues = tuple(issues)
1904
1905
1906 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1907 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1908 epi_ids = map(lambda x:x[0], rows)
1909 if episodes is None:
1910 episodes = []
1911 episodes.extend(epi_ids)
1912
1913 if (episodes is not None) and (len(episodes) > 0):
1914 episodes = tuple(episodes)
1915
1916
1917 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1918 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1919 enc_ids = map(lambda x:x[0], rows)
1920 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1921
1922 return filtered_encounters
1923
1925 """Retrieves first encounter for a particular issue and/or episode.
1926
1927 issue_id - First encounter associated health issue
1928 episode - First encounter associated episode
1929 """
1930
1931 if issue_id is None:
1932 issues = None
1933 else:
1934 issues = [issue_id]
1935
1936 if episode_id is None:
1937 episodes = None
1938 else:
1939 episodes = [episode_id]
1940
1941 encounters = self.get_encounters(issues=issues, episodes=episodes)
1942 if len(encounters) == 0:
1943 return None
1944
1945
1946 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1947 return encounters[0]
1948
1950 args = {'pat': self.pk_patient}
1951 cmd = u"""
1952 SELECT MIN(earliest) FROM (
1953 (
1954 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
1955
1956 ) UNION ALL (
1957
1958 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
1959
1960 ) UNION ALL (
1961
1962 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
1963
1964 ) UNION ALL (
1965
1966 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
1967
1968 ) UNION ALL (
1969
1970 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
1971
1972 ) UNION ALL (
1973
1974 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
1975
1976 ) UNION ALL (
1977
1978 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
1979
1980 )
1981 ) AS candidates"""
1982 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1983 return rows[0][0]
1984
1985 earliest_care_date = property(get_earliest_care_date, lambda x:x)
1986
1988 """Retrieves last encounter for a concrete issue and/or episode
1989
1990 issue_id - Last encounter associated health issue
1991 episode_id - Last encounter associated episode
1992 """
1993
1994
1995 if issue_id is None:
1996 issues = None
1997 else:
1998 issues = [issue_id]
1999
2000 if episode_id is None:
2001 episodes = None
2002 else:
2003 episodes = [episode_id]
2004
2005 encounters = self.get_encounters(issues=issues, episodes=episodes)
2006 if len(encounters) == 0:
2007 return None
2008
2009
2010 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
2011 return encounters[-1]
2012
2013 last_encounter = property(get_last_encounter, lambda x:x)
2014
2016 args = {'pat': self.pk_patient, 'range': cover_period}
2017 where_parts = [u'pk_patient = %(pat)s']
2018 if cover_period is not None:
2019 where_parts.append(u'last_affirmed > now() - %(range)s')
2020
2021 cmd = u"""
2022 SELECT l10n_type, count(1) AS frequency
2023 FROM clin.v_pat_encounters
2024 WHERE
2025 %s
2026 GROUP BY l10n_type
2027 ORDER BY frequency DESC
2028 """ % u' AND '.join(where_parts)
2029 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2030 return rows
2031
2033
2034 args = {'pat': self.pk_patient}
2035
2036 if (issue_id is None) and (episode_id is None):
2037
2038 cmd = u"""
2039 SELECT * FROM clin.v_pat_encounters
2040 WHERE pk_patient = %(pat)s
2041 ORDER BY started DESC
2042 LIMIT 2
2043 """
2044 else:
2045 where_parts = []
2046
2047 if issue_id is not None:
2048 where_parts.append(u'pk_health_issue = %(issue)s')
2049 args['issue'] = issue_id
2050
2051 if episode_id is not None:
2052 where_parts.append(u'pk_episode = %(epi)s')
2053 args['epi'] = episode_id
2054
2055 cmd = u"""
2056 SELECT *
2057 FROM clin.v_pat_encounters
2058 WHERE
2059 pk_patient = %%(pat)s
2060 AND
2061 pk_encounter IN (
2062 SELECT distinct pk_encounter
2063 FROM clin.v_pat_narrative
2064 WHERE
2065 %s
2066 )
2067 ORDER BY started DESC
2068 LIMIT 2
2069 """ % u' AND '.join(where_parts)
2070
2071 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2072
2073 if len(rows) == 0:
2074 return None
2075
2076
2077 if len(rows) == 1:
2078
2079 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2080
2081 return None
2082
2083 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2084
2085
2086 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2087 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2088
2089 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2090
2092 cfg_db = gmCfg.cCfgSQL()
2093 ttl = cfg_db.get2 (
2094 option = u'encounter.ttl_if_empty',
2095 workplace = _here.active_workplace,
2096 bias = u'user',
2097 default = u'1 week'
2098 )
2099
2100
2101 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2102 args = {'pat': self.pk_patient, 'ttl': ttl}
2103 try:
2104 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2105 except:
2106 _log.exception('error deleting empty encounters')
2107
2108 return True
2109
2110
2111
2119
2121 if order_by is None:
2122 order_by = u''
2123 else:
2124 order_by = u'ORDER BY %s' % order_by
2125 cmd = u"""
2126 SELECT * FROM clin.v_test_results
2127 WHERE
2128 pk_patient = %%(pat)s
2129 AND
2130 reviewed IS FALSE
2131 %s""" % order_by
2132 args = {'pat': self.pk_patient}
2133 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2134 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2135
2136
2138 """Retrieve data about test types for which this patient has results."""
2139
2140 cmd = u"""
2141 SELECT * FROM (
2142 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2143 FROM clin.v_test_results
2144 WHERE pk_patient = %(pat)s
2145 ) AS foo
2146 ORDER BY clin_when desc, unified_name
2147 """
2148 args = {'pat': self.pk_patient}
2149 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2150 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
2151
2153 """Retrieve details on tests grouped under unified names for this patient's results."""
2154 cmd = u"""
2155 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
2156 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
2157 from clin.v_test_results
2158 WHERE pk_patient = %(pat)s
2159 )
2160 order by unified_name"""
2161 args = {'pat': self.pk_patient}
2162 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2163 return rows, idx
2164
2166 """Get the dates for which we have results."""
2167 cmd = u"""
2168 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2169 from clin.v_test_results
2170 WHERE pk_patient = %(pat)s
2171 order by cwhen desc"""
2172 args = {'pat': self.pk_patient}
2173 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2174 return rows
2175
2177
2178 cmd = u"""
2179 SELECT *, xmin_test_result FROM clin.v_test_results
2180 WHERE pk_patient = %(pat)s
2181 order by clin_when desc, pk_episode, unified_name"""
2182 args = {'pat': self.pk_patient}
2183 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2184
2185 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2186
2187 if episodes is not None:
2188 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2189
2190 if encounter is not None:
2191 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2192
2193 return tests
2194
2195 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2196
2197 try:
2198 epi = int(episode)
2199 except:
2200 epi = episode['pk_episode']
2201
2202 try:
2203 type = int(type)
2204 except:
2205 type = type['pk_test_type']
2206
2207 if intended_reviewer is None:
2208 intended_reviewer = _me['pk_staff']
2209
2210 tr = gmPathLab.create_test_result (
2211 encounter = self.current_encounter['pk_encounter'],
2212 episode = epi,
2213 type = type,
2214 intended_reviewer = intended_reviewer,
2215 val_num = val_num,
2216 val_alpha = val_alpha,
2217 unit = unit
2218 )
2219
2220 return tr
2221
2241
2242
2243 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2244 """Retrieves lab result clinical items.
2245
2246 limit - maximum number of results to retrieve
2247 since - initial date
2248 until - final date
2249 encounters - list of encounters
2250 episodes - list of episodes
2251 issues - list of health issues
2252 """
2253 try:
2254 return self.__db_cache['lab results']
2255 except KeyError:
2256 pass
2257 self.__db_cache['lab results'] = []
2258 if limit is None:
2259 lim = ''
2260 else:
2261
2262 if since is None and until is None and encounters is None and episodes is None and issues is None:
2263 lim = "limit %s" % limit
2264 else:
2265 lim = ''
2266
2267 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2268 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2269 if rows is None:
2270 return False
2271 for row in rows:
2272 lab_row = {
2273 'pk_field': 'pk_result',
2274 'idx': idx,
2275 'data': row
2276 }
2277 lab_result = gmPathLab.cLabResult(row=lab_row)
2278 self.__db_cache['lab results'].append(lab_result)
2279
2280
2281 filtered_lab_results = []
2282 filtered_lab_results.extend(self.__db_cache['lab results'])
2283 if since is not None:
2284 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2285 if until is not None:
2286 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2287 if issues is not None:
2288 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2289 if episodes is not None:
2290 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2291 if encounters is not None:
2292 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2293 return filtered_lab_results
2294
2299
2300 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2314
2315
2316
2317 if __name__ == "__main__":
2318
2319 if len(sys.argv) == 1:
2320 sys.exit()
2321
2322 if sys.argv[1] != 'test':
2323 sys.exit()
2324
2325 from Gnumed.pycommon import gmLog2
2326
2340
2347
2354
2356 emr = cClinicalRecord(aPKey=12)
2357 rows, idx = emr.get_measurements_by_date()
2358 print "test results:"
2359 for row in rows:
2360 print row
2361
2368
2375
2380
2382 emr = cClinicalRecord(aPKey=12)
2383
2384 probs = emr.get_problems()
2385 print "normal probs (%s):" % len(probs)
2386 for p in probs:
2387 print u'%s (%s)' % (p['problem'], p['type'])
2388
2389 probs = emr.get_problems(include_closed_episodes=True)
2390 print "probs + closed episodes (%s):" % len(probs)
2391 for p in probs:
2392 print u'%s (%s)' % (p['problem'], p['type'])
2393
2394 probs = emr.get_problems(include_irrelevant_issues=True)
2395 print "probs + issues (%s):" % len(probs)
2396 for p in probs:
2397 print u'%s (%s)' % (p['problem'], p['type'])
2398
2399 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2400 print "probs + issues + epis (%s):" % len(probs)
2401 for p in probs:
2402 print u'%s (%s)' % (p['problem'], p['type'])
2403
2405 emr = cClinicalRecord(aPKey=12)
2406 tr = emr.add_test_result (
2407 episode = 1,
2408 intended_reviewer = 1,
2409 type = 1,
2410 val_num = 75,
2411 val_alpha = u'somewhat obese',
2412 unit = u'kg'
2413 )
2414 print tr
2415
2419
2424
2429
2433
2435 emr = cClinicalRecord(aPKey = 12)
2436 for journal_line in emr.get_as_journal():
2437
2438 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2439 print ""
2440
2444
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466 test_episodes()
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522