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