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 from Gnumed.business import gmAllergy
45 from Gnumed.business import gmPathLab
46 from Gnumed.business import gmClinNarrative
47 from Gnumed.business import gmEMRStructItems
48 from Gnumed.business import gmMedication, gmVaccination
49
50
51 _log = logging.getLogger('gm.emr')
52 _log.debug(__version__)
53
54 _me = None
55 _here = None
56
57
58
59 _func_ask_user = None
60
62 if not callable(a_func):
63 _log.error('[%] not callable, not setting _func_ask_user', a_func)
64 return False
65
66 _log.debug('setting _func_ask_user to [%s]', a_func)
67
68 global _func_ask_user
69 _func_ask_user = a_func
70
71
73
74 _clin_root_item_children_union_query = None
75
133
136
138 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
139
140 return True
141
142
143
148
178
181
183 try:
184 del self.__db_cache['health issues']
185 except KeyError:
186 pass
187 return 1
188
190
191
192
193
194 return 1
195
197 _log.debug('DB: clin_root_item modification')
198
199
200
212
221
222
223
235
241
242
243
244 - def add_notes(self, notes=None, episode=None, encounter=None):
260
275
276 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
277 """Get SOAP notes pertinent to this encounter.
278
279 since
280 - initial date for narrative items
281 until
282 - final date for narrative items
283 encounters
284 - list of encounters whose narrative are to be retrieved
285 episodes
286 - list of episodes whose narrative are to be retrieved
287 issues
288 - list of health issues whose narrative are to be retrieved
289 soap_cats
290 - list of SOAP categories of the narrative to be retrieved
291 """
292 cmd = u"""
293 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
294 from clin.v_pat_narrative cvpn
295 WHERE pk_patient = %s
296 order by date, soap_rank
297 """
298
299
300
301
302 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
303
304 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
305
306 if since is not None:
307 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
308
309 if until is not None:
310 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
311
312 if issues is not None:
313 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
314
315 if episodes is not None:
316 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
317
318 if encounters is not None:
319 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
320
321 if soap_cats is not None:
322 soap_cats = map(lambda c: c.lower(), soap_cats)
323 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
324
325 if providers is not None:
326 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
327
328 return filtered_narrative
329
330 - 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):
331 return gmClinNarrative.get_as_journal (
332 patient = self.pk_patient,
333 since = since,
334 until = until,
335 encounters = encounters,
336 episodes = episodes,
337 issues = issues,
338 soap_cats = soap_cats,
339 providers = providers,
340 order_by = order_by,
341 time_range = time_range
342 )
343
345
346 search_term = search_term.strip()
347 if search_term == '':
348 return []
349
350 cmd = u"""
351 SELECT
352 *,
353 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
354 as episode,
355 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
356 as health_issue,
357 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
358 as encounter_started,
359 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
360 as encounter_ended,
361 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
362 as encounter_type
363 from clin.v_narrative4search vn4s
364 WHERE
365 pk_patient = %(pat)s and
366 vn4s.narrative ~ %(term)s
367 order by
368 encounter_started
369 """
370 rows, idx = gmPG2.run_ro_queries(queries = [
371 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
372 ])
373 return rows
374
376
377
378
379
380
381
382 try:
383 return self.__db_cache['text dump old']
384 except KeyError:
385 pass
386
387 fields = [
388 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
389 'modified_by',
390 'clin_when',
391 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
392 'pk_item',
393 'pk_encounter',
394 'pk_episode',
395 'pk_health_issue',
396 'src_table'
397 ]
398 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ')
399 ro_conn = self._conn_pool.GetConnection('historica')
400 curs = ro_conn.cursor()
401 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
402 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
403 curs.close()
404 return None
405 rows = curs.fetchall()
406 view_col_idx = gmPG2.get_col_indices(curs)
407
408
409 items_by_table = {}
410 for item in rows:
411 src_table = item[view_col_idx['src_table']]
412 pk_item = item[view_col_idx['pk_item']]
413 if not items_by_table.has_key(src_table):
414 items_by_table[src_table] = {}
415 items_by_table[src_table][pk_item] = item
416
417
418 issues = self.get_health_issues()
419 issue_map = {}
420 for issue in issues:
421 issue_map[issue['pk']] = issue['description']
422 episodes = self.get_episodes()
423 episode_map = {}
424 for episode in episodes:
425 episode_map[episode['pk_episode']] = episode['description']
426 emr_data = {}
427
428 for src_table in items_by_table.keys():
429 item_ids = items_by_table[src_table].keys()
430
431
432 if len(item_ids) == 0:
433 _log.info('no items in table [%s] ?!?' % src_table)
434 continue
435 elif len(item_ids) == 1:
436 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
437 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
438 _log.error('cannot load items from table [%s]' % src_table)
439
440 continue
441 elif len(item_ids) > 1:
442 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
443 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
444 _log.error('cannot load items from table [%s]' % src_table)
445
446 continue
447 rows = curs.fetchall()
448 table_col_idx = gmPG.get_col_indices(curs)
449
450 for row in rows:
451
452 pk_item = row[table_col_idx['pk_item']]
453 view_row = items_by_table[src_table][pk_item]
454 age = view_row[view_col_idx['age']]
455
456 try:
457 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
458 except:
459 episode_name = view_row[view_col_idx['pk_episode']]
460 try:
461 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
462 except:
463 issue_name = view_row[view_col_idx['pk_health_issue']]
464
465 if not emr_data.has_key(age):
466 emr_data[age] = []
467
468 emr_data[age].append(
469 _('%s: encounter (%s)') % (
470 view_row[view_col_idx['clin_when']],
471 view_row[view_col_idx['pk_encounter']]
472 )
473 )
474 emr_data[age].append(_('health issue: %s') % issue_name)
475 emr_data[age].append(_('episode : %s') % episode_name)
476
477
478
479 cols2ignore = [
480 'pk_audit', 'row_version', 'modified_when', 'modified_by',
481 'pk_item', 'id', 'fk_encounter', 'fk_episode'
482 ]
483 col_data = []
484 for col_name in table_col_idx.keys():
485 if col_name in cols2ignore:
486 continue
487 emr_data[age].append("=> %s:" % col_name)
488 emr_data[age].append(row[table_col_idx[col_name]])
489 emr_data[age].append("----------------------------------------------------")
490 emr_data[age].append("-- %s from table %s" % (
491 view_row[view_col_idx['modified_string']],
492 src_table
493 ))
494 emr_data[age].append("-- written %s by %s" % (
495 view_row[view_col_idx['modified_when']],
496 view_row[view_col_idx['modified_by']]
497 ))
498 emr_data[age].append("----------------------------------------------------")
499 curs.close()
500 self._conn_pool.ReleaseConnection('historica')
501 return emr_data
502
503 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
504
505
506
507
508
509
510 try:
511 return self.__db_cache['text dump']
512 except KeyError:
513 pass
514
515
516 fields = [
517 'age',
518 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
519 'modified_by',
520 'clin_when',
521 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
522 'pk_item',
523 'pk_encounter',
524 'pk_episode',
525 'pk_health_issue',
526 'src_table'
527 ]
528 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
529
530 where_snippets = []
531 params = {}
532 where_snippets.append('pk_patient=%(pat_id)s')
533 params['pat_id'] = self.pk_patient
534 if not since is None:
535 where_snippets.append('clin_when >= %(since)s')
536 params['since'] = since
537 if not until is None:
538 where_snippets.append('clin_when <= %(until)s')
539 params['until'] = until
540
541
542
543 if not encounters is None and len(encounters) > 0:
544 params['enc'] = encounters
545 if len(encounters) > 1:
546 where_snippets.append('fk_encounter in %(enc)s')
547 else:
548 where_snippets.append('fk_encounter=%(enc)s')
549
550 if not episodes is None and len(episodes) > 0:
551 params['epi'] = episodes
552 if len(episodes) > 1:
553 where_snippets.append('fk_episode in %(epi)s')
554 else:
555 where_snippets.append('fk_episode=%(epi)s')
556
557 if not issues is None and len(issues) > 0:
558 params['issue'] = issues
559 if len(issues) > 1:
560 where_snippets.append('fk_health_issue in %(issue)s')
561 else:
562 where_snippets.append('fk_health_issue=%(issue)s')
563
564 where_clause = ' and '.join(where_snippets)
565 order_by = 'order by src_table, age'
566 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
567
568 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
569 if rows is None:
570 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
571 return None
572
573
574
575
576 items_by_table = {}
577 for item in rows:
578 src_table = item[view_col_idx['src_table']]
579 pk_item = item[view_col_idx['pk_item']]
580 if not items_by_table.has_key(src_table):
581 items_by_table[src_table] = {}
582 items_by_table[src_table][pk_item] = item
583
584
585 issues = self.get_health_issues()
586 issue_map = {}
587 for issue in issues:
588 issue_map[issue['pk_health_issue']] = issue['description']
589 episodes = self.get_episodes()
590 episode_map = {}
591 for episode in episodes:
592 episode_map[episode['pk_episode']] = episode['description']
593 emr_data = {}
594
595 ro_conn = self._conn_pool.GetConnection('historica')
596 curs = ro_conn.cursor()
597 for src_table in items_by_table.keys():
598 item_ids = items_by_table[src_table].keys()
599
600
601 if len(item_ids) == 0:
602 _log.info('no items in table [%s] ?!?' % src_table)
603 continue
604 elif len(item_ids) == 1:
605 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
606 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
607 _log.error('cannot load items from table [%s]' % src_table)
608
609 continue
610 elif len(item_ids) > 1:
611 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
612 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
613 _log.error('cannot load items from table [%s]' % src_table)
614
615 continue
616 rows = curs.fetchall()
617 table_col_idx = gmPG.get_col_indices(curs)
618
619 for row in rows:
620
621 pk_item = row[table_col_idx['pk_item']]
622 view_row = items_by_table[src_table][pk_item]
623 age = view_row[view_col_idx['age']]
624
625 try:
626 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
627 except:
628 episode_name = view_row[view_col_idx['pk_episode']]
629 try:
630 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
631 except:
632 issue_name = view_row[view_col_idx['pk_health_issue']]
633
634 if not emr_data.has_key(age):
635 emr_data[age] = []
636
637 emr_data[age].append(
638 _('%s: encounter (%s)') % (
639 view_row[view_col_idx['clin_when']],
640 view_row[view_col_idx['pk_encounter']]
641 )
642 )
643 emr_data[age].append(_('health issue: %s') % issue_name)
644 emr_data[age].append(_('episode : %s') % episode_name)
645
646
647
648 cols2ignore = [
649 'pk_audit', 'row_version', 'modified_when', 'modified_by',
650 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
651 ]
652 col_data = []
653 for col_name in table_col_idx.keys():
654 if col_name in cols2ignore:
655 continue
656 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
657 emr_data[age].append("----------------------------------------------------")
658 emr_data[age].append("-- %s from table %s" % (
659 view_row[view_col_idx['modified_string']],
660 src_table
661 ))
662 emr_data[age].append("-- written %s by %s" % (
663 view_row[view_col_idx['modified_when']],
664 view_row[view_col_idx['modified_by']]
665 ))
666 emr_data[age].append("----------------------------------------------------")
667 curs.close()
668 return emr_data
669
671 return self.pk_patient
672
674 union_query = u'\n union all\n'.join ([
675 u"""
676 SELECT ((
677 -- all relevant health issues + active episodes WITH health issue
678 SELECT COUNT(1)
679 FROM clin.v_problem_list
680 WHERE
681 pk_patient = %(pat)s
682 AND
683 pk_health_issue is not null
684 ) + (
685 -- active episodes WITHOUT health issue
686 SELECT COUNT(1)
687 FROM clin.v_problem_list
688 WHERE
689 pk_patient = %(pat)s
690 AND
691 pk_health_issue is null
692 ))""",
693 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
694 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
695 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
696 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
697 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
698 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
699
700 u"""
701 SELECT count(1)
702 from clin.v_pat_substance_intake
703 WHERE
704 pk_patient = %(pat)s
705 and is_currently_active in (null, true)
706 and intake_is_approved_of in (null, true)""",
707 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
708 ])
709
710 rows, idx = gmPG2.run_ro_queries (
711 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
712 get_col_idx = False
713 )
714
715 stats = dict (
716 problems = rows[0][0],
717 encounters = rows[1][0],
718 items = rows[2][0],
719 documents = rows[3][0],
720 results = rows[4][0],
721 stays = rows[5][0],
722 procedures = rows[6][0],
723 active_drugs = rows[7][0],
724 vaccinations = rows[8][0]
725 )
726
727 return stats
728
740
810
811
812
813 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
814 """Retrieves patient allergy items.
815
816 remove_sensitivities
817 - retrieve real allergies only, without sensitivities
818 since
819 - initial date for allergy items
820 until
821 - final date for allergy items
822 encounters
823 - list of encounters whose allergies are to be retrieved
824 episodes
825 - list of episodes whose allergies are to be retrieved
826 issues
827 - list of health issues whose allergies are to be retrieved
828 """
829 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
830 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
831 allergies = []
832 for r in rows:
833 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
834
835
836 filtered_allergies = []
837 filtered_allergies.extend(allergies)
838
839 if ID_list is not None:
840 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
841 if len(filtered_allergies) == 0:
842 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
843
844 return None
845 else:
846 return filtered_allergies
847
848 if remove_sensitivities:
849 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
850 if since is not None:
851 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
852 if until is not None:
853 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
854 if issues is not None:
855 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
856 if episodes is not None:
857 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
858 if encounters is not None:
859 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
860
861 return filtered_allergies
862
863 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
864 if encounter_id is None:
865 encounter_id = self.current_encounter['pk_encounter']
866
867 if episode_id is None:
868 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
869 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
870 episode_id = epi['pk_episode']
871
872 new_allergy = gmAllergy.create_allergy (
873 allergene = allergene,
874 allg_type = allg_type,
875 encounter_id = encounter_id,
876 episode_id = episode_id
877 )
878
879 return new_allergy
880
882 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
883 args = {'pk_allg': pk_allergy}
884 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
885
887 """Cave: only use with one potential allergic agent
888 otherwise you won't know which of the agents the allergy is to."""
889
890
891 if self.allergy_state is None:
892 return None
893
894
895 if self.allergy_state == 0:
896 return False
897
898 args = {
899 'atcs': atcs,
900 'inns': inns,
901 'brand': brand,
902 'pat': self.pk_patient
903 }
904 allergenes = []
905 where_parts = []
906
907 if len(atcs) == 0:
908 atcs = None
909 if atcs is not None:
910 where_parts.append(u'atc_code in %(atcs)s')
911 if len(inns) == 0:
912 inns = None
913 if inns is not None:
914 where_parts.append(u'generics in %(inns)s')
915 allergenes.extend(inns)
916 if brand is not None:
917 where_parts.append(u'substance = %(brand)s')
918 allergenes.append(brand)
919
920 if len(allergenes) != 0:
921 where_parts.append(u'allergene in %(allgs)s')
922 args['allgs'] = tuple(allergenes)
923
924 cmd = u"""
925 SELECT * FROM clin.v_pat_allergies
926 WHERE
927 pk_patient = %%(pat)s
928 AND ( %s )""" % u' OR '.join(where_parts)
929
930 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
931
932 if len(rows) == 0:
933 return False
934
935 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
936
946
949
950 allergy_state = property(_get_allergy_state, _set_allergy_state)
951
952
953
954 - def get_episodes(self, id_list=None, issues=None, open_status=None):
955 """Fetches from backend patient episodes.
956
957 id_list - Episodes' PKs list
958 issues - Health issues' PKs list to filter episodes by
959 open_status - return all episodes, only open or closed one(s)
960 """
961 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
962 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
963 tmp = []
964 for r in rows:
965 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
966
967
968 if (id_list is None) and (issues is None) and (open_status is None):
969 return tmp
970
971
972 filtered_episodes = []
973 filtered_episodes.extend(tmp)
974 if open_status is not None:
975 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
976
977 if issues is not None:
978 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
979
980 if id_list is not None:
981 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
982
983 return filtered_episodes
984
986 cmd = u"""SELECT distinct pk_episode
987 from clin.v_pat_items
988 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
989 args = {
990 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
991 'pat': self.pk_patient
992 }
993 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
994 if len(rows) == 0:
995 return []
996 epis = []
997 for row in rows:
998 epis.append(row[0])
999 return self.get_episodes(id_list=epis)
1000
1001 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
1002 """Add episode 'episode_name' for a patient's health issue.
1003
1004 - silently returns if episode already exists
1005 """
1006 episode = gmEMRStructItems.create_episode (
1007 pk_health_issue = pk_health_issue,
1008 episode_name = episode_name,
1009 is_open = is_open,
1010 encounter = self.current_encounter['pk_encounter']
1011 )
1012 return episode
1013
1015
1016
1017 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1018
1019 cmd = u"""
1020 SELECT pk
1021 from clin.episode
1022 WHERE pk = (
1023 SELECT distinct on(pk_episode) pk_episode
1024 from clin.v_pat_items
1025 WHERE
1026 pk_patient = %%(pat)s
1027 and
1028 modified_when = (
1029 SELECT max(vpi.modified_when)
1030 from clin.v_pat_items vpi
1031 WHERE vpi.pk_patient = %%(pat)s
1032 )
1033 %s
1034 -- guard against several episodes created at the same moment of time
1035 limit 1
1036 )""" % issue_where
1037 rows, idx = gmPG2.run_ro_queries(queries = [
1038 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1039 ])
1040 if len(rows) != 0:
1041 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1042
1043
1044
1045 cmd = u"""
1046 SELECT vpe0.pk_episode
1047 from
1048 clin.v_pat_episodes vpe0
1049 WHERE
1050 vpe0.pk_patient = %%(pat)s
1051 and
1052 vpe0.episode_modified_when = (
1053 SELECT max(vpe1.episode_modified_when)
1054 from clin.v_pat_episodes vpe1
1055 WHERE vpe1.pk_episode = vpe0.pk_episode
1056 )
1057 %s""" % issue_where
1058 rows, idx = gmPG2.run_ro_queries(queries = [
1059 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1060 ])
1061 if len(rows) != 0:
1062 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1063
1064 return None
1065
1068
1069
1070
1071 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1072 """Retrieve a patient's problems.
1073
1074 "Problems" are the UNION of:
1075
1076 - issues which are .clinically_relevant
1077 - episodes which are .is_open
1078
1079 Therefore, both an issue and the open episode
1080 thereof can each be listed as a problem.
1081
1082 include_closed_episodes/include_irrelevant_issues will
1083 include those -- which departs from the definition of
1084 the problem list being "active" items only ...
1085
1086 episodes - episodes' PKs to filter problems by
1087 issues - health issues' PKs to filter problems by
1088 """
1089
1090
1091 args = {'pat': self.pk_patient}
1092
1093 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
1094 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1095
1096
1097 problems = []
1098 for row in rows:
1099 pk_args = {
1100 u'pk_patient': self.pk_patient,
1101 u'pk_health_issue': row['pk_health_issue'],
1102 u'pk_episode': row['pk_episode']
1103 }
1104 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1105
1106
1107 other_rows = []
1108 if include_closed_episodes:
1109 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1110 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1111 other_rows.extend(rows)
1112
1113 if include_irrelevant_issues:
1114 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1115 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1116 other_rows.extend(rows)
1117
1118 if len(other_rows) > 0:
1119 for row in other_rows:
1120 pk_args = {
1121 u'pk_patient': self.pk_patient,
1122 u'pk_health_issue': row['pk_health_issue'],
1123 u'pk_episode': row['pk_episode']
1124 }
1125 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1126
1127
1128 if (episodes is None) and (issues is None):
1129 return problems
1130
1131
1132 if issues is not None:
1133 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1134 if episodes is not None:
1135 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1136
1137 return problems
1138
1141
1144
1147
1148
1149
1151
1152 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1153 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1154 issues = []
1155 for row in rows:
1156 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1157 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1158
1159 if id_list is None:
1160 return issues
1161
1162 if len(id_list) == 0:
1163 raise ValueError('id_list to filter by is empty, most likely a programming error')
1164
1165 filtered_issues = []
1166 for issue in issues:
1167 if issue['pk_health_issue'] in id_list:
1168 filtered_issues.append(issue)
1169
1170 return filtered_issues
1171
1179
1182
1183
1184
1186
1187 where_parts = [u'pk_patient = %(pat)s']
1188
1189 if not include_inactive:
1190 where_parts.append(u'is_currently_active in (true, null)')
1191
1192 if not include_unapproved:
1193 where_parts.append(u'intake_is_approved_of in (true, null)')
1194
1195 if order_by is None:
1196 order_by = u''
1197 else:
1198 order_by = u'order by %s' % order_by
1199
1200 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1201 u'\nand '.join(where_parts),
1202 order_by
1203 )
1204
1205 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1206
1207 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1208
1209 if episodes is not None:
1210 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1211
1212 if issues is not None:
1213 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1214
1215 return meds
1216
1217 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1225
1226
1227
1235
1237 """Returns latest given vaccination for each vaccinated indication.
1238
1239 as a dict {'l10n_indication': cVaccination instance}
1240
1241 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1242 """
1243
1244 args = {'pat': self.pk_patient}
1245 where_parts = [u'pk_patient = %(pat)s']
1246
1247 if (episodes is not None) and (len(episodes) > 0):
1248 where_parts.append(u'pk_episode IN %(epis)s')
1249 args['epis'] = tuple(episodes)
1250
1251 if (issues is not None) and (len(issues) > 0):
1252 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1253 args['issues'] = tuple(issues)
1254
1255 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1256 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1257
1258
1259 if len(rows) == 0:
1260 return {}
1261
1262 vpks = [ ind['pk_vaccination'] for ind in rows ]
1263 vinds = [ ind['l10n_indication'] for ind in rows ]
1264 ind_counts = [ ind['indication_count'] for ind in rows ]
1265
1266
1267 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1268 args = {'pks': tuple(vpks)}
1269 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1270
1271 vaccs = {}
1272 for idx in range(len(vpks)):
1273 pk = vpks[idx]
1274 ind_count = ind_counts[idx]
1275 for r in rows:
1276 if r['pk_vaccination'] == pk:
1277 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1278
1279 return vaccs
1280
1281 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1282
1283 args = {'pat': self.pk_patient}
1284 where_parts = [u'pk_patient = %(pat)s']
1285
1286 if order_by is None:
1287 order_by = u''
1288 else:
1289 order_by = u'order by %s' % order_by
1290
1291 if (episodes is not None) and (len(episodes) > 0):
1292 where_parts.append(u'pk_episode IN %(epis)s')
1293 args['epis'] = tuple(episodes)
1294
1295 if (issues is not None) and (len(issues) > 0):
1296 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1297 args['issues'] = tuple(issues)
1298
1299 if (encounters is not None) and (len(encounters) > 0):
1300 where_parts.append(u'pk_encounter IN %(encs)s')
1301 args['encs'] = tuple(encounters)
1302
1303 cmd = u'%s %s' % (
1304 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1305 order_by
1306 )
1307 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1308 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1309
1310 return vaccs
1311
1312
1313
1315 """Retrieves vaccination regimes the patient is on.
1316
1317 optional:
1318 * ID - PK of the vaccination regime
1319 * indications - indications we want to retrieve vaccination
1320 regimes for, must be primary language, not l10n_indication
1321 """
1322
1323 try:
1324 self.__db_cache['vaccinations']['scheduled regimes']
1325 except KeyError:
1326
1327 self.__db_cache['vaccinations']['scheduled regimes'] = []
1328 cmd = """SELECT distinct on(pk_course) pk_course
1329 FROM clin.v_vaccs_scheduled4pat
1330 WHERE pk_patient=%s"""
1331 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1332 if rows is None:
1333 _log.error('cannot retrieve scheduled vaccination courses')
1334 del self.__db_cache['vaccinations']['scheduled regimes']
1335 return None
1336
1337 for row in rows:
1338 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1339
1340
1341 filtered_regimes = []
1342 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1343 if ID is not None:
1344 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1345 if len(filtered_regimes) == 0:
1346 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1347 return []
1348 else:
1349 return filtered_regimes[0]
1350 if indications is not None:
1351 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1352
1353 return filtered_regimes
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1382 """Retrieves list of vaccinations the patient has received.
1383
1384 optional:
1385 * ID - PK of a vaccination
1386 * indications - indications we want to retrieve vaccination
1387 items for, must be primary language, not l10n_indication
1388 * since - initial date for allergy items
1389 * until - final date for allergy items
1390 * encounters - list of encounters whose allergies are to be retrieved
1391 * episodes - list of episodes whose allergies are to be retrieved
1392 * issues - list of health issues whose allergies are to be retrieved
1393 """
1394 try:
1395 self.__db_cache['vaccinations']['vaccinated']
1396 except KeyError:
1397 self.__db_cache['vaccinations']['vaccinated'] = []
1398
1399 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1400 WHERE pk_patient=%s
1401 order by indication, date"""
1402 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1403 if rows is None:
1404 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1405 del self.__db_cache['vaccinations']['vaccinated']
1406 return None
1407
1408 vaccs_by_ind = {}
1409 for row in rows:
1410 vacc_row = {
1411 'pk_field': 'pk_vaccination',
1412 'idx': idx,
1413 'data': row
1414 }
1415 vacc = gmVaccination.cVaccination(row=vacc_row)
1416 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1417
1418 try:
1419 vaccs_by_ind[vacc['indication']].append(vacc)
1420 except KeyError:
1421 vaccs_by_ind[vacc['indication']] = [vacc]
1422
1423
1424 for ind in vaccs_by_ind.keys():
1425 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1426 for vacc in vaccs_by_ind[ind]:
1427
1428
1429 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1430 vacc['seq_no'] = seq_no
1431
1432
1433 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1434 continue
1435 if seq_no > vacc_regimes[0]['shots']:
1436 vacc['is_booster'] = True
1437 del vaccs_by_ind
1438
1439
1440 filtered_shots = []
1441 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1442 if ID is not None:
1443 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1444 if len(filtered_shots) == 0:
1445 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1446 return None
1447 else:
1448 return filtered_shots[0]
1449 if since is not None:
1450 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1451 if until is not None:
1452 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1453 if issues is not None:
1454 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1455 if episodes is not None:
1456 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1457 if encounters is not None:
1458 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1459 if indications is not None:
1460 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1461 return filtered_shots
1462
1464 """Retrieves vaccinations scheduled for a regime a patient is on.
1465
1466 The regime is referenced by its indication (not l10n)
1467
1468 * indications - List of indications (not l10n) of regimes we want scheduled
1469 vaccinations to be fetched for
1470 """
1471 try:
1472 self.__db_cache['vaccinations']['scheduled']
1473 except KeyError:
1474 self.__db_cache['vaccinations']['scheduled'] = []
1475 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1476 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1477 if rows is None:
1478 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1479 del self.__db_cache['vaccinations']['scheduled']
1480 return None
1481
1482 for row in rows:
1483 vacc_row = {
1484 'pk_field': 'pk_vacc_def',
1485 'idx': idx,
1486 'data': row
1487 }
1488 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1489
1490
1491 if indications is None:
1492 return self.__db_cache['vaccinations']['scheduled']
1493 filtered_shots = []
1494 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1495 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1496 return filtered_shots
1497
1499 try:
1500 self.__db_cache['vaccinations']['missing']
1501 except KeyError:
1502 self.__db_cache['vaccinations']['missing'] = {}
1503
1504 self.__db_cache['vaccinations']['missing']['due'] = []
1505
1506 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1507 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1508 if rows is None:
1509 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1510 return None
1511 pk_args = {'pat_id': self.pk_patient}
1512 if rows is not None:
1513 for row in rows:
1514 pk_args['indication'] = row[0]
1515 pk_args['seq_no'] = row[1]
1516 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1517
1518
1519 self.__db_cache['vaccinations']['missing']['boosters'] = []
1520
1521 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1522 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1523 if rows is None:
1524 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1525 return None
1526 pk_args = {'pat_id': self.pk_patient}
1527 if rows is not None:
1528 for row in rows:
1529 pk_args['indication'] = row[0]
1530 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1531
1532
1533 if indications is None:
1534 return self.__db_cache['vaccinations']['missing']
1535 if len(indications) == 0:
1536 return self.__db_cache['vaccinations']['missing']
1537
1538 filtered_shots = {
1539 'due': [],
1540 'boosters': []
1541 }
1542 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1543 if due_shot['indication'] in indications:
1544 filtered_shots['due'].append(due_shot)
1545 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1546 if due_shot['indication'] in indications:
1547 filtered_shots['boosters'].append(due_shot)
1548 return filtered_shots
1549
1550
1551
1553 return self.__encounter
1554
1556
1557
1558 if self.__encounter is None:
1559 _log.debug('first setting of active encounter in this clinical record instance')
1560 else:
1561 _log.debug('switching of active encounter')
1562
1563 if self.__encounter.is_modified():
1564 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1565 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1566
1567
1568 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1569 encounter['last_affirmed'] = gmDateTime.pydt_now_here()
1570 encounter.save()
1571 self.__encounter = encounter
1572 gmDispatcher.send(u'current_encounter_switched')
1573
1574 return True
1575
1576 current_encounter = property(_get_current_encounter, _set_current_encounter)
1577 active_encounter = property(_get_current_encounter, _set_current_encounter)
1578
1580
1581
1582 if self.__activate_very_recent_encounter():
1583 return True
1584
1585
1586 if self.__activate_fairly_recent_encounter():
1587 return True
1588
1589
1590 self.start_new_encounter()
1591 return True
1592
1594 """Try to attach to a "very recent" encounter if there is one.
1595
1596 returns:
1597 False: no "very recent" encounter, create new one
1598 True: success
1599 """
1600 cfg_db = gmCfg.cCfgSQL()
1601 min_ttl = cfg_db.get2 (
1602 option = u'encounter.minimum_ttl',
1603 workplace = _here.active_workplace,
1604 bias = u'user',
1605 default = u'1 hour 30 minutes'
1606 )
1607 cmd = u"""
1608 SELECT pk_encounter
1609 FROM clin.v_most_recent_encounters
1610 WHERE
1611 pk_patient = %s
1612 and
1613 last_affirmed > (now() - %s::interval)"""
1614 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1615
1616 if len(enc_rows) == 0:
1617 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1618 return False
1619
1620 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1621 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1622 return True
1623
1625 """Try to attach to a "fairly recent" encounter if there is one.
1626
1627 returns:
1628 False: no "fairly recent" encounter, create new one
1629 True: success
1630 """
1631 if _func_ask_user is None:
1632 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1633 return False
1634
1635 cfg_db = gmCfg.cCfgSQL()
1636 min_ttl = cfg_db.get2 (
1637 option = u'encounter.minimum_ttl',
1638 workplace = _here.active_workplace,
1639 bias = u'user',
1640 default = u'1 hour 30 minutes'
1641 )
1642 max_ttl = cfg_db.get2 (
1643 option = u'encounter.maximum_ttl',
1644 workplace = _here.active_workplace,
1645 bias = u'user',
1646 default = u'6 hours'
1647 )
1648 cmd = u"""
1649 SELECT pk_encounter
1650 FROM clin.v_most_recent_encounters
1651 WHERE
1652 pk_patient=%s
1653 AND
1654 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1655 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1656
1657 if len(enc_rows) == 0:
1658 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1659 return False
1660 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1661
1662 cmd = u"""
1663 SELECT title, firstnames, lastnames, gender, dob
1664 FROM dem.v_basic_person WHERE pk_identity=%s"""
1665 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1666 pat = pats[0]
1667 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1668 gmTools.coalesce(pat[0], u'')[:5],
1669 pat[1][:15],
1670 pat[2][:15],
1671 pat[3],
1672 pat[4].strftime('%x'),
1673 self.pk_patient
1674 )
1675 enc = gmI18N.get_encoding()
1676 msg = _(
1677 '%s\n'
1678 '\n'
1679 "This patient's chart was worked on only recently:\n"
1680 '\n'
1681 ' %s %s - %s (%s)\n'
1682 '\n'
1683 ' Request: %s\n'
1684 ' Outcome: %s\n'
1685 '\n'
1686 'Do you want to continue that consultation\n'
1687 'or do you want to start a new one ?\n'
1688 ) % (
1689 pat_str,
1690 encounter['started'].strftime('%x').decode(enc),
1691 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1692 encounter['l10n_type'],
1693 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1694 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1695 )
1696 attach = False
1697 try:
1698 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1699 except:
1700 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1701 return False
1702 if not attach:
1703 return False
1704
1705
1706 self.current_encounter = encounter
1707
1708 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1709 return True
1710
1722
1723 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None):
1724 """Retrieves patient's encounters.
1725
1726 id_list - PKs of encounters to fetch
1727 since - initial date for encounter items, DateTime instance
1728 until - final date for encounter items, DateTime instance
1729 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1730 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1731
1732 NOTE: if you specify *both* issues and episodes
1733 you will get the *aggregate* of all encounters even
1734 if the episodes all belong to the health issues listed.
1735 IOW, the issues broaden the episode list rather than
1736 the episode list narrowing the episodes-from-issues
1737 list.
1738 Rationale: If it was the other way round it would be
1739 redundant to specify the list of issues at all.
1740 """
1741
1742 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1743 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1744 encounters = []
1745 for r in rows:
1746 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1747
1748
1749 filtered_encounters = []
1750 filtered_encounters.extend(encounters)
1751 if id_list is not None:
1752 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1753 if since is not None:
1754 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1755 if until is not None:
1756 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1757
1758 if (issues is not None) and (len(issues) > 0):
1759
1760 issues = tuple(issues)
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1779 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1780 epi_ids = map(lambda x:x[0], rows)
1781 if episodes is None:
1782 episodes = []
1783 episodes.extend(epi_ids)
1784
1785 if (episodes is not None) and (len(episodes) > 0):
1786
1787 episodes = tuple(episodes)
1788
1789
1790
1791 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1792 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1793 enc_ids = map(lambda x:x[0], rows)
1794 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1795
1796 return filtered_encounters
1797
1799 """Retrieves first encounter for a particular issue and/or episode
1800
1801 issue_id - First encounter associated health issue
1802 episode - First encounter associated episode
1803 """
1804
1805
1806 if issue_id is None:
1807 issues = None
1808 else:
1809 issues = [issue_id]
1810
1811 if episode_id is None:
1812 episodes = None
1813 else:
1814 episodes = [episode_id]
1815
1816 encounters = self.get_encounters(issues=issues, episodes=episodes)
1817 if len(encounters) == 0:
1818 return None
1819
1820
1821 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1822 return encounters[0]
1823
1825 """Retrieves last encounter for a concrete issue and/or episode
1826
1827 issue_id - Last encounter associated health issue
1828 episode_id - Last encounter associated episode
1829 """
1830
1831
1832 if issue_id is None:
1833 issues = None
1834 else:
1835 issues = [issue_id]
1836
1837 if episode_id is None:
1838 episodes = None
1839 else:
1840 episodes = [episode_id]
1841
1842 encounters = self.get_encounters(issues=issues, episodes=episodes)
1843 if len(encounters) == 0:
1844 return None
1845
1846
1847 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1848 return encounters[-1]
1849
1851
1852 args = {'pat': self.pk_patient}
1853
1854 if (issue_id is None) and (episode_id is None):
1855
1856 cmd = u"""
1857 SELECT * FROM clin.v_pat_encounters
1858 WHERE pk_patient = %(pat)s
1859 ORDER BY started DESC
1860 LIMIT 2
1861 """
1862 else:
1863 where_parts = []
1864
1865 if issue_id is not None:
1866 where_parts.append(u'pk_health_issue = %(issue)s')
1867 args['issue'] = issue_id
1868
1869 if episode_id is not None:
1870 where_parts.append(u'pk_episode = %(epi)s')
1871 args['epi'] = episode_id
1872
1873 cmd = u"""
1874 SELECT *
1875 FROM clin.v_pat_encounters
1876 WHERE
1877 pk_patient = %%(pat)s
1878 AND
1879 pk_encounter IN (
1880 SELECT distinct pk_encounter
1881 FROM clin.v_pat_narrative
1882 WHERE
1883 %s
1884 )
1885 ORDER BY started DESC
1886 LIMIT 2
1887 """ % u' AND '.join(where_parts)
1888
1889 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1890
1891 if len(rows) == 0:
1892 return None
1893
1894
1895 if len(rows) == 1:
1896
1897 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1898
1899 return None
1900
1901 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1902
1903
1904 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1905 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1906
1907 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1908
1910 cfg_db = gmCfg.cCfgSQL()
1911 ttl = cfg_db.get2 (
1912 option = u'encounter.ttl_if_empty',
1913 workplace = _here.active_workplace,
1914 bias = u'user',
1915 default = u'1 week'
1916 )
1917
1918
1919 cmd = u"""
1920 delete FROM clin.encounter
1921 WHERE
1922 clin.encounter.fk_patient = %(pat)s
1923 and
1924 age(clin.encounter.last_affirmed) > %(ttl)s::interval
1925 and
1926 not exists (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
1927 and
1928 not exists (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk)
1929 and
1930 not exists (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk)
1931 and
1932 not exists (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk)
1933 and
1934 not exists (SELECT 1 FROM clin.operation WHERE fk_encounter = clin.encounter.pk)
1935 and
1936 not exists (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk)
1937 """
1938 try:
1939 rows, idx = gmPG2.run_rw_queries(queries = [{
1940 'cmd': cmd,
1941 'args': {'pat': self.pk_patient, 'ttl': ttl}
1942 }])
1943 except:
1944 _log.exception('error deleting empty encounters')
1945
1946 return True
1947
1948
1949
1950
1952 """Retrieve data about test types for which this patient has results."""
1953
1954 cmd = u"""
1955 SELECT * FROM (
1956 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
1957 FROM clin.v_test_results
1958 WHERE pk_patient = %(pat)s
1959 ) AS foo
1960 ORDER BY clin_when desc, unified_name
1961 """
1962 args = {'pat': self.pk_patient}
1963 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1964 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1965
1967 """Retrieve details on tests grouped under unified names for this patient's results."""
1968 cmd = u"""
1969 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
1970 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
1971 from clin.v_test_results
1972 WHERE pk_patient = %(pat)s
1973 )
1974 order by unified_name"""
1975 args = {'pat': self.pk_patient}
1976 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1977 return rows, idx
1978
1980 """Get the dates for which we have results."""
1981 cmd = u"""
1982 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
1983 from clin.v_test_results
1984 WHERE pk_patient = %(pat)s
1985 order by cwhen desc"""
1986 args = {'pat': self.pk_patient}
1987 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1988 return rows
1989
1991
1992 cmd = u"""
1993 SELECT *, xmin_test_result FROM clin.v_test_results
1994 WHERE pk_patient = %(pat)s
1995 order by clin_when desc, pk_episode, unified_name"""
1996 args = {'pat': self.pk_patient}
1997 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1998
1999 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2000
2001 if episodes is not None:
2002 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2003
2004 if encounter is not None:
2005 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2006
2007 return tests
2008
2009 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2010
2011 try:
2012 epi = int(episode)
2013 except:
2014 epi = episode['pk_episode']
2015
2016 try:
2017 type = int(type)
2018 except:
2019 type = type['pk_test_type']
2020
2021 if intended_reviewer is None:
2022 from Gnumed.business import gmPerson
2023 intended_reviewer = _me['pk_staff']
2024
2025 tr = gmPathLab.create_test_result (
2026 encounter = self.current_encounter['pk_encounter'],
2027 episode = epi,
2028 type = type,
2029 intended_reviewer = intended_reviewer,
2030 val_num = val_num,
2031 val_alpha = val_alpha,
2032 unit = unit
2033 )
2034
2035 return tr
2036
2056
2057
2058 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2059 """Retrieves lab result clinical items.
2060
2061 limit - maximum number of results to retrieve
2062 since - initial date
2063 until - final date
2064 encounters - list of encounters
2065 episodes - list of episodes
2066 issues - list of health issues
2067 """
2068 try:
2069 return self.__db_cache['lab results']
2070 except KeyError:
2071 pass
2072 self.__db_cache['lab results'] = []
2073 if limit is None:
2074 lim = ''
2075 else:
2076
2077 if since is None and until is None and encounters is None and episodes is None and issues is None:
2078 lim = "limit %s" % limit
2079 else:
2080 lim = ''
2081
2082 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2083 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2084 if rows is None:
2085 return False
2086 for row in rows:
2087 lab_row = {
2088 'pk_field': 'pk_result',
2089 'idx': idx,
2090 'data': row
2091 }
2092 lab_result = gmPathLab.cLabResult(row=lab_row)
2093 self.__db_cache['lab results'].append(lab_result)
2094
2095
2096 filtered_lab_results = []
2097 filtered_lab_results.extend(self.__db_cache['lab results'])
2098 if since is not None:
2099 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2100 if until is not None:
2101 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2102 if issues is not None:
2103 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2104 if episodes is not None:
2105 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2106 if encounters is not None:
2107 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2108 return filtered_lab_results
2109
2114
2115 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2129
2130
2131
2132 if __name__ == "__main__":
2133
2134 if len(sys.argv) == 1:
2135 sys.exit()
2136
2137 if sys.argv[1] != 'test':
2138 sys.exit()
2139
2140 from Gnumed.pycommon import gmLog2
2141
2155
2162
2169
2171 emr = cClinicalRecord(aPKey=12)
2172 rows, idx = emr.get_measurements_by_date()
2173 print "test results:"
2174 for row in rows:
2175 print row
2176
2183
2190
2195
2197 emr = cClinicalRecord(aPKey=12)
2198
2199 probs = emr.get_problems()
2200 print "normal probs (%s):" % len(probs)
2201 for p in probs:
2202 print u'%s (%s)' % (p['problem'], p['type'])
2203
2204 probs = emr.get_problems(include_closed_episodes=True)
2205 print "probs + closed episodes (%s):" % len(probs)
2206 for p in probs:
2207 print u'%s (%s)' % (p['problem'], p['type'])
2208
2209 probs = emr.get_problems(include_irrelevant_issues=True)
2210 print "probs + issues (%s):" % len(probs)
2211 for p in probs:
2212 print u'%s (%s)' % (p['problem'], p['type'])
2213
2214 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2215 print "probs + issues + epis (%s):" % len(probs)
2216 for p in probs:
2217 print u'%s (%s)' % (p['problem'], p['type'])
2218
2220 emr = cClinicalRecord(aPKey=12)
2221 tr = emr.add_test_result (
2222 episode = 1,
2223 intended_reviewer = 1,
2224 type = 1,
2225 val_num = 75,
2226 val_alpha = u'somewhat obese',
2227 unit = u'kg'
2228 )
2229 print tr
2230
2234
2239
2244
2248
2250 emr = cClinicalRecord(aPKey = 12)
2251 for journal_line in emr.get_as_journal():
2252
2253 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2254 print ""
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270 test_get_as_journal()
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326