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