1
2 """GNUmed clinical patient record.
3
4 Make sure to call set_func_ask_user() and set_encounter_ttl() early on in
5 your code (before cClinicalRecord.__init__() is called for the first time).
6 """
7
8 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later"
10
11
12 import sys
13 import logging
14
15
16 if __name__ == '__main__':
17 sys.path.insert(0, '../../')
18 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
19 gmI18N.activate_locale()
20 gmI18N.install_domain()
21 gmDateTime.init()
22
23 from Gnumed.pycommon import gmExceptions
24 from Gnumed.pycommon import gmPG2
25 from Gnumed.pycommon import gmDispatcher
26 from Gnumed.pycommon import gmI18N
27 from Gnumed.pycommon import gmCfg
28 from Gnumed.pycommon import gmTools
29 from Gnumed.pycommon import gmDateTime
30
31 from Gnumed.business import gmAllergy
32 from Gnumed.business import gmPathLab
33 from Gnumed.business import gmLOINC
34 from Gnumed.business import gmClinNarrative
35 from Gnumed.business import gmEMRStructItems
36 from Gnumed.business import gmMedication
37 from Gnumed.business import gmVaccination
38 from Gnumed.business import gmFamilyHistory
39 from Gnumed.business.gmDemographicRecord import get_occupations
40
41
42 _log = logging.getLogger('gm.emr')
43
44 _here = None
45
46
47
48 _func_ask_user = None
49
51 if not callable(a_func):
52 _log.error('[%] not callable, not setting _func_ask_user', a_func)
53 return False
54
55 _log.debug('setting _func_ask_user to [%s]', a_func)
56
57 global _func_ask_user
58 _func_ask_user = a_func
59
60
62
63 - def __init__(self, aPKey=None, allow_user_interaction=True):
104
107
109 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
110 return True
111
112
113
118
120
121
122
123 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
124
125
126
127
128
129
130
131
132
133
134
135
136
137 if self.current_encounter.is_modified():
138 _log.debug('unsaved changes in active encounter, cannot switch to another one')
139 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
140
141 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
142 _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
143 return True
144
145
146
147
148
149 _log.debug('active encounter modified remotely, reloading and announcing the modification')
150 self.current_encounter.refetch_payload()
151 gmDispatcher.send(u'current_encounter_modified')
152
153 return True
154
155
156
157 - def get_family_history(self, episodes=None, issues=None, encounters=None):
158 fhx = gmFamilyHistory.get_family_history (
159 order_by = u'l10n_relation, condition',
160 patient = self.pk_patient
161 )
162
163 if episodes is not None:
164 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
165
166 if issues is not None:
167 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
168
169 if encounters is not None:
170 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
171
172 return fhx
173
174 - def add_family_history(self, episode=None, condition=None, relation=None):
175 return gmFamilyHistory.create_family_history (
176 encounter = self.current_encounter['pk_encounter'],
177 episode = episode,
178 condition = condition,
179 relation = relation
180 )
181
182
183
195
196 performed_procedures = property(get_performed_procedures, lambda x:x)
197
200
209
210
211
219
220 hospital_stays = property(get_hospital_stays, lambda x:x)
221
224
231
233 args = {'pat': self.pk_patient, 'range': cover_period}
234 where_parts = [u'pk_patient = %(pat)s']
235 if cover_period is not None:
236 where_parts.append(u'discharge > (now() - %(range)s)')
237
238 cmd = u"""
239 SELECT hospital, count(1) AS frequency
240 FROM clin.v_hospital_stays
241 WHERE
242 %s
243 GROUP BY hospital
244 ORDER BY frequency DESC
245 """ % u' AND '.join(where_parts)
246
247 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
248 return rows
249
250
251
252 - def add_notes(self, notes=None, episode=None, encounter=None):
268
285
286 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
287 """Get SOAP notes pertinent to this encounter.
288
289 since
290 - initial date for narrative items
291 until
292 - final date for narrative items
293 encounters
294 - list of encounters whose narrative are to be retrieved
295 episodes
296 - list of episodes whose narrative are to be retrieved
297 issues
298 - list of health issues whose narrative are to be retrieved
299 soap_cats
300 - list of SOAP categories of the narrative to be retrieved
301 """
302 where_parts = [u'pk_patient = %(pat)s']
303 args = {u'pat': self.pk_patient}
304
305 if issues is not None:
306 where_parts.append(u'pk_health_issue IN %(issues)s')
307 args['issues'] = tuple(issues)
308
309 if episodes is not None:
310 where_parts.append(u'pk_episode IN %(epis)s')
311 args['epis'] = tuple(episodes)
312
313 if encounters is not None:
314 where_parts.append(u'pk_encounter IN %(encs)s')
315 args['encs'] = tuple(encounters)
316
317 if soap_cats is not None:
318 where_parts.append(u'c_vn.soap_cat IN %(cats)s')
319 soap_cats = list(soap_cats)
320 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
321 if None in soap_cats:
322 args['cats'].append(None)
323 args['cats'] = tuple(args['cats'])
324
325 cmd = u"""
326 SELECT
327 c_vn.*,
328 c_scr.rank AS soap_rank
329 FROM
330 clin.v_narrative c_vn
331 LEFT JOIN clin.soap_cat_ranks c_scr on c_vn.soap_cat = c_scr.soap_cat
332 WHERE %s
333 ORDER BY date, soap_rank
334 """ % u' AND '.join(where_parts)
335
336 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
337
338 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
339
340 if since is not None:
341 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
342
343 if until is not None:
344 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
345
346 if providers is not None:
347 filtered_narrative = filter(lambda narr: narr['modified_by'] in providers, filtered_narrative)
348
349 return filtered_narrative
350
351 - def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
352 return gmClinNarrative.get_as_journal (
353 patient = self.pk_patient,
354 since = since,
355 until = until,
356 encounters = encounters,
357 episodes = episodes,
358 issues = issues,
359 soap_cats = soap_cats,
360 providers = providers,
361 order_by = order_by,
362 time_range = time_range
363 )
364
366
367 search_term = search_term.strip()
368 if search_term == '':
369 return []
370
371 cmd = u"""
372 SELECT
373 *,
374 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
375 as episode,
376 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
377 as health_issue,
378 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
379 as encounter_started,
380 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
381 as encounter_ended,
382 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
383 as encounter_type
384 from clin.v_narrative4search vn4s
385 WHERE
386 pk_patient = %(pat)s and
387 vn4s.narrative ~ %(term)s
388 order by
389 encounter_started
390 """
391 rows, idx = gmPG2.run_ro_queries(queries = [
392 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
393 ])
394 return rows
395
396 - def get_text_dump(self, since=None, until=None, encounters=None, episodes=None, issues=None):
397 fields = [
398 'age',
399 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
400 'modified_by',
401 'clin_when',
402 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
403 'pk_item',
404 'pk_encounter',
405 'pk_episode',
406 'pk_health_issue',
407 'src_table'
408 ]
409 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
410
411 where_snippets = []
412 params = {}
413 where_snippets.append('pk_patient=%(pat_id)s')
414 params['pat_id'] = self.pk_patient
415 if not since is None:
416 where_snippets.append('clin_when >= %(since)s')
417 params['since'] = since
418 if not until is None:
419 where_snippets.append('clin_when <= %(until)s')
420 params['until'] = until
421
422
423
424 if not encounters is None and len(encounters) > 0:
425 params['enc'] = encounters
426 if len(encounters) > 1:
427 where_snippets.append('fk_encounter in %(enc)s')
428 else:
429 where_snippets.append('fk_encounter=%(enc)s')
430
431 if not episodes is None and len(episodes) > 0:
432 params['epi'] = episodes
433 if len(episodes) > 1:
434 where_snippets.append('fk_episode in %(epi)s')
435 else:
436 where_snippets.append('fk_episode=%(epi)s')
437
438 if not issues is None and len(issues) > 0:
439 params['issue'] = issues
440 if len(issues) > 1:
441 where_snippets.append('fk_health_issue in %(issue)s')
442 else:
443 where_snippets.append('fk_health_issue=%(issue)s')
444
445 where_clause = ' and '.join(where_snippets)
446 order_by = 'order by src_table, age'
447 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
448
449 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
450 if rows is None:
451 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
452 return None
453
454
455
456
457 items_by_table = {}
458 for item in rows:
459 src_table = item[view_col_idx['src_table']]
460 pk_item = item[view_col_idx['pk_item']]
461 if not items_by_table.has_key(src_table):
462 items_by_table[src_table] = {}
463 items_by_table[src_table][pk_item] = item
464
465
466 issues = self.get_health_issues()
467 issue_map = {}
468 for issue in issues:
469 issue_map[issue['pk_health_issue']] = issue['description']
470 episodes = self.get_episodes()
471 episode_map = {}
472 for episode in episodes:
473 episode_map[episode['pk_episode']] = episode['description']
474 emr_data = {}
475
476 ro_conn = self._conn_pool.GetConnection('historica')
477 curs = ro_conn.cursor()
478 for src_table in items_by_table.keys():
479 item_ids = items_by_table[src_table].keys()
480
481
482 if len(item_ids) == 0:
483 _log.info('no items in table [%s] ?!?' % src_table)
484 continue
485 elif len(item_ids) == 1:
486 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
487 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
488 _log.error('cannot load items from table [%s]' % src_table)
489
490 continue
491 elif len(item_ids) > 1:
492 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
493 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
494 _log.error('cannot load items from table [%s]' % src_table)
495
496 continue
497 rows = curs.fetchall()
498 table_col_idx = gmPG.get_col_indices(curs)
499
500 for row in rows:
501
502 pk_item = row[table_col_idx['pk_item']]
503 view_row = items_by_table[src_table][pk_item]
504 age = view_row[view_col_idx['age']]
505
506 try:
507 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
508 except:
509 episode_name = view_row[view_col_idx['pk_episode']]
510 try:
511 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
512 except:
513 issue_name = view_row[view_col_idx['pk_health_issue']]
514
515 if not emr_data.has_key(age):
516 emr_data[age] = []
517
518 emr_data[age].append(
519 _('%s: encounter (%s)') % (
520 view_row[view_col_idx['clin_when']],
521 view_row[view_col_idx['pk_encounter']]
522 )
523 )
524 emr_data[age].append(_('health issue: %s') % issue_name)
525 emr_data[age].append(_('episode : %s') % episode_name)
526
527
528
529 cols2ignore = [
530 'pk_audit', 'row_version', 'modified_when', 'modified_by',
531 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
532 ]
533 col_data = []
534 for col_name in table_col_idx.keys():
535 if col_name in cols2ignore:
536 continue
537 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
538 emr_data[age].append("----------------------------------------------------")
539 emr_data[age].append("-- %s from table %s" % (
540 view_row[view_col_idx['modified_string']],
541 src_table
542 ))
543 emr_data[age].append("-- written %s by %s" % (
544 view_row[view_col_idx['modified_when']],
545 view_row[view_col_idx['modified_by']]
546 ))
547 emr_data[age].append("----------------------------------------------------")
548 curs.close()
549 return emr_data
550
552 return self.pk_patient
553
555 union_query = u'\n union all\n'.join ([
556 u"""
557 SELECT ((
558 -- all relevant health issues + active episodes WITH health issue
559 SELECT COUNT(1)
560 FROM clin.v_problem_list
561 WHERE
562 pk_patient = %(pat)s
563 AND
564 pk_health_issue is not null
565 ) + (
566 -- active episodes WITHOUT health issue
567 SELECT COUNT(1)
568 FROM clin.v_problem_list
569 WHERE
570 pk_patient = %(pat)s
571 AND
572 pk_health_issue is null
573 ))""",
574 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
575 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
576 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
577 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
578 u'SELECT count(1) FROM clin.v_hospital_stays WHERE pk_patient = %(pat)s',
579 u'SELECT count(1) FROM clin.v_procedures WHERE pk_patient = %(pat)s',
580
581 u"""
582 SELECT count(1)
583 FROM clin.v_substance_intakes
584 WHERE
585 pk_patient = %(pat)s
586 AND
587 is_currently_active IN (null, true)
588 AND
589 intake_is_approved_of IN (null, true)""",
590 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
591 ])
592
593 rows, idx = gmPG2.run_ro_queries (
594 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
595 get_col_idx = False
596 )
597
598 stats = dict (
599 problems = rows[0][0],
600 encounters = rows[1][0],
601 items = rows[2][0],
602 documents = rows[3][0],
603 results = rows[4][0],
604 stays = rows[5][0],
605 procedures = rows[6][0],
606 active_drugs = rows[7][0],
607 vaccinations = rows[8][0]
608 )
609
610 return stats
611
624
742
763
764
765
766 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
767 """Retrieves patient allergy items.
768
769 remove_sensitivities
770 - retrieve real allergies only, without sensitivities
771 since
772 - initial date for allergy items
773 until
774 - final date for allergy items
775 encounters
776 - list of encounters whose allergies are to be retrieved
777 episodes
778 - list of episodes whose allergies are to be retrieved
779 issues
780 - list of health issues whose allergies are to be retrieved
781 """
782 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
783 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
784 allergies = []
785 for r in rows:
786 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
787
788
789 filtered_allergies = []
790 filtered_allergies.extend(allergies)
791
792 if ID_list is not None:
793 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
794 if len(filtered_allergies) == 0:
795 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
796
797 return None
798 else:
799 return filtered_allergies
800
801 if remove_sensitivities:
802 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
803 if since is not None:
804 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
805 if until is not None:
806 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
807 if issues is not None:
808 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
809 if episodes is not None:
810 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
811 if encounters is not None:
812 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
813
814 return filtered_allergies
815
816 - def add_allergy(self, allergene=None, allg_type=None, encounter_id=None, episode_id=None):
817 if encounter_id is None:
818 encounter_id = self.current_encounter['pk_encounter']
819
820 if episode_id is None:
821 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
822 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
823 episode_id = epi['pk_episode']
824
825 new_allergy = gmAllergy.create_allergy (
826 allergene = allergene,
827 allg_type = allg_type,
828 encounter_id = encounter_id,
829 episode_id = episode_id
830 )
831
832 return new_allergy
833
835 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
836 args = {'pk_allg': pk_allergy}
837 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
838
840 """Cave: only use with one potential allergic agent
841 otherwise you won't know which of the agents the allergy is to."""
842
843
844 if self.allergy_state is None:
845 return None
846
847
848 if self.allergy_state == 0:
849 return False
850
851 args = {
852 'atcs': atcs,
853 'inns': inns,
854 'brand': brand,
855 'pat': self.pk_patient
856 }
857 allergenes = []
858 where_parts = []
859
860 if len(atcs) == 0:
861 atcs = None
862 if atcs is not None:
863 where_parts.append(u'atc_code in %(atcs)s')
864 if len(inns) == 0:
865 inns = None
866 if inns is not None:
867 where_parts.append(u'generics in %(inns)s')
868 allergenes.extend(inns)
869 if brand is not None:
870 where_parts.append(u'substance = %(brand)s')
871 allergenes.append(brand)
872
873 if len(allergenes) != 0:
874 where_parts.append(u'allergene in %(allgs)s')
875 args['allgs'] = tuple(allergenes)
876
877 cmd = u"""
878 SELECT * FROM clin.v_pat_allergies
879 WHERE
880 pk_patient = %%(pat)s
881 AND ( %s )""" % u' OR '.join(where_parts)
882
883 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
884
885 if len(rows) == 0:
886 return False
887
888 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
889
899
902
903 allergy_state = property(_get_allergy_state, _set_allergy_state)
904
905
906
907 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
908 """Fetches from backend patient episodes.
909
910 id_list - Episodes' PKs list
911 issues - Health issues' PKs list to filter episodes by
912 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
913 """
914 if (unlinked_only is True) and (issues is not None):
915 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
916
917 if order_by is None:
918 order_by = u''
919 else:
920 order_by = u'ORDER BY %s' % order_by
921
922 args = {
923 'pat': self.pk_patient,
924 'open': open_status
925 }
926 where_parts = [u'pk_patient = %(pat)s']
927
928 if open_status is not None:
929 where_parts.append(u'episode_open IS %(open)s')
930
931 if unlinked_only:
932 where_parts.append(u'pk_health_issue is NULL')
933
934 if issues is not None:
935 where_parts.append(u'pk_health_issue IN %(issues)s')
936 args['issues'] = tuple(issues)
937
938 if id_list is not None:
939 where_parts.append(u'pk_episode IN %(epis)s')
940 args['epis'] = tuple(id_list)
941
942 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
943 u' AND '.join(where_parts),
944 order_by
945 )
946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
947
948 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
949
950 episodes = property(get_episodes, lambda x:x)
951
953 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
954
955 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
956
958 cmd = u"""SELECT distinct pk_episode
959 from clin.v_pat_items
960 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
961 args = {
962 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
963 'pat': self.pk_patient
964 }
965 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
966 if len(rows) == 0:
967 return []
968 epis = []
969 for row in rows:
970 epis.append(row[0])
971 return self.get_episodes(id_list=epis)
972
973 - def add_episode(self, episode_name=None, pk_health_issue=None, is_open=False):
985
987
988
989 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
990
991 cmd = u"""
992 SELECT pk
993 from clin.episode
994 WHERE pk = (
995 SELECT distinct on(pk_episode) pk_episode
996 from clin.v_pat_items
997 WHERE
998 pk_patient = %%(pat)s
999 and
1000 modified_when = (
1001 SELECT max(vpi.modified_when)
1002 from clin.v_pat_items vpi
1003 WHERE vpi.pk_patient = %%(pat)s
1004 )
1005 %s
1006 -- guard against several episodes created at the same moment of time
1007 limit 1
1008 )""" % issue_where
1009 rows, idx = gmPG2.run_ro_queries(queries = [
1010 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1011 ])
1012 if len(rows) != 0:
1013 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1014
1015
1016
1017 cmd = u"""
1018 SELECT vpe0.pk_episode
1019 from
1020 clin.v_pat_episodes vpe0
1021 WHERE
1022 vpe0.pk_patient = %%(pat)s
1023 and
1024 vpe0.episode_modified_when = (
1025 SELECT max(vpe1.episode_modified_when)
1026 from clin.v_pat_episodes vpe1
1027 WHERE vpe1.pk_episode = vpe0.pk_episode
1028 )
1029 %s""" % issue_where
1030 rows, idx = gmPG2.run_ro_queries(queries = [
1031 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1032 ])
1033 if len(rows) != 0:
1034 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1035
1036 return None
1037
1040
1041
1042
1043 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1044 """Retrieve a patient's problems.
1045
1046 "Problems" are the UNION of:
1047
1048 - issues which are .clinically_relevant
1049 - episodes which are .is_open
1050
1051 Therefore, both an issue and the open episode
1052 thereof can each be listed as a problem.
1053
1054 include_closed_episodes/include_irrelevant_issues will
1055 include those -- which departs from the definition of
1056 the problem list being "active" items only ...
1057
1058 episodes - episodes' PKs to filter problems by
1059 issues - health issues' PKs to filter problems by
1060 """
1061
1062
1063 args = {'pat': self.pk_patient}
1064
1065 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1066 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1067
1068
1069 problems = []
1070 for row in rows:
1071 pk_args = {
1072 u'pk_patient': self.pk_patient,
1073 u'pk_health_issue': row['pk_health_issue'],
1074 u'pk_episode': row['pk_episode']
1075 }
1076 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1077
1078
1079 other_rows = []
1080 if include_closed_episodes:
1081 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1082 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1083 other_rows.extend(rows)
1084
1085 if include_irrelevant_issues:
1086 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1087 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1088 other_rows.extend(rows)
1089
1090 if len(other_rows) > 0:
1091 for row in other_rows:
1092 pk_args = {
1093 u'pk_patient': self.pk_patient,
1094 u'pk_health_issue': row['pk_health_issue'],
1095 u'pk_episode': row['pk_episode']
1096 }
1097 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1098
1099
1100 if (episodes is None) and (issues is None):
1101 return problems
1102
1103
1104 if issues is not None:
1105 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1106 if episodes is not None:
1107 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1108
1109 return problems
1110
1113
1116
1119
1120
1121
1123
1124 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1125 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1126 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1127
1128 if id_list is None:
1129 return issues
1130
1131 if len(id_list) == 0:
1132 raise ValueError('id_list to filter by is empty, most likely a programming error')
1133
1134 filtered_issues = []
1135 for issue in issues:
1136 if issue['pk_health_issue'] in id_list:
1137 filtered_issues.append(issue)
1138
1139 return filtered_issues
1140
1141 health_issues = property(get_health_issues, lambda x:x)
1142
1150
1153
1154
1155
1157
1158 where_parts = [u'pk_patient = %(pat)s']
1159 args = {'pat': self.pk_patient}
1160
1161 if not include_inactive:
1162 where_parts.append(u'is_currently_active IN (true, null)')
1163
1164 if not include_unapproved:
1165 where_parts.append(u'intake_is_approved_of IN (true, null)')
1166
1167 if order_by is None:
1168 order_by = u''
1169 else:
1170 order_by = u'ORDER BY %s' % order_by
1171
1172 cmd = u"SELECT * FROM clin.v_substance_intakes WHERE %s %s" % (
1173 u'\nAND '.join(where_parts),
1174 order_by
1175 )
1176 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1177 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1178
1179 if episodes is not None:
1180 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1181
1182 if issues is not None:
1183 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1184
1185 return meds
1186
1187 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1195
1202
1203
1204
1212
1214 """Returns latest given vaccination for each vaccinated indication.
1215
1216 as a dict {'l10n_indication': cVaccination instance}
1217
1218 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1219 """
1220
1221 args = {'pat': self.pk_patient}
1222 where_parts = [u'pk_patient = %(pat)s']
1223
1224 if (episodes is not None) and (len(episodes) > 0):
1225 where_parts.append(u'pk_episode IN %(epis)s')
1226 args['epis'] = tuple(episodes)
1227
1228 if (issues is not None) and (len(issues) > 0):
1229 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1230 args['issues'] = tuple(issues)
1231
1232 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1233 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1234
1235
1236 if len(rows) == 0:
1237 return {}
1238
1239 vpks = [ ind['pk_vaccination'] for ind in rows ]
1240 vinds = [ ind['l10n_indication'] for ind in rows ]
1241 ind_counts = [ ind['indication_count'] for ind in rows ]
1242
1243
1244 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1245 args = {'pks': tuple(vpks)}
1246 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1247
1248 vaccs = {}
1249 for idx in range(len(vpks)):
1250 pk = vpks[idx]
1251 ind_count = ind_counts[idx]
1252 for r in rows:
1253 if r['pk_vaccination'] == pk:
1254 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1255
1256 return vaccs
1257
1258 - def get_vaccinations(self, order_by=None, episodes=None, issues=None, encounters=None):
1259
1260 args = {'pat': self.pk_patient}
1261 where_parts = [u'pk_patient = %(pat)s']
1262
1263 if order_by is None:
1264 order_by = u''
1265 else:
1266 order_by = u'ORDER BY %s' % order_by
1267
1268 if (episodes is not None) and (len(episodes) > 0):
1269 where_parts.append(u'pk_episode IN %(epis)s')
1270 args['epis'] = tuple(episodes)
1271
1272 if (issues is not None) and (len(issues) > 0):
1273 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1274 args['issues'] = tuple(issues)
1275
1276 if (encounters is not None) and (len(encounters) > 0):
1277 where_parts.append(u'pk_encounter IN %(encs)s')
1278 args['encs'] = tuple(encounters)
1279
1280 cmd = u'%s %s' % (
1281 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1282 order_by
1283 )
1284 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1285 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1286
1287 return vaccs
1288
1289 vaccinations = property(get_vaccinations, lambda x:x)
1290
1291
1292
1294 """Retrieves vaccination regimes the patient is on.
1295
1296 optional:
1297 * ID - PK of the vaccination regime
1298 * indications - indications we want to retrieve vaccination
1299 regimes for, must be primary language, not l10n_indication
1300 """
1301
1302
1303 cmd = """SELECT distinct on(pk_course) pk_course
1304 FROM clin.v_vaccs_scheduled4pat
1305 WHERE pk_patient=%s"""
1306 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1307 if rows is None:
1308 _log.error('cannot retrieve scheduled vaccination courses')
1309 return None
1310
1311 for row in rows:
1312 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1313
1314
1315 filtered_regimes = []
1316 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1317 if ID is not None:
1318 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1319 if len(filtered_regimes) == 0:
1320 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1321 return []
1322 else:
1323 return filtered_regimes[0]
1324 if indications is not None:
1325 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1326
1327 return filtered_regimes
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1356 """Retrieves list of vaccinations the patient has received.
1357
1358 optional:
1359 * ID - PK of a vaccination
1360 * indications - indications we want to retrieve vaccination
1361 items for, must be primary language, not l10n_indication
1362 * since - initial date for allergy items
1363 * until - final date for allergy items
1364 * encounters - list of encounters whose allergies are to be retrieved
1365 * episodes - list of episodes whose allergies are to be retrieved
1366 * issues - list of health issues whose allergies are to be retrieved
1367 """
1368 try:
1369 self.__db_cache['vaccinations']['vaccinated']
1370 except KeyError:
1371 self.__db_cache['vaccinations']['vaccinated'] = []
1372
1373 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1374 WHERE pk_patient=%s
1375 order by indication, date"""
1376 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1377 if rows is None:
1378 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1379 del self.__db_cache['vaccinations']['vaccinated']
1380 return None
1381
1382 vaccs_by_ind = {}
1383 for row in rows:
1384 vacc_row = {
1385 'pk_field': 'pk_vaccination',
1386 'idx': idx,
1387 'data': row
1388 }
1389 vacc = gmVaccination.cVaccination(row=vacc_row)
1390 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1391
1392 try:
1393 vaccs_by_ind[vacc['indication']].append(vacc)
1394 except KeyError:
1395 vaccs_by_ind[vacc['indication']] = [vacc]
1396
1397
1398 for ind in vaccs_by_ind.keys():
1399 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1400 for vacc in vaccs_by_ind[ind]:
1401
1402
1403 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1404 vacc['seq_no'] = seq_no
1405
1406
1407 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1408 continue
1409 if seq_no > vacc_regimes[0]['shots']:
1410 vacc['is_booster'] = True
1411 del vaccs_by_ind
1412
1413
1414 filtered_shots = []
1415 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1416 if ID is not None:
1417 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1418 if len(filtered_shots) == 0:
1419 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1420 return None
1421 else:
1422 return filtered_shots[0]
1423 if since is not None:
1424 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1425 if until is not None:
1426 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1427 if issues is not None:
1428 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1429 if episodes is not None:
1430 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1431 if encounters is not None:
1432 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1433 if indications is not None:
1434 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1435 return filtered_shots
1436
1438 """Retrieves vaccinations scheduled for a regime a patient is on.
1439
1440 The regime is referenced by its indication (not l10n)
1441
1442 * indications - List of indications (not l10n) of regimes we want scheduled
1443 vaccinations to be fetched for
1444 """
1445 try:
1446 self.__db_cache['vaccinations']['scheduled']
1447 except KeyError:
1448 self.__db_cache['vaccinations']['scheduled'] = []
1449 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1450 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1451 if rows is None:
1452 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1453 del self.__db_cache['vaccinations']['scheduled']
1454 return None
1455
1456 for row in rows:
1457 vacc_row = {
1458 'pk_field': 'pk_vacc_def',
1459 'idx': idx,
1460 'data': row
1461 }
1462 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1463
1464
1465 if indications is None:
1466 return self.__db_cache['vaccinations']['scheduled']
1467 filtered_shots = []
1468 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1469 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1470 return filtered_shots
1471
1473 try:
1474 self.__db_cache['vaccinations']['missing']
1475 except KeyError:
1476 self.__db_cache['vaccinations']['missing'] = {}
1477
1478 self.__db_cache['vaccinations']['missing']['due'] = []
1479
1480 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1481 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1482 if rows is None:
1483 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1484 return None
1485 pk_args = {'pat_id': self.pk_patient}
1486 if rows is not None:
1487 for row in rows:
1488 pk_args['indication'] = row[0]
1489 pk_args['seq_no'] = row[1]
1490 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1491
1492
1493 self.__db_cache['vaccinations']['missing']['boosters'] = []
1494
1495 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1496 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1497 if rows is None:
1498 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1499 return None
1500 pk_args = {'pat_id': self.pk_patient}
1501 if rows is not None:
1502 for row in rows:
1503 pk_args['indication'] = row[0]
1504 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1505
1506
1507 if indications is None:
1508 return self.__db_cache['vaccinations']['missing']
1509 if len(indications) == 0:
1510 return self.__db_cache['vaccinations']['missing']
1511
1512 filtered_shots = {
1513 'due': [],
1514 'boosters': []
1515 }
1516 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1517 if due_shot['indication'] in indications:
1518 filtered_shots['due'].append(due_shot)
1519 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1520 if due_shot['indication'] in indications:
1521 filtered_shots['boosters'].append(due_shot)
1522 return filtered_shots
1523
1524
1525
1527 return self.__encounter
1528
1530
1531
1532 if self.__encounter is None:
1533 _log.debug('first setting of active encounter in this clinical record instance')
1534 else:
1535 _log.debug('switching of active encounter')
1536
1537 if self.__encounter.is_modified():
1538 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1539 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549 self.__encounter = encounter
1550 gmDispatcher.send(u'current_encounter_switched')
1551
1552 return True
1553
1554 current_encounter = property(_get_current_encounter, _set_current_encounter)
1555 active_encounter = property(_get_current_encounter, _set_current_encounter)
1556
1558
1559
1560 if self.__activate_very_recent_encounter():
1561 return True
1562
1563
1564 if self.__activate_fairly_recent_encounter(allow_user_interaction = allow_user_interaction):
1565 return True
1566
1567
1568 self.start_new_encounter()
1569 return True
1570
1572 """Try to attach to a "very recent" encounter if there is one.
1573
1574 returns:
1575 False: no "very recent" encounter, create new one
1576 True: success
1577 """
1578 cfg_db = gmCfg.cCfgSQL()
1579 min_ttl = cfg_db.get2 (
1580 option = u'encounter.minimum_ttl',
1581 workplace = _here.active_workplace,
1582 bias = u'user',
1583 default = u'1 hour 30 minutes'
1584 )
1585 cmd = u"""
1586 SELECT pk_encounter
1587 FROM clin.v_most_recent_encounters
1588 WHERE
1589 pk_patient = %s
1590 and
1591 last_affirmed > (now() - %s::interval)
1592 ORDER BY
1593 last_affirmed DESC"""
1594 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1595
1596 if len(enc_rows) == 0:
1597 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1598 return False
1599
1600 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1601 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1602 return True
1603
1605 """Try to attach to a "fairly recent" encounter if there is one.
1606
1607 returns:
1608 False: no "fairly recent" encounter, create new one
1609 True: success
1610 """
1611 if _func_ask_user is None:
1612 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1613 return False
1614
1615 if not allow_user_interaction:
1616 _log.exception('user interaction not desired, not looking for fairly recent encounter')
1617 return False
1618
1619 cfg_db = gmCfg.cCfgSQL()
1620 min_ttl = cfg_db.get2 (
1621 option = u'encounter.minimum_ttl',
1622 workplace = _here.active_workplace,
1623 bias = u'user',
1624 default = u'1 hour 30 minutes'
1625 )
1626 max_ttl = cfg_db.get2 (
1627 option = u'encounter.maximum_ttl',
1628 workplace = _here.active_workplace,
1629 bias = u'user',
1630 default = u'6 hours'
1631 )
1632 cmd = u"""
1633 SELECT pk_encounter
1634 FROM clin.v_most_recent_encounters
1635 WHERE
1636 pk_patient=%s
1637 AND
1638 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1639 ORDER BY
1640 last_affirmed DESC"""
1641 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1642
1643 if len(enc_rows) == 0:
1644 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1645 return False
1646
1647 _log.debug('"fairly recent" encounter [%s] found', enc_rows[0][0])
1648
1649 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1650
1651 cmd = u"""
1652 SELECT title, firstnames, lastnames, gender, dob
1653 FROM dem.v_basic_person WHERE pk_identity=%s"""
1654 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1655 pat = pats[0]
1656 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1657 gmTools.coalesce(pat[0], u'')[:5],
1658 pat[1][:15],
1659 pat[2][:15],
1660 pat[3],
1661 gmDateTime.pydt_strftime(pat[4], '%Y %b %d'),
1662 self.pk_patient
1663 )
1664 msg = _(
1665 '%s\n'
1666 '\n'
1667 "This patient's chart was worked on only recently:\n"
1668 '\n'
1669 ' %s %s - %s (%s)\n'
1670 '\n'
1671 ' Reason for Encounter:\n'
1672 ' %s\n'
1673 ' Assessment of Encounter:\n'
1674 ' %s\n'
1675 '\n'
1676 'Do you want to continue that consultation\n'
1677 'or do you want to start a new one ?\n'
1678 ) % (
1679 pat_str,
1680 gmDateTime.pydt_strftime(encounter['started'], '%Y %b %d'),
1681 gmDateTime.pydt_strftime(encounter['started'], '%H:%M'), gmDateTime.pydt_strftime(encounter['last_affirmed'], '%H:%M'),
1682 encounter['l10n_type'],
1683 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1684 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1685 )
1686 attach = False
1687 try:
1688 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1689 except:
1690 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1691 return False
1692 if not attach:
1693 return False
1694
1695
1696 self.current_encounter = encounter
1697 _log.debug('"fairly recent" encounter re-activated')
1698 return True
1699
1716
1717 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1718 """Retrieves patient's encounters.
1719
1720 id_list - PKs of encounters to fetch
1721 since - initial date for encounter items, DateTime instance
1722 until - final date for encounter items, DateTime instance
1723 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1724 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1725 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
1726
1727 NOTE: if you specify *both* issues and episodes
1728 you will get the *aggregate* of all encounters even
1729 if the episodes all belong to the health issues listed.
1730 IOW, the issues broaden the episode list rather than
1731 the episode list narrowing the episodes-from-issues
1732 list.
1733 Rationale: If it was the other way round it would be
1734 redundant to specify the list of issues at all.
1735 """
1736 where_parts = [u'c_vpe.pk_patient = %(pat)s']
1737 args = {'pat': self.pk_patient}
1738
1739 if skip_empty:
1740 where_parts.append(u"""NOT (
1741 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
1742 AND
1743 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
1744 AND
1745 NOT EXISTS (
1746 SELECT 1 FROM clin.v_pat_items c_vpi WHERE c_vpi.pk_patient = %(pat)s AND c_vpi.pk_encounter = c_vpe.pk_encounter
1747 UNION ALL
1748 SELECT 1 FROM blobs.v_doc_med b_vdm WHERE b_vdm.pk_patient = %(pat)s AND b_vdm.pk_encounter = c_vpe.pk_encounter
1749 ))""")
1750
1751 if since is not None:
1752 where_parts.append(u'c_vpe.started >= %(start)s')
1753 args['start'] = since
1754
1755 if until is not None:
1756 where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
1757 args['end'] = since
1758
1759 cmd = u"""
1760 SELECT *
1761 FROM clin.v_pat_encounters c_vpe
1762 WHERE
1763 %s
1764 ORDER BY started
1765 """ % u' AND '.join(where_parts)
1766 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1767 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
1768
1769
1770 filtered_encounters = []
1771 filtered_encounters.extend(encounters)
1772
1773 if id_list is not None:
1774 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1775
1776 if (issues is not None) and (len(issues) > 0):
1777 issues = tuple(issues)
1778
1779
1780 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1781 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1782 epi_ids = map(lambda x:x[0], rows)
1783 if episodes is None:
1784 episodes = []
1785 episodes.extend(epi_ids)
1786
1787 if (episodes is not None) and (len(episodes) > 0):
1788 episodes = tuple(episodes)
1789
1790
1791 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1792 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1793 enc_ids = map(lambda x:x[0], rows)
1794 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1795
1796 return filtered_encounters
1797
1799 """Retrieves first encounter for a particular issue and/or episode.
1800
1801 issue_id - First encounter associated health issue
1802 episode - First encounter associated episode
1803 """
1804
1805 if issue_id is None:
1806 issues = None
1807 else:
1808 issues = [issue_id]
1809
1810 if episode_id is None:
1811 episodes = None
1812 else:
1813 episodes = [episode_id]
1814
1815 encounters = self.get_encounters(issues=issues, episodes=episodes)
1816 if len(encounters) == 0:
1817 return None
1818
1819
1820 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1821 return encounters[0]
1822
1824 args = {'pat': self.pk_patient}
1825 cmd = u"""
1826 SELECT MIN(earliest) FROM (
1827 (
1828 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
1829
1830 ) UNION ALL (
1831
1832 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
1833
1834 ) UNION ALL (
1835
1836 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
1837
1838 ) UNION ALL (
1839
1840 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
1841
1842 ) UNION ALL (
1843
1844 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
1845
1846 ) UNION ALL (
1847
1848 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
1849
1850 ) UNION ALL (
1851
1852 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
1853
1854 )
1855 ) AS candidates"""
1856 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1857 return rows[0][0]
1858
1859 earliest_care_date = property(get_earliest_care_date, lambda x:x)
1860
1862 """Retrieves last encounter for a concrete issue and/or episode
1863
1864 issue_id - Last encounter associated health issue
1865 episode_id - Last encounter associated episode
1866 """
1867
1868
1869 if issue_id is None:
1870 issues = None
1871 else:
1872 issues = [issue_id]
1873
1874 if episode_id is None:
1875 episodes = None
1876 else:
1877 episodes = [episode_id]
1878
1879 encounters = self.get_encounters(issues=issues, episodes=episodes)
1880 if len(encounters) == 0:
1881 return None
1882
1883
1884 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1885 return encounters[-1]
1886
1887 last_encounter = property(get_last_encounter, lambda x:x)
1888
1890 args = {'pat': self.pk_patient, 'range': cover_period}
1891 where_parts = [u'pk_patient = %(pat)s']
1892 if cover_period is not None:
1893 where_parts.append(u'last_affirmed > now() - %(range)s')
1894
1895 cmd = u"""
1896 SELECT l10n_type, count(1) AS frequency
1897 FROM clin.v_pat_encounters
1898 WHERE
1899 %s
1900 GROUP BY l10n_type
1901 ORDER BY frequency DESC
1902 """ % u' AND '.join(where_parts)
1903 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1904 return rows
1905
1907
1908 args = {'pat': self.pk_patient}
1909
1910 if (issue_id is None) and (episode_id is None):
1911 cmd = u"""
1912 SELECT * FROM clin.v_pat_encounters
1913 WHERE pk_patient = %(pat)s
1914 ORDER BY started DESC
1915 LIMIT 2
1916 """
1917 else:
1918 where_parts = []
1919
1920 if issue_id is not None:
1921 where_parts.append(u'pk_health_issue = %(issue)s')
1922 args['issue'] = issue_id
1923
1924 if episode_id is not None:
1925 where_parts.append(u'pk_episode = %(epi)s')
1926 args['epi'] = episode_id
1927
1928 cmd = u"""
1929 SELECT *
1930 FROM clin.v_pat_encounters
1931 WHERE
1932 pk_patient = %%(pat)s
1933 AND
1934 pk_encounter IN (
1935 SELECT distinct pk_encounter
1936 FROM clin.v_narrative
1937 WHERE
1938 %s
1939 )
1940 ORDER BY started DESC
1941 LIMIT 2
1942 """ % u' AND '.join(where_parts)
1943
1944 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1945
1946 if len(rows) == 0:
1947 return None
1948
1949
1950 if len(rows) == 1:
1951
1952 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1953
1954 return None
1955
1956 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1957
1958
1959 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1960 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1961
1962 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1963
1965 cfg_db = gmCfg.cCfgSQL()
1966 ttl = cfg_db.get2 (
1967 option = u'encounter.ttl_if_empty',
1968 workplace = _here.active_workplace,
1969 bias = u'user',
1970 default = u'1 week'
1971 )
1972
1973
1974 cmd = u"SELECT clin.remove_old_empty_encounters(%(pat)s::INTEGER, %(ttl)s::INTERVAL)"
1975 args = {'pat': self.pk_patient, 'ttl': ttl}
1976 try:
1977 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1978 except:
1979 _log.exception('error deleting empty encounters')
1980
1981 return True
1982
1983
1984
1992
2001
2003 if order_by is None:
2004 order_by = u''
2005 else:
2006 order_by = u'ORDER BY %s' % order_by
2007 cmd = u"""
2008 SELECT * FROM clin.v_test_results
2009 WHERE
2010 pk_patient = %%(pat)s
2011 AND
2012 reviewed IS FALSE
2013 %s""" % order_by
2014 args = {'pat': self.pk_patient}
2015 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2016 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2017
2018
2020 """Retrieve data about test types for which this patient has results."""
2021
2022 cmd = u"""
2023 SELECT * FROM (
2024 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2025 FROM clin.v_test_results
2026 WHERE pk_patient = %(pat)s
2027 ) AS foo
2028 ORDER BY clin_when desc, unified_name
2029 """
2030 args = {'pat': self.pk_patient}
2031 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2032 return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
2033
2035 """Get the dates for which we have results."""
2036 where_parts = [u'pk_patient = %(pat)s']
2037 args = {'pat': self.pk_patient}
2038
2039 if tests is not None:
2040 where_parts.append(u'pk_test_type IN %(tests)s')
2041 args['tests'] = tuple(tests)
2042
2043 cmd = u"""
2044 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2045 FROM clin.v_test_results
2046 WHERE %s
2047 ORDER BY cwhen %s
2048 """ % (
2049 u' AND '.join(where_parts),
2050 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2051 )
2052 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2053 return rows
2054
2055 - def get_test_results(self, encounters=None, episodes=None, tests=None, order_by=None):
2062
2064
2065 where_parts = [u'pk_patient = %(pat)s']
2066 args = {'pat': self.pk_patient}
2067
2068 if tests is not None:
2069 where_parts.append(u'pk_test_type IN %(tests)s')
2070 args['tests'] = tuple(tests)
2071
2072 if encounter is not None:
2073 where_parts.append(u'pk_encounter = %(enc)s')
2074 args['enc'] = encounter
2075
2076 if episodes is not None:
2077 where_parts.append(u'pk_episode IN %(epis)s')
2078 args['epis'] = tuple(episodes)
2079
2080 cmd = u"""
2081 SELECT * FROM clin.v_test_results
2082 WHERE %s
2083 ORDER BY clin_when %s, pk_episode, unified_name
2084 """ % (
2085 u' AND '.join(where_parts),
2086 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2087 )
2088 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2089
2090 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2091
2092 return tests
2093
2094 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2095
2096 try:
2097 epi = int(episode)
2098 except:
2099 epi = episode['pk_episode']
2100
2101 try:
2102 type = int(type)
2103 except:
2104 type = type['pk_test_type']
2105
2106 if intended_reviewer is None:
2107 intended_reviewer = gmStaff.gmCurrentProvider()['pk_staff']
2108
2109 tr = gmPathLab.create_test_result (
2110 encounter = self.current_encounter['pk_encounter'],
2111 episode = epi,
2112 type = type,
2113 intended_reviewer = intended_reviewer,
2114 val_num = val_num,
2115 val_alpha = val_alpha,
2116 unit = unit
2117 )
2118
2119 return tr
2120
2121
2122
2123
2128
2129 - def add_lab_request(self, lab=None, req_id=None, encounter_id=None, episode_id=None):
2143
2144
2145
2146
2147 if __name__ == "__main__":
2148
2149 if len(sys.argv) == 1:
2150 sys.exit()
2151
2152 if sys.argv[1] != 'test':
2153 sys.exit()
2154
2155 from Gnumed.pycommon import gmLog2
2156
2170
2177
2184
2186 emr = cClinicalRecord(aPKey=12)
2187 rows, idx = emr.get_measurements_by_date()
2188 print "test results:"
2189 for row in rows:
2190 print row
2191
2198
2203
2205 emr = cClinicalRecord(aPKey=12)
2206
2207 probs = emr.get_problems()
2208 print "normal probs (%s):" % len(probs)
2209 for p in probs:
2210 print u'%s (%s)' % (p['problem'], p['type'])
2211
2212 probs = emr.get_problems(include_closed_episodes=True)
2213 print "probs + closed episodes (%s):" % len(probs)
2214 for p in probs:
2215 print u'%s (%s)' % (p['problem'], p['type'])
2216
2217 probs = emr.get_problems(include_irrelevant_issues=True)
2218 print "probs + issues (%s):" % len(probs)
2219 for p in probs:
2220 print u'%s (%s)' % (p['problem'], p['type'])
2221
2222 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2223 print "probs + issues + epis (%s):" % len(probs)
2224 for p in probs:
2225 print u'%s (%s)' % (p['problem'], p['type'])
2226
2228 emr = cClinicalRecord(aPKey=12)
2229 tr = emr.add_test_result (
2230 episode = 1,
2231 intended_reviewer = 1,
2232 type = 1,
2233 val_num = 75,
2234 val_alpha = u'somewhat obese',
2235 unit = u'kg'
2236 )
2237 print tr
2238
2242
2247
2252
2256
2258 emr = cClinicalRecord(aPKey = 12)
2259 for journal_line in emr.get_as_journal():
2260
2261 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2262 print ""
2263
2267
2272
2273
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297 test_format_as_journal()
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