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