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
891
892
893
894 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
895 """Retrieves patient allergy items.
896
897 remove_sensitivities
898 - retrieve real allergies only, without sensitivities
899 since
900 - initial date for allergy items
901 until
902 - final date for allergy items
903 encounters
904 - list of encounters whose allergies are to be retrieved
905 episodes
906 - list of episodes whose allergies are to be retrieved
907 issues
908 - list of health issues whose allergies are to be retrieved
909 """
910 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
911 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
912 allergies = []
913 for r in rows:
914 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
915
916
917 filtered_allergies = []
918 filtered_allergies.extend(allergies)
919
920 if ID_list is not None:
921 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
922 if len(filtered_allergies) == 0:
923 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
924
925 return None
926 else:
927 return filtered_allergies
928
929 if remove_sensitivities:
930 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
931 if since is not None:
932 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
933 if until is not None:
934 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
935 if issues is not None:
936 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
937 if episodes is not None:
938 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
939 if encounters is not None:
940 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
941
942 return filtered_allergies
943
944 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
945 if encounter_id is None:
946 encounter_id = self.current_encounter['pk_encounter']
947
948 if episode_id is None:
949 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
950 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
951 episode_id = epi['pk_episode']
952
953 new_allergy = gmAllergy.create_allergy (
954 allergene = allergene,
955 allg_type = allg_type,
956 encounter_id = encounter_id,
957 episode_id = episode_id
958 )
959
960 return new_allergy
961
963 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
964 args = {'pk_allg': pk_allergy}
965 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
966
968 """Cave: only use with one potential allergic agent
969 otherwise you won't know which of the agents the allergy is to."""
970
971
972 if self.allergy_state is None:
973 return None
974
975
976 if self.allergy_state == 0:
977 return False
978
979 args = {
980 'atcs': atcs,
981 'inns': inns,
982 'brand': brand,
983 'pat': self.pk_patient
984 }
985 allergenes = []
986 where_parts = []
987
988 if len(atcs) == 0:
989 atcs = None
990 if atcs is not None:
991 where_parts.append(u'atc_code in %(atcs)s')
992 if len(inns) == 0:
993 inns = None
994 if inns is not None:
995 where_parts.append(u'generics in %(inns)s')
996 allergenes.extend(inns)
997 if brand is not None:
998 where_parts.append(u'substance = %(brand)s')
999 allergenes.append(brand)
1000
1001 if len(allergenes) != 0:
1002 where_parts.append(u'allergene in %(allgs)s')
1003 args['allgs'] = tuple(allergenes)
1004
1005 cmd = u"""
1006 SELECT * FROM clin.v_pat_allergies
1007 WHERE
1008 pk_patient = %%(pat)s
1009 AND ( %s )""" % u' OR '.join(where_parts)
1010
1011 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1012
1013 if len(rows) == 0:
1014 return False
1015
1016 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1017
1027
1030
1031 allergy_state = property(_get_allergy_state, _set_allergy_state)
1032
1033
1034
1035 - def get_episodes(self, id_list=None, issues=None, open_status=None):
1036 """Fetches from backend patient episodes.
1037
1038 id_list - Episodes' PKs list
1039 issues - Health issues' PKs list to filter episodes by
1040 open_status - return all episodes, only open or closed one(s)
1041 """
1042 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
1043 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1044 tmp = []
1045 for r in rows:
1046 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
1047
1048
1049 if (id_list is None) and (issues is None) and (open_status is None):
1050 return tmp
1051
1052
1053 filtered_episodes = []
1054 filtered_episodes.extend(tmp)
1055 if open_status is not None:
1056 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
1057
1058 if issues is not None:
1059 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
1060
1061 if id_list is not None:
1062 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
1063
1064 return filtered_episodes
1065
1067 cmd = u"""SELECT distinct pk_episode
1068 from clin.v_pat_items
1069 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1070 args = {
1071 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1072 'pat': self.pk_patient
1073 }
1074 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1075 if len(rows) == 0:
1076 return []
1077 epis = []
1078 for row in rows:
1079 epis.append(row[0])
1080 return self.get_episodes(id_list=epis)
1081
1082 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1083 """Add episode 'episode_name' for a patient's health issue.
1084
1085 - silently returns if episode already exists
1086 """
1087 episode = gmEMRStructItems.create_episode (
1088 pk_health_issue = pk_health_issue,
1089 episode_name = episode_name,
1090 is_open = is_open,
1091 encounter = self.current_encounter['pk_encounter']
1092 )
1093 return episode
1094
1096
1097
1098 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1099
1100 cmd = u"""
1101 SELECT pk
1102 from clin.episode
1103 WHERE pk = (
1104 SELECT distinct on(pk_episode) pk_episode
1105 from clin.v_pat_items
1106 WHERE
1107 pk_patient = %%(pat)s
1108 and
1109 modified_when = (
1110 SELECT max(vpi.modified_when)
1111 from clin.v_pat_items vpi
1112 WHERE vpi.pk_patient = %%(pat)s
1113 )
1114 %s
1115 -- guard against several episodes created at the same moment of time
1116 limit 1
1117 )""" % issue_where
1118 rows, idx = gmPG2.run_ro_queries(queries = [
1119 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1120 ])
1121 if len(rows) != 0:
1122 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1123
1124
1125
1126 cmd = u"""
1127 SELECT vpe0.pk_episode
1128 from
1129 clin.v_pat_episodes vpe0
1130 WHERE
1131 vpe0.pk_patient = %%(pat)s
1132 and
1133 vpe0.episode_modified_when = (
1134 SELECT max(vpe1.episode_modified_when)
1135 from clin.v_pat_episodes vpe1
1136 WHERE vpe1.pk_episode = vpe0.pk_episode
1137 )
1138 %s""" % issue_where
1139 rows, idx = gmPG2.run_ro_queries(queries = [
1140 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1141 ])
1142 if len(rows) != 0:
1143 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1144
1145 return None
1146
1149
1150
1151
1152 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1153 """Retrieve a patient's problems.
1154
1155 "Problems" are the UNION of:
1156
1157 - issues which are .clinically_relevant
1158 - episodes which are .is_open
1159
1160 Therefore, both an issue and the open episode
1161 thereof can each be listed as a problem.
1162
1163 include_closed_episodes/include_irrelevant_issues will
1164 include those -- which departs from the definition of
1165 the problem list being "active" items only ...
1166
1167 episodes - episodes' PKs to filter problems by
1168 issues - health issues' PKs to filter problems by
1169 """
1170
1171
1172 args = {'pat': self.pk_patient}
1173
1174 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1175 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1176
1177
1178 problems = []
1179 for row in rows:
1180 pk_args = {
1181 u'pk_patient': self.pk_patient,
1182 u'pk_health_issue': row['pk_health_issue'],
1183 u'pk_episode': row['pk_episode']
1184 }
1185 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1186
1187
1188 other_rows = []
1189 if include_closed_episodes:
1190 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1191 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1192 other_rows.extend(rows)
1193
1194 if include_irrelevant_issues:
1195 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1196 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1197 other_rows.extend(rows)
1198
1199 if len(other_rows) > 0:
1200 for row in other_rows:
1201 pk_args = {
1202 u'pk_patient': self.pk_patient,
1203 u'pk_health_issue': row['pk_health_issue'],
1204 u'pk_episode': row['pk_episode']
1205 }
1206 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1207
1208
1209 if (episodes is None) and (issues is None):
1210 return problems
1211
1212
1213 if issues is not None:
1214 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1215 if episodes is not None:
1216 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1217
1218 return problems
1219
1222
1225
1228
1229
1230
1232
1233 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1234 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1235 issues = []
1236 for row in rows:
1237 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1238 issues.append(gmEMRStructItems.cHealthIssue(row = r))
1239
1240 if id_list is None:
1241 return issues
1242
1243 if len(id_list) == 0:
1244 raise ValueError('id_list to filter by is empty, most likely a programming error')
1245
1246 filtered_issues = []
1247 for issue in issues:
1248 if issue['pk_health_issue'] in id_list:
1249 filtered_issues.append(issue)
1250
1251 return filtered_issues
1252
1260
1263
1264
1265
1267
1268 where_parts = [u'pk_patient = %(pat)s']
1269
1270 if not include_inactive:
1271 where_parts.append(u'is_currently_active in (true, null)')
1272
1273 if not include_unapproved:
1274 where_parts.append(u'intake_is_approved_of in (true, null)')
1275
1276 if order_by is None:
1277 order_by = u''
1278 else:
1279 order_by = u'order by %s' % order_by
1280
1281 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1282 u'\nand '.join(where_parts),
1283 order_by
1284 )
1285
1286 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1287
1288 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1289
1290 if episodes is not None:
1291 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1292
1293 if issues is not None:
1294 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1295
1296 return meds
1297
1298 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1306
1313
1314
1315
1323
1325 """Returns latest given vaccination for each vaccinated indication.
1326
1327 as a dict {'l10n_indication': cVaccination instance}
1328
1329 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1330 """
1331
1332 args = {'pat': self.pk_patient}
1333 where_parts = [u'pk_patient = %(pat)s']
1334
1335 if (episodes is not None) and (len(episodes) > 0):
1336 where_parts.append(u'pk_episode IN %(epis)s')
1337 args['epis'] = tuple(episodes)
1338
1339 if (issues is not None) and (len(issues) > 0):
1340 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1341 args['issues'] = tuple(issues)
1342
1343 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1344 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1345
1346
1347 if len(rows) == 0:
1348 return {}
1349
1350 vpks = [ ind['pk_vaccination'] for ind in rows ]
1351 vinds = [ ind['l10n_indication'] for ind in rows ]
1352 ind_counts = [ ind['indication_count'] for ind in rows ]
1353
1354
1355 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1356 args = {'pks': tuple(vpks)}
1357 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1358
1359 vaccs = {}
1360 for idx in range(len(vpks)):
1361 pk = vpks[idx]
1362 ind_count = ind_counts[idx]
1363 for r in rows:
1364 if r['pk_vaccination'] == pk:
1365 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1366
1367 return vaccs
1368
1369 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1370
1371 args = {'pat': self.pk_patient}
1372 where_parts = [u'pk_patient = %(pat)s']
1373
1374 if order_by is None:
1375 order_by = u''
1376 else:
1377 order_by = u'ORDER BY %s' % order_by
1378
1379 if (episodes is not None) and (len(episodes) > 0):
1380 where_parts.append(u'pk_episode IN %(epis)s')
1381 args['epis'] = tuple(episodes)
1382
1383 if (issues is not None) and (len(issues) > 0):
1384 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1385 args['issues'] = tuple(issues)
1386
1387 if (encounters is not None) and (len(encounters) > 0):
1388 where_parts.append(u'pk_encounter IN %(encs)s')
1389 args['encs'] = tuple(encounters)
1390
1391 cmd = u'%s %s' % (
1392 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1393 order_by
1394 )
1395 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1396 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1397
1398 return vaccs
1399
1400
1401
1403 """Retrieves vaccination regimes the patient is on.
1404
1405 optional:
1406 * ID - PK of the vaccination regime
1407 * indications - indications we want to retrieve vaccination
1408 regimes for, must be primary language, not l10n_indication
1409 """
1410
1411 try:
1412 self.__db_cache['vaccinations']['scheduled regimes']
1413 except KeyError:
1414
1415 self.__db_cache['vaccinations']['scheduled regimes'] = []
1416 cmd = """SELECT distinct on(pk_course) pk_course
1417 FROM clin.v_vaccs_scheduled4pat
1418 WHERE pk_patient=%s"""
1419 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1420 if rows is None:
1421 _log.error('cannot retrieve scheduled vaccination courses')
1422 del self.__db_cache['vaccinations']['scheduled regimes']
1423 return None
1424
1425 for row in rows:
1426 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1427
1428
1429 filtered_regimes = []
1430 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1431 if ID is not None:
1432 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1433 if len(filtered_regimes) == 0:
1434 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1435 return []
1436 else:
1437 return filtered_regimes[0]
1438 if indications is not None:
1439 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1440
1441 return filtered_regimes
1442
1443
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 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1470 """Retrieves list of vaccinations the patient has received.
1471
1472 optional:
1473 * ID - PK of a vaccination
1474 * indications - indications we want to retrieve vaccination
1475 items for, must be primary language, not l10n_indication
1476 * since - initial date for allergy items
1477 * until - final date for allergy items
1478 * encounters - list of encounters whose allergies are to be retrieved
1479 * episodes - list of episodes whose allergies are to be retrieved
1480 * issues - list of health issues whose allergies are to be retrieved
1481 """
1482 try:
1483 self.__db_cache['vaccinations']['vaccinated']
1484 except KeyError:
1485 self.__db_cache['vaccinations']['vaccinated'] = []
1486
1487 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1488 WHERE pk_patient=%s
1489 order by indication, date"""
1490 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1491 if rows is None:
1492 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1493 del self.__db_cache['vaccinations']['vaccinated']
1494 return None
1495
1496 vaccs_by_ind = {}
1497 for row in rows:
1498 vacc_row = {
1499 'pk_field': 'pk_vaccination',
1500 'idx': idx,
1501 'data': row
1502 }
1503 vacc = gmVaccination.cVaccination(row=vacc_row)
1504 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1505
1506 try:
1507 vaccs_by_ind[vacc['indication']].append(vacc)
1508 except KeyError:
1509 vaccs_by_ind[vacc['indication']] = [vacc]
1510
1511
1512 for ind in vaccs_by_ind.keys():
1513 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1514 for vacc in vaccs_by_ind[ind]:
1515
1516
1517 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1518 vacc['seq_no'] = seq_no
1519
1520
1521 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1522 continue
1523 if seq_no > vacc_regimes[0]['shots']:
1524 vacc['is_booster'] = True
1525 del vaccs_by_ind
1526
1527
1528 filtered_shots = []
1529 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1530 if ID is not None:
1531 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1532 if len(filtered_shots) == 0:
1533 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1534 return None
1535 else:
1536 return filtered_shots[0]
1537 if since is not None:
1538 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1539 if until is not None:
1540 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1541 if issues is not None:
1542 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1543 if episodes is not None:
1544 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1545 if encounters is not None:
1546 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1547 if indications is not None:
1548 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1549 return filtered_shots
1550
1552 """Retrieves vaccinations scheduled for a regime a patient is on.
1553
1554 The regime is referenced by its indication (not l10n)
1555
1556 * indications - List of indications (not l10n) of regimes we want scheduled
1557 vaccinations to be fetched for
1558 """
1559 try:
1560 self.__db_cache['vaccinations']['scheduled']
1561 except KeyError:
1562 self.__db_cache['vaccinations']['scheduled'] = []
1563 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1564 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1565 if rows is None:
1566 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1567 del self.__db_cache['vaccinations']['scheduled']
1568 return None
1569
1570 for row in rows:
1571 vacc_row = {
1572 'pk_field': 'pk_vacc_def',
1573 'idx': idx,
1574 'data': row
1575 }
1576 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1577
1578
1579 if indications is None:
1580 return self.__db_cache['vaccinations']['scheduled']
1581 filtered_shots = []
1582 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1583 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1584 return filtered_shots
1585
1587 try:
1588 self.__db_cache['vaccinations']['missing']
1589 except KeyError:
1590 self.__db_cache['vaccinations']['missing'] = {}
1591
1592 self.__db_cache['vaccinations']['missing']['due'] = []
1593
1594 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs 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 (indication, seq_no) for due/overdue vaccinations 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 pk_args['seq_no'] = row[1]
1604 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1605
1606
1607 self.__db_cache['vaccinations']['missing']['boosters'] = []
1608
1609 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1610 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1611 if rows is None:
1612 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1613 return None
1614 pk_args = {'pat_id': self.pk_patient}
1615 if rows is not None:
1616 for row in rows:
1617 pk_args['indication'] = row[0]
1618 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1619
1620
1621 if indications is None:
1622 return self.__db_cache['vaccinations']['missing']
1623 if len(indications) == 0:
1624 return self.__db_cache['vaccinations']['missing']
1625
1626 filtered_shots = {
1627 'due': [],
1628 'boosters': []
1629 }
1630 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1631 if due_shot['indication'] in indications:
1632 filtered_shots['due'].append(due_shot)
1633 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1634 if due_shot['indication'] in indications:
1635 filtered_shots['boosters'].append(due_shot)
1636 return filtered_shots
1637
1638
1639
1641 return self.__encounter
1642
1644
1645
1646 if self.__encounter is None:
1647 _log.debug('first setting of active encounter in this clinical record instance')
1648 else:
1649 _log.debug('switching of active encounter')
1650
1651 if self.__encounter.is_modified():
1652 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1653 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1654
1655
1656 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1657 encounter['last_affirmed'] = gmDateTime.pydt_now_here()
1658 encounter.save()
1659 self.__encounter = encounter
1660 gmDispatcher.send(u'current_encounter_switched')
1661
1662 return True
1663
1664 current_encounter = property(_get_current_encounter, _set_current_encounter)
1665 active_encounter = property(_get_current_encounter, _set_current_encounter)
1666
1668
1669
1670 if self.__activate_very_recent_encounter():
1671 return True
1672
1673
1674 if self.__activate_fairly_recent_encounter():
1675 return True
1676
1677
1678 self.start_new_encounter()
1679 return True
1680
1682 """Try to attach to a "very recent" encounter if there is one.
1683
1684 returns:
1685 False: no "very recent" encounter, create new one
1686 True: success
1687 """
1688 cfg_db = gmCfg.cCfgSQL()
1689 min_ttl = cfg_db.get2 (
1690 option = u'encounter.minimum_ttl',
1691 workplace = _here.active_workplace,
1692 bias = u'user',
1693 default = u'1 hour 30 minutes'
1694 )
1695 cmd = u"""
1696 SELECT pk_encounter
1697 FROM clin.v_most_recent_encounters
1698 WHERE
1699 pk_patient = %s
1700 and
1701 last_affirmed > (now() - %s::interval)"""
1702 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1703
1704 if len(enc_rows) == 0:
1705 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1706 return False
1707
1708 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1709 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1710 return True
1711
1713 """Try to attach to a "fairly recent" encounter if there is one.
1714
1715 returns:
1716 False: no "fairly recent" encounter, create new one
1717 True: success
1718 """
1719 if _func_ask_user is None:
1720 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1721 return False
1722
1723 cfg_db = gmCfg.cCfgSQL()
1724 min_ttl = cfg_db.get2 (
1725 option = u'encounter.minimum_ttl',
1726 workplace = _here.active_workplace,
1727 bias = u'user',
1728 default = u'1 hour 30 minutes'
1729 )
1730 max_ttl = cfg_db.get2 (
1731 option = u'encounter.maximum_ttl',
1732 workplace = _here.active_workplace,
1733 bias = u'user',
1734 default = u'6 hours'
1735 )
1736 cmd = u"""
1737 SELECT pk_encounter
1738 FROM clin.v_most_recent_encounters
1739 WHERE
1740 pk_patient=%s
1741 AND
1742 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1743 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1744
1745 if len(enc_rows) == 0:
1746 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1747 return False
1748 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1749
1750 cmd = u"""
1751 SELECT title, firstnames, lastnames, gender, dob
1752 FROM dem.v_basic_person WHERE pk_identity=%s"""
1753 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1754 pat = pats[0]
1755 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1756 gmTools.coalesce(pat[0], u'')[:5],
1757 pat[1][:15],
1758 pat[2][:15],
1759 pat[3],
1760 pat[4].strftime('%x'),
1761 self.pk_patient
1762 )
1763 enc = gmI18N.get_encoding()
1764 msg = _(
1765 '%s\n'
1766 '\n'
1767 "This patient's chart was worked on only recently:\n"
1768 '\n'
1769 ' %s %s - %s (%s)\n'
1770 '\n'
1771 ' Request: %s\n'
1772 ' Outcome: %s\n'
1773 '\n'
1774 'Do you want to continue that consultation\n'
1775 'or do you want to start a new one ?\n'
1776 ) % (
1777 pat_str,
1778 encounter['started'].strftime('%x').decode(enc),
1779 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1780 encounter['l10n_type'],
1781 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1782 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1783 )
1784 attach = False
1785 try:
1786 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1787 except:
1788 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1789 return False
1790 if not attach:
1791 return False
1792
1793
1794 self.current_encounter = encounter
1795
1796 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1797 return True
1798
1810
1811 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None):
1812 """Retrieves patient's encounters.
1813
1814 id_list - PKs of encounters to fetch
1815 since - initial date for encounter items, DateTime instance
1816 until - final date for encounter items, DateTime instance
1817 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1818 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1819
1820 NOTE: if you specify *both* issues and episodes
1821 you will get the *aggregate* of all encounters even
1822 if the episodes all belong to the health issues listed.
1823 IOW, the issues broaden the episode list rather than
1824 the episode list narrowing the episodes-from-issues
1825 list.
1826 Rationale: If it was the other way round it would be
1827 redundant to specify the list of issues at all.
1828 """
1829
1830 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1831 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1832 encounters = []
1833 for r in rows:
1834 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1835
1836
1837 filtered_encounters = []
1838 filtered_encounters.extend(encounters)
1839 if id_list is not None:
1840 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1841 if since is not None:
1842 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1843 if until is not None:
1844 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1845
1846 if (issues is not None) and (len(issues) > 0):
1847
1848 issues = tuple(issues)
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1867 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1868 epi_ids = map(lambda x:x[0], rows)
1869 if episodes is None:
1870 episodes = []
1871 episodes.extend(epi_ids)
1872
1873 if (episodes is not None) and (len(episodes) > 0):
1874
1875 episodes = tuple(episodes)
1876
1877
1878
1879 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1880 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1881 enc_ids = map(lambda x:x[0], rows)
1882 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1883
1884 return filtered_encounters
1885
1887 """Retrieves first encounter for a particular issue and/or episode.
1888
1889 issue_id - First encounter associated health issue
1890 episode - First encounter associated episode
1891 """
1892
1893 if issue_id is None:
1894 issues = None
1895 else:
1896 issues = [issue_id]
1897
1898 if episode_id is None:
1899 episodes = None
1900 else:
1901 episodes = [episode_id]
1902
1903 encounters = self.get_encounters(issues=issues, episodes=episodes)
1904 if len(encounters) == 0:
1905 return None
1906
1907
1908 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1909 return encounters[0]
1910
1912 """Retrieves last encounter for a concrete issue and/or episode
1913
1914 issue_id - Last encounter associated health issue
1915 episode_id - Last encounter associated episode
1916 """
1917
1918
1919 if issue_id is None:
1920 issues = None
1921 else:
1922 issues = [issue_id]
1923
1924 if episode_id is None:
1925 episodes = None
1926 else:
1927 episodes = [episode_id]
1928
1929 encounters = self.get_encounters(issues=issues, episodes=episodes)
1930 if len(encounters) == 0:
1931 return None
1932
1933
1934 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1935 return encounters[-1]
1936
1938 args = {'pat': self.pk_patient, 'range': cover_period}
1939 where_parts = [u'pk_patient = %(pat)s']
1940 if cover_period is not None:
1941 where_parts.append(u'last_affirmed > now() - %(range)s')
1942
1943 cmd = u"""
1944 SELECT l10n_type, count(1) AS frequency
1945 FROM clin.v_pat_encounters
1946 WHERE
1947 %s
1948 GROUP BY l10n_type
1949 ORDER BY frequency DESC
1950 """ % u' AND '.join(where_parts)
1951 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1952 return rows
1953
1955
1956 args = {'pat': self.pk_patient}
1957
1958 if (issue_id is None) and (episode_id is None):
1959
1960 cmd = u"""
1961 SELECT * FROM clin.v_pat_encounters
1962 WHERE pk_patient = %(pat)s
1963 ORDER BY started DESC
1964 LIMIT 2
1965 """
1966 else:
1967 where_parts = []
1968
1969 if issue_id is not None:
1970 where_parts.append(u'pk_health_issue = %(issue)s')
1971 args['issue'] = issue_id
1972
1973 if episode_id is not None:
1974 where_parts.append(u'pk_episode = %(epi)s')
1975 args['epi'] = episode_id
1976
1977 cmd = u"""
1978 SELECT *
1979 FROM clin.v_pat_encounters
1980 WHERE
1981 pk_patient = %%(pat)s
1982 AND
1983 pk_encounter IN (
1984 SELECT distinct pk_encounter
1985 FROM clin.v_pat_narrative
1986 WHERE
1987 %s
1988 )
1989 ORDER BY started DESC
1990 LIMIT 2
1991 """ % u' AND '.join(where_parts)
1992
1993 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1994
1995 if len(rows) == 0:
1996 return None
1997
1998
1999 if len(rows) == 1:
2000
2001 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2002
2003 return None
2004
2005 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2006
2007
2008 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2009 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2010
2011 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2012
2014 cfg_db = gmCfg.cCfgSQL()
2015 ttl = cfg_db.get2 (
2016 option = u'encounter.ttl_if_empty',
2017 workplace = _here.active_workplace,
2018 bias = u'user',
2019 default = u'1 week'
2020 )
2021
2022
2023 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2024 args = {'pat': self.pk_patient, 'ttl': ttl}
2025 try:
2026 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2027 except:
2028 _log.exception('error deleting empty encounters')
2029
2030 return True
2031
2032
2033
2034
2036 """Retrieve data about test types for which this patient has results."""
2037
2038 cmd = u"""
2039 SELECT * FROM (
2040 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2041 FROM clin.v_test_results
2042 WHERE pk_patient = %(pat)s
2043 ) AS foo
2044 ORDER BY clin_when desc, unified_name
2045 """
2046 args = {'pat': self.pk_patient}
2047 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2048 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
2049
2051 """Retrieve details on tests grouped under unified names for this patient's results."""
2052 cmd = u"""
2053 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
2054 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
2055 from clin.v_test_results
2056 WHERE pk_patient = %(pat)s
2057 )
2058 order by unified_name"""
2059 args = {'pat': self.pk_patient}
2060 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2061 return rows, idx
2062
2064 """Get the dates for which we have results."""
2065 cmd = u"""
2066 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2067 from clin.v_test_results
2068 WHERE pk_patient = %(pat)s
2069 order by cwhen desc"""
2070 args = {'pat': self.pk_patient}
2071 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2072 return rows
2073
2075
2076 cmd = u"""
2077 SELECT *, xmin_test_result FROM clin.v_test_results
2078 WHERE pk_patient = %(pat)s
2079 order by clin_when desc, pk_episode, unified_name"""
2080 args = {'pat': self.pk_patient}
2081 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2082
2083 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2084
2085 if episodes is not None:
2086 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2087
2088 if encounter is not None:
2089 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2090
2091 return tests
2092
2093 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2094
2095 try:
2096 epi = int(episode)
2097 except:
2098 epi = episode['pk_episode']
2099
2100 try:
2101 type = int(type)
2102 except:
2103 type = type['pk_test_type']
2104
2105 if intended_reviewer is None:
2106 intended_reviewer = _me['pk_staff']
2107
2108 tr = gmPathLab.create_test_result (
2109 encounter = self.current_encounter['pk_encounter'],
2110 episode = epi,
2111 type = type,
2112 intended_reviewer = intended_reviewer,
2113 val_num = val_num,
2114 val_alpha = val_alpha,
2115 unit = unit
2116 )
2117
2118 return tr
2119
2139
2140
2141 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2142 """Retrieves lab result clinical items.
2143
2144 limit - maximum number of results to retrieve
2145 since - initial date
2146 until - final date
2147 encounters - list of encounters
2148 episodes - list of episodes
2149 issues - list of health issues
2150 """
2151 try:
2152 return self.__db_cache['lab results']
2153 except KeyError:
2154 pass
2155 self.__db_cache['lab results'] = []
2156 if limit is None:
2157 lim = ''
2158 else:
2159
2160 if since is None and until is None and encounters is None and episodes is None and issues is None:
2161 lim = "limit %s" % limit
2162 else:
2163 lim = ''
2164
2165 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2166 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2167 if rows is None:
2168 return False
2169 for row in rows:
2170 lab_row = {
2171 'pk_field': 'pk_result',
2172 'idx': idx,
2173 'data': row
2174 }
2175 lab_result = gmPathLab.cLabResult(row=lab_row)
2176 self.__db_cache['lab results'].append(lab_result)
2177
2178
2179 filtered_lab_results = []
2180 filtered_lab_results.extend(self.__db_cache['lab results'])
2181 if since is not None:
2182 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2183 if until is not None:
2184 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2185 if issues is not None:
2186 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2187 if episodes is not None:
2188 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2189 if encounters is not None:
2190 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2191 return filtered_lab_results
2192
2197
2198 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2212
2213
2214
2215 if __name__ == "__main__":
2216
2217 if len(sys.argv) == 1:
2218 sys.exit()
2219
2220 if sys.argv[1] != 'test':
2221 sys.exit()
2222
2223 from Gnumed.pycommon import gmLog2
2224
2238
2245
2252
2254 emr = cClinicalRecord(aPKey=12)
2255 rows, idx = emr.get_measurements_by_date()
2256 print "test results:"
2257 for row in rows:
2258 print row
2259
2266
2273
2278
2280 emr = cClinicalRecord(aPKey=12)
2281
2282 probs = emr.get_problems()
2283 print "normal probs (%s):" % len(probs)
2284 for p in probs:
2285 print u'%s (%s)' % (p['problem'], p['type'])
2286
2287 probs = emr.get_problems(include_closed_episodes=True)
2288 print "probs + closed episodes (%s):" % len(probs)
2289 for p in probs:
2290 print u'%s (%s)' % (p['problem'], p['type'])
2291
2292 probs = emr.get_problems(include_irrelevant_issues=True)
2293 print "probs + issues (%s):" % len(probs)
2294 for p in probs:
2295 print u'%s (%s)' % (p['problem'], p['type'])
2296
2297 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2298 print "probs + issues + epis (%s):" % len(probs)
2299 for p in probs:
2300 print u'%s (%s)' % (p['problem'], p['type'])
2301
2303 emr = cClinicalRecord(aPKey=12)
2304 tr = emr.add_test_result (
2305 episode = 1,
2306 intended_reviewer = 1,
2307 type = 1,
2308 val_num = 75,
2309 val_alpha = u'somewhat obese',
2310 unit = u'kg'
2311 )
2312 print tr
2313
2317
2322
2327
2331
2333 emr = cClinicalRecord(aPKey = 12)
2334 for journal_line in emr.get_as_journal():
2335
2336 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2337 print ""
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353 test_get_as_journal()
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
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409