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