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
245
254
255
256
268
271
277
278
279
280 - def add_notes(self, notes=None, episode=None, encounter=None):
296
311
312 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
313 """Get SOAP notes pertinent to this encounter.
314
315 since
316 - initial date for narrative items
317 until
318 - final date for narrative items
319 encounters
320 - list of encounters whose narrative are to be retrieved
321 episodes
322 - list of episodes whose narrative are to be retrieved
323 issues
324 - list of health issues whose narrative are to be retrieved
325 soap_cats
326 - list of SOAP categories of the narrative to be retrieved
327 """
328 cmd = u"""
329 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
330 from clin.v_pat_narrative cvpn
331 WHERE pk_patient = %s
332 order by date, soap_rank
333 """
334
335
336
337
338 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
339
340 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
341
342 if since is not None:
343 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
344
345 if until is not None:
346 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
347
348 if issues is not None:
349 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
350
351 if episodes is not None:
352 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
353
354 if encounters is not None:
355 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
356
357 if soap_cats is not None:
358 soap_cats = map(lambda c: c.lower(), soap_cats)
359 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
360
361 if providers is not None:
362 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
363
364 return filtered_narrative
365
366 - 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):
367 return gmClinNarrative.get_as_journal (
368 patient = self.pk_patient,
369 since = since,
370 until = until,
371 encounters = encounters,
372 episodes = episodes,
373 issues = issues,
374 soap_cats = soap_cats,
375 providers = providers,
376 order_by = order_by,
377 time_range = time_range
378 )
379
381
382 search_term = search_term.strip()
383 if search_term == '':
384 return []
385
386 cmd = u"""
387 SELECT
388 *,
389 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
390 as episode,
391 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
392 as health_issue,
393 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
394 as encounter_started,
395 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
396 as encounter_ended,
397 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
398 as encounter_type
399 from clin.v_narrative4search vn4s
400 WHERE
401 pk_patient = %(pat)s and
402 vn4s.narrative ~ %(term)s
403 order by
404 encounter_started
405 """
406 rows, idx = gmPG2.run_ro_queries(queries = [
407 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
408 ])
409 return rows
410
412
413
414
415
416
417
418 try:
419 return self.__db_cache['text dump old']
420 except KeyError:
421 pass
422
423 fields = [
424 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
425 'modified_by',
426 'clin_when',
427 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
428 'pk_item',
429 'pk_encounter',
430 'pk_episode',
431 'pk_health_issue',
432 'src_table'
433 ]
434 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ')
435 ro_conn = self._conn_pool.GetConnection('historica')
436 curs = ro_conn.cursor()
437 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
438 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
439 curs.close()
440 return None
441 rows = curs.fetchall()
442 view_col_idx = gmPG2.get_col_indices(curs)
443
444
445 items_by_table = {}
446 for item in rows:
447 src_table = item[view_col_idx['src_table']]
448 pk_item = item[view_col_idx['pk_item']]
449 if not items_by_table.has_key(src_table):
450 items_by_table[src_table] = {}
451 items_by_table[src_table][pk_item] = item
452
453
454 issues = self.get_health_issues()
455 issue_map = {}
456 for issue in issues:
457 issue_map[issue['pk']] = issue['description']
458 episodes = self.get_episodes()
459 episode_map = {}
460 for episode in episodes:
461 episode_map[episode['pk_episode']] = episode['description']
462 emr_data = {}
463
464 for src_table in items_by_table.keys():
465 item_ids = items_by_table[src_table].keys()
466
467
468 if len(item_ids) == 0:
469 _log.info('no items in table [%s] ?!?' % src_table)
470 continue
471 elif len(item_ids) == 1:
472 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
473 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
474 _log.error('cannot load items from table [%s]' % src_table)
475
476 continue
477 elif len(item_ids) > 1:
478 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
479 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
480 _log.error('cannot load items from table [%s]' % src_table)
481
482 continue
483 rows = curs.fetchall()
484 table_col_idx = gmPG.get_col_indices(curs)
485
486 for row in rows:
487
488 pk_item = row[table_col_idx['pk_item']]
489 view_row = items_by_table[src_table][pk_item]
490 age = view_row[view_col_idx['age']]
491
492 try:
493 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
494 except:
495 episode_name = view_row[view_col_idx['pk_episode']]
496 try:
497 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
498 except:
499 issue_name = view_row[view_col_idx['pk_health_issue']]
500
501 if not emr_data.has_key(age):
502 emr_data[age] = []
503
504 emr_data[age].append(
505 _('%s: encounter (%s)') % (
506 view_row[view_col_idx['clin_when']],
507 view_row[view_col_idx['pk_encounter']]
508 )
509 )
510 emr_data[age].append(_('health issue: %s') % issue_name)
511 emr_data[age].append(_('episode : %s') % episode_name)
512
513
514
515 cols2ignore = [
516 'pk_audit', 'row_version', 'modified_when', 'modified_by',
517 'pk_item', 'id', 'fk_encounter', 'fk_episode'
518 ]
519 col_data = []
520 for col_name in table_col_idx.keys():
521 if col_name in cols2ignore:
522 continue
523 emr_data[age].append("=> %s:" % col_name)
524 emr_data[age].append(row[table_col_idx[col_name]])
525 emr_data[age].append("----------------------------------------------------")
526 emr_data[age].append("-- %s from table %s" % (
527 view_row[view_col_idx['modified_string']],
528 src_table
529 ))
530 emr_data[age].append("-- written %s by %s" % (
531 view_row[view_col_idx['modified_when']],
532 view_row[view_col_idx['modified_by']]
533 ))
534 emr_data[age].append("----------------------------------------------------")
535 curs.close()
536 self._conn_pool.ReleaseConnection('historica')
537 return emr_data
538
539 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
540
541
542
543
544
545
546 try:
547 return self.__db_cache['text dump']
548 except KeyError:
549 pass
550
551
552 fields = [
553 'age',
554 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
555 'modified_by',
556 'clin_when',
557 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
558 'pk_item',
559 'pk_encounter',
560 'pk_episode',
561 'pk_health_issue',
562 'src_table'
563 ]
564 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
565
566 where_snippets = []
567 params = {}
568 where_snippets.append('pk_patient=%(pat_id)s')
569 params['pat_id'] = self.pk_patient
570 if not since is None:
571 where_snippets.append('clin_when >= %(since)s')
572 params['since'] = since
573 if not until is None:
574 where_snippets.append('clin_when <= %(until)s')
575 params['until'] = until
576
577
578
579 if not encounters is None and len(encounters) > 0:
580 params['enc'] = encounters
581 if len(encounters) > 1:
582 where_snippets.append('fk_encounter in %(enc)s')
583 else:
584 where_snippets.append('fk_encounter=%(enc)s')
585
586 if not episodes is None and len(episodes) > 0:
587 params['epi'] = episodes
588 if len(episodes) > 1:
589 where_snippets.append('fk_episode in %(epi)s')
590 else:
591 where_snippets.append('fk_episode=%(epi)s')
592
593 if not issues is None and len(issues) > 0:
594 params['issue'] = issues
595 if len(issues) > 1:
596 where_snippets.append('fk_health_issue in %(issue)s')
597 else:
598 where_snippets.append('fk_health_issue=%(issue)s')
599
600 where_clause = ' and '.join(where_snippets)
601 order_by = 'order by src_table, age'
602 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
603
604 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
605 if rows is None:
606 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
607 return None
608
609
610
611
612 items_by_table = {}
613 for item in rows:
614 src_table = item[view_col_idx['src_table']]
615 pk_item = item[view_col_idx['pk_item']]
616 if not items_by_table.has_key(src_table):
617 items_by_table[src_table] = {}
618 items_by_table[src_table][pk_item] = item
619
620
621 issues = self.get_health_issues()
622 issue_map = {}
623 for issue in issues:
624 issue_map[issue['pk_health_issue']] = issue['description']
625 episodes = self.get_episodes()
626 episode_map = {}
627 for episode in episodes:
628 episode_map[episode['pk_episode']] = episode['description']
629 emr_data = {}
630
631 ro_conn = self._conn_pool.GetConnection('historica')
632 curs = ro_conn.cursor()
633 for src_table in items_by_table.keys():
634 item_ids = items_by_table[src_table].keys()
635
636
637 if len(item_ids) == 0:
638 _log.info('no items in table [%s] ?!?' % src_table)
639 continue
640 elif len(item_ids) == 1:
641 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
642 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
643 _log.error('cannot load items from table [%s]' % src_table)
644
645 continue
646 elif len(item_ids) > 1:
647 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
648 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
649 _log.error('cannot load items from table [%s]' % src_table)
650
651 continue
652 rows = curs.fetchall()
653 table_col_idx = gmPG.get_col_indices(curs)
654
655 for row in rows:
656
657 pk_item = row[table_col_idx['pk_item']]
658 view_row = items_by_table[src_table][pk_item]
659 age = view_row[view_col_idx['age']]
660
661 try:
662 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
663 except:
664 episode_name = view_row[view_col_idx['pk_episode']]
665 try:
666 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
667 except:
668 issue_name = view_row[view_col_idx['pk_health_issue']]
669
670 if not emr_data.has_key(age):
671 emr_data[age] = []
672
673 emr_data[age].append(
674 _('%s: encounter (%s)') % (
675 view_row[view_col_idx['clin_when']],
676 view_row[view_col_idx['pk_encounter']]
677 )
678 )
679 emr_data[age].append(_('health issue: %s') % issue_name)
680 emr_data[age].append(_('episode : %s') % episode_name)
681
682
683
684 cols2ignore = [
685 'pk_audit', 'row_version', 'modified_when', 'modified_by',
686 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
687 ]
688 col_data = []
689 for col_name in table_col_idx.keys():
690 if col_name in cols2ignore:
691 continue
692 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
693 emr_data[age].append("----------------------------------------------------")
694 emr_data[age].append("-- %s from table %s" % (
695 view_row[view_col_idx['modified_string']],
696 src_table
697 ))
698 emr_data[age].append("-- written %s by %s" % (
699 view_row[view_col_idx['modified_when']],
700 view_row[view_col_idx['modified_by']]
701 ))
702 emr_data[age].append("----------------------------------------------------")
703 curs.close()
704 return emr_data
705
707 return self.pk_patient
708
710 union_query = u'\n union all\n'.join ([
711 u"""
712 SELECT ((
713 -- all relevant health issues + active episodes WITH health issue
714 SELECT COUNT(1)
715 FROM clin.v_problem_list
716 WHERE
717 pk_patient = %(pat)s
718 AND
719 pk_health_issue is not null
720 ) + (
721 -- active episodes WITHOUT health issue
722 SELECT COUNT(1)
723 FROM clin.v_problem_list
724 WHERE
725 pk_patient = %(pat)s
726 AND
727 pk_health_issue is null
728 ))""",
729 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
730 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
731 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
732 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
733 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
734 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
735
736 u"""
737 SELECT count(1)
738 from clin.v_pat_substance_intake
739 WHERE
740 pk_patient = %(pat)s
741 and is_currently_active in (null, true)
742 and intake_is_approved_of in (null, true)""",
743 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
744 ])
745
746 rows, idx = gmPG2.run_ro_queries (
747 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
748 get_col_idx = False
749 )
750
751 stats = dict (
752 problems = rows[0][0],
753 encounters = rows[1][0],
754 items = rows[2][0],
755 documents = rows[3][0],
756 results = rows[4][0],
757 stays = rows[5][0],
758 procedures = rows[6][0],
759 active_drugs = rows[7][0],
760 vaccinations = rows[8][0]
761 )
762
763 return stats
764
777
877
878
879
880 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
881 """Retrieves patient allergy items.
882
883 remove_sensitivities
884 - retrieve real allergies only, without sensitivities
885 since
886 - initial date for allergy items
887 until
888 - final date for allergy items
889 encounters
890 - list of encounters whose allergies are to be retrieved
891 episodes
892 - list of episodes whose allergies are to be retrieved
893 issues
894 - list of health issues whose allergies are to be retrieved
895 """
896 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
897 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
898 allergies = []
899 for r in rows:
900 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
901
902
903 filtered_allergies = []
904 filtered_allergies.extend(allergies)
905
906 if ID_list is not None:
907 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
908 if len(filtered_allergies) == 0:
909 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
910
911 return None
912 else:
913 return filtered_allergies
914
915 if remove_sensitivities:
916 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
917 if since is not None:
918 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
919 if until is not None:
920 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
921 if issues is not None:
922 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
923 if episodes is not None:
924 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
925 if encounters is not None:
926 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
927
928 return filtered_allergies
929
930 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
931 if encounter_id is None:
932 encounter_id = self.current_encounter['pk_encounter']
933
934 if episode_id is None:
935 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
936 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
937 episode_id = epi['pk_episode']
938
939 new_allergy = gmAllergy.create_allergy (
940 allergene = allergene,
941 allg_type = allg_type,
942 encounter_id = encounter_id,
943 episode_id = episode_id
944 )
945
946 return new_allergy
947
949 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
950 args = {'pk_allg': pk_allergy}
951 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
952
954 """Cave: only use with one potential allergic agent
955 otherwise you won't know which of the agents the allergy is to."""
956
957
958 if self.allergy_state is None:
959 return None
960
961
962 if self.allergy_state == 0:
963 return False
964
965 args = {
966 'atcs': atcs,
967 'inns': inns,
968 'brand': brand,
969 'pat': self.pk_patient
970 }
971 allergenes = []
972 where_parts = []
973
974 if len(atcs) == 0:
975 atcs = None
976 if atcs is not None:
977 where_parts.append(u'atc_code in %(atcs)s')
978 if len(inns) == 0:
979 inns = None
980 if inns is not None:
981 where_parts.append(u'generics in %(inns)s')
982 allergenes.extend(inns)
983 if brand is not None:
984 where_parts.append(u'substance = %(brand)s')
985 allergenes.append(brand)
986
987 if len(allergenes) != 0:
988 where_parts.append(u'allergene in %(allgs)s')
989 args['allgs'] = tuple(allergenes)
990
991 cmd = u"""
992 SELECT * FROM clin.v_pat_allergies
993 WHERE
994 pk_patient = %%(pat)s
995 AND ( %s )""" % u' OR '.join(where_parts)
996
997 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
998
999 if len(rows) == 0:
1000 return False
1001
1002 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1003
1013
1016
1017 allergy_state = property(_get_allergy_state, _set_allergy_state)
1018
1019
1020
1021 - def get_episodes(self, id_list=None, issues=None, open_status=None):
1022 """Fetches from backend patient episodes.
1023
1024 id_list - Episodes' PKs list
1025 issues - Health issues' PKs list to filter episodes by
1026 open_status - return all episodes, only open or closed one(s)
1027 """
1028 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
1029 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1030 tmp = []
1031 for r in rows:
1032 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
1033
1034
1035 if (id_list is None) and (issues is None) and (open_status is None):
1036 return tmp
1037
1038
1039 filtered_episodes = []
1040 filtered_episodes.extend(tmp)
1041 if open_status is not None:
1042 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
1043
1044 if issues is not None:
1045 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
1046
1047 if id_list is not None:
1048 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
1049
1050 return filtered_episodes
1051
1053 cmd = u"""SELECT distinct pk_episode
1054 from clin.v_pat_items
1055 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1056 args = {
1057 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1058 'pat': self.pk_patient
1059 }
1060 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1061 if len(rows) == 0:
1062 return []
1063 epis = []
1064 for row in rows:
1065 epis.append(row[0])
1066 return self.get_episodes(id_list=epis)
1067
1068 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1069 """Add episode 'episode_name' for a patient's health issue.
1070
1071 - silently returns if episode already exists
1072 """
1073 episode = gmEMRStructItems.create_episode (
1074 pk_health_issue = pk_health_issue,
1075 episode_name = episode_name,
1076 is_open = is_open,
1077 encounter = self.current_encounter['pk_encounter']
1078 )
1079 return episode
1080
1082
1083
1084 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1085
1086 cmd = u"""
1087 SELECT pk
1088 from clin.episode
1089 WHERE pk = (
1090 SELECT distinct on(pk_episode) pk_episode
1091 from clin.v_pat_items
1092 WHERE
1093 pk_patient = %%(pat)s
1094 and
1095 modified_when = (
1096 SELECT max(vpi.modified_when)
1097 from clin.v_pat_items vpi
1098 WHERE vpi.pk_patient = %%(pat)s
1099 )
1100 %s
1101 -- guard against several episodes created at the same moment of time
1102 limit 1
1103 )""" % issue_where
1104 rows, idx = gmPG2.run_ro_queries(queries = [
1105 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1106 ])
1107 if len(rows) != 0:
1108 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1109
1110
1111
1112 cmd = u"""
1113 SELECT vpe0.pk_episode
1114 from
1115 clin.v_pat_episodes vpe0
1116 WHERE
1117 vpe0.pk_patient = %%(pat)s
1118 and
1119 vpe0.episode_modified_when = (
1120 SELECT max(vpe1.episode_modified_when)
1121 from clin.v_pat_episodes vpe1
1122 WHERE vpe1.pk_episode = vpe0.pk_episode
1123 )
1124 %s""" % issue_where
1125 rows, idx = gmPG2.run_ro_queries(queries = [
1126 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1127 ])
1128 if len(rows) != 0:
1129 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1130
1131 return None
1132
1135
1136
1137
1138 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1139 """Retrieve a patient's problems.
1140
1141 "Problems" are the UNION of:
1142
1143 - issues which are .clinically_relevant
1144 - episodes which are .is_open
1145
1146 Therefore, both an issue and the open episode
1147 thereof can each be listed as a problem.
1148
1149 include_closed_episodes/include_irrelevant_issues will
1150 include those -- which departs from the definition of
1151 the problem list being "active" items only ...
1152
1153 episodes - episodes' PKs to filter problems by
1154 issues - health issues' PKs to filter problems by
1155 """
1156
1157
1158 args = {'pat': self.pk_patient}
1159
1160 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
1161 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1162
1163
1164 problems = []
1165 for row in rows:
1166 pk_args = {
1167 u'pk_patient': self.pk_patient,
1168 u'pk_health_issue': row['pk_health_issue'],
1169 u'pk_episode': row['pk_episode']
1170 }
1171 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1172
1173
1174 other_rows = []
1175 if include_closed_episodes:
1176 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1177 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1178 other_rows.extend(rows)
1179
1180 if include_irrelevant_issues:
1181 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1182 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1183 other_rows.extend(rows)
1184
1185 if len(other_rows) > 0:
1186 for row in other_rows:
1187 pk_args = {
1188 u'pk_patient': self.pk_patient,
1189 u'pk_health_issue': row['pk_health_issue'],
1190 u'pk_episode': row['pk_episode']
1191 }
1192 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1193
1194
1195 if (episodes is None) and (issues is None):
1196 return problems
1197
1198
1199 if issues is not None:
1200 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1201 if episodes is not None:
1202 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1203
1204 return problems
1205
1208
1211
1214
1215
1216
1218
1219 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1220 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1221 issues = []
1222 for row in rows:
1223 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1224 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1225
1226 if id_list is None:
1227 return issues
1228
1229 if len(id_list) == 0:
1230 raise ValueError('id_list to filter by is empty, most likely a programming error')
1231
1232 filtered_issues = []
1233 for issue in issues:
1234 if issue['pk_health_issue'] in id_list:
1235 filtered_issues.append(issue)
1236
1237 return filtered_issues
1238
1246
1249
1250
1251
1253
1254 where_parts = [u'pk_patient = %(pat)s']
1255
1256 if not include_inactive:
1257 where_parts.append(u'is_currently_active in (true, null)')
1258
1259 if not include_unapproved:
1260 where_parts.append(u'intake_is_approved_of in (true, null)')
1261
1262 if order_by is None:
1263 order_by = u''
1264 else:
1265 order_by = u'order by %s' % order_by
1266
1267 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1268 u'\nand '.join(where_parts),
1269 order_by
1270 )
1271
1272 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1273
1274 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1275
1276 if episodes is not None:
1277 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1278
1279 if issues is not None:
1280 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1281
1282 return meds
1283
1284 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1292
1298
1299
1300
1308
1310 """Returns latest given vaccination for each vaccinated indication.
1311
1312 as a dict {'l10n_indication': cVaccination instance}
1313
1314 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1315 """
1316
1317 args = {'pat': self.pk_patient}
1318 where_parts = [u'pk_patient = %(pat)s']
1319
1320 if (episodes is not None) and (len(episodes) > 0):
1321 where_parts.append(u'pk_episode IN %(epis)s')
1322 args['epis'] = tuple(episodes)
1323
1324 if (issues is not None) and (len(issues) > 0):
1325 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1326 args['issues'] = tuple(issues)
1327
1328 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1329 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1330
1331
1332 if len(rows) == 0:
1333 return {}
1334
1335 vpks = [ ind['pk_vaccination'] for ind in rows ]
1336 vinds = [ ind['l10n_indication'] for ind in rows ]
1337 ind_counts = [ ind['indication_count'] for ind in rows ]
1338
1339
1340 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1341 args = {'pks': tuple(vpks)}
1342 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1343
1344 vaccs = {}
1345 for idx in range(len(vpks)):
1346 pk = vpks[idx]
1347 ind_count = ind_counts[idx]
1348 for r in rows:
1349 if r['pk_vaccination'] == pk:
1350 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1351
1352 return vaccs
1353
1354 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1355
1356 args = {'pat': self.pk_patient}
1357 where_parts = [u'pk_patient = %(pat)s']
1358
1359 if order_by is None:
1360 order_by = u''
1361 else:
1362 order_by = u'ORDER BY %s' % order_by
1363
1364 if (episodes is not None) and (len(episodes) > 0):
1365 where_parts.append(u'pk_episode IN %(epis)s')
1366 args['epis'] = tuple(episodes)
1367
1368 if (issues is not None) and (len(issues) > 0):
1369 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1370 args['issues'] = tuple(issues)
1371
1372 if (encounters is not None) and (len(encounters) > 0):
1373 where_parts.append(u'pk_encounter IN %(encs)s')
1374 args['encs'] = tuple(encounters)
1375
1376 cmd = u'%s %s' % (
1377 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1378 order_by
1379 )
1380 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1381 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1382
1383 return vaccs
1384
1385
1386
1388 """Retrieves vaccination regimes the patient is on.
1389
1390 optional:
1391 * ID - PK of the vaccination regime
1392 * indications - indications we want to retrieve vaccination
1393 regimes for, must be primary language, not l10n_indication
1394 """
1395
1396 try:
1397 self.__db_cache['vaccinations']['scheduled regimes']
1398 except KeyError:
1399
1400 self.__db_cache['vaccinations']['scheduled regimes'] = []
1401 cmd = """SELECT distinct on(pk_course) pk_course
1402 FROM clin.v_vaccs_scheduled4pat
1403 WHERE pk_patient=%s"""
1404 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1405 if rows is None:
1406 _log.error('cannot retrieve scheduled vaccination courses')
1407 del self.__db_cache['vaccinations']['scheduled regimes']
1408 return None
1409
1410 for row in rows:
1411 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1412
1413
1414 filtered_regimes = []
1415 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1416 if ID is not None:
1417 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1418 if len(filtered_regimes) == 0:
1419 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1420 return []
1421 else:
1422 return filtered_regimes[0]
1423 if indications is not None:
1424 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1425
1426 return filtered_regimes
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1455 """Retrieves list of vaccinations the patient has received.
1456
1457 optional:
1458 * ID - PK of a vaccination
1459 * indications - indications we want to retrieve vaccination
1460 items for, must be primary language, not l10n_indication
1461 * since - initial date for allergy items
1462 * until - final date for allergy items
1463 * encounters - list of encounters whose allergies are to be retrieved
1464 * episodes - list of episodes whose allergies are to be retrieved
1465 * issues - list of health issues whose allergies are to be retrieved
1466 """
1467 try:
1468 self.__db_cache['vaccinations']['vaccinated']
1469 except KeyError:
1470 self.__db_cache['vaccinations']['vaccinated'] = []
1471
1472 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1473 WHERE pk_patient=%s
1474 order by indication, date"""
1475 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1476 if rows is None:
1477 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1478 del self.__db_cache['vaccinations']['vaccinated']
1479 return None
1480
1481 vaccs_by_ind = {}
1482 for row in rows:
1483 vacc_row = {
1484 'pk_field': 'pk_vaccination',
1485 'idx': idx,
1486 'data': row
1487 }
1488 vacc = gmVaccination.cVaccination(row=vacc_row)
1489 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1490
1491 try:
1492 vaccs_by_ind[vacc['indication']].append(vacc)
1493 except KeyError:
1494 vaccs_by_ind[vacc['indication']] = [vacc]
1495
1496
1497 for ind in vaccs_by_ind.keys():
1498 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1499 for vacc in vaccs_by_ind[ind]:
1500
1501
1502 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1503 vacc['seq_no'] = seq_no
1504
1505
1506 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1507 continue
1508 if seq_no > vacc_regimes[0]['shots']:
1509 vacc['is_booster'] = True
1510 del vaccs_by_ind
1511
1512
1513 filtered_shots = []
1514 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1515 if ID is not None:
1516 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1517 if len(filtered_shots) == 0:
1518 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1519 return None
1520 else:
1521 return filtered_shots[0]
1522 if since is not None:
1523 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1524 if until is not None:
1525 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1526 if issues is not None:
1527 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1528 if episodes is not None:
1529 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1530 if encounters is not None:
1531 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1532 if indications is not None:
1533 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1534 return filtered_shots
1535
1537 """Retrieves vaccinations scheduled for a regime a patient is on.
1538
1539 The regime is referenced by its indication (not l10n)
1540
1541 * indications - List of indications (not l10n) of regimes we want scheduled
1542 vaccinations to be fetched for
1543 """
1544 try:
1545 self.__db_cache['vaccinations']['scheduled']
1546 except KeyError:
1547 self.__db_cache['vaccinations']['scheduled'] = []
1548 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1549 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1550 if rows is None:
1551 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1552 del self.__db_cache['vaccinations']['scheduled']
1553 return None
1554
1555 for row in rows:
1556 vacc_row = {
1557 'pk_field': 'pk_vacc_def',
1558 'idx': idx,
1559 'data': row
1560 }
1561 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1562
1563
1564 if indications is None:
1565 return self.__db_cache['vaccinations']['scheduled']
1566 filtered_shots = []
1567 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1568 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1569 return filtered_shots
1570
1572 try:
1573 self.__db_cache['vaccinations']['missing']
1574 except KeyError:
1575 self.__db_cache['vaccinations']['missing'] = {}
1576
1577 self.__db_cache['vaccinations']['missing']['due'] = []
1578
1579 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1580 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1581 if rows is None:
1582 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1583 return None
1584 pk_args = {'pat_id': self.pk_patient}
1585 if rows is not None:
1586 for row in rows:
1587 pk_args['indication'] = row[0]
1588 pk_args['seq_no'] = row[1]
1589 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1590
1591
1592 self.__db_cache['vaccinations']['missing']['boosters'] = []
1593
1594 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1595 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1596 if rows is None:
1597 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1598 return None
1599 pk_args = {'pat_id': self.pk_patient}
1600 if rows is not None:
1601 for row in rows:
1602 pk_args['indication'] = row[0]
1603 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1604
1605
1606 if indications is None:
1607 return self.__db_cache['vaccinations']['missing']
1608 if len(indications) == 0:
1609 return self.__db_cache['vaccinations']['missing']
1610
1611 filtered_shots = {
1612 'due': [],
1613 'boosters': []
1614 }
1615 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1616 if due_shot['indication'] in indications:
1617 filtered_shots['due'].append(due_shot)
1618 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1619 if due_shot['indication'] in indications:
1620 filtered_shots['boosters'].append(due_shot)
1621 return filtered_shots
1622
1623
1624
1626 return self.__encounter
1627
1629
1630
1631 if self.__encounter is None:
1632 _log.debug('first setting of active encounter in this clinical record instance')
1633 else:
1634 _log.debug('switching of active encounter')
1635
1636 if self.__encounter.is_modified():
1637 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1638 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1639
1640
1641 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1642 encounter['last_affirmed'] = gmDateTime.pydt_now_here()
1643 encounter.save()
1644 self.__encounter = encounter
1645 gmDispatcher.send(u'current_encounter_switched')
1646
1647 return True
1648
1649 current_encounter = property(_get_current_encounter, _set_current_encounter)
1650 active_encounter = property(_get_current_encounter, _set_current_encounter)
1651
1653
1654
1655 if self.__activate_very_recent_encounter():
1656 return True
1657
1658
1659 if self.__activate_fairly_recent_encounter():
1660 return True
1661
1662
1663 self.start_new_encounter()
1664 return True
1665
1667 """Try to attach to a "very recent" encounter if there is one.
1668
1669 returns:
1670 False: no "very recent" encounter, create new one
1671 True: success
1672 """
1673 cfg_db = gmCfg.cCfgSQL()
1674 min_ttl = cfg_db.get2 (
1675 option = u'encounter.minimum_ttl',
1676 workplace = _here.active_workplace,
1677 bias = u'user',
1678 default = u'1 hour 30 minutes'
1679 )
1680 cmd = u"""
1681 SELECT pk_encounter
1682 FROM clin.v_most_recent_encounters
1683 WHERE
1684 pk_patient = %s
1685 and
1686 last_affirmed > (now() - %s::interval)"""
1687 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1688
1689 if len(enc_rows) == 0:
1690 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1691 return False
1692
1693 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1694 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1695 return True
1696
1698 """Try to attach to a "fairly recent" encounter if there is one.
1699
1700 returns:
1701 False: no "fairly recent" encounter, create new one
1702 True: success
1703 """
1704 if _func_ask_user is None:
1705 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1706 return False
1707
1708 cfg_db = gmCfg.cCfgSQL()
1709 min_ttl = cfg_db.get2 (
1710 option = u'encounter.minimum_ttl',
1711 workplace = _here.active_workplace,
1712 bias = u'user',
1713 default = u'1 hour 30 minutes'
1714 )
1715 max_ttl = cfg_db.get2 (
1716 option = u'encounter.maximum_ttl',
1717 workplace = _here.active_workplace,
1718 bias = u'user',
1719 default = u'6 hours'
1720 )
1721 cmd = u"""
1722 SELECT pk_encounter
1723 FROM clin.v_most_recent_encounters
1724 WHERE
1725 pk_patient=%s
1726 AND
1727 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1728 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1729
1730 if len(enc_rows) == 0:
1731 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1732 return False
1733 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1734
1735 cmd = u"""
1736 SELECT title, firstnames, lastnames, gender, dob
1737 FROM dem.v_basic_person WHERE pk_identity=%s"""
1738 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1739 pat = pats[0]
1740 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1741 gmTools.coalesce(pat[0], u'')[:5],
1742 pat[1][:15],
1743 pat[2][:15],
1744 pat[3],
1745 pat[4].strftime('%x'),
1746 self.pk_patient
1747 )
1748 enc = gmI18N.get_encoding()
1749 msg = _(
1750 '%s\n'
1751 '\n'
1752 "This patient's chart was worked on only recently:\n"
1753 '\n'
1754 ' %s %s - %s (%s)\n'
1755 '\n'
1756 ' Request: %s\n'
1757 ' Outcome: %s\n'
1758 '\n'
1759 'Do you want to continue that consultation\n'
1760 'or do you want to start a new one ?\n'
1761 ) % (
1762 pat_str,
1763 encounter['started'].strftime('%x').decode(enc),
1764 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1765 encounter['l10n_type'],
1766 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1767 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1768 )
1769 attach = False
1770 try:
1771 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1772 except:
1773 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1774 return False
1775 if not attach:
1776 return False
1777
1778
1779 self.current_encounter = encounter
1780
1781 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1782 return True
1783
1795
1796 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None):
1797 """Retrieves patient's encounters.
1798
1799 id_list - PKs of encounters to fetch
1800 since - initial date for encounter items, DateTime instance
1801 until - final date for encounter items, DateTime instance
1802 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1803 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1804
1805 NOTE: if you specify *both* issues and episodes
1806 you will get the *aggregate* of all encounters even
1807 if the episodes all belong to the health issues listed.
1808 IOW, the issues broaden the episode list rather than
1809 the episode list narrowing the episodes-from-issues
1810 list.
1811 Rationale: If it was the other way round it would be
1812 redundant to specify the list of issues at all.
1813 """
1814
1815 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1816 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1817 encounters = []
1818 for r in rows:
1819 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1820
1821
1822 filtered_encounters = []
1823 filtered_encounters.extend(encounters)
1824 if id_list is not None:
1825 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1826 if since is not None:
1827 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1828 if until is not None:
1829 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1830
1831 if (issues is not None) and (len(issues) > 0):
1832
1833 issues = tuple(issues)
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1852 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1853 epi_ids = map(lambda x:x[0], rows)
1854 if episodes is None:
1855 episodes = []
1856 episodes.extend(epi_ids)
1857
1858 if (episodes is not None) and (len(episodes) > 0):
1859
1860 episodes = tuple(episodes)
1861
1862
1863
1864 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1865 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1866 enc_ids = map(lambda x:x[0], rows)
1867 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1868
1869 return filtered_encounters
1870
1872 """Retrieves first encounter for a particular issue and/or episode
1873
1874 issue_id - First encounter associated health issue
1875 episode - First encounter associated episode
1876 """
1877
1878
1879 if issue_id is None:
1880 issues = None
1881 else:
1882 issues = [issue_id]
1883
1884 if episode_id is None:
1885 episodes = None
1886 else:
1887 episodes = [episode_id]
1888
1889 encounters = self.get_encounters(issues=issues, episodes=episodes)
1890 if len(encounters) == 0:
1891 return None
1892
1893
1894 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1895 return encounters[0]
1896
1898 """Retrieves last encounter for a concrete issue and/or episode
1899
1900 issue_id - Last encounter associated health issue
1901 episode_id - Last encounter associated episode
1902 """
1903
1904
1905 if issue_id is None:
1906 issues = None
1907 else:
1908 issues = [issue_id]
1909
1910 if episode_id is None:
1911 episodes = None
1912 else:
1913 episodes = [episode_id]
1914
1915 encounters = self.get_encounters(issues=issues, episodes=episodes)
1916 if len(encounters) == 0:
1917 return None
1918
1919
1920 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1921 return encounters[-1]
1922
1924
1925 args = {'pat': self.pk_patient}
1926
1927 if (issue_id is None) and (episode_id is None):
1928
1929 cmd = u"""
1930 SELECT * FROM clin.v_pat_encounters
1931 WHERE pk_patient = %(pat)s
1932 ORDER BY started DESC
1933 LIMIT 2
1934 """
1935 else:
1936 where_parts = []
1937
1938 if issue_id is not None:
1939 where_parts.append(u'pk_health_issue = %(issue)s')
1940 args['issue'] = issue_id
1941
1942 if episode_id is not None:
1943 where_parts.append(u'pk_episode = %(epi)s')
1944 args['epi'] = episode_id
1945
1946 cmd = u"""
1947 SELECT *
1948 FROM clin.v_pat_encounters
1949 WHERE
1950 pk_patient = %%(pat)s
1951 AND
1952 pk_encounter IN (
1953 SELECT distinct pk_encounter
1954 FROM clin.v_pat_narrative
1955 WHERE
1956 %s
1957 )
1958 ORDER BY started DESC
1959 LIMIT 2
1960 """ % u' AND '.join(where_parts)
1961
1962 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1963
1964 if len(rows) == 0:
1965 return None
1966
1967
1968 if len(rows) == 1:
1969
1970 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1971
1972 return None
1973
1974 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1975
1976
1977 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1978 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1979
1980 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1981
1983 cfg_db = gmCfg.cCfgSQL()
1984 ttl = cfg_db.get2 (
1985 option = u'encounter.ttl_if_empty',
1986 workplace = _here.active_workplace,
1987 bias = u'user',
1988 default = u'1 week'
1989 )
1990
1991
1992 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
1993 args = {'pat': self.pk_patient, 'ttl': ttl}
1994 try:
1995 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1996 except:
1997 _log.exception('error deleting empty encounters')
1998
1999 return True
2000
2001
2002
2003
2005 """Retrieve data about test types for which this patient has results."""
2006
2007 cmd = u"""
2008 SELECT * FROM (
2009 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2010 FROM clin.v_test_results
2011 WHERE pk_patient = %(pat)s
2012 ) AS foo
2013 ORDER BY clin_when desc, unified_name
2014 """
2015 args = {'pat': self.pk_patient}
2016 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2017 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
2018
2020 """Retrieve details on tests grouped under unified names for this patient's results."""
2021 cmd = u"""
2022 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
2023 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
2024 from clin.v_test_results
2025 WHERE pk_patient = %(pat)s
2026 )
2027 order by unified_name"""
2028 args = {'pat': self.pk_patient}
2029 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2030 return rows, idx
2031
2033 """Get the dates for which we have results."""
2034 cmd = u"""
2035 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2036 from clin.v_test_results
2037 WHERE pk_patient = %(pat)s
2038 order by cwhen desc"""
2039 args = {'pat': self.pk_patient}
2040 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2041 return rows
2042
2044
2045 cmd = u"""
2046 SELECT *, xmin_test_result FROM clin.v_test_results
2047 WHERE pk_patient = %(pat)s
2048 order by clin_when desc, pk_episode, unified_name"""
2049 args = {'pat': self.pk_patient}
2050 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2051
2052 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2053
2054 if episodes is not None:
2055 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2056
2057 if encounter is not None:
2058 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2059
2060 return tests
2061
2062 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2063
2064 try:
2065 epi = int(episode)
2066 except:
2067 epi = episode['pk_episode']
2068
2069 try:
2070 type = int(type)
2071 except:
2072 type = type['pk_test_type']
2073
2074 if intended_reviewer is None:
2075 intended_reviewer = _me['pk_staff']
2076
2077 tr = gmPathLab.create_test_result (
2078 encounter = self.current_encounter['pk_encounter'],
2079 episode = epi,
2080 type = type,
2081 intended_reviewer = intended_reviewer,
2082 val_num = val_num,
2083 val_alpha = val_alpha,
2084 unit = unit
2085 )
2086
2087 return tr
2088
2108
2109
2110 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2111 """Retrieves lab result clinical items.
2112
2113 limit - maximum number of results to retrieve
2114 since - initial date
2115 until - final date
2116 encounters - list of encounters
2117 episodes - list of episodes
2118 issues - list of health issues
2119 """
2120 try:
2121 return self.__db_cache['lab results']
2122 except KeyError:
2123 pass
2124 self.__db_cache['lab results'] = []
2125 if limit is None:
2126 lim = ''
2127 else:
2128
2129 if since is None and until is None and encounters is None and episodes is None and issues is None:
2130 lim = "limit %s" % limit
2131 else:
2132 lim = ''
2133
2134 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2135 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2136 if rows is None:
2137 return False
2138 for row in rows:
2139 lab_row = {
2140 'pk_field': 'pk_result',
2141 'idx': idx,
2142 'data': row
2143 }
2144 lab_result = gmPathLab.cLabResult(row=lab_row)
2145 self.__db_cache['lab results'].append(lab_result)
2146
2147
2148 filtered_lab_results = []
2149 filtered_lab_results.extend(self.__db_cache['lab results'])
2150 if since is not None:
2151 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2152 if until is not None:
2153 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2154 if issues is not None:
2155 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2156 if episodes is not None:
2157 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2158 if encounters is not None:
2159 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2160 return filtered_lab_results
2161
2166
2167 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2181
2182
2183
2184 if __name__ == "__main__":
2185
2186 if len(sys.argv) == 1:
2187 sys.exit()
2188
2189 if sys.argv[1] != 'test':
2190 sys.exit()
2191
2192 from Gnumed.pycommon import gmLog2
2193
2207
2214
2221
2223 emr = cClinicalRecord(aPKey=12)
2224 rows, idx = emr.get_measurements_by_date()
2225 print "test results:"
2226 for row in rows:
2227 print row
2228
2235
2242
2247
2249 emr = cClinicalRecord(aPKey=12)
2250
2251 probs = emr.get_problems()
2252 print "normal probs (%s):" % len(probs)
2253 for p in probs:
2254 print u'%s (%s)' % (p['problem'], p['type'])
2255
2256 probs = emr.get_problems(include_closed_episodes=True)
2257 print "probs + closed episodes (%s):" % len(probs)
2258 for p in probs:
2259 print u'%s (%s)' % (p['problem'], p['type'])
2260
2261 probs = emr.get_problems(include_irrelevant_issues=True)
2262 print "probs + issues (%s):" % len(probs)
2263 for p in probs:
2264 print u'%s (%s)' % (p['problem'], p['type'])
2265
2266 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2267 print "probs + issues + epis (%s):" % len(probs)
2268 for p in probs:
2269 print u'%s (%s)' % (p['problem'], p['type'])
2270
2272 emr = cClinicalRecord(aPKey=12)
2273 tr = emr.add_test_result (
2274 episode = 1,
2275 intended_reviewer = 1,
2276 type = 1,
2277 val_num = 75,
2278 val_alpha = u'somewhat obese',
2279 unit = u'kg'
2280 )
2281 print tr
2282
2286
2291
2296
2300
2302 emr = cClinicalRecord(aPKey = 12)
2303 for journal_line in emr.get_as_journal():
2304
2305 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2306 print ""
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322 test_get_as_journal()
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378