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