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