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