| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf8 -*-
2 """GNUmed clinical patient record.
3
4 This is a clinical record object intended to let a useful
5 client-side API crystallize from actual use in true XP fashion.
6
7 Make sure to call set_func_ask_user() and set_encounter_ttl()
8 early on in your code (before cClinicalRecord.__init__() is
9 called for the first time).
10 """
11 #============================================================
12 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
13 __license__ = "GPL v2 or later"
14
15 #===================================================
16 # TODO
17 # Basically we'll probably have to:
18 #
19 # a) serialize access to re-getting data from the cache so
20 # that later-but-concurrent cache accesses spin until
21 # the first one completes the refetch from the database
22 #
23 # b) serialize access to the cache per-se such that cache
24 # flushes vs. cache regets happen atomically (where
25 # flushes would abort/restart current regets)
26 #===================================================
27
28 # standard libs
29 import sys
30 import logging
31
32
33 if __name__ == '__main__':
34 sys.path.insert(0, '../../')
35 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
36 gmI18N.activate_locale()
37 gmI18N.install_domain()
38 gmDateTime.init()
39
40 from Gnumed.pycommon import gmExceptions
41 from Gnumed.pycommon import gmPG2
42 from Gnumed.pycommon import gmDispatcher
43 from Gnumed.pycommon import gmI18N
44 from Gnumed.pycommon import gmCfg
45 from Gnumed.pycommon import gmTools
46 from Gnumed.pycommon import gmDateTime
47
48 from Gnumed.business import gmAllergy
49 from Gnumed.business import gmPathLab
50 from Gnumed.business import gmLOINC
51 from Gnumed.business import gmClinNarrative
52 from Gnumed.business import gmEMRStructItems
53 from Gnumed.business import gmMedication
54 from Gnumed.business import gmVaccination
55 from Gnumed.business import gmFamilyHistory
56 from Gnumed.business.gmDemographicRecord import get_occupations
57
58
59 _log = logging.getLogger('gm.emr')
60
61 _me = None
62 _here = None
63 #============================================================
64 # helper functions
65 #------------------------------------------------------------
66 _func_ask_user = None
67
69 if not callable(a_func):
70 _log.error('[%] not callable, not setting _func_ask_user', a_func)
71 return False
72
73 _log.debug('setting _func_ask_user to [%s]', a_func)
74
75 global _func_ask_user
76 _func_ask_user = a_func
77
78 #============================================================
80
81 _clin_root_item_children_union_query = None
82
84 """Fails if
85
86 - no connection to database possible
87 - patient referenced by aPKey does not exist
88 """
89 self.pk_patient = aPKey # == identity.pk == primary key
90
91 # log access to patient record (HIPAA, for example)
92 cmd = u'SELECT gm.log_access2emr(%(todo)s)'
93 args = {'todo': u'patient [%s]' % aPKey}
94 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
95
96 from Gnumed.business import gmSurgery, gmStaff
97 global _me
98 if _me is None:
99 _me = gmStaff.gmCurrentProvider()
100 global _here
101 if _here is None:
102 _here = gmSurgery.gmCurrentPractice()
103
104 # ...........................................
105 # this is a hack to speed up get_encounters()
106 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item')
107 if cClinicalRecord._clin_root_item_children_union_query is None:
108 union_phrase = u"""
109 SELECT fk_encounter from
110 %s.%s cn
111 inner join
112 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi
113 on (cn.fk_episode = epi.pk)
114 """
115 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join (
116 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ]
117 )
118 # ...........................................
119
120 self.__db_cache = {}
121
122 # load current or create new encounter
123 if _func_ask_user is None:
124 _log.error('[_func_ask_user] is None')
125 print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
126 self.remove_empty_encounters()
127 self.__encounter = None
128 if not self.__initiate_active_encounter():
129 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey
130
131 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
132
133 # register backend notification interests
134 # (keep this last so we won't hang on threads when
135 # failing this constructor for other reasons ...)
136 if not self._register_interests():
137 raise gmExceptions.ConstructorError, "cannot register signal interests"
138
139 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
140 #--------------------------------------------------------
143 #--------------------------------------------------------
145 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
146 return True
147 #--------------------------------------------------------
148 # messaging
149 #--------------------------------------------------------
151 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
152
153 return True
154 #--------------------------------------------------------
156
157 # get the current encounter as an extra instance
158 # from the database to check for changes
159 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
160
161 # the encounter just retrieved and the active encounter
162 # have got the same transaction ID so there's no change
163 # in the database, there could be a local change in
164 # the active encounter but that doesn't matter
165 # THIS DOES NOT WORK
166 # if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
167 # return True
168
169 # there must have been a change to the active encounter
170 # committed to the database from elsewhere,
171 # we must fail propagating the change, however, if
172 # there are local changes
173 if self.current_encounter.is_modified():
174 _log.debug('unsaved changes in active encounter, cannot switch to another one')
175 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
176
177 if self.current_encounter.same_payload(another_object = curr_enc_in_db):
178 _log.debug('encounter_mod_db received but no change to active encounter payload')
179 return True
180
181 # there was a change in the database from elsewhere,
182 # locally, however, we don't have any changes, therefore
183 # we can propagate the remote change locally without
184 # losing anything
185 _log.debug('active encounter modified remotely, reloading and announcing the modification')
186 self.current_encounter.refetch_payload()
187 gmDispatcher.send(u'current_encounter_modified')
188
189 return True
190 #--------------------------------------------------------
193 #--------------------------------------------------------
200 #--------------------------------------------------------
207 #--------------------------------------------------------
209 _log.debug('DB: clin_root_item modification')
210 #--------------------------------------------------------
211 # API: family history
212 #--------------------------------------------------------
214 fhx = gmFamilyHistory.get_family_history (
215 order_by = u'l10n_relation, condition',
216 patient = self.pk_patient
217 )
218
219 if episodes is not None:
220 fhx = filter(lambda f: f['pk_episode'] in episodes, fhx)
221
222 if issues is not None:
223 fhx = filter(lambda f: f['pk_health_issue'] in issues, fhx)
224
225 if encounters is not None:
226 fhx = filter(lambda f: f['pk_encounter'] in encounters, fhx)
227
228 return fhx
229 #--------------------------------------------------------
231 return gmFamilyHistory.create_family_history (
232 encounter = self.current_encounter['pk_encounter'],
233 episode = episode,
234 condition = condition,
235 relation = relation
236 )
237 #--------------------------------------------------------
238 # API: performed procedures
239 #--------------------------------------------------------
241
242 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
243
244 if episodes is not None:
245 procs = filter(lambda p: p['pk_episode'] in episodes, procs)
246
247 if issues is not None:
248 procs = filter(lambda p: p['pk_health_issue'] in issues, procs)
249
250 return procs
251
252 performed_procedures = property(get_performed_procedures, lambda x:x)
253 #--------------------------------------------------------
256 #--------------------------------------------------------
257 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
258 return gmEMRStructItems.create_performed_procedure (
259 encounter = self.current_encounter['pk_encounter'],
260 episode = episode,
261 location = location,
262 hospital_stay = hospital_stay,
263 procedure = procedure
264 )
265 #--------------------------------------------------------
266 # API: hospitalizations
267 #--------------------------------------------------------
269 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient, ongoing_only = ongoing_only)
270 if episodes is not None:
271 stays = filter(lambda s: s['pk_episode'] in episodes, stays)
272 if issues is not None:
273 stays = filter(lambda s: s['pk_health_issue'] in issues, stays)
274 return stays
275
276 hospital_stays = property(get_hospital_stays, lambda x:x)
277 #--------------------------------------------------------
280 #--------------------------------------------------------
282 return gmEMRStructItems.create_hospital_stay (
283 encounter = self.current_encounter['pk_encounter'],
284 episode = episode
285 )
286 #--------------------------------------------------------
288 args = {'pat': self.pk_patient, 'range': cover_period}
289 where_parts = [u'pk_patient = %(pat)s']
290 if cover_period is not None:
291 where_parts.append(u'discharge > (now() - %(range)s)')
292
293 cmd = u"""
294 SELECT hospital, count(1) AS frequency
295 FROM clin.v_pat_hospital_stays
296 WHERE
297 %s
298 GROUP BY hospital
299 ORDER BY frequency DESC
300 """ % u' AND '.join(where_parts)
301
302 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
303 return rows
304 #--------------------------------------------------------
305 # API: narrative
306 #--------------------------------------------------------
308
309 enc = gmTools.coalesce (
310 encounter,
311 self.current_encounter['pk_encounter']
312 )
313
314 for note in notes:
315 success, data = gmClinNarrative.create_clin_narrative (
316 narrative = note[1],
317 soap_cat = note[0],
318 episode_id = episode,
319 encounter_id = enc
320 )
321
322 return True
323 #--------------------------------------------------------
325 if note.strip() == '':
326 _log.info('will not create empty clinical note')
327 return None
328 status, data = gmClinNarrative.create_clin_narrative (
329 narrative = note,
330 soap_cat = soap_cat,
331 episode_id = episode['pk_episode'],
332 encounter_id = self.current_encounter['pk_encounter']
333 )
334 if not status:
335 _log.error(str(data))
336 return None
337 return data
338 #--------------------------------------------------------
339 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
340 """Get SOAP notes pertinent to this encounter.
341
342 since
343 - initial date for narrative items
344 until
345 - final date for narrative items
346 encounters
347 - list of encounters whose narrative are to be retrieved
348 episodes
349 - list of episodes whose narrative are to be retrieved
350 issues
351 - list of health issues whose narrative are to be retrieved
352 soap_cats
353 - list of SOAP categories of the narrative to be retrieved
354 """
355 where_parts = [u'pk_patient = %(pat)s']
356 args = {u'pat': self.pk_patient}
357
358 if issues is not None:
359 where_parts.append(u'pk_health_issue IN %(issues)s')
360 args['issues'] = tuple(issues)
361
362 if episodes is not None:
363 where_parts.append(u'pk_episode IN %(epis)s')
364 args['epis'] = tuple(episodes)
365
366 if encounters is not None:
367 where_parts.append(u'pk_encounter IN %(encs)s')
368 args['encs'] = tuple(encounters)
369
370 if soap_cats is not None:
371 where_parts.append(u'soap_cat IN %(cats)s')
372 soap_cats = list(soap_cats)
373 args['cats'] = [ cat.lower() for cat in soap_cats if cat is not None ]
374 if None in soap_cats:
375 args['cats'].append(None)
376 args['cats'] = tuple(args['cats'])
377
378 cmd = u"""
379 SELECT
380 c_vpn.*,
381 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = c_vpn.soap_cat) AS soap_rank
382 FROM clin.v_pat_narrative c_vpn
383 WHERE %s
384 ORDER BY date, soap_rank
385 """ % u' AND '.join(where_parts)
386
387 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
388
389 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
390
391 if since is not None:
392 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
393
394 if until is not None:
395 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
396
397 if providers is not None:
398 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
399
400 return filtered_narrative
401 #--------------------------------------------------------
402 - 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):
403 return gmClinNarrative.get_as_journal (
404 patient = self.pk_patient,
405 since = since,
406 until = until,
407 encounters = encounters,
408 episodes = episodes,
409 issues = issues,
410 soap_cats = soap_cats,
411 providers = providers,
412 order_by = order_by,
413 time_range = time_range
414 )
415 #--------------------------------------------------------
417
418 search_term = search_term.strip()
419 if search_term == '':
420 return []
421
422 cmd = u"""
423 SELECT
424 *,
425 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
426 as episode,
427 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
428 as health_issue,
429 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
430 as encounter_started,
431 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
432 as encounter_ended,
433 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
434 as encounter_type
435 from clin.v_narrative4search vn4s
436 WHERE
437 pk_patient = %(pat)s and
438 vn4s.narrative ~ %(term)s
439 order by
440 encounter_started
441 """ # case sensitive
442 rows, idx = gmPG2.run_ro_queries(queries = [
443 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
444 ])
445 return rows
446 #--------------------------------------------------------
448 # don't know how to invalidate this by means of
449 # a notify without catching notifies from *all*
450 # child tables, the best solution would be if
451 # inserts in child tables would also fire triggers
452 # of ancestor tables, but oh well,
453 # until then the text dump will not be cached ...
454 try:
455 return self.__db_cache['text dump old']
456 except KeyError:
457 pass
458 # not cached so go get it
459 fields = [
460 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
461 'modified_by',
462 'clin_when',
463 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
464 'pk_item',
465 'pk_encounter',
466 'pk_episode',
467 'pk_health_issue',
468 'src_table'
469 ]
470 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % ', '.join(fields)
471 ro_conn = self._conn_pool.GetConnection('historica')
472 curs = ro_conn.cursor()
473 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
474 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
475 curs.close()
476 return None
477 rows = curs.fetchall()
478 view_col_idx = gmPG2.get_col_indices(curs)
479
480 # aggregate by src_table for item retrieval
481 items_by_table = {}
482 for item in rows:
483 src_table = item[view_col_idx['src_table']]
484 pk_item = item[view_col_idx['pk_item']]
485 if not items_by_table.has_key(src_table):
486 items_by_table[src_table] = {}
487 items_by_table[src_table][pk_item] = item
488
489 # get mapping for issue/episode IDs
490 issues = self.get_health_issues()
491 issue_map = {}
492 for issue in issues:
493 issue_map[issue['pk']] = issue['description']
494 episodes = self.get_episodes()
495 episode_map = {}
496 for episode in episodes:
497 episode_map[episode['pk_episode']] = episode['description']
498 emr_data = {}
499 # get item data from all source tables
500 for src_table in items_by_table.keys():
501 item_ids = items_by_table[src_table].keys()
502 # we don't know anything about the columns of
503 # the source tables but, hey, this is a dump
504 if len(item_ids) == 0:
505 _log.info('no items in table [%s] ?!?' % src_table)
506 continue
507 elif len(item_ids) == 1:
508 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
509 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
510 _log.error('cannot load items from table [%s]' % src_table)
511 # skip this table
512 continue
513 elif len(item_ids) > 1:
514 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
515 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
516 _log.error('cannot load items from table [%s]' % src_table)
517 # skip this table
518 continue
519 rows = curs.fetchall()
520 table_col_idx = gmPG.get_col_indices(curs)
521 # format per-table items
522 for row in rows:
523 # FIXME: make this get_pkey_name()
524 pk_item = row[table_col_idx['pk_item']]
525 view_row = items_by_table[src_table][pk_item]
526 age = view_row[view_col_idx['age']]
527 # format metadata
528 try:
529 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
530 except:
531 episode_name = view_row[view_col_idx['pk_episode']]
532 try:
533 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
534 except:
535 issue_name = view_row[view_col_idx['pk_health_issue']]
536
537 if not emr_data.has_key(age):
538 emr_data[age] = []
539
540 emr_data[age].append(
541 _('%s: encounter (%s)') % (
542 view_row[view_col_idx['clin_when']],
543 view_row[view_col_idx['pk_encounter']]
544 )
545 )
546 emr_data[age].append(_('health issue: %s') % issue_name)
547 emr_data[age].append(_('episode : %s') % episode_name)
548 # format table specific data columns
549 # - ignore those, they are metadata, some
550 # are in clin.v_pat_items data already
551 cols2ignore = [
552 'pk_audit', 'row_version', 'modified_when', 'modified_by',
553 'pk_item', 'id', 'fk_encounter', 'fk_episode'
554 ]
555 col_data = []
556 for col_name in table_col_idx.keys():
557 if col_name in cols2ignore:
558 continue
559 emr_data[age].append("=> %s:" % col_name)
560 emr_data[age].append(row[table_col_idx[col_name]])
561 emr_data[age].append("----------------------------------------------------")
562 emr_data[age].append("-- %s from table %s" % (
563 view_row[view_col_idx['modified_string']],
564 src_table
565 ))
566 emr_data[age].append("-- written %s by %s" % (
567 view_row[view_col_idx['modified_when']],
568 view_row[view_col_idx['modified_by']]
569 ))
570 emr_data[age].append("----------------------------------------------------")
571 curs.close()
572 self._conn_pool.ReleaseConnection('historica')
573 return emr_data
574 #--------------------------------------------------------
576 # don't know how to invalidate this by means of
577 # a notify without catching notifies from *all*
578 # child tables, the best solution would be if
579 # inserts in child tables would also fire triggers
580 # of ancestor tables, but oh well,
581 # until then the text dump will not be cached ...
582 try:
583 return self.__db_cache['text dump']
584 except KeyError:
585 pass
586 # not cached so go get it
587 # -- get the data --
588 fields = [
589 'age',
590 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
591 'modified_by',
592 'clin_when',
593 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
594 'pk_item',
595 'pk_encounter',
596 'pk_episode',
597 'pk_health_issue',
598 'src_table'
599 ]
600 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
601 # handle constraint conditions
602 where_snippets = []
603 params = {}
604 where_snippets.append('pk_patient=%(pat_id)s')
605 params['pat_id'] = self.pk_patient
606 if not since is None:
607 where_snippets.append('clin_when >= %(since)s')
608 params['since'] = since
609 if not until is None:
610 where_snippets.append('clin_when <= %(until)s')
611 params['until'] = until
612 # FIXME: these are interrelated, eg if we constrain encounter
613 # we automatically constrain issue/episode, so handle that,
614 # encounters
615 if not encounters is None and len(encounters) > 0:
616 params['enc'] = encounters
617 if len(encounters) > 1:
618 where_snippets.append('fk_encounter in %(enc)s')
619 else:
620 where_snippets.append('fk_encounter=%(enc)s')
621 # episodes
622 if not episodes is None and len(episodes) > 0:
623 params['epi'] = episodes
624 if len(episodes) > 1:
625 where_snippets.append('fk_episode in %(epi)s')
626 else:
627 where_snippets.append('fk_episode=%(epi)s')
628 # health issues
629 if not issues is None and len(issues) > 0:
630 params['issue'] = issues
631 if len(issues) > 1:
632 where_snippets.append('fk_health_issue in %(issue)s')
633 else:
634 where_snippets.append('fk_health_issue=%(issue)s')
635
636 where_clause = ' and '.join(where_snippets)
637 order_by = 'order by src_table, age'
638 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
639
640 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
641 if rows is None:
642 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
643 return None
644
645 # -- sort the data --
646 # FIXME: by issue/encounter/episode, eg formatting
647 # aggregate by src_table for item retrieval
648 items_by_table = {}
649 for item in rows:
650 src_table = item[view_col_idx['src_table']]
651 pk_item = item[view_col_idx['pk_item']]
652 if not items_by_table.has_key(src_table):
653 items_by_table[src_table] = {}
654 items_by_table[src_table][pk_item] = item
655
656 # get mapping for issue/episode IDs
657 issues = self.get_health_issues()
658 issue_map = {}
659 for issue in issues:
660 issue_map[issue['pk_health_issue']] = issue['description']
661 episodes = self.get_episodes()
662 episode_map = {}
663 for episode in episodes:
664 episode_map[episode['pk_episode']] = episode['description']
665 emr_data = {}
666 # get item data from all source tables
667 ro_conn = self._conn_pool.GetConnection('historica')
668 curs = ro_conn.cursor()
669 for src_table in items_by_table.keys():
670 item_ids = items_by_table[src_table].keys()
671 # we don't know anything about the columns of
672 # the source tables but, hey, this is a dump
673 if len(item_ids) == 0:
674 _log.info('no items in table [%s] ?!?' % src_table)
675 continue
676 elif len(item_ids) == 1:
677 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
678 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
679 _log.error('cannot load items from table [%s]' % src_table)
680 # skip this table
681 continue
682 elif len(item_ids) > 1:
683 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
684 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
685 _log.error('cannot load items from table [%s]' % src_table)
686 # skip this table
687 continue
688 rows = curs.fetchall()
689 table_col_idx = gmPG.get_col_indices(curs)
690 # format per-table items
691 for row in rows:
692 # FIXME: make this get_pkey_name()
693 pk_item = row[table_col_idx['pk_item']]
694 view_row = items_by_table[src_table][pk_item]
695 age = view_row[view_col_idx['age']]
696 # format metadata
697 try:
698 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
699 except:
700 episode_name = view_row[view_col_idx['pk_episode']]
701 try:
702 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
703 except:
704 issue_name = view_row[view_col_idx['pk_health_issue']]
705
706 if not emr_data.has_key(age):
707 emr_data[age] = []
708
709 emr_data[age].append(
710 _('%s: encounter (%s)') % (
711 view_row[view_col_idx['clin_when']],
712 view_row[view_col_idx['pk_encounter']]
713 )
714 )
715 emr_data[age].append(_('health issue: %s') % issue_name)
716 emr_data[age].append(_('episode : %s') % episode_name)
717 # format table specific data columns
718 # - ignore those, they are metadata, some
719 # are in clin.v_pat_items data already
720 cols2ignore = [
721 'pk_audit', 'row_version', 'modified_when', 'modified_by',
722 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
723 ]
724 col_data = []
725 for col_name in table_col_idx.keys():
726 if col_name in cols2ignore:
727 continue
728 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
729 emr_data[age].append("----------------------------------------------------")
730 emr_data[age].append("-- %s from table %s" % (
731 view_row[view_col_idx['modified_string']],
732 src_table
733 ))
734 emr_data[age].append("-- written %s by %s" % (
735 view_row[view_col_idx['modified_when']],
736 view_row[view_col_idx['modified_by']]
737 ))
738 emr_data[age].append("----------------------------------------------------")
739 curs.close()
740 return emr_data
741 #--------------------------------------------------------
744 #--------------------------------------------------------
746 union_query = u'\n union all\n'.join ([
747 u"""
748 SELECT ((
749 -- all relevant health issues + active episodes WITH health issue
750 SELECT COUNT(1)
751 FROM clin.v_problem_list
752 WHERE
753 pk_patient = %(pat)s
754 AND
755 pk_health_issue is not null
756 ) + (
757 -- active episodes WITHOUT health issue
758 SELECT COUNT(1)
759 FROM clin.v_problem_list
760 WHERE
761 pk_patient = %(pat)s
762 AND
763 pk_health_issue is null
764 ))""",
765 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
766 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
767 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
768 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
769 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
770 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
771 # active and approved substances == medication
772 u"""
773 SELECT count(1)
774 from clin.v_pat_substance_intake
775 WHERE
776 pk_patient = %(pat)s
777 and is_currently_active in (null, true)
778 and intake_is_approved_of in (null, true)""",
779 u'SELECT count(1) FROM clin.v_pat_vaccinations WHERE pk_patient = %(pat)s'
780 ])
781
782 rows, idx = gmPG2.run_ro_queries (
783 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
784 get_col_idx = False
785 )
786
787 stats = dict (
788 problems = rows[0][0],
789 encounters = rows[1][0],
790 items = rows[2][0],
791 documents = rows[3][0],
792 results = rows[4][0],
793 stays = rows[5][0],
794 procedures = rows[6][0],
795 active_drugs = rows[7][0],
796 vaccinations = rows[8][0]
797 )
798
799 return stats
800 #--------------------------------------------------------
802 return _(
803 'Medical problems: %(problems)s\n'
804 'Total encounters: %(encounters)s\n'
805 'Total EMR entries: %(items)s\n'
806 'Active medications: %(active_drugs)s\n'
807 'Documents: %(documents)s\n'
808 'Test results: %(results)s\n'
809 'Hospitalizations: %(stays)s\n'
810 'Procedures: %(procedures)s\n'
811 'Vaccinations: %(vaccinations)s'
812 ) % self.get_statistics()
813 #--------------------------------------------------------
815
816 stats = self.get_statistics()
817 first = self.get_first_encounter()
818 last = self.get_last_encounter()
819 probs = self.get_problems()
820
821 txt = u''
822 if len(probs) > 0:
823 txt += _(' %s known problems, clinically relevant thereof:\n') % stats['problems']
824 else:
825 txt += _(' %s known problems\n') % stats['problems']
826 for prob in probs:
827 if not prob['clinically_relevant']:
828 continue
829 txt += u' \u00BB%s\u00AB (%s)\n' % (
830 prob['problem'],
831 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
832 )
833 txt += u'\n'
834 txt += _(' %s encounters from %s to %s\n') % (
835 stats['encounters'],
836 first['started'].strftime('%x').decode(gmI18N.get_encoding()),
837 last['started'].strftime('%x').decode(gmI18N.get_encoding())
838 )
839 txt += _(' %s active medications\n') % stats['active_drugs']
840 txt += _(' %s documents\n') % stats['documents']
841 txt += _(' %s test results\n') % stats['results']
842 txt += _(' %s hospitalizations') % stats['stays']
843 if stats['stays'] == 0:
844 txt += u'\n'
845 else:
846 txt += _(', most recently:\n%s\n') % self.get_latest_hospital_stay().format(left_margin = 3)
847 # FIXME: perhaps only count "ongoing ones"
848 txt += _(' %s performed procedures') % stats['procedures']
849 if stats['procedures'] == 0:
850 txt += u'\n'
851 else:
852 txt += _(', most recently:\n%s\n') % self.get_latest_performed_procedure().format(left_margin = 3)
853
854 txt += u'\n'
855 txt += _('Allergies and Intolerances\n')
856
857 allg_state = self.allergy_state
858 txt += (u' ' + allg_state.state_string)
859 if allg_state['last_confirmed'] is not None:
860 txt += (_(' (last confirmed %s)') % allg_state['last_confirmed'].strftime('%x').decode(gmI18N.get_encoding()))
861 txt += u'\n'
862 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n')
863 for allg in self.get_allergies():
864 txt += u' %s: %s\n' % (
865 allg['descriptor'],
866 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
867 )
868
869 txt += u'\n'
870 txt += _('Family History')
871 txt += u'\n'
872 fhx = self.get_family_history()
873 for f in fhx:
874 txt += u'%s\n' % f.format(left_margin = 1)
875
876 txt += u'\n'
877 txt += _('Occupations')
878 txt += u'\n'
879 jobs = get_occupations(pk_identity = self.pk_patient)
880 for job in jobs:
881 txt += u' %s%s\n' % (
882 job['l10n_occupation'],
883 gmTools.coalesce(job['activities'], u'', u': %s')
884 )
885
886 txt += u'\n'
887 txt += _('Vaccinations')
888 txt += u'\n'
889 vaccs = self.get_latest_vaccinations()
890 inds = sorted(vaccs.keys())
891 for ind in inds:
892 ind_count, vacc = vaccs[ind]
893 if dob is None:
894 age_given = u''
895 else:
896 age_given = u' @ %s' % gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
897 start = dob,
898 end = vacc['date_given']
899 ))
900 since = _('%s ago') % gmDateTime.format_interval_medically(vacc['interval_since_given'])
901 txt += u' %s (%s%s): %s%s (%s %s%s%s)\n' % (
902 ind,
903 gmTools.u_sum,
904 ind_count,
905 #vacc['date_given'].strftime('%b %Y').decode(gmI18N.get_encoding()),
906 since,
907 age_given,
908 vacc['vaccine'],
909 gmTools.u_left_double_angle_quote,
910 vacc['batch_no'],
911 gmTools.u_right_double_angle_quote
912 )
913
914 return txt
915 #--------------------------------------------------------
917 txt = u''
918 for enc in self.get_encounters(skip_empty = True):
919 txt += gmTools.u_box_horiz_4dashes * 70 + u'\n'
920 txt += enc.format (
921 episodes = None, # means: each touched upon
922 left_margin = left_margin,
923 patient = patient,
924 fancy_header = False,
925 with_soap = True,
926 with_docs = True,
927 with_tests = True,
928 with_vaccinations = True,
929 with_co_encountlet_hints = False, # irrelevant
930 with_rfe_aoe = True,
931 with_family_history = True,
932 by_episode = True
933 )
934
935 return txt
936 #--------------------------------------------------------
937 # API: allergy
938 #--------------------------------------------------------
939 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
940 """Retrieves patient allergy items.
941
942 remove_sensitivities
943 - retrieve real allergies only, without sensitivities
944 since
945 - initial date for allergy items
946 until
947 - final date for allergy items
948 encounters
949 - list of encounters whose allergies are to be retrieved
950 episodes
951 - list of episodes whose allergies are to be retrieved
952 issues
953 - list of health issues whose allergies are to be retrieved
954 """
955 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
956 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
957 allergies = []
958 for r in rows:
959 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
960
961 # ok, let's constrain our list
962 filtered_allergies = []
963 filtered_allergies.extend(allergies)
964
965 if ID_list is not None:
966 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
967 if len(filtered_allergies) == 0:
968 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
969 # better fail here contrary to what we do elsewhere
970 return None
971 else:
972 return filtered_allergies
973
974 if remove_sensitivities:
975 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
976 if since is not None:
977 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
978 if until is not None:
979 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
980 if issues is not None:
981 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
982 if episodes is not None:
983 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
984 if encounters is not None:
985 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
986
987 return filtered_allergies
988 #--------------------------------------------------------
990 if encounter_id is None:
991 encounter_id = self.current_encounter['pk_encounter']
992
993 if episode_id is None:
994 issue = self.add_health_issue(issue_name = _('Allergies/Intolerances'))
995 epi = self.add_episode(episode_name = _('Allergy detail: %s') % allergene, pk_health_issue = issue['pk_health_issue'])
996 episode_id = epi['pk_episode']
997
998 new_allergy = gmAllergy.create_allergy (
999 allergene = allergene,
1000 allg_type = allg_type,
1001 encounter_id = encounter_id,
1002 episode_id = episode_id
1003 )
1004
1005 return new_allergy
1006 #--------------------------------------------------------
1008 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
1009 args = {'pk_allg': pk_allergy}
1010 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
1011 #--------------------------------------------------------
1013 """Cave: only use with one potential allergic agent
1014 otherwise you won't know which of the agents the allergy is to."""
1015
1016 # we don't know the state
1017 if self.allergy_state is None:
1018 return None
1019
1020 # we know there's no allergies
1021 if self.allergy_state == 0:
1022 return False
1023
1024 args = {
1025 'atcs': atcs,
1026 'inns': inns,
1027 'brand': brand,
1028 'pat': self.pk_patient
1029 }
1030 allergenes = []
1031 where_parts = []
1032
1033 if len(atcs) == 0:
1034 atcs = None
1035 if atcs is not None:
1036 where_parts.append(u'atc_code in %(atcs)s')
1037 if len(inns) == 0:
1038 inns = None
1039 if inns is not None:
1040 where_parts.append(u'generics in %(inns)s')
1041 allergenes.extend(inns)
1042 if brand is not None:
1043 where_parts.append(u'substance = %(brand)s')
1044 allergenes.append(brand)
1045
1046 if len(allergenes) != 0:
1047 where_parts.append(u'allergene in %(allgs)s')
1048 args['allgs'] = tuple(allergenes)
1049
1050 cmd = u"""
1051 SELECT * FROM clin.v_pat_allergies
1052 WHERE
1053 pk_patient = %%(pat)s
1054 AND ( %s )""" % u' OR '.join(where_parts)
1055
1056 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1057
1058 if len(rows) == 0:
1059 return False
1060
1061 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1062 #--------------------------------------------------------
1064
1065 if state not in gmAllergy.allergy_states:
1066 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
1067
1068 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1069 allg_state['has_allergy'] = state
1070 allg_state.save_payload()
1071 return True
1072
1075
1076 allergy_state = property(_get_allergy_state, _set_allergy_state)
1077 #--------------------------------------------------------
1078 # API: episodes
1079 #--------------------------------------------------------
1080 - def get_episodes(self, id_list=None, issues=None, open_status=None, order_by=None, unlinked_only=False):
1081 """Fetches from backend patient episodes.
1082
1083 id_list - Episodes' PKs list
1084 issues - Health issues' PKs list to filter episodes by
1085 open_status - return all (None) episodes, only open (True) or closed (False) one(s)
1086 """
1087 if (unlinked_only is True) and (issues is not None):
1088 raise ValueError('<unlinked_only> cannot be TRUE if <issues> is not None')
1089
1090 if order_by is None:
1091 order_by = u''
1092 else:
1093 order_by = u'ORDER BY %s' % order_by
1094
1095 args = {
1096 'pat': self.pk_patient,
1097 'open': open_status
1098 }
1099 where_parts = [u'pk_patient = %(pat)s']
1100
1101 if open_status is not None:
1102 where_parts.append(u'episode_open IS %(open)s')
1103
1104 if unlinked_only:
1105 where_parts.append(u'pk_health_issue is NULL')
1106
1107 if issues is not None:
1108 where_parts.append(u'pk_health_issue IN %(issues)s')
1109 args['issues'] = tuple(issues)
1110
1111 if id_list is not None:
1112 where_parts.append(u'pk_episode IN %(epis)s')
1113 args['epis'] = tuple(id_list)
1114
1115 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE %s %s" % (
1116 u' AND '.join(where_parts),
1117 order_by
1118 )
1119 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1120
1121 return [ gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}) for r in rows ]
1122
1123 episodes = property(get_episodes, lambda x:x)
1124 #------------------------------------------------------------------
1126 return self.get_episodes(open_status = open_status, order_by = order_by, unlinked_only = True)
1127
1128 unlinked_episodes = property(get_unlinked_episodes, lambda x:x)
1129 #------------------------------------------------------------------
1131 cmd = u"""SELECT distinct pk_episode
1132 from clin.v_pat_items
1133 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1134 args = {
1135 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1136 'pat': self.pk_patient
1137 }
1138 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1139 if len(rows) == 0:
1140 return []
1141 epis = []
1142 for row in rows:
1143 epis.append(row[0])
1144 return self.get_episodes(id_list=epis)
1145 #------------------------------------------------------------------
1147 """Add episode 'episode_name' for a patient's health issue.
1148
1149 - silently returns if episode already exists
1150 """
1151 episode = gmEMRStructItems.create_episode (
1152 pk_health_issue = pk_health_issue,
1153 episode_name = episode_name,
1154 is_open = is_open,
1155 encounter = self.current_encounter['pk_encounter']
1156 )
1157 return episode
1158 #--------------------------------------------------------
1160 # try to find the episode with the most recently modified clinical item
1161
1162 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1163
1164 cmd = u"""
1165 SELECT pk
1166 from clin.episode
1167 WHERE pk = (
1168 SELECT distinct on(pk_episode) pk_episode
1169 from clin.v_pat_items
1170 WHERE
1171 pk_patient = %%(pat)s
1172 and
1173 modified_when = (
1174 SELECT max(vpi.modified_when)
1175 from clin.v_pat_items vpi
1176 WHERE vpi.pk_patient = %%(pat)s
1177 )
1178 %s
1179 -- guard against several episodes created at the same moment of time
1180 limit 1
1181 )""" % issue_where
1182 rows, idx = gmPG2.run_ro_queries(queries = [
1183 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1184 ])
1185 if len(rows) != 0:
1186 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1187
1188 # no clinical items recorded, so try to find
1189 # the youngest episode for this patient
1190 cmd = u"""
1191 SELECT vpe0.pk_episode
1192 from
1193 clin.v_pat_episodes vpe0
1194 WHERE
1195 vpe0.pk_patient = %%(pat)s
1196 and
1197 vpe0.episode_modified_when = (
1198 SELECT max(vpe1.episode_modified_when)
1199 from clin.v_pat_episodes vpe1
1200 WHERE vpe1.pk_episode = vpe0.pk_episode
1201 )
1202 %s""" % issue_where
1203 rows, idx = gmPG2.run_ro_queries(queries = [
1204 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1205 ])
1206 if len(rows) != 0:
1207 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1208
1209 return None
1210 #--------------------------------------------------------
1213 #--------------------------------------------------------
1214 # API: problems
1215 #--------------------------------------------------------
1216 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1217 """Retrieve a patient's problems.
1218
1219 "Problems" are the UNION of:
1220
1221 - issues which are .clinically_relevant
1222 - episodes which are .is_open
1223
1224 Therefore, both an issue and the open episode
1225 thereof can each be listed as a problem.
1226
1227 include_closed_episodes/include_irrelevant_issues will
1228 include those -- which departs from the definition of
1229 the problem list being "active" items only ...
1230
1231 episodes - episodes' PKs to filter problems by
1232 issues - health issues' PKs to filter problems by
1233 """
1234 # FIXME: this could use a good measure of streamlining, probably
1235
1236 args = {'pat': self.pk_patient}
1237
1238 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1239 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1240
1241 # Instantiate problem items
1242 problems = []
1243 for row in rows:
1244 pk_args = {
1245 u'pk_patient': self.pk_patient,
1246 u'pk_health_issue': row['pk_health_issue'],
1247 u'pk_episode': row['pk_episode']
1248 }
1249 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1250
1251 # include non-problems ?
1252 other_rows = []
1253 if include_closed_episodes:
1254 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1255 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1256 other_rows.extend(rows)
1257
1258 if include_irrelevant_issues:
1259 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1260 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1261 other_rows.extend(rows)
1262
1263 if len(other_rows) > 0:
1264 for row in other_rows:
1265 pk_args = {
1266 u'pk_patient': self.pk_patient,
1267 u'pk_health_issue': row['pk_health_issue'],
1268 u'pk_episode': row['pk_episode']
1269 }
1270 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1271
1272 # filter ?
1273 if (episodes is None) and (issues is None):
1274 return problems
1275
1276 # filter
1277 if issues is not None:
1278 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1279 if episodes is not None:
1280 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1281
1282 return problems
1283 #--------------------------------------------------------
1286 #--------------------------------------------------------
1289 #--------------------------------------------------------
1292 #--------------------------------------------------------
1293 # API: health issues
1294 #--------------------------------------------------------
1296
1297 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient = %(pat)s ORDER BY description"
1298 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1299 issues = [ gmEMRStructItems.cHealthIssue(row = {'idx': idx, 'data': r, 'pk_field': 'pk_health_issue'}) for r in rows ]
1300
1301 if id_list is None:
1302 return issues
1303
1304 if len(id_list) == 0:
1305 raise ValueError('id_list to filter by is empty, most likely a programming error')
1306
1307 filtered_issues = []
1308 for issue in issues:
1309 if issue['pk_health_issue'] in id_list:
1310 filtered_issues.append(issue)
1311
1312 return filtered_issues
1313
1314 health_issues = property(get_health_issues, lambda x:x)
1315 #------------------------------------------------------------------
1317 """Adds patient health issue."""
1318 return gmEMRStructItems.create_health_issue (
1319 description = issue_name,
1320 encounter = self.current_encounter['pk_encounter'],
1321 patient = self.pk_patient
1322 )
1323 #--------------------------------------------------------
1326 #--------------------------------------------------------
1327 # API: substance intake
1328 #--------------------------------------------------------
1329 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1330
1331 where_parts = [u'pk_patient = %(pat)s']
1332
1333 if not include_inactive:
1334 where_parts.append(u'is_currently_active in (true, null)')
1335
1336 if not include_unapproved:
1337 where_parts.append(u'intake_is_approved_of in (true, null)')
1338
1339 if order_by is None:
1340 order_by = u''
1341 else:
1342 order_by = u'order by %s' % order_by
1343
1344 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1345 u'\nand '.join(where_parts),
1346 order_by
1347 )
1348
1349 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1350
1351 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1352
1353 if episodes is not None:
1354 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1355
1356 if issues is not None:
1357 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1358
1359 return meds
1360 #--------------------------------------------------------
1361 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1362 return gmMedication.create_substance_intake (
1363 pk_substance = pk_substance,
1364 pk_component = pk_component,
1365 encounter = self.current_encounter['pk_encounter'],
1366 episode = episode,
1367 preparation = preparation
1368 )
1369 #--------------------------------------------------------
1371 return gmMedication.substance_intake_exists (
1372 pk_component = pk_component,
1373 pk_substance = pk_substance,
1374 pk_identity = self.pk_patient
1375 )
1376 #--------------------------------------------------------
1377 # API: vaccinations
1378 #--------------------------------------------------------
1380 return gmVaccination.create_vaccination (
1381 encounter = self.current_encounter['pk_encounter'],
1382 episode = episode,
1383 vaccine = vaccine,
1384 batch_no = batch_no
1385 )
1386 #--------------------------------------------------------
1388 """Returns latest given vaccination for each vaccinated indication.
1389
1390 as a dict {'l10n_indication': cVaccination instance}
1391
1392 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1393 """
1394 # find the PKs
1395 args = {'pat': self.pk_patient}
1396 where_parts = [u'pk_patient = %(pat)s']
1397
1398 if (episodes is not None) and (len(episodes) > 0):
1399 where_parts.append(u'pk_episode IN %(epis)s')
1400 args['epis'] = tuple(episodes)
1401
1402 if (issues is not None) and (len(issues) > 0):
1403 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1404 args['issues'] = tuple(issues)
1405
1406 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1407 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1408
1409 # none found
1410 if len(rows) == 0:
1411 return {}
1412
1413 vpks = [ ind['pk_vaccination'] for ind in rows ]
1414 vinds = [ ind['l10n_indication'] for ind in rows ]
1415 ind_counts = [ ind['indication_count'] for ind in rows ]
1416
1417 # turn them into vaccinations
1418 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1419 args = {'pks': tuple(vpks)}
1420 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1421
1422 vaccs = {}
1423 for idx in range(len(vpks)):
1424 pk = vpks[idx]
1425 ind_count = ind_counts[idx]
1426 for r in rows:
1427 if r['pk_vaccination'] == pk:
1428 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1429
1430 return vaccs
1431 #--------------------------------------------------------
1433
1434 args = {'pat': self.pk_patient}
1435 where_parts = [u'pk_patient = %(pat)s']
1436
1437 if order_by is None:
1438 order_by = u''
1439 else:
1440 order_by = u'ORDER BY %s' % order_by
1441
1442 if (episodes is not None) and (len(episodes) > 0):
1443 where_parts.append(u'pk_episode IN %(epis)s')
1444 args['epis'] = tuple(episodes)
1445
1446 if (issues is not None) and (len(issues) > 0):
1447 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1448 args['issues'] = tuple(issues)
1449
1450 if (encounters is not None) and (len(encounters) > 0):
1451 where_parts.append(u'pk_encounter IN %(encs)s')
1452 args['encs'] = tuple(encounters)
1453
1454 cmd = u'%s %s' % (
1455 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1456 order_by
1457 )
1458 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1459 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1460
1461 return vaccs
1462
1463 vaccinations = property(get_vaccinations, lambda x:x)
1464 #--------------------------------------------------------
1465 # old/obsolete:
1466 #--------------------------------------------------------
1468 """Retrieves vaccination regimes the patient is on.
1469
1470 optional:
1471 * ID - PK of the vaccination regime
1472 * indications - indications we want to retrieve vaccination
1473 regimes for, must be primary language, not l10n_indication
1474 """
1475 # FIXME: use course, not regime
1476 try:
1477 self.__db_cache['vaccinations']['scheduled regimes']
1478 except KeyError:
1479 # retrieve vaccination regimes definitions
1480 self.__db_cache['vaccinations']['scheduled regimes'] = []
1481 cmd = """SELECT distinct on(pk_course) pk_course
1482 FROM clin.v_vaccs_scheduled4pat
1483 WHERE pk_patient=%s"""
1484 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1485 if rows is None:
1486 _log.error('cannot retrieve scheduled vaccination courses')
1487 del self.__db_cache['vaccinations']['scheduled regimes']
1488 return None
1489 # Instantiate vaccination items and keep cache
1490 for row in rows:
1491 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1492
1493 # ok, let's constrain our list
1494 filtered_regimes = []
1495 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1496 if ID is not None:
1497 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1498 if len(filtered_regimes) == 0:
1499 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1500 return []
1501 else:
1502 return filtered_regimes[0]
1503 if indications is not None:
1504 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1505
1506 return filtered_regimes
1507 #--------------------------------------------------------
1508 # def get_vaccinated_indications(self):
1509 # """Retrieves patient vaccinated indications list.
1510 #
1511 # Note that this does NOT rely on the patient being on
1512 # some schedule or other but rather works with what the
1513 # patient has ACTUALLY been vaccinated against. This is
1514 # deliberate !
1515 # """
1516 # # most likely, vaccinations will be fetched close
1517 # # by so it makes sense to count on the cache being
1518 # # filled (or fill it for nearby use)
1519 # vaccinations = self.get_vaccinations()
1520 # if vaccinations is None:
1521 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1522 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1523 # if len(vaccinations) == 0:
1524 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1525 # v_indications = []
1526 # for vacc in vaccinations:
1527 # tmp = [vacc['indication'], vacc['l10n_indication']]
1528 # # remove duplicates
1529 # if tmp in v_indications:
1530 # continue
1531 # v_indications.append(tmp)
1532 # return (True, v_indications)
1533 #--------------------------------------------------------
1534 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1535 """Retrieves list of vaccinations the patient has received.
1536
1537 optional:
1538 * ID - PK of a vaccination
1539 * indications - indications we want to retrieve vaccination
1540 items for, must be primary language, not l10n_indication
1541 * since - initial date for allergy items
1542 * until - final date for allergy items
1543 * encounters - list of encounters whose allergies are to be retrieved
1544 * episodes - list of episodes whose allergies are to be retrieved
1545 * issues - list of health issues whose allergies are to be retrieved
1546 """
1547 try:
1548 self.__db_cache['vaccinations']['vaccinated']
1549 except KeyError:
1550 self.__db_cache['vaccinations']['vaccinated'] = []
1551 # Important fetch ordering by indication, date to know if a vaccination is booster
1552 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1553 WHERE pk_patient=%s
1554 order by indication, date"""
1555 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1556 if rows is None:
1557 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1558 del self.__db_cache['vaccinations']['vaccinated']
1559 return None
1560 # Instantiate vaccination items
1561 vaccs_by_ind = {}
1562 for row in rows:
1563 vacc_row = {
1564 'pk_field': 'pk_vaccination',
1565 'idx': idx,
1566 'data': row
1567 }
1568 vacc = gmVaccination.cVaccination(row=vacc_row)
1569 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1570 # keep them, ordered by indication
1571 try:
1572 vaccs_by_ind[vacc['indication']].append(vacc)
1573 except KeyError:
1574 vaccs_by_ind[vacc['indication']] = [vacc]
1575
1576 # calculate sequence number and is_booster
1577 for ind in vaccs_by_ind.keys():
1578 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1579 for vacc in vaccs_by_ind[ind]:
1580 # due to the "order by indication, date" the vaccinations are in the
1581 # right temporal order inside the indication-keyed dicts
1582 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1583 vacc['seq_no'] = seq_no
1584 # if no active schedule for indication we cannot
1585 # check for booster status (eg. seq_no > max_shot)
1586 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1587 continue
1588 if seq_no > vacc_regimes[0]['shots']:
1589 vacc['is_booster'] = True
1590 del vaccs_by_ind
1591
1592 # ok, let's constrain our list
1593 filtered_shots = []
1594 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1595 if ID is not None:
1596 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1597 if len(filtered_shots) == 0:
1598 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1599 return None
1600 else:
1601 return filtered_shots[0]
1602 if since is not None:
1603 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1604 if until is not None:
1605 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1606 if issues is not None:
1607 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1608 if episodes is not None:
1609 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1610 if encounters is not None:
1611 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1612 if indications is not None:
1613 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1614 return filtered_shots
1615 #--------------------------------------------------------
1617 """Retrieves vaccinations scheduled for a regime a patient is on.
1618
1619 The regime is referenced by its indication (not l10n)
1620
1621 * indications - List of indications (not l10n) of regimes we want scheduled
1622 vaccinations to be fetched for
1623 """
1624 try:
1625 self.__db_cache['vaccinations']['scheduled']
1626 except KeyError:
1627 self.__db_cache['vaccinations']['scheduled'] = []
1628 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1629 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1630 if rows is None:
1631 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1632 del self.__db_cache['vaccinations']['scheduled']
1633 return None
1634 # Instantiate vaccination items
1635 for row in rows:
1636 vacc_row = {
1637 'pk_field': 'pk_vacc_def',
1638 'idx': idx,
1639 'data': row
1640 }
1641 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1642
1643 # ok, let's constrain our list
1644 if indications is None:
1645 return self.__db_cache['vaccinations']['scheduled']
1646 filtered_shots = []
1647 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1648 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1649 return filtered_shots
1650 #--------------------------------------------------------
1652 try:
1653 self.__db_cache['vaccinations']['missing']
1654 except KeyError:
1655 self.__db_cache['vaccinations']['missing'] = {}
1656 # 1) non-booster
1657 self.__db_cache['vaccinations']['missing']['due'] = []
1658 # get list of (indication, seq_no) tuples
1659 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1660 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1661 if rows is None:
1662 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1663 return None
1664 pk_args = {'pat_id': self.pk_patient}
1665 if rows is not None:
1666 for row in rows:
1667 pk_args['indication'] = row[0]
1668 pk_args['seq_no'] = row[1]
1669 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1670
1671 # 2) boosters
1672 self.__db_cache['vaccinations']['missing']['boosters'] = []
1673 # get list of indications
1674 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1675 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1676 if rows is None:
1677 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1678 return None
1679 pk_args = {'pat_id': self.pk_patient}
1680 if rows is not None:
1681 for row in rows:
1682 pk_args['indication'] = row[0]
1683 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1684
1685 # if any filters ...
1686 if indications is None:
1687 return self.__db_cache['vaccinations']['missing']
1688 if len(indications) == 0:
1689 return self.__db_cache['vaccinations']['missing']
1690 # ... apply them
1691 filtered_shots = {
1692 'due': [],
1693 'boosters': []
1694 }
1695 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1696 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
1697 filtered_shots['due'].append(due_shot)
1698 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1699 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
1700 filtered_shots['boosters'].append(due_shot)
1701 return filtered_shots
1702 #------------------------------------------------------------------
1703 # API: encounters
1704 #------------------------------------------------------------------
1707
1709
1710 # first ever setting ?
1711 if self.__encounter is None:
1712 _log.debug('first setting of active encounter in this clinical record instance')
1713 else:
1714 _log.debug('switching of active encounter')
1715 # fail if the currently active encounter has unsaved changes
1716 if self.__encounter.is_modified():
1717 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1718 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1719
1720 # set the currently active encounter and announce that change
1721 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1722 now = gmDateTime.pydt_now_here()
1723 if now > encounter['started']:
1724 encounter['last_affirmed'] = now # this will trigger an "encounter_mod_db"
1725 encounter.save()
1726 self.__encounter = encounter
1727 gmDispatcher.send(u'current_encounter_switched')
1728
1729 return True
1730
1731 current_encounter = property(_get_current_encounter, _set_current_encounter)
1732 active_encounter = property(_get_current_encounter, _set_current_encounter)
1733 #------------------------------------------------------------------
1735
1736 # 1) "very recent" encounter recorded ?
1737 if self.__activate_very_recent_encounter():
1738 return True
1739
1740 # 2) "fairly recent" encounter recorded ?
1741 if self.__activate_fairly_recent_encounter():
1742 return True
1743
1744 # 3) start a completely new encounter
1745 self.start_new_encounter()
1746 return True
1747 #------------------------------------------------------------------
1749 """Try to attach to a "very recent" encounter if there is one.
1750
1751 returns:
1752 False: no "very recent" encounter, create new one
1753 True: success
1754 """
1755 cfg_db = gmCfg.cCfgSQL()
1756 min_ttl = cfg_db.get2 (
1757 option = u'encounter.minimum_ttl',
1758 workplace = _here.active_workplace,
1759 bias = u'user',
1760 default = u'1 hour 30 minutes'
1761 )
1762 cmd = u"""
1763 SELECT pk_encounter
1764 FROM clin.v_most_recent_encounters
1765 WHERE
1766 pk_patient = %s
1767 and
1768 last_affirmed > (now() - %s::interval)
1769 ORDER BY
1770 last_affirmed DESC"""
1771 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1772 # none found
1773 if len(enc_rows) == 0:
1774 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1775 return False
1776 # attach to existing
1777 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1778 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1779 return True
1780 #------------------------------------------------------------------
1782 """Try to attach to a "fairly recent" encounter if there is one.
1783
1784 returns:
1785 False: no "fairly recent" encounter, create new one
1786 True: success
1787 """
1788 if _func_ask_user is None:
1789 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1790 return False
1791
1792 cfg_db = gmCfg.cCfgSQL()
1793 min_ttl = cfg_db.get2 (
1794 option = u'encounter.minimum_ttl',
1795 workplace = _here.active_workplace,
1796 bias = u'user',
1797 default = u'1 hour 30 minutes'
1798 )
1799 max_ttl = cfg_db.get2 (
1800 option = u'encounter.maximum_ttl',
1801 workplace = _here.active_workplace,
1802 bias = u'user',
1803 default = u'6 hours'
1804 )
1805 cmd = u"""
1806 SELECT pk_encounter
1807 FROM clin.v_most_recent_encounters
1808 WHERE
1809 pk_patient=%s
1810 AND
1811 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)
1812 ORDER BY
1813 last_affirmed DESC"""
1814 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1815 # none found
1816 if len(enc_rows) == 0:
1817 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1818 return False
1819 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1820 # ask user whether to attach or not
1821 cmd = u"""
1822 SELECT title, firstnames, lastnames, gender, dob
1823 FROM dem.v_basic_person WHERE pk_identity=%s"""
1824 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1825 pat = pats[0]
1826 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1827 gmTools.coalesce(pat[0], u'')[:5],
1828 pat[1][:15],
1829 pat[2][:15],
1830 pat[3],
1831 pat[4].strftime('%x'),
1832 self.pk_patient
1833 )
1834 enc = gmI18N.get_encoding()
1835 msg = _(
1836 '%s\n'
1837 '\n'
1838 "This patient's chart was worked on only recently:\n"
1839 '\n'
1840 ' %s %s - %s (%s)\n'
1841 '\n'
1842 ' Request: %s\n'
1843 ' Outcome: %s\n'
1844 '\n'
1845 'Do you want to continue that consultation\n'
1846 'or do you want to start a new one ?\n'
1847 ) % (
1848 pat_str,
1849 encounter['started'].strftime('%x').decode(enc),
1850 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1851 encounter['l10n_type'],
1852 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1853 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1854 )
1855 attach = False
1856 try:
1857 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1858 except:
1859 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1860 return False
1861 if not attach:
1862 return False
1863
1864 # attach to existing
1865 self.current_encounter = encounter
1866
1867 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1868 return True
1869 #------------------------------------------------------------------
1871 cfg_db = gmCfg.cCfgSQL()
1872 # FIXME: look for MRU/MCU encounter type config here
1873 enc_type = cfg_db.get2 (
1874 option = u'encounter.default_type',
1875 workplace = _here.active_workplace,
1876 bias = u'user',
1877 default = u'in surgery'
1878 )
1879 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
1880 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1881 #------------------------------------------------------------------
1882 - def get_encounters(self, since=None, until=None, id_list=None, episodes=None, issues=None, skip_empty=False):
1883 """Retrieves patient's encounters.
1884
1885 id_list - PKs of encounters to fetch
1886 since - initial date for encounter items, DateTime instance
1887 until - final date for encounter items, DateTime instance
1888 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1889 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1890 skip_empty - do NOT return those which do not have any of documents/clinical items/RFE/AOE
1891
1892 NOTE: if you specify *both* issues and episodes
1893 you will get the *aggregate* of all encounters even
1894 if the episodes all belong to the health issues listed.
1895 IOW, the issues broaden the episode list rather than
1896 the episode list narrowing the episodes-from-issues
1897 list.
1898 Rationale: If it was the other way round it would be
1899 redundant to specify the list of issues at all.
1900 """
1901 where_parts = [u'c_vpe.pk_patient = %(pat)s']
1902 args = {'pat': self.pk_patient}
1903
1904 if skip_empty:
1905 where_parts.append(u"""NOT (
1906 gm.is_null_or_blank_string(c_vpe.reason_for_encounter)
1907 AND
1908 gm.is_null_or_blank_string(c_vpe.assessment_of_encounter)
1909 AND
1910 NOT EXISTS (
1911 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
1912 UNION ALL
1913 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
1914 ))""")
1915
1916 if since is not None:
1917 where_parts.append(u'c_vpe.started >= %(start)s')
1918 args['start'] = since
1919
1920 if until is not None:
1921 where_parts.append(u'c_vpe.last_affirmed <= %(end)s')
1922 args['end'] = since
1923
1924 cmd = u"""
1925 SELECT *
1926 FROM clin.v_pat_encounters c_vpe
1927 WHERE
1928 %s
1929 ORDER BY started
1930 """ % u' AND '.join(where_parts)
1931 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1932 encounters = [ gmEMRStructItems.cEncounter(row = {'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}) for r in rows ]
1933
1934 # we've got the encounters, start filtering
1935 filtered_encounters = []
1936 filtered_encounters.extend(encounters)
1937
1938 if id_list is not None:
1939 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1940
1941 if (issues is not None) and (len(issues) > 0):
1942 issues = tuple(issues)
1943 # however, this seems like the proper approach:
1944 # - find episodes corresponding to the health issues in question
1945 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1946 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1947 epi_ids = map(lambda x:x[0], rows)
1948 if episodes is None:
1949 episodes = []
1950 episodes.extend(epi_ids)
1951
1952 if (episodes is not None) and (len(episodes) > 0):
1953 episodes = tuple(episodes)
1954 # if the episodes to filter by belong to the patient in question so will
1955 # the encounters found with them - hence we don't need a WHERE on the patient ...
1956 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1957 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1958 enc_ids = map(lambda x:x[0], rows)
1959 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1960
1961 return filtered_encounters
1962 #--------------------------------------------------------
1964 """Retrieves first encounter for a particular issue and/or episode.
1965
1966 issue_id - First encounter associated health issue
1967 episode - First encounter associated episode
1968 """
1969 # FIXME: use direct query
1970 if issue_id is None:
1971 issues = None
1972 else:
1973 issues = [issue_id]
1974
1975 if episode_id is None:
1976 episodes = None
1977 else:
1978 episodes = [episode_id]
1979
1980 encounters = self.get_encounters(issues=issues, episodes=episodes)
1981 if len(encounters) == 0:
1982 return None
1983
1984 # FIXME: this does not scale particularly well, I assume
1985 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1986 return encounters[0]
1987 #--------------------------------------------------------
1989 args = {'pat': self.pk_patient}
1990 cmd = u"""
1991 SELECT MIN(earliest) FROM (
1992 (
1993 SELECT MIN(episode_modified_when) AS earliest FROM clin.v_pat_episodes WHERE pk_patient = %(pat)s
1994
1995 ) UNION ALL (
1996
1997 SELECT MIN(modified_when) AS earliest FROM clin.v_health_issues WHERE pk_patient = %(pat)s
1998
1999 ) UNION ALL (
2000
2001 SELECT MIN(modified_when) AS earliest FROM clin.encounter WHERE fk_patient = %(pat)s
2002
2003 ) UNION ALL (
2004
2005 SELECT MIN(started) AS earliest FROM clin.v_pat_encounters WHERE pk_patient = %(pat)s
2006
2007 ) UNION ALL (
2008
2009 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_items WHERE pk_patient = %(pat)s
2010
2011 ) UNION ALL (
2012
2013 SELECT MIN(modified_when) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2014
2015 ) UNION ALL (
2016
2017 SELECT MIN(last_confirmed) AS earliest FROM clin.v_pat_allergy_state WHERE pk_patient = %(pat)s
2018
2019 )
2020 ) AS candidates"""
2021 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2022 return rows[0][0]
2023
2024 earliest_care_date = property(get_earliest_care_date, lambda x:x)
2025 #--------------------------------------------------------
2027 """Retrieves last encounter for a concrete issue and/or episode
2028
2029 issue_id - Last encounter associated health issue
2030 episode_id - Last encounter associated episode
2031 """
2032 # FIXME: use direct query
2033
2034 if issue_id is None:
2035 issues = None
2036 else:
2037 issues = [issue_id]
2038
2039 if episode_id is None:
2040 episodes = None
2041 else:
2042 episodes = [episode_id]
2043
2044 encounters = self.get_encounters(issues=issues, episodes=episodes)
2045 if len(encounters) == 0:
2046 return None
2047
2048 # FIXME: this does not scale particularly well, I assume
2049 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
2050 return encounters[-1]
2051
2052 last_encounter = property(get_last_encounter, lambda x:x)
2053 #------------------------------------------------------------------
2055 args = {'pat': self.pk_patient, 'range': cover_period}
2056 where_parts = [u'pk_patient = %(pat)s']
2057 if cover_period is not None:
2058 where_parts.append(u'last_affirmed > now() - %(range)s')
2059
2060 cmd = u"""
2061 SELECT l10n_type, count(1) AS frequency
2062 FROM clin.v_pat_encounters
2063 WHERE
2064 %s
2065 GROUP BY l10n_type
2066 ORDER BY frequency DESC
2067 """ % u' AND '.join(where_parts)
2068 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2069 return rows
2070 #------------------------------------------------------------------
2072
2073 args = {'pat': self.pk_patient}
2074
2075 if (issue_id is None) and (episode_id is None):
2076
2077 cmd = u"""
2078 SELECT * FROM clin.v_pat_encounters
2079 WHERE pk_patient = %(pat)s
2080 ORDER BY started DESC
2081 LIMIT 2
2082 """
2083 else:
2084 where_parts = []
2085
2086 if issue_id is not None:
2087 where_parts.append(u'pk_health_issue = %(issue)s')
2088 args['issue'] = issue_id
2089
2090 if episode_id is not None:
2091 where_parts.append(u'pk_episode = %(epi)s')
2092 args['epi'] = episode_id
2093
2094 cmd = u"""
2095 SELECT *
2096 FROM clin.v_pat_encounters
2097 WHERE
2098 pk_patient = %%(pat)s
2099 AND
2100 pk_encounter IN (
2101 SELECT distinct pk_encounter
2102 FROM clin.v_pat_narrative
2103 WHERE
2104 %s
2105 )
2106 ORDER BY started DESC
2107 LIMIT 2
2108 """ % u' AND '.join(where_parts)
2109
2110 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2111
2112 if len(rows) == 0:
2113 return None
2114
2115 # just one encounter within the above limits
2116 if len(rows) == 1:
2117 # is it the current encounter ?
2118 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2119 # yes
2120 return None
2121 # no
2122 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2123
2124 # more than one encounter
2125 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2126 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2127
2128 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2129 #------------------------------------------------------------------
2131 cfg_db = gmCfg.cCfgSQL()
2132 ttl = cfg_db.get2 (
2133 option = u'encounter.ttl_if_empty',
2134 workplace = _here.active_workplace,
2135 bias = u'user',
2136 default = u'1 week'
2137 )
2138
2139 # # FIXME: this should be done async
2140 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2141 args = {'pat': self.pk_patient, 'ttl': ttl}
2142 try:
2143 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2144 except:
2145 _log.exception('error deleting empty encounters')
2146
2147 return True
2148 #------------------------------------------------------------------
2149 # API: measurements / test results
2150 #------------------------------------------------------------------
2152 return gmPathLab.get_most_recent_results (
2153 test_type = test_type,
2154 loinc = loinc,
2155 no_of_results = no_of_results,
2156 patient = self.pk_patient
2157 )
2158 #------------------------------------------------------------------
2159 - def get_result_at_timestamp(self, timestamp=None, test_type=None, loinc=None, tolerance_interval='12 hours'):
2160 return gmPathLab.get_result_at_timestamp (
2161 timestamp = timestamp,
2162 test_type = test_type,
2163 loinc = loinc,
2164 tolerance_interval = tolerance_interval,
2165 patient = self.pk_patient
2166 )
2167 #------------------------------------------------------------------
2169 if order_by is None:
2170 order_by = u''
2171 else:
2172 order_by = u'ORDER BY %s' % order_by
2173 cmd = u"""
2174 SELECT * FROM clin.v_test_results
2175 WHERE
2176 pk_patient = %%(pat)s
2177 AND
2178 reviewed IS FALSE
2179 %s""" % order_by
2180 args = {'pat': self.pk_patient}
2181 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2182 return [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2183 #------------------------------------------------------------------
2184 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2186 """Retrieve data about test types for which this patient has results."""
2187
2188 cmd = u"""
2189 SELECT * FROM (
2190 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2191 FROM clin.v_test_results
2192 WHERE pk_patient = %(pat)s
2193 ) AS foo
2194 ORDER BY clin_when desc, unified_name
2195 """
2196 args = {'pat': self.pk_patient}
2197 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2198 return [ gmPathLab.cMeasurementType(aPK_obj = row['pk_test_type']) for row in rows ]
2199 #------------------------------------------------------------------
2201 """Get the dates for which we have results."""
2202 where_parts = [u'pk_patient = %(pat)s']
2203 args = {'pat': self.pk_patient}
2204
2205 if tests is not None:
2206 where_parts.append(u'pk_test_type IN %(tests)s')
2207 args['tests'] = tuple(tests)
2208
2209 cmd = u"""
2210 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2211 FROM clin.v_test_results
2212 WHERE %s
2213 ORDER BY cwhen %s
2214 """ % (
2215 u' AND '.join(where_parts),
2216 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2217 )
2218 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2219 return rows
2220 #------------------------------------------------------------------
2222 return gmPathLab.get_test_results (
2223 pk_patient = self.pk_patient,
2224 encounters = encounters,
2225 episodes = episodes,
2226 order_by = order_by
2227 )
2228 #------------------------------------------------------------------
2229 - def get_test_results_by_date(self, encounter=None, episodes=None, tests=None, reverse_chronological=True):
2230
2231 where_parts = [u'pk_patient = %(pat)s']
2232 args = {'pat': self.pk_patient}
2233
2234 if tests is not None:
2235 where_parts.append(u'pk_test_type IN %(tests)s')
2236 args['tests'] = tuple(tests)
2237
2238 if encounter is not None:
2239 where_parts.append(u'pk_encounter = %(enc)s')
2240 args['enc'] = encounter
2241
2242 if episodes is not None:
2243 where_parts.append(u'pk_episode IN %(epis)s')
2244 args['epis'] = tuple(episodes)
2245
2246 cmd = u"""
2247 SELECT * FROM clin.v_test_results
2248 WHERE %s
2249 ORDER BY clin_when %s, pk_episode, unified_name
2250 """ % (
2251 u' AND '.join(where_parts),
2252 gmTools.bool2subst(reverse_chronological, u'DESC', u'ASC', u'DESC')
2253 )
2254 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2255
2256 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2257
2258 return tests
2259 #------------------------------------------------------------------
2260 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2261
2262 try:
2263 epi = int(episode)
2264 except:
2265 epi = episode['pk_episode']
2266
2267 try:
2268 type = int(type)
2269 except:
2270 type = type['pk_test_type']
2271
2272 if intended_reviewer is None:
2273 intended_reviewer = _me['pk_staff']
2274
2275 tr = gmPathLab.create_test_result (
2276 encounter = self.current_encounter['pk_encounter'],
2277 episode = epi,
2278 type = type,
2279 intended_reviewer = intended_reviewer,
2280 val_num = val_num,
2281 val_alpha = val_alpha,
2282 unit = unit
2283 )
2284
2285 return tr
2286 #------------------------------------------------------------------
2287 #------------------------------------------------------------------
2288 #------------------------------------------------------------------
2289 #------------------------------------------------------------------
2291 # FIXME: verify that it is our patient ? ...
2292 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
2293 return req
2294 #------------------------------------------------------------------
2296 if encounter_id is None:
2297 encounter_id = self.current_encounter['pk_encounter']
2298 status, data = gmPathLab.create_lab_request(
2299 lab=lab,
2300 req_id=req_id,
2301 pat_id=self.pk_patient,
2302 encounter_id=encounter_id,
2303 episode_id=episode_id
2304 )
2305 if not status:
2306 _log.error(str(data))
2307 return None
2308 return data
2309
2310 #============================================================
2311 # main
2312 #------------------------------------------------------------
2313 if __name__ == "__main__":
2314
2315 if len(sys.argv) == 1:
2316 sys.exit()
2317
2318 if sys.argv[1] != 'test':
2319 sys.exit()
2320
2321 from Gnumed.pycommon import gmLog2
2322 #-----------------------------------------
2324 emr = cClinicalRecord(aPKey=1)
2325 state = emr.allergy_state
2326 print "allergy state is:", state
2327
2328 print "setting state to 0"
2329 emr.allergy_state = 0
2330
2331 print "setting state to None"
2332 emr.allergy_state = None
2333
2334 print "setting state to 'abc'"
2335 emr.allergy_state = 'abc'
2336 #-----------------------------------------
2338 emr = cClinicalRecord(aPKey=12)
2339 rows = emr.get_test_types_for_results()
2340 print "test result names:"
2341 for row in rows:
2342 print row
2343 #-----------------------------------------
2345 emr = cClinicalRecord(aPKey=12)
2346 rows = emr.get_dates_for_results()
2347 print "test result dates:"
2348 for row in rows:
2349 print row
2350 #-----------------------------------------
2352 emr = cClinicalRecord(aPKey=12)
2353 rows, idx = emr.get_measurements_by_date()
2354 print "test results:"
2355 for row in rows:
2356 print row
2357 #-----------------------------------------
2359 emr = cClinicalRecord(aPKey=12)
2360 tests = emr.get_test_results_by_date()
2361 print "test results:"
2362 for test in tests:
2363 print test
2364 #-----------------------------------------
2366 emr = cClinicalRecord(aPKey=12)
2367 for key, item in emr.get_statistics().iteritems():
2368 print key, ":", item
2369 #-----------------------------------------
2371 emr = cClinicalRecord(aPKey=12)
2372
2373 probs = emr.get_problems()
2374 print "normal probs (%s):" % len(probs)
2375 for p in probs:
2376 print u'%s (%s)' % (p['problem'], p['type'])
2377
2378 probs = emr.get_problems(include_closed_episodes=True)
2379 print "probs + closed episodes (%s):" % len(probs)
2380 for p in probs:
2381 print u'%s (%s)' % (p['problem'], p['type'])
2382
2383 probs = emr.get_problems(include_irrelevant_issues=True)
2384 print "probs + issues (%s):" % len(probs)
2385 for p in probs:
2386 print u'%s (%s)' % (p['problem'], p['type'])
2387
2388 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2389 print "probs + issues + epis (%s):" % len(probs)
2390 for p in probs:
2391 print u'%s (%s)' % (p['problem'], p['type'])
2392 #-----------------------------------------
2394 emr = cClinicalRecord(aPKey=12)
2395 tr = emr.add_test_result (
2396 episode = 1,
2397 intended_reviewer = 1,
2398 type = 1,
2399 val_num = 75,
2400 val_alpha = u'somewhat obese',
2401 unit = u'kg'
2402 )
2403 print tr
2404 #-----------------------------------------
2408 #-----------------------------------------
2410 emr = cClinicalRecord(aPKey=12)
2411 print emr.get_last_encounter(issue_id=2)
2412 print emr.get_last_but_one_encounter(issue_id=2)
2413 #-----------------------------------------
2415 emr = cClinicalRecord(aPKey=12)
2416 for med in emr.get_current_substance_intake():
2417 print med
2418 #-----------------------------------------
2420 emr = cClinicalRecord(aPKey = 12)
2421 print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
2422 #-----------------------------------------
2424 emr = cClinicalRecord(aPKey = 12)
2425 for journal_line in emr.get_as_journal():
2426 #print journal_line.keys()
2427 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2428 print ""
2429 #-----------------------------------------
2433 #-----------------------------------------
2435 emr = cClinicalRecord(aPKey=12)
2436 print "episodes:", emr.episodes
2437 print "unlinked:", emr.unlinked_episodes
2438
2439 #-----------------------------------------
2441 emr = cClinicalRecord(aPKey=12)
2442 from Gnumed.business.gmPerson import cPatient
2443 pat = cPatient(aPK_obj = 12)
2444 print emr.format_as_journal(left_margin = 1, patient = pat)
2445 #-----------------------------------------
2446
2447 #test_allergy_state()
2448 #test_is_allergic_to()
2449
2450 #test_get_test_names()
2451 #test_get_dates_for_results()
2452 #test_get_measurements()
2453 #test_get_test_results_by_date()
2454 #test_get_statistics()
2455 #test_get_problems()
2456 #test_add_test_result()
2457 #test_get_most_recent_episode()
2458 #test_get_almost_recent_encounter()
2459 #test_get_meds()
2460 #test_get_as_journal()
2461 #test_get_most_recent()
2462 #test_episodes()
2463 test_format_as_journal()
2464
2465 # emr = cClinicalRecord(aPKey = 12)
2466
2467 # # Vacc regimes
2468 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
2469 # print '\nVaccination regimes: '
2470 # for a_regime in vacc_regimes:
2471 # pass
2472 # #print a_regime
2473 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
2474 # #print vacc_regime
2475
2476 # # vaccination regimes and vaccinations for regimes
2477 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
2478 # print 'Vaccinations for the regime:'
2479 # for a_scheduled_vacc in scheduled_vaccs:
2480 # pass
2481 # #print ' %s' %(a_scheduled_vacc)
2482
2483 # # vaccination next shot and booster
2484 # vaccinations = emr.get_vaccinations()
2485 # for a_vacc in vaccinations:
2486 # print '\nVaccination %s , date: %s, booster: %s, seq no: %s' %(a_vacc['batch_no'], a_vacc['date'].strftime('%Y-%m-%d'), a_vacc['is_booster'], a_vacc['seq_no'])
2487
2488 # # first and last encounters
2489 # first_encounter = emr.get_first_encounter(issue_id = 1)
2490 # print '\nFirst encounter: ' + str(first_encounter)
2491 # last_encounter = emr.get_last_encounter(episode_id = 1)
2492 # print '\nLast encounter: ' + str(last_encounter)
2493 # print ''
2494
2495 #dump = record.get_missing_vaccinations()
2496 #f = open('vaccs.lst', 'wb')
2497 #if dump is not None:
2498 # print "=== due ==="
2499 # f.write("=== due ===\n")
2500 # for row in dump['due']:
2501 # print row
2502 # f.write(repr(row))
2503 # f.write('\n')
2504 # print "=== overdue ==="
2505 # f.write("=== overdue ===\n")
2506 # for row in dump['overdue']:
2507 # print row
2508 # f.write(repr(row))
2509 # f.write('\n')
2510 #f.close()
2511
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Feb 1 03:57:10 2013 | http://epydoc.sourceforge.net |