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