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