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
12
13 __version__ = "$Revision: 1.308 $"
14 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
15 __license__ = "GPL"
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 import sys, string, time, copy, locale
32
33
34
35 import mx.DateTime as mxDT, psycopg2, logging
36
37
38 if __name__ == '__main__':
39 sys.path.insert(0, '../../')
40 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
41 gmI18N.activate_locale()
42 gmI18N.install_domain()
43 gmDateTime.init()
44 from Gnumed.pycommon import gmExceptions, gmPG2, gmDispatcher, gmI18N, gmCfg, gmTools
45 from Gnumed.business import gmAllergy, gmEMRStructItems, gmClinNarrative, gmPathLab, gmMedication
46
47
48 _log = logging.getLogger('gm.emr')
49 _log.debug(__version__)
50
51 _me = None
52 _here = None
53
54 _func_ask_user = None
55
57
58 _clin_root_item_children_union_query = None
59
116
119
121 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
122
123 return True
124
125
126
131
161
163 try:
164 self.__db_cache['vaccinations'] = {}
165 except KeyError:
166 pass
167 return True
168
170 try:
171 del self.__db_cache['health issues']
172 except KeyError:
173 pass
174 return 1
175
177
178
179
180
181 return 1
182
184 _log.debug('DB: clin_root_item modification')
185
186
187
199
208
209
210
222
223
224
225 - def add_notes(self, notes=None, episode=None):
236
251
252 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
253 """Get SOAP notes pertinent to this encounter.
254
255 since
256 - initial date for narrative items
257 until
258 - final date for narrative items
259 encounters
260 - list of encounters whose narrative are to be retrieved
261 episodes
262 - list of episodes whose narrative are to be retrieved
263 issues
264 - list of health issues whose narrative are to be retrieved
265 soap_cats
266 - list of SOAP categories of the narrative to be retrieved
267 """
268 cmd = u"""
269 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
270 from clin.v_pat_narrative cvpn
271 WHERE pk_patient = %s
272 order by date, soap_rank
273 """
274
275
276
277
278 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
279
280 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
281
282 if since is not None:
283 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
284
285 if until is not None:
286 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
287
288 if issues is not None:
289 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
290
291 if episodes is not None:
292 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
293
294 if encounters is not None:
295 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
296
297 if soap_cats is not None:
298 soap_cats = map(lambda c: c.lower(), soap_cats)
299 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
300
301 if providers is not None:
302 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
303
304 return filtered_narrative
305
307
308 search_term = search_term.strip()
309 if search_term == '':
310 return []
311
312 cmd = u"""
313 SELECT
314 *,
315 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
316 as episode,
317 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
318 as health_issue,
319 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
320 as encounter_started,
321 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
322 as encounter_ended,
323 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
324 as encounter_type
325 from clin.v_narrative4search vn4s
326 WHERE
327 pk_patient = %(pat)s and
328 vn4s.narrative ~ %(term)s
329 order by
330 encounter_started
331 """
332 rows, idx = gmPG2.run_ro_queries(queries = [
333 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
334 ])
335 return rows
336
338
339
340
341
342
343
344 try:
345 return self.__db_cache['text dump old']
346 except KeyError:
347 pass
348
349 fields = [
350 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
351 'modified_by',
352 'clin_when',
353 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
354 'pk_item',
355 'pk_encounter',
356 'pk_episode',
357 'pk_health_issue',
358 'src_table'
359 ]
360 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ')
361 ro_conn = self._conn_pool.GetConnection('historica')
362 curs = ro_conn.cursor()
363 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
364 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
365 curs.close()
366 return None
367 rows = curs.fetchall()
368 view_col_idx = gmPG2.get_col_indices(curs)
369
370
371 items_by_table = {}
372 for item in rows:
373 src_table = item[view_col_idx['src_table']]
374 pk_item = item[view_col_idx['pk_item']]
375 if not items_by_table.has_key(src_table):
376 items_by_table[src_table] = {}
377 items_by_table[src_table][pk_item] = item
378
379
380 issues = self.get_health_issues()
381 issue_map = {}
382 for issue in issues:
383 issue_map[issue['pk']] = issue['description']
384 episodes = self.get_episodes()
385 episode_map = {}
386 for episode in episodes:
387 episode_map[episode['pk_episode']] = episode['description']
388 emr_data = {}
389
390 for src_table in items_by_table.keys():
391 item_ids = items_by_table[src_table].keys()
392
393
394 if len(item_ids) == 0:
395 _log.info('no items in table [%s] ?!?' % src_table)
396 continue
397 elif len(item_ids) == 1:
398 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
399 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
400 _log.error('cannot load items from table [%s]' % src_table)
401
402 continue
403 elif len(item_ids) > 1:
404 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
405 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
406 _log.error('cannot load items from table [%s]' % src_table)
407
408 continue
409 rows = curs.fetchall()
410 table_col_idx = gmPG.get_col_indices(curs)
411
412 for row in rows:
413
414 pk_item = row[table_col_idx['pk_item']]
415 view_row = items_by_table[src_table][pk_item]
416 age = view_row[view_col_idx['age']]
417
418 try:
419 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
420 except:
421 episode_name = view_row[view_col_idx['pk_episode']]
422 try:
423 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
424 except:
425 issue_name = view_row[view_col_idx['pk_health_issue']]
426
427 if not emr_data.has_key(age):
428 emr_data[age] = []
429
430 emr_data[age].append(
431 _('%s: encounter (%s)') % (
432 view_row[view_col_idx['clin_when']],
433 view_row[view_col_idx['pk_encounter']]
434 )
435 )
436 emr_data[age].append(_('health issue: %s') % issue_name)
437 emr_data[age].append(_('episode : %s') % episode_name)
438
439
440
441 cols2ignore = [
442 'pk_audit', 'row_version', 'modified_when', 'modified_by',
443 'pk_item', 'id', 'fk_encounter', 'fk_episode'
444 ]
445 col_data = []
446 for col_name in table_col_idx.keys():
447 if col_name in cols2ignore:
448 continue
449 emr_data[age].append("=> %s:" % col_name)
450 emr_data[age].append(row[table_col_idx[col_name]])
451 emr_data[age].append("----------------------------------------------------")
452 emr_data[age].append("-- %s from table %s" % (
453 view_row[view_col_idx['modified_string']],
454 src_table
455 ))
456 emr_data[age].append("-- written %s by %s" % (
457 view_row[view_col_idx['modified_when']],
458 view_row[view_col_idx['modified_by']]
459 ))
460 emr_data[age].append("----------------------------------------------------")
461 curs.close()
462 self._conn_pool.ReleaseConnection('historica')
463 return emr_data
464
465 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
466
467
468
469
470
471
472 try:
473 return self.__db_cache['text dump']
474 except KeyError:
475 pass
476
477
478 fields = [
479 'age',
480 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
481 'modified_by',
482 'clin_when',
483 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
484 'pk_item',
485 'pk_encounter',
486 'pk_episode',
487 'pk_health_issue',
488 'src_table'
489 ]
490 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
491
492 where_snippets = []
493 params = {}
494 where_snippets.append('pk_patient=%(pat_id)s')
495 params['pat_id'] = self.pk_patient
496 if not since is None:
497 where_snippets.append('clin_when >= %(since)s')
498 params['since'] = since
499 if not until is None:
500 where_snippets.append('clin_when <= %(until)s')
501 params['until'] = until
502
503
504
505 if not encounters is None and len(encounters) > 0:
506 params['enc'] = encounters
507 if len(encounters) > 1:
508 where_snippets.append('fk_encounter in %(enc)s')
509 else:
510 where_snippets.append('fk_encounter=%(enc)s')
511
512 if not episodes is None and len(episodes) > 0:
513 params['epi'] = episodes
514 if len(episodes) > 1:
515 where_snippets.append('fk_episode in %(epi)s')
516 else:
517 where_snippets.append('fk_episode=%(epi)s')
518
519 if not issues is None and len(issues) > 0:
520 params['issue'] = issues
521 if len(issues) > 1:
522 where_snippets.append('fk_health_issue in %(issue)s')
523 else:
524 where_snippets.append('fk_health_issue=%(issue)s')
525
526 where_clause = ' and '.join(where_snippets)
527 order_by = 'order by src_table, age'
528 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
529
530 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
531 if rows is None:
532 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
533 return None
534
535
536
537
538 items_by_table = {}
539 for item in rows:
540 src_table = item[view_col_idx['src_table']]
541 pk_item = item[view_col_idx['pk_item']]
542 if not items_by_table.has_key(src_table):
543 items_by_table[src_table] = {}
544 items_by_table[src_table][pk_item] = item
545
546
547 issues = self.get_health_issues()
548 issue_map = {}
549 for issue in issues:
550 issue_map[issue['pk_health_issue']] = issue['description']
551 episodes = self.get_episodes()
552 episode_map = {}
553 for episode in episodes:
554 episode_map[episode['pk_episode']] = episode['description']
555 emr_data = {}
556
557 ro_conn = self._conn_pool.GetConnection('historica')
558 curs = ro_conn.cursor()
559 for src_table in items_by_table.keys():
560 item_ids = items_by_table[src_table].keys()
561
562
563 if len(item_ids) == 0:
564 _log.info('no items in table [%s] ?!?' % src_table)
565 continue
566 elif len(item_ids) == 1:
567 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
568 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
569 _log.error('cannot load items from table [%s]' % src_table)
570
571 continue
572 elif len(item_ids) > 1:
573 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
574 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
575 _log.error('cannot load items from table [%s]' % src_table)
576
577 continue
578 rows = curs.fetchall()
579 table_col_idx = gmPG.get_col_indices(curs)
580
581 for row in rows:
582
583 pk_item = row[table_col_idx['pk_item']]
584 view_row = items_by_table[src_table][pk_item]
585 age = view_row[view_col_idx['age']]
586
587 try:
588 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
589 except:
590 episode_name = view_row[view_col_idx['pk_episode']]
591 try:
592 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
593 except:
594 issue_name = view_row[view_col_idx['pk_health_issue']]
595
596 if not emr_data.has_key(age):
597 emr_data[age] = []
598
599 emr_data[age].append(
600 _('%s: encounter (%s)') % (
601 view_row[view_col_idx['clin_when']],
602 view_row[view_col_idx['pk_encounter']]
603 )
604 )
605 emr_data[age].append(_('health issue: %s') % issue_name)
606 emr_data[age].append(_('episode : %s') % episode_name)
607
608
609
610 cols2ignore = [
611 'pk_audit', 'row_version', 'modified_when', 'modified_by',
612 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
613 ]
614 col_data = []
615 for col_name in table_col_idx.keys():
616 if col_name in cols2ignore:
617 continue
618 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
619 emr_data[age].append("----------------------------------------------------")
620 emr_data[age].append("-- %s from table %s" % (
621 view_row[view_col_idx['modified_string']],
622 src_table
623 ))
624 emr_data[age].append("-- written %s by %s" % (
625 view_row[view_col_idx['modified_when']],
626 view_row[view_col_idx['modified_by']]
627 ))
628 emr_data[age].append("----------------------------------------------------")
629 curs.close()
630 return emr_data
631
633 return self.pk_patient
634
636 union_query = u'\n union all\n'.join ([
637 u"""
638 SELECT ((
639 -- all relevant health issues + active episodes WITH health issue
640 SELECT COUNT(1)
641 FROM clin.v_problem_list
642 WHERE
643 pk_patient = %(pat)s
644 AND
645 pk_health_issue is not null
646 ) + (
647 -- active episodes WITHOUT health issue
648 SELECT COUNT(1)
649 FROM clin.v_problem_list
650 WHERE
651 pk_patient = %(pat)s
652 AND
653 pk_health_issue is null
654 ))""",
655 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
656 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
657 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
658 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
659 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
660 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
661
662 u"""
663 SELECT count(1)
664 from clin.v_pat_substance_intake
665 WHERE
666 pk_patient = %(pat)s
667 and is_currently_active in (null, true)
668 and intake_is_approved_of in (null, true)"""
669 ])
670
671 rows, idx = gmPG2.run_ro_queries (
672 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
673 get_col_idx = False
674 )
675
676 stats = dict (
677 problems = rows[0][0],
678 encounters = rows[1][0],
679 items = rows[2][0],
680 documents = rows[3][0],
681 results = rows[4][0],
682 stays = rows[5][0],
683 procedures = rows[6][0],
684 active_drugs = rows[7][0]
685 )
686
687 return stats
688
699
745
746
747
748 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
749 """Retrieves patient allergy items.
750
751 remove_sensitivities
752 - retrieve real allergies only, without sensitivities
753 since
754 - initial date for allergy items
755 until
756 - final date for allergy items
757 encounters
758 - list of encounters whose allergies are to be retrieved
759 episodes
760 - list of episodes whose allergies are to be retrieved
761 issues
762 - list of health issues whose allergies are to be retrieved
763 """
764 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
765 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
766 allergies = []
767 for r in rows:
768 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
769
770
771 filtered_allergies = []
772 filtered_allergies.extend(allergies)
773
774 if ID_list is not None:
775 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
776 if len(filtered_allergies) == 0:
777 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
778
779 return None
780 else:
781 return filtered_allergies
782
783 if remove_sensitivities:
784 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
785 if since is not None:
786 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
787 if until is not None:
788 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
789 if issues is not None:
790 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
791 if episodes is not None:
792 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
793 if encounters is not None:
794 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
795
796 return filtered_allergies
797
798 - def add_allergy(self, substance=None, allg_type=None, encounter_id=None, episode_id=None):
799 if encounter_id is None:
800 encounter_id = self.current_encounter['pk_encounter']
801
802 if episode_id is None:
803 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
804 epi = self.add_episode(episode_name = substance, pk_health_issue = issue['pk_health_issue'])
805 episode_id = epi['pk_episode']
806
807 new_allergy = gmAllergy.create_allergy (
808 substance = substance,
809 allg_type = allg_type,
810 encounter_id = encounter_id,
811 episode_id = episode_id
812 )
813
814 return new_allergy
815
817 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
818 args = {'pk_allg': pk_allergy}
819 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
820
830
833
834 allergy_state = property(_get_allergy_state, _set_allergy_state)
835
836
837
838 - def get_episodes(self, id_list=None, issues=None, open_status=None):
839 """Fetches from backend patient episodes.
840
841 id_list - Episodes' PKs list
842 issues - Health issues' PKs list to filter episodes by
843 open_status - return all episodes, only open or closed one(s)
844 """
845 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
846 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
847 tmp = []
848 for r in rows:
849 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
850
851
852 if (id_list is None) and (issues is None) and (open_status is None):
853 return tmp
854
855
856 filtered_episodes = []
857 filtered_episodes.extend(tmp)
858 if open_status is not None:
859 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
860
861 if issues is not None:
862 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
863
864 if id_list is not None:
865 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
866
867 return filtered_episodes
868
870 cmd = u"""SELECT distinct pk_episode
871 from clin.v_pat_items
872 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
873 args = {
874 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
875 'pat': self.pk_patient
876 }
877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
878 if len(rows) == 0:
879 return []
880 epis = []
881 for row in rows:
882 epis.append(row[0])
883 return self.get_episodes(id_list=epis)
884
885 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
886 """Add episode 'episode_name' for a patient's health issue.
887
888 - silently returns if episode already exists
889 """
890 episode = gmEMRStructItems.create_episode (
891 pk_health_issue = pk_health_issue,
892 episode_name = episode_name,
893 is_open = is_open,
894 encounter = self.current_encounter['pk_encounter']
895 )
896 return episode
897
899
900
901 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
902
903 cmd = u"""
904 SELECT pk
905 from clin.episode
906 WHERE pk = (
907 SELECT distinct on(pk_episode) pk_episode
908 from clin.v_pat_items
909 WHERE
910 pk_patient = %%(pat)s
911 and
912 modified_when = (
913 SELECT max(vpi.modified_when)
914 from clin.v_pat_items vpi
915 WHERE vpi.pk_patient = %%(pat)s
916 )
917 %s
918 -- guard against several episodes created at the same moment of time
919 limit 1
920 )""" % issue_where
921 rows, idx = gmPG2.run_ro_queries(queries = [
922 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
923 ])
924 if len(rows) != 0:
925 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
926
927
928
929 cmd = u"""
930 SELECT vpe0.pk_episode
931 from
932 clin.v_pat_episodes vpe0
933 WHERE
934 vpe0.pk_patient = %%(pat)s
935 and
936 vpe0.episode_modified_when = (
937 SELECT max(vpe1.episode_modified_when)
938 from clin.v_pat_episodes vpe1
939 WHERE vpe1.pk_episode = vpe0.pk_episode
940 )
941 %s""" % issue_where
942 rows, idx = gmPG2.run_ro_queries(queries = [
943 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
944 ])
945 if len(rows) != 0:
946 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
947
948 return None
949
952
953
954
955 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
956 """Retrieve a patient's problems.
957
958 "Problems" are the UNION of:
959
960 - issues which are .clinically_relevant
961 - episodes which are .is_open
962
963 Therefore, both an issue and the open episode
964 thereof can each be listed as a problem.
965
966 include_closed_episodes/include_irrelevant_issues will
967 include those -- which departs from the definition of
968 the problem list being "active" items only ...
969
970 episodes - episodes' PKs to filter problems by
971 issues - health issues' PKs to filter problems by
972 """
973
974
975 args = {'pat': self.pk_patient}
976
977 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
978 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
979
980
981 problems = []
982 for row in rows:
983 pk_args = {
984 u'pk_patient': self.pk_patient,
985 u'pk_health_issue': row['pk_health_issue'],
986 u'pk_episode': row['pk_episode']
987 }
988 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
989
990
991 other_rows = []
992 if include_closed_episodes:
993 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
994 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
995 other_rows.extend(rows)
996
997 if include_irrelevant_issues:
998 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
999 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1000 other_rows.extend(rows)
1001
1002 if len(other_rows) > 0:
1003 for row in other_rows:
1004 pk_args = {
1005 u'pk_patient': self.pk_patient,
1006 u'pk_health_issue': row['pk_health_issue'],
1007 u'pk_episode': row['pk_episode']
1008 }
1009 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1010
1011
1012 if (episodes is None) and (issues is None):
1013 return problems
1014
1015
1016 if issues is not None:
1017 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1018 if episodes is not None:
1019 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1020
1021 return problems
1022
1024 """
1025 Retrieve the cEpisode instance equivalent to the given problem.
1026 The problem's type attribute must be 'episode'
1027
1028 @param problem: The problem to retrieve its related episode for
1029 @type problem: A gmEMRStructItems.cProblem instance
1030 """
1031 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'episode'):
1032 return self.get_episodes(id_list=[problem['pk_episode']])[0]
1033
1034 if isinstance(problem, gmEMRStructItems.cEpisode):
1035 return problem
1036
1037 raise TypeError('cannot convert [%s] to episode' % problem)
1038
1040 """
1041 Retrieve the cIssue instance equivalent to the given problem.
1042 The problem's type attribute must be 'issue'.
1043
1044 @param problem: The problem to retrieve the corresponding issue for
1045 @type problem: A gmEMRStructItems.cProblem instance
1046 """
1047 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'issue'):
1048 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0]
1049
1050 if isinstance(problem, gmEMRStructItems.cHealthIssue):
1051 return problem
1052
1053 raise TypeError('cannot convert [%s] to health issue' % problem)
1054
1056 """Transform given problem into either episode or health issue instance.
1057 """
1058 if not isinstance(problem, gmEMRStructItems.cProblem):
1059 _log.debug(str(problem))
1060 raise TypeError, 'cannot reclass [%s] instance to problem' % type(problem)
1061 if problem['type'] == 'episode':
1062 return self.get_episodes(id_list=[problem['pk_episode']])[0]
1063 if problem['type'] == 'issue':
1064 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0]
1065 return None
1066
1067
1068
1070
1071 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1072 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1073 issues = []
1074 for row in rows:
1075 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1076 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1077
1078 if id_list is None:
1079 return issues
1080
1081 if len(id_list) == 0:
1082 raise ValueError('id_list to filter by is empty, most likely a programming error')
1083
1084 filtered_issues = []
1085 for issue in issues:
1086 if issue['pk_health_issue'] in id_list:
1087 filtered_issues.append(issue)
1088
1089 return filtered_issues
1090
1098
1101
1102
1103
1105
1106 where_parts = [u'pk_patient = %(pat)s']
1107
1108 if not include_inactive:
1109 where_parts.append(u'is_currently_active in (true, null)')
1110
1111 if not include_unapproved:
1112 where_parts.append(u'intake_is_approved_of in (true, null)')
1113
1114 if order_by is None:
1115 order_by = u''
1116 else:
1117 order_by = u'order by %s' % order_by
1118
1119 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1120 u'\nand '.join(where_parts),
1121 order_by
1122 )
1123
1124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1125
1126 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1127
1128 if episodes is not None:
1129 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1130
1131 if issues is not None:
1132 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1133
1134 return meds
1135
1144
1145
1146
1148 """Retrieves vaccination regimes the patient is on.
1149
1150 optional:
1151 * ID - PK of the vaccination regime
1152 * indications - indications we want to retrieve vaccination
1153 regimes for, must be primary language, not l10n_indication
1154 """
1155
1156 try:
1157 self.__db_cache['vaccinations']['scheduled regimes']
1158 except KeyError:
1159
1160 self.__db_cache['vaccinations']['scheduled regimes'] = []
1161 cmd = """SELECT distinct on(pk_course) pk_course
1162 FROM clin.v_vaccs_scheduled4pat
1163 WHERE pk_patient=%s"""
1164 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1165 if rows is None:
1166 _log.error('cannot retrieve scheduled vaccination courses')
1167 del self.__db_cache['vaccinations']['scheduled regimes']
1168 return None
1169
1170 for row in rows:
1171 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1172
1173
1174 filtered_regimes = []
1175 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1176 if ID is not None:
1177 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1178 if len(filtered_regimes) == 0:
1179 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1180 return []
1181 else:
1182 return filtered_regimes[0]
1183 if indications is not None:
1184 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1185
1186 return filtered_regimes
1187
1189 """Retrieves patient vaccinated indications list.
1190
1191 Note that this does NOT rely on the patient being on
1192 some schedule or other but rather works with what the
1193 patient has ACTUALLY been vaccinated against. This is
1194 deliberate !
1195 """
1196
1197
1198
1199 vaccinations = self.get_vaccinations()
1200 if vaccinations is None:
1201 _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1202 return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1203 if len(vaccinations) == 0:
1204 return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1205 v_indications = []
1206 for vacc in vaccinations:
1207 tmp = [vacc['indication'], vacc['l10n_indication']]
1208
1209 if tmp in v_indications:
1210 continue
1211 v_indications.append(tmp)
1212 return (True, v_indications)
1213
1214 - def get_vaccinations(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1215 """Retrieves list of vaccinations the patient has received.
1216
1217 optional:
1218 * ID - PK of a vaccination
1219 * indications - indications we want to retrieve vaccination
1220 items for, must be primary language, not l10n_indication
1221 * since - initial date for allergy items
1222 * until - final date for allergy items
1223 * encounters - list of encounters whose allergies are to be retrieved
1224 * episodes - list of episodes whose allergies are to be retrieved
1225 * issues - list of health issues whose allergies are to be retrieved
1226 """
1227 try:
1228 self.__db_cache['vaccinations']['vaccinated']
1229 except KeyError:
1230 self.__db_cache['vaccinations']['vaccinated'] = []
1231
1232 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1233 WHERE pk_patient=%s
1234 order by indication, date"""
1235 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1236 if rows is None:
1237 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1238 del self.__db_cache['vaccinations']['vaccinated']
1239 return None
1240
1241 vaccs_by_ind = {}
1242 for row in rows:
1243 vacc_row = {
1244 'pk_field': 'pk_vaccination',
1245 'idx': idx,
1246 'data': row
1247 }
1248 vacc = gmVaccination.cVaccination(row=vacc_row)
1249 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1250
1251 try:
1252 vaccs_by_ind[vacc['indication']].append(vacc)
1253 except KeyError:
1254 vaccs_by_ind[vacc['indication']] = [vacc]
1255
1256
1257 for ind in vaccs_by_ind.keys():
1258 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1259 for vacc in vaccs_by_ind[ind]:
1260
1261
1262 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1263 vacc['seq_no'] = seq_no
1264
1265
1266 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1267 continue
1268 if seq_no > vacc_regimes[0]['shots']:
1269 vacc['is_booster'] = True
1270 del vaccs_by_ind
1271
1272
1273 filtered_shots = []
1274 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1275 if ID is not None:
1276 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1277 if len(filtered_shots) == 0:
1278 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1279 return None
1280 else:
1281 return filtered_shots[0]
1282 if since is not None:
1283 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1284 if until is not None:
1285 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1286 if issues is not None:
1287 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1288 if episodes is not None:
1289 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1290 if encounters is not None:
1291 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1292 if indications is not None:
1293 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1294 return filtered_shots
1295
1297 """Retrieves vaccinations scheduled for a regime a patient is on.
1298
1299 The regime is referenced by its indication (not l10n)
1300
1301 * indications - List of indications (not l10n) of regimes we want scheduled
1302 vaccinations to be fetched for
1303 """
1304 try:
1305 self.__db_cache['vaccinations']['scheduled']
1306 except KeyError:
1307 self.__db_cache['vaccinations']['scheduled'] = []
1308 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1309 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1310 if rows is None:
1311 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1312 del self.__db_cache['vaccinations']['scheduled']
1313 return None
1314
1315 for row in rows:
1316 vacc_row = {
1317 'pk_field': 'pk_vacc_def',
1318 'idx': idx,
1319 'data': row
1320 }
1321 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1322
1323
1324 if indications is None:
1325 return self.__db_cache['vaccinations']['scheduled']
1326 filtered_shots = []
1327 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1328 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1329 return filtered_shots
1330
1332 try:
1333 self.__db_cache['vaccinations']['missing']
1334 except KeyError:
1335 self.__db_cache['vaccinations']['missing'] = {}
1336
1337 self.__db_cache['vaccinations']['missing']['due'] = []
1338
1339 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1340 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1341 if rows is None:
1342 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1343 return None
1344 pk_args = {'pat_id': self.pk_patient}
1345 if rows is not None:
1346 for row in rows:
1347 pk_args['indication'] = row[0]
1348 pk_args['seq_no'] = row[1]
1349 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1350
1351
1352 self.__db_cache['vaccinations']['missing']['boosters'] = []
1353
1354 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1355 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1356 if rows is None:
1357 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1358 return None
1359 pk_args = {'pat_id': self.pk_patient}
1360 if rows is not None:
1361 for row in rows:
1362 pk_args['indication'] = row[0]
1363 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1364
1365
1366 if indications is None:
1367 return self.__db_cache['vaccinations']['missing']
1368 if len(indications) == 0:
1369 return self.__db_cache['vaccinations']['missing']
1370
1371 filtered_shots = {
1372 'due': [],
1373 'boosters': []
1374 }
1375 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1376 if due_shot['indication'] in indications:
1377 filtered_shots['due'].append(due_shot)
1378 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1379 if due_shot['indication'] in indications:
1380 filtered_shots['boosters'].append(due_shot)
1381 return filtered_shots
1382
1384 """Creates a new vaccination entry in backend."""
1385 return gmVaccination.create_vaccination (
1386 patient_id = self.pk_patient,
1387 episode_id = episode['pk_episode'],
1388 encounter_id = self.current_encounter['pk_encounter'],
1389 staff_id = _me['pk_staff'],
1390 vaccine = vaccine
1391 )
1392
1393
1394
1396 return self.__encounter
1397
1399
1400
1401 if self.__encounter is None:
1402 _log.debug('first setting of active encounter in this clinical record instance')
1403 else:
1404 _log.debug('switching of active encounter')
1405
1406 if self.__encounter.is_modified():
1407 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1408 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1409
1410
1411 self.__encounter = encounter
1412 gmDispatcher.send(u'current_encounter_switched')
1413
1414
1415 self.__encounter.set_active(staff_id = _me['pk_staff'])
1416
1417 return True
1418
1419 current_encounter = property(_get_current_encounter, _set_current_encounter)
1420 active_encounter = property(_get_current_encounter, _set_current_encounter)
1421
1423
1424 if self.__activate_very_recent_encounter():
1425 return True
1426
1427 if self.__activate_fairly_recent_encounter():
1428 return True
1429 self.start_new_encounter()
1430 return True
1431
1433 """Try to attach to a "very recent" encounter if there is one.
1434
1435 returns:
1436 False: no "very recent" encounter, create new one
1437 True: success
1438 """
1439 cfg_db = gmCfg.cCfgSQL()
1440 min_ttl = cfg_db.get2 (
1441 option = u'encounter.minimum_ttl',
1442 workplace = _here.active_workplace,
1443 bias = u'user',
1444 default = u'1 hour 30 minutes'
1445 )
1446 cmd = u"""
1447 SELECT pk_encounter
1448 from clin.v_most_recent_encounters
1449 WHERE
1450 pk_patient = %s
1451 and
1452 last_affirmed > (now() - %s::interval)"""
1453 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1454
1455 if len(enc_rows) == 0:
1456 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1457 return False
1458
1459 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1460 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1461 return True
1462
1464 """Try to attach to a "fairly recent" encounter if there is one.
1465
1466 returns:
1467 False: no "fairly recent" encounter, create new one
1468 True: success
1469 """
1470 cfg_db = gmCfg.cCfgSQL()
1471 min_ttl = cfg_db.get2 (
1472 option = u'encounter.minimum_ttl',
1473 workplace = _here.active_workplace,
1474 bias = u'user',
1475 default = u'1 hour 30 minutes'
1476 )
1477 max_ttl = cfg_db.get2 (
1478 option = u'encounter.maximum_ttl',
1479 workplace = _here.active_workplace,
1480 bias = u'user',
1481 default = u'6 hours'
1482 )
1483 cmd = u"""
1484 SELECT pk_encounter
1485 from clin.v_most_recent_encounters
1486 WHERE
1487 pk_patient=%s
1488 and
1489 last_affirmed between (now() - %s::interval) and (now() - %s::interval)"""
1490 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1491
1492 if len(enc_rows) == 0:
1493 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1494 return False
1495 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1496
1497 cmd = u"""
1498 SELECT title, firstnames, lastnames, gender, dob
1499 from dem.v_basic_person WHERE pk_identity=%s"""
1500 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1501 pat = pats[0]
1502 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1503 gmTools.coalesce(pat[0], u'')[:5],
1504 pat[1][:15],
1505 pat[2][:15],
1506 pat[3],
1507 pat[4].strftime('%x'),
1508 self.pk_patient
1509 )
1510 enc = gmI18N.get_encoding()
1511 msg = _(
1512 '%s\n'
1513 '\n'
1514 "This patient's chart was worked on only recently:\n"
1515 '\n'
1516 ' %s %s - %s (%s)\n'
1517 '\n'
1518 ' Request: %s\n'
1519 ' Outcome: %s\n'
1520 '\n'
1521 'Do you want to continue that consultation\n'
1522 'or do you want to start a new one ?\n'
1523 ) % (
1524 pat_str,
1525 encounter['started'].strftime('%x').decode(enc),
1526 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1527 encounter['l10n_type'],
1528 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1529 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1530 )
1531 attach = False
1532 try:
1533 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1534 except:
1535 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1536 return False
1537 if not attach:
1538 return False
1539
1540
1541 self.current_encounter = encounter
1542
1543 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1544 return True
1545
1557
1558 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None):
1559 """Retrieves patient's encounters.
1560
1561 id_list - PKs of encounters to fetch
1562 since - initial date for encounter items, DateTime instance
1563 until - final date for encounter items, DateTime instance
1564 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1565 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1566
1567 NOTE: if you specify *both* issues and episodes
1568 you will get the *aggregate* of all encounters even
1569 if the episodes all belong to the health issues listed.
1570 IOW, the issues broaden the episode list rather than
1571 the episode list narrowing the episodes-from-issues
1572 list.
1573 Rationale: If it was the other way round it would be
1574 redundant to specify the list of issues at all.
1575 """
1576
1577 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1578 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1579 encounters = []
1580 for r in rows:
1581 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1582
1583
1584 filtered_encounters = []
1585 filtered_encounters.extend(encounters)
1586 if id_list is not None:
1587 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1588 if since is not None:
1589 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1590 if until is not None:
1591 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1592
1593 if (issues is not None) and (len(issues) > 0):
1594
1595 issues = tuple(issues)
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1614 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1615 epi_ids = map(lambda x:x[0], rows)
1616 if episodes is None:
1617 episodes = []
1618 episodes.extend(epi_ids)
1619
1620 if (episodes is not None) and (len(episodes) > 0):
1621
1622 episodes = tuple(episodes)
1623
1624
1625
1626 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1627 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1628 enc_ids = map(lambda x:x[0], rows)
1629 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1630
1631 return filtered_encounters
1632
1634 """Retrieves first encounter for a particular issue and/or episode
1635
1636 issue_id - First encounter associated health issue
1637 episode - First encounter associated episode
1638 """
1639
1640
1641 if issue_id is None:
1642 issues = None
1643 else:
1644 issues = [issue_id]
1645
1646 if episode_id is None:
1647 episodes = None
1648 else:
1649 episodes = [episode_id]
1650
1651 encounters = self.get_encounters(issues=issues, episodes=episodes)
1652 if len(encounters) == 0:
1653 return None
1654
1655
1656 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1657 return encounters[0]
1658
1660 """Retrieves last encounter for a concrete issue and/or episode
1661
1662 issue_id - Last encounter associated health issue
1663 episode_id - Last encounter associated episode
1664 """
1665
1666
1667 if issue_id is None:
1668 issues = None
1669 else:
1670 issues = [issue_id]
1671
1672 if episode_id is None:
1673 episodes = None
1674 else:
1675 episodes = [episode_id]
1676
1677 encounters = self.get_encounters(issues=issues, episodes=episodes)
1678 if len(encounters) == 0:
1679 return None
1680
1681
1682 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1683 return encounters[-1]
1684
1686
1687 args = {'pat': self.pk_patient}
1688
1689 if (issue_id is None) and (episode_id is None):
1690
1691 cmd = u"""
1692 SELECT * FROM clin.v_pat_encounters
1693 WHERE pk_patient = %(pat)s
1694 order by started desc
1695 limit 2
1696 """
1697 else:
1698 where_parts = []
1699
1700 if issue_id is not None:
1701 where_parts.append(u'pk_health_issue = %(issue)s')
1702 args['issue'] = issue_id
1703
1704 if episode_id is not None:
1705 where_parts.append(u'pk_episode = %(epi)s')
1706 args['epi'] = episode_id
1707
1708 cmd = u"""
1709 SELECT *
1710 from clin.v_pat_encounters
1711 WHERE
1712 pk_patient = %%(pat)s
1713 and
1714 pk_encounter in (
1715 SELECT distinct pk_encounter
1716 from clin.v_pat_narrative
1717 WHERE
1718 %s
1719 )
1720 order by started desc
1721 limit 2
1722 """ % u' and '.join(where_parts)
1723
1724 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1725
1726 if len(rows) == 0:
1727 return None
1728
1729
1730 if len(rows) == 1:
1731
1732 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1733
1734 return None
1735
1736 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1737
1738
1739 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1740 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1741
1742 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1743
1745 cfg_db = gmCfg.cCfgSQL()
1746 ttl = cfg_db.get2 (
1747 option = u'encounter.ttl_if_empty',
1748 workplace = _here.active_workplace,
1749 bias = u'user',
1750 default = u'1 week'
1751 )
1752
1753
1754 cmd = u"""
1755 delete FROM clin.encounter
1756 WHERE
1757 clin.encounter.fk_patient = %(pat)s
1758 and
1759 age(clin.encounter.last_affirmed) > %(ttl)s::interval
1760 and
1761 not exists (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
1762 and
1763 not exists (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk)
1764 and
1765 not exists (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk)
1766 and
1767 not exists (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk)
1768 and
1769 not exists (SELECT 1 FROM clin.operation WHERE fk_encounter = clin.encounter.pk)
1770 and
1771 not exists (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk)
1772 """
1773 try:
1774 rows, idx = gmPG2.run_rw_queries(queries = [{
1775 'cmd': cmd,
1776 'args': {'pat': self.pk_patient, 'ttl': ttl}
1777 }])
1778 except:
1779 _log.exception('error deleting empty encounters')
1780
1781 return True
1782
1783
1784
1785
1787 """Retrieve data about test types for which this patient has results."""
1788
1789 cmd = u"""
1790 SELECT * FROM (
1791 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
1792 FROM clin.v_test_results
1793 WHERE pk_patient = %(pat)s
1794 ) AS foo
1795 ORDER BY clin_when desc, unified_name
1796 """
1797 args = {'pat': self.pk_patient}
1798 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1799 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1800
1802 """Retrieve details on tests grouped under unified names for this patient's results."""
1803 cmd = u"""
1804 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
1805 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
1806 from clin.v_test_results
1807 WHERE pk_patient = %(pat)s
1808 )
1809 order by unified_name"""
1810 args = {'pat': self.pk_patient}
1811 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1812 return rows, idx
1813
1815 """Get the dates for which we have results."""
1816 cmd = u"""
1817 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
1818 from clin.v_test_results
1819 WHERE pk_patient = %(pat)s
1820 order by cwhen desc"""
1821 args = {'pat': self.pk_patient}
1822 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1823 return rows
1824
1826
1827 cmd = u"""
1828 SELECT *, xmin_test_result FROM clin.v_test_results
1829 WHERE pk_patient = %(pat)s
1830 order by clin_when desc, pk_episode, unified_name"""
1831 args = {'pat': self.pk_patient}
1832 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1833
1834 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1835
1836 if episodes is not None:
1837 tests = [ t for t in tests if t['pk_episode'] in episodes ]
1838
1839 if encounter is not None:
1840 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
1841
1842 return tests
1843
1844 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1845
1846 try:
1847 epi = int(episode)
1848 except:
1849 epi = episode['pk_episode']
1850
1851 try:
1852 type = int(type)
1853 except:
1854 type = type['pk_test_type']
1855
1856 if intended_reviewer is None:
1857 from Gnumed.business import gmPerson
1858 intended_reviewer = _me['pk_staff']
1859
1860 tr = gmPathLab.create_test_result (
1861 encounter = self.current_encounter['pk_encounter'],
1862 episode = epi,
1863 type = type,
1864 intended_reviewer = intended_reviewer,
1865 val_num = val_num,
1866 val_alpha = val_alpha,
1867 unit = unit
1868 )
1869
1870 return tr
1871
1872
1873 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1874 """Retrieves lab result clinical items.
1875
1876 limit - maximum number of results to retrieve
1877 since - initial date
1878 until - final date
1879 encounters - list of encounters
1880 episodes - list of episodes
1881 issues - list of health issues
1882 """
1883 try:
1884 return self.__db_cache['lab results']
1885 except KeyError:
1886 pass
1887 self.__db_cache['lab results'] = []
1888 if limit is None:
1889 lim = ''
1890 else:
1891
1892 if since is None and until is None and encounters is None and episodes is None and issues is None:
1893 lim = "limit %s" % limit
1894 else:
1895 lim = ''
1896
1897 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
1898 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1899 if rows is None:
1900 return False
1901 for row in rows:
1902 lab_row = {
1903 'pk_field': 'pk_result',
1904 'idx': idx,
1905 'data': row
1906 }
1907 lab_result = gmPathLab.cLabResult(row=lab_row)
1908 self.__db_cache['lab results'].append(lab_result)
1909
1910
1911 filtered_lab_results = []
1912 filtered_lab_results.extend(self.__db_cache['lab results'])
1913 if since is not None:
1914 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
1915 if until is not None:
1916 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
1917 if issues is not None:
1918 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
1919 if episodes is not None:
1920 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
1921 if encounters is not None:
1922 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
1923 return filtered_lab_results
1924
1929
1930 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
1944
1945
1946
1951
1952
1953
1954 if __name__ == "__main__":
1955
1956 from Gnumed.pycommon import gmLog2
1957
1958
1972
1979
1986
1988 emr = cClinicalRecord(aPKey=12)
1989 rows, idx = emr.get_measurements_by_date()
1990 print "test results:"
1991 for row in rows:
1992 print row
1993
2000
2007
2012
2014 emr = cClinicalRecord(aPKey=12)
2015
2016 probs = emr.get_problems()
2017 print "normal probs (%s):" % len(probs)
2018 for p in probs:
2019 print u'%s (%s)' % (p['problem'], p['type'])
2020
2021 probs = emr.get_problems(include_closed_episodes=True)
2022 print "probs + closed episodes (%s):" % len(probs)
2023 for p in probs:
2024 print u'%s (%s)' % (p['problem'], p['type'])
2025
2026 probs = emr.get_problems(include_irrelevant_issues=True)
2027 print "probs + issues (%s):" % len(probs)
2028 for p in probs:
2029 print u'%s (%s)' % (p['problem'], p['type'])
2030
2031 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2032 print "probs + issues + epis (%s):" % len(probs)
2033 for p in probs:
2034 print u'%s (%s)' % (p['problem'], p['type'])
2035
2037 emr = cClinicalRecord(aPKey=12)
2038 tr = emr.add_test_result (
2039 episode = 1,
2040 intended_reviewer = 1,
2041 type = 1,
2042 val_num = 75,
2043 val_alpha = u'somewhat obese',
2044 unit = u'kg'
2045 )
2046 print tr
2047
2051
2056
2061
2062 if (len(sys.argv) > 0) and (sys.argv[1] == 'test'):
2063
2064 test_get_test_names()
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
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
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259