| 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: hospital stays
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 'Hospital stays: %(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 hospital stays') % 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 txt += u' %s (%s%s): %s%s (%s %s%s%s)\n' % (
879 ind,
880 gmTools.u_sum,
881 ind_count,
882 vacc['date_given'].strftime('%b %Y').decode(gmI18N.get_encoding()),
883 age_given,
884 vacc['vaccine'],
885 gmTools.u_left_double_angle_quote,
886 vacc['batch_no'],
887 gmTools.u_right_double_angle_quote
888 )
889
890 return txt
891 #--------------------------------------------------------
892 # API: allergy
893 #--------------------------------------------------------
894 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
895 """Retrieves patient allergy items.
896
897 remove_sensitivities
898 - retrieve real allergies only, without sensitivities
899 since
900 - initial date for allergy items
901 until
902 - final date for allergy items
903 encounters
904 - list of encounters whose allergies are to be retrieved
905 episodes
906 - list of episodes whose allergies are to be retrieved
907 issues
908 - list of health issues whose allergies are to be retrieved
909 """
910 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
911 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
912 allergies = []
913 for r in rows:
914 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
915
916 # ok, let's constrain our list
917 filtered_allergies = []
918 filtered_allergies.extend(allergies)
919
920 if ID_list is not None:
921 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
922 if len(filtered_allergies) == 0:
923 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
924 # better fail here contrary to what we do elsewhere
925 return None
926 else:
927 return filtered_allergies
928
929 if remove_sensitivities:
930 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
931 if since is not None:
932 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
933 if until is not None:
934 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
935 if issues is not None:
936 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
937 if episodes is not None:
938 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
939 if encounters is not None:
940 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
941
942 return filtered_allergies
943 #--------------------------------------------------------
945 if encounter_id is None:
946 encounter_id = self.current_encounter['pk_encounter']
947
948 if episode_id is None:
949 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
950 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
951 episode_id = epi['pk_episode']
952
953 new_allergy = gmAllergy.create_allergy (
954 allergene = allergene,
955 allg_type = allg_type,
956 encounter_id = encounter_id,
957 episode_id = episode_id
958 )
959
960 return new_allergy
961 #--------------------------------------------------------
963 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
964 args = {'pk_allg': pk_allergy}
965 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
966 #--------------------------------------------------------
968 """Cave: only use with one potential allergic agent
969 otherwise you won't know which of the agents the allergy is to."""
970
971 # we don't know the state
972 if self.allergy_state is None:
973 return None
974
975 # we know there's no allergies
976 if self.allergy_state == 0:
977 return False
978
979 args = {
980 'atcs': atcs,
981 'inns': inns,
982 'brand': brand,
983 'pat': self.pk_patient
984 }
985 allergenes = []
986 where_parts = []
987
988 if len(atcs) == 0:
989 atcs = None
990 if atcs is not None:
991 where_parts.append(u'atc_code in %(atcs)s')
992 if len(inns) == 0:
993 inns = None
994 if inns is not None:
995 where_parts.append(u'generics in %(inns)s')
996 allergenes.extend(inns)
997 if brand is not None:
998 where_parts.append(u'substance = %(brand)s')
999 allergenes.append(brand)
1000
1001 if len(allergenes) != 0:
1002 where_parts.append(u'allergene in %(allgs)s')
1003 args['allgs'] = tuple(allergenes)
1004
1005 cmd = u"""
1006 SELECT * FROM clin.v_pat_allergies
1007 WHERE
1008 pk_patient = %%(pat)s
1009 AND ( %s )""" % u' OR '.join(where_parts)
1010
1011 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1012
1013 if len(rows) == 0:
1014 return False
1015
1016 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
1017 #--------------------------------------------------------
1019
1020 if state not in gmAllergy.allergy_states:
1021 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
1022
1023 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
1024 allg_state['has_allergy'] = state
1025 allg_state.save_payload()
1026 return True
1027
1030
1031 allergy_state = property(_get_allergy_state, _set_allergy_state)
1032 #--------------------------------------------------------
1033 # API: episodes
1034 #--------------------------------------------------------
1036 """Fetches from backend patient episodes.
1037
1038 id_list - Episodes' PKs list
1039 issues - Health issues' PKs list to filter episodes by
1040 open_status - return all episodes, only open or closed one(s)
1041 """
1042 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
1043 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1044 tmp = []
1045 for r in rows:
1046 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
1047
1048 # now filter
1049 if (id_list is None) and (issues is None) and (open_status is None):
1050 return tmp
1051
1052 # ok, let's filter episode list
1053 filtered_episodes = []
1054 filtered_episodes.extend(tmp)
1055 if open_status is not None:
1056 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
1057
1058 if issues is not None:
1059 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
1060
1061 if id_list is not None:
1062 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
1063
1064 return filtered_episodes
1065 #------------------------------------------------------------------
1067 cmd = u"""SELECT distinct pk_episode
1068 from clin.v_pat_items
1069 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1070 args = {
1071 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1072 'pat': self.pk_patient
1073 }
1074 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1075 if len(rows) == 0:
1076 return []
1077 epis = []
1078 for row in rows:
1079 epis.append(row[0])
1080 return self.get_episodes(id_list=epis)
1081 #------------------------------------------------------------------
1083 """Add episode 'episode_name' for a patient's health issue.
1084
1085 - silently returns if episode already exists
1086 """
1087 episode = gmEMRStructItems.create_episode (
1088 pk_health_issue = pk_health_issue,
1089 episode_name = episode_name,
1090 is_open = is_open,
1091 encounter = self.current_encounter['pk_encounter']
1092 )
1093 return episode
1094 #--------------------------------------------------------
1096 # try to find the episode with the most recently modified clinical item
1097
1098 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1099
1100 cmd = u"""
1101 SELECT pk
1102 from clin.episode
1103 WHERE pk = (
1104 SELECT distinct on(pk_episode) pk_episode
1105 from clin.v_pat_items
1106 WHERE
1107 pk_patient = %%(pat)s
1108 and
1109 modified_when = (
1110 SELECT max(vpi.modified_when)
1111 from clin.v_pat_items vpi
1112 WHERE vpi.pk_patient = %%(pat)s
1113 )
1114 %s
1115 -- guard against several episodes created at the same moment of time
1116 limit 1
1117 )""" % issue_where
1118 rows, idx = gmPG2.run_ro_queries(queries = [
1119 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1120 ])
1121 if len(rows) != 0:
1122 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1123
1124 # no clinical items recorded, so try to find
1125 # the youngest episode for this patient
1126 cmd = u"""
1127 SELECT vpe0.pk_episode
1128 from
1129 clin.v_pat_episodes vpe0
1130 WHERE
1131 vpe0.pk_patient = %%(pat)s
1132 and
1133 vpe0.episode_modified_when = (
1134 SELECT max(vpe1.episode_modified_when)
1135 from clin.v_pat_episodes vpe1
1136 WHERE vpe1.pk_episode = vpe0.pk_episode
1137 )
1138 %s""" % issue_where
1139 rows, idx = gmPG2.run_ro_queries(queries = [
1140 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1141 ])
1142 if len(rows) != 0:
1143 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1144
1145 return None
1146 #--------------------------------------------------------
1149 #--------------------------------------------------------
1150 # API: problems
1151 #--------------------------------------------------------
1152 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1153 """Retrieve a patient's problems.
1154
1155 "Problems" are the UNION of:
1156
1157 - issues which are .clinically_relevant
1158 - episodes which are .is_open
1159
1160 Therefore, both an issue and the open episode
1161 thereof can each be listed as a problem.
1162
1163 include_closed_episodes/include_irrelevant_issues will
1164 include those -- which departs from the definition of
1165 the problem list being "active" items only ...
1166
1167 episodes - episodes' PKs to filter problems by
1168 issues - health issues' PKs to filter problems by
1169 """
1170 # FIXME: this could use a good measure of streamlining, probably
1171
1172 args = {'pat': self.pk_patient}
1173
1174 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s ORDER BY problem"""
1175 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1176
1177 # Instantiate problem items
1178 problems = []
1179 for row in rows:
1180 pk_args = {
1181 u'pk_patient': self.pk_patient,
1182 u'pk_health_issue': row['pk_health_issue'],
1183 u'pk_episode': row['pk_episode']
1184 }
1185 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1186
1187 # include non-problems ?
1188 other_rows = []
1189 if include_closed_episodes:
1190 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1191 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1192 other_rows.extend(rows)
1193
1194 if include_irrelevant_issues:
1195 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1196 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1197 other_rows.extend(rows)
1198
1199 if len(other_rows) > 0:
1200 for row in other_rows:
1201 pk_args = {
1202 u'pk_patient': self.pk_patient,
1203 u'pk_health_issue': row['pk_health_issue'],
1204 u'pk_episode': row['pk_episode']
1205 }
1206 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1207
1208 # filter ?
1209 if (episodes is None) and (issues is None):
1210 return problems
1211
1212 # filter
1213 if issues is not None:
1214 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1215 if episodes is not None:
1216 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1217
1218 return problems
1219 #--------------------------------------------------------
1222 #--------------------------------------------------------
1225 #--------------------------------------------------------
1228 #--------------------------------------------------------
1229 # API: health issues
1230 #--------------------------------------------------------
1232
1233 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1234 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1235 issues = []
1236 for row in rows:
1237 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1238 issues.append(gmEMRStructItems.cHealthIssue(row = r))
1239
1240 if id_list is None:
1241 return issues
1242
1243 if len(id_list) == 0:
1244 raise ValueError('id_list to filter by is empty, most likely a programming error')
1245
1246 filtered_issues = []
1247 for issue in issues:
1248 if issue['pk_health_issue'] in id_list:
1249 filtered_issues.append(issue)
1250
1251 return filtered_issues
1252 #------------------------------------------------------------------
1254 """Adds patient health issue."""
1255 return gmEMRStructItems.create_health_issue (
1256 description = issue_name,
1257 encounter = self.current_encounter['pk_encounter'],
1258 patient = self.pk_patient
1259 )
1260 #--------------------------------------------------------
1263 #--------------------------------------------------------
1264 # API: substance intake
1265 #--------------------------------------------------------
1266 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1267
1268 where_parts = [u'pk_patient = %(pat)s']
1269
1270 if not include_inactive:
1271 where_parts.append(u'is_currently_active in (true, null)')
1272
1273 if not include_unapproved:
1274 where_parts.append(u'intake_is_approved_of in (true, null)')
1275
1276 if order_by is None:
1277 order_by = u''
1278 else:
1279 order_by = u'order by %s' % order_by
1280
1281 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1282 u'\nand '.join(where_parts),
1283 order_by
1284 )
1285
1286 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1287
1288 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1289
1290 if episodes is not None:
1291 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1292
1293 if issues is not None:
1294 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1295
1296 return meds
1297 #--------------------------------------------------------
1298 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1299 return gmMedication.create_substance_intake (
1300 pk_substance = pk_substance,
1301 pk_component = pk_component,
1302 encounter = self.current_encounter['pk_encounter'],
1303 episode = episode,
1304 preparation = preparation
1305 )
1306 #--------------------------------------------------------
1308 return gmMedication.substance_intake_exists (
1309 pk_component = pk_component,
1310 pk_substance = pk_substance,
1311 pk_identity = self.pk_patient
1312 )
1313 #--------------------------------------------------------
1314 # API: vaccinations
1315 #--------------------------------------------------------
1317 return gmVaccination.create_vaccination (
1318 encounter = self.current_encounter['pk_encounter'],
1319 episode = episode,
1320 vaccine = vaccine,
1321 batch_no = batch_no
1322 )
1323 #--------------------------------------------------------
1325 """Returns latest given vaccination for each vaccinated indication.
1326
1327 as a dict {'l10n_indication': cVaccination instance}
1328
1329 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1330 """
1331 # find the PKs
1332 args = {'pat': self.pk_patient}
1333 where_parts = [u'pk_patient = %(pat)s']
1334
1335 if (episodes is not None) and (len(episodes) > 0):
1336 where_parts.append(u'pk_episode IN %(epis)s')
1337 args['epis'] = tuple(episodes)
1338
1339 if (issues is not None) and (len(issues) > 0):
1340 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1341 args['issues'] = tuple(issues)
1342
1343 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1344 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1345
1346 # none found
1347 if len(rows) == 0:
1348 return {}
1349
1350 vpks = [ ind['pk_vaccination'] for ind in rows ]
1351 vinds = [ ind['l10n_indication'] for ind in rows ]
1352 ind_counts = [ ind['indication_count'] for ind in rows ]
1353
1354 # turn them into vaccinations
1355 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1356 args = {'pks': tuple(vpks)}
1357 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1358
1359 vaccs = {}
1360 for idx in range(len(vpks)):
1361 pk = vpks[idx]
1362 ind_count = ind_counts[idx]
1363 for r in rows:
1364 if r['pk_vaccination'] == pk:
1365 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1366
1367 return vaccs
1368 #--------------------------------------------------------
1370
1371 args = {'pat': self.pk_patient}
1372 where_parts = [u'pk_patient = %(pat)s']
1373
1374 if order_by is None:
1375 order_by = u''
1376 else:
1377 order_by = u'ORDER BY %s' % order_by
1378
1379 if (episodes is not None) and (len(episodes) > 0):
1380 where_parts.append(u'pk_episode IN %(epis)s')
1381 args['epis'] = tuple(episodes)
1382
1383 if (issues is not None) and (len(issues) > 0):
1384 where_parts.append(u'pk_episode IN (SELECT pk FROM clin.episode WHERE fk_health_issue IN %(issues)s)')
1385 args['issues'] = tuple(issues)
1386
1387 if (encounters is not None) and (len(encounters) > 0):
1388 where_parts.append(u'pk_encounter IN %(encs)s')
1389 args['encs'] = tuple(encounters)
1390
1391 cmd = u'%s %s' % (
1392 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1393 order_by
1394 )
1395 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1396 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1397
1398 return vaccs
1399 #--------------------------------------------------------
1400 # old/obsolete:
1401 #--------------------------------------------------------
1403 """Retrieves vaccination regimes the patient is on.
1404
1405 optional:
1406 * ID - PK of the vaccination regime
1407 * indications - indications we want to retrieve vaccination
1408 regimes for, must be primary language, not l10n_indication
1409 """
1410 # FIXME: use course, not regime
1411 try:
1412 self.__db_cache['vaccinations']['scheduled regimes']
1413 except KeyError:
1414 # retrieve vaccination regimes definitions
1415 self.__db_cache['vaccinations']['scheduled regimes'] = []
1416 cmd = """SELECT distinct on(pk_course) pk_course
1417 FROM clin.v_vaccs_scheduled4pat
1418 WHERE pk_patient=%s"""
1419 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1420 if rows is None:
1421 _log.error('cannot retrieve scheduled vaccination courses')
1422 del self.__db_cache['vaccinations']['scheduled regimes']
1423 return None
1424 # Instantiate vaccination items and keep cache
1425 for row in rows:
1426 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1427
1428 # ok, let's constrain our list
1429 filtered_regimes = []
1430 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1431 if ID is not None:
1432 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1433 if len(filtered_regimes) == 0:
1434 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1435 return []
1436 else:
1437 return filtered_regimes[0]
1438 if indications is not None:
1439 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1440
1441 return filtered_regimes
1442 #--------------------------------------------------------
1443 # def get_vaccinated_indications(self):
1444 # """Retrieves patient vaccinated indications list.
1445 #
1446 # Note that this does NOT rely on the patient being on
1447 # some schedule or other but rather works with what the
1448 # patient has ACTUALLY been vaccinated against. This is
1449 # deliberate !
1450 # """
1451 # # most likely, vaccinations will be fetched close
1452 # # by so it makes sense to count on the cache being
1453 # # filled (or fill it for nearby use)
1454 # vaccinations = self.get_vaccinations()
1455 # if vaccinations is None:
1456 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1457 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1458 # if len(vaccinations) == 0:
1459 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1460 # v_indications = []
1461 # for vacc in vaccinations:
1462 # tmp = [vacc['indication'], vacc['l10n_indication']]
1463 # # remove duplicates
1464 # if tmp in v_indications:
1465 # continue
1466 # v_indications.append(tmp)
1467 # return (True, v_indications)
1468 #--------------------------------------------------------
1469 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1470 """Retrieves list of vaccinations the patient has received.
1471
1472 optional:
1473 * ID - PK of a vaccination
1474 * indications - indications we want to retrieve vaccination
1475 items for, must be primary language, not l10n_indication
1476 * since - initial date for allergy items
1477 * until - final date for allergy items
1478 * encounters - list of encounters whose allergies are to be retrieved
1479 * episodes - list of episodes whose allergies are to be retrieved
1480 * issues - list of health issues whose allergies are to be retrieved
1481 """
1482 try:
1483 self.__db_cache['vaccinations']['vaccinated']
1484 except KeyError:
1485 self.__db_cache['vaccinations']['vaccinated'] = []
1486 # Important fetch ordering by indication, date to know if a vaccination is booster
1487 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1488 WHERE pk_patient=%s
1489 order by indication, date"""
1490 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1491 if rows is None:
1492 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1493 del self.__db_cache['vaccinations']['vaccinated']
1494 return None
1495 # Instantiate vaccination items
1496 vaccs_by_ind = {}
1497 for row in rows:
1498 vacc_row = {
1499 'pk_field': 'pk_vaccination',
1500 'idx': idx,
1501 'data': row
1502 }
1503 vacc = gmVaccination.cVaccination(row=vacc_row)
1504 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1505 # keep them, ordered by indication
1506 try:
1507 vaccs_by_ind[vacc['indication']].append(vacc)
1508 except KeyError:
1509 vaccs_by_ind[vacc['indication']] = [vacc]
1510
1511 # calculate sequence number and is_booster
1512 for ind in vaccs_by_ind.keys():
1513 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1514 for vacc in vaccs_by_ind[ind]:
1515 # due to the "order by indication, date" the vaccinations are in the
1516 # right temporal order inside the indication-keyed dicts
1517 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1518 vacc['seq_no'] = seq_no
1519 # if no active schedule for indication we cannot
1520 # check for booster status (eg. seq_no > max_shot)
1521 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1522 continue
1523 if seq_no > vacc_regimes[0]['shots']:
1524 vacc['is_booster'] = True
1525 del vaccs_by_ind
1526
1527 # ok, let's constrain our list
1528 filtered_shots = []
1529 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1530 if ID is not None:
1531 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1532 if len(filtered_shots) == 0:
1533 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1534 return None
1535 else:
1536 return filtered_shots[0]
1537 if since is not None:
1538 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1539 if until is not None:
1540 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1541 if issues is not None:
1542 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1543 if episodes is not None:
1544 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1545 if encounters is not None:
1546 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1547 if indications is not None:
1548 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1549 return filtered_shots
1550 #--------------------------------------------------------
1552 """Retrieves vaccinations scheduled for a regime a patient is on.
1553
1554 The regime is referenced by its indication (not l10n)
1555
1556 * indications - List of indications (not l10n) of regimes we want scheduled
1557 vaccinations to be fetched for
1558 """
1559 try:
1560 self.__db_cache['vaccinations']['scheduled']
1561 except KeyError:
1562 self.__db_cache['vaccinations']['scheduled'] = []
1563 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1564 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1565 if rows is None:
1566 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1567 del self.__db_cache['vaccinations']['scheduled']
1568 return None
1569 # Instantiate vaccination items
1570 for row in rows:
1571 vacc_row = {
1572 'pk_field': 'pk_vacc_def',
1573 'idx': idx,
1574 'data': row
1575 }
1576 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1577
1578 # ok, let's constrain our list
1579 if indications is None:
1580 return self.__db_cache['vaccinations']['scheduled']
1581 filtered_shots = []
1582 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1583 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1584 return filtered_shots
1585 #--------------------------------------------------------
1587 try:
1588 self.__db_cache['vaccinations']['missing']
1589 except KeyError:
1590 self.__db_cache['vaccinations']['missing'] = {}
1591 # 1) non-booster
1592 self.__db_cache['vaccinations']['missing']['due'] = []
1593 # get list of (indication, seq_no) tuples
1594 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1595 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1596 if rows is None:
1597 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1598 return None
1599 pk_args = {'pat_id': self.pk_patient}
1600 if rows is not None:
1601 for row in rows:
1602 pk_args['indication'] = row[0]
1603 pk_args['seq_no'] = row[1]
1604 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1605
1606 # 2) boosters
1607 self.__db_cache['vaccinations']['missing']['boosters'] = []
1608 # get list of indications
1609 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1610 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1611 if rows is None:
1612 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1613 return None
1614 pk_args = {'pat_id': self.pk_patient}
1615 if rows is not None:
1616 for row in rows:
1617 pk_args['indication'] = row[0]
1618 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1619
1620 # if any filters ...
1621 if indications is None:
1622 return self.__db_cache['vaccinations']['missing']
1623 if len(indications) == 0:
1624 return self.__db_cache['vaccinations']['missing']
1625 # ... apply them
1626 filtered_shots = {
1627 'due': [],
1628 'boosters': []
1629 }
1630 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1631 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
1632 filtered_shots['due'].append(due_shot)
1633 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1634 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
1635 filtered_shots['boosters'].append(due_shot)
1636 return filtered_shots
1637 #------------------------------------------------------------------
1638 # API: encounters
1639 #------------------------------------------------------------------
1642
1644
1645 # first ever setting ?
1646 if self.__encounter is None:
1647 _log.debug('first setting of active encounter in this clinical record instance')
1648 else:
1649 _log.debug('switching of active encounter')
1650 # fail if the currently active encounter has unsaved changes
1651 if self.__encounter.is_modified():
1652 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1653 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1654
1655 # set the currently active encounter and announce that change
1656 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1657 encounter['last_affirmed'] = gmDateTime.pydt_now_here() # this will trigger an "encounter_mod_db"
1658 encounter.save()
1659 self.__encounter = encounter
1660 gmDispatcher.send(u'current_encounter_switched')
1661
1662 return True
1663
1664 current_encounter = property(_get_current_encounter, _set_current_encounter)
1665 active_encounter = property(_get_current_encounter, _set_current_encounter)
1666 #------------------------------------------------------------------
1668
1669 # 1) "very recent" encounter recorded ?
1670 if self.__activate_very_recent_encounter():
1671 return True
1672
1673 # 2) "fairly recent" encounter recorded ?
1674 if self.__activate_fairly_recent_encounter():
1675 return True
1676
1677 # 3) start a completely new encounter
1678 self.start_new_encounter()
1679 return True
1680 #------------------------------------------------------------------
1682 """Try to attach to a "very recent" encounter if there is one.
1683
1684 returns:
1685 False: no "very recent" encounter, create new one
1686 True: success
1687 """
1688 cfg_db = gmCfg.cCfgSQL()
1689 min_ttl = cfg_db.get2 (
1690 option = u'encounter.minimum_ttl',
1691 workplace = _here.active_workplace,
1692 bias = u'user',
1693 default = u'1 hour 30 minutes'
1694 )
1695 cmd = u"""
1696 SELECT pk_encounter
1697 FROM clin.v_most_recent_encounters
1698 WHERE
1699 pk_patient = %s
1700 and
1701 last_affirmed > (now() - %s::interval)"""
1702 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1703 # none found
1704 if len(enc_rows) == 0:
1705 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1706 return False
1707 # attach to existing
1708 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1709 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1710 return True
1711 #------------------------------------------------------------------
1713 """Try to attach to a "fairly recent" encounter if there is one.
1714
1715 returns:
1716 False: no "fairly recent" encounter, create new one
1717 True: success
1718 """
1719 if _func_ask_user is None:
1720 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1721 return False
1722
1723 cfg_db = gmCfg.cCfgSQL()
1724 min_ttl = cfg_db.get2 (
1725 option = u'encounter.minimum_ttl',
1726 workplace = _here.active_workplace,
1727 bias = u'user',
1728 default = u'1 hour 30 minutes'
1729 )
1730 max_ttl = cfg_db.get2 (
1731 option = u'encounter.maximum_ttl',
1732 workplace = _here.active_workplace,
1733 bias = u'user',
1734 default = u'6 hours'
1735 )
1736 cmd = u"""
1737 SELECT pk_encounter
1738 FROM clin.v_most_recent_encounters
1739 WHERE
1740 pk_patient=%s
1741 AND
1742 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1743 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1744 # none found
1745 if len(enc_rows) == 0:
1746 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1747 return False
1748 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1749 # ask user whether to attach or not
1750 cmd = u"""
1751 SELECT title, firstnames, lastnames, gender, dob
1752 FROM dem.v_basic_person WHERE pk_identity=%s"""
1753 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1754 pat = pats[0]
1755 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1756 gmTools.coalesce(pat[0], u'')[:5],
1757 pat[1][:15],
1758 pat[2][:15],
1759 pat[3],
1760 pat[4].strftime('%x'),
1761 self.pk_patient
1762 )
1763 enc = gmI18N.get_encoding()
1764 msg = _(
1765 '%s\n'
1766 '\n'
1767 "This patient's chart was worked on only recently:\n"
1768 '\n'
1769 ' %s %s - %s (%s)\n'
1770 '\n'
1771 ' Request: %s\n'
1772 ' Outcome: %s\n'
1773 '\n'
1774 'Do you want to continue that consultation\n'
1775 'or do you want to start a new one ?\n'
1776 ) % (
1777 pat_str,
1778 encounter['started'].strftime('%x').decode(enc),
1779 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1780 encounter['l10n_type'],
1781 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1782 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1783 )
1784 attach = False
1785 try:
1786 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1787 except:
1788 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1789 return False
1790 if not attach:
1791 return False
1792
1793 # attach to existing
1794 self.current_encounter = encounter
1795
1796 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1797 return True
1798 #------------------------------------------------------------------
1800 cfg_db = gmCfg.cCfgSQL()
1801 # FIXME: look for MRU/MCU encounter type config here
1802 enc_type = cfg_db.get2 (
1803 option = u'encounter.default_type',
1804 workplace = _here.active_workplace,
1805 bias = u'user',
1806 default = u'in surgery'
1807 )
1808 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
1809 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1810 #------------------------------------------------------------------
1812 """Retrieves patient's encounters.
1813
1814 id_list - PKs of encounters to fetch
1815 since - initial date for encounter items, DateTime instance
1816 until - final date for encounter items, DateTime instance
1817 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1818 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1819
1820 NOTE: if you specify *both* issues and episodes
1821 you will get the *aggregate* of all encounters even
1822 if the episodes all belong to the health issues listed.
1823 IOW, the issues broaden the episode list rather than
1824 the episode list narrowing the episodes-from-issues
1825 list.
1826 Rationale: If it was the other way round it would be
1827 redundant to specify the list of issues at all.
1828 """
1829 # fetch all encounters for patient
1830 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1831 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1832 encounters = []
1833 for r in rows:
1834 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1835
1836 # we've got the encounters, start filtering
1837 filtered_encounters = []
1838 filtered_encounters.extend(encounters)
1839 if id_list is not None:
1840 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1841 if since is not None:
1842 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1843 if until is not None:
1844 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1845
1846 if (issues is not None) and (len(issues) > 0):
1847
1848 issues = tuple(issues)
1849
1850 # Syan attests that an explicit union of child tables is way faster
1851 # as there seem to be problems with parent table expansion and use
1852 # of child table indexes, so if get_encounter() runs very slow on
1853 # your machine use the lines below
1854
1855 # rows = gmPG.run_ro_query('historica', cClinicalRecord._clin_root_item_children_union_query, None, (tuple(issues),))
1856 # if rows is None:
1857 # _log.error('cannot load encounters for issues [%s] (patient [%s])' % (str(issues), self.pk_patient))
1858 # else:
1859 # enc_ids = map(lambda x:x[0], rows)
1860 # filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1861
1862 # this problem seems fixed for us as of PostgreSQL 8.2 :-)
1863
1864 # however, this seems like the proper approach:
1865 # - find episodes corresponding to the health issues in question
1866 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1867 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1868 epi_ids = map(lambda x:x[0], rows)
1869 if episodes is None:
1870 episodes = []
1871 episodes.extend(epi_ids)
1872
1873 if (episodes is not None) and (len(episodes) > 0):
1874
1875 episodes = tuple(episodes)
1876
1877 # if the episodes to filter by belong to the patient in question so will
1878 # the encounters found with them - hence we don't need a WHERE on the patient ...
1879 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1880 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1881 enc_ids = map(lambda x:x[0], rows)
1882 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1883
1884 return filtered_encounters
1885 #--------------------------------------------------------
1887 """Retrieves first encounter for a particular issue and/or episode.
1888
1889 issue_id - First encounter associated health issue
1890 episode - First encounter associated episode
1891 """
1892 # FIXME: use direct query
1893 if issue_id is None:
1894 issues = None
1895 else:
1896 issues = [issue_id]
1897
1898 if episode_id is None:
1899 episodes = None
1900 else:
1901 episodes = [episode_id]
1902
1903 encounters = self.get_encounters(issues=issues, episodes=episodes)
1904 if len(encounters) == 0:
1905 return None
1906
1907 # FIXME: this does not scale particularly well, I assume
1908 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1909 return encounters[0]
1910 #--------------------------------------------------------
1912 """Retrieves last encounter for a concrete issue and/or episode
1913
1914 issue_id - Last encounter associated health issue
1915 episode_id - Last encounter associated episode
1916 """
1917 # FIXME: use direct query
1918
1919 if issue_id is None:
1920 issues = None
1921 else:
1922 issues = [issue_id]
1923
1924 if episode_id is None:
1925 episodes = None
1926 else:
1927 episodes = [episode_id]
1928
1929 encounters = self.get_encounters(issues=issues, episodes=episodes)
1930 if len(encounters) == 0:
1931 return None
1932
1933 # FIXME: this does not scale particularly well, I assume
1934 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1935 return encounters[-1]
1936 #------------------------------------------------------------------
1938 args = {'pat': self.pk_patient, 'range': cover_period}
1939 where_parts = [u'pk_patient = %(pat)s']
1940 if cover_period is not None:
1941 where_parts.append(u'last_affirmed > now() - %(range)s')
1942
1943 cmd = u"""
1944 SELECT l10n_type, count(1) AS frequency
1945 FROM clin.v_pat_encounters
1946 WHERE
1947 %s
1948 GROUP BY l10n_type
1949 ORDER BY frequency DESC
1950 """ % u' AND '.join(where_parts)
1951 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1952 return rows
1953 #------------------------------------------------------------------
1955
1956 args = {'pat': self.pk_patient}
1957
1958 if (issue_id is None) and (episode_id is None):
1959
1960 cmd = u"""
1961 SELECT * FROM clin.v_pat_encounters
1962 WHERE pk_patient = %(pat)s
1963 ORDER BY started DESC
1964 LIMIT 2
1965 """
1966 else:
1967 where_parts = []
1968
1969 if issue_id is not None:
1970 where_parts.append(u'pk_health_issue = %(issue)s')
1971 args['issue'] = issue_id
1972
1973 if episode_id is not None:
1974 where_parts.append(u'pk_episode = %(epi)s')
1975 args['epi'] = episode_id
1976
1977 cmd = u"""
1978 SELECT *
1979 FROM clin.v_pat_encounters
1980 WHERE
1981 pk_patient = %%(pat)s
1982 AND
1983 pk_encounter IN (
1984 SELECT distinct pk_encounter
1985 FROM clin.v_pat_narrative
1986 WHERE
1987 %s
1988 )
1989 ORDER BY started DESC
1990 LIMIT 2
1991 """ % u' AND '.join(where_parts)
1992
1993 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1994
1995 if len(rows) == 0:
1996 return None
1997
1998 # just one encounter within the above limits
1999 if len(rows) == 1:
2000 # is it the current encounter ?
2001 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2002 # yes
2003 return None
2004 # no
2005 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2006
2007 # more than one encounter
2008 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
2009 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
2010
2011 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
2012 #------------------------------------------------------------------
2014 cfg_db = gmCfg.cCfgSQL()
2015 ttl = cfg_db.get2 (
2016 option = u'encounter.ttl_if_empty',
2017 workplace = _here.active_workplace,
2018 bias = u'user',
2019 default = u'1 week'
2020 )
2021
2022 # # FIXME: this should be done async
2023 cmd = u"select clin.remove_old_empty_encounters(%(pat)s::integer, %(ttl)s::interval)"
2024 args = {'pat': self.pk_patient, 'ttl': ttl}
2025 try:
2026 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
2027 except:
2028 _log.exception('error deleting empty encounters')
2029
2030 return True
2031 #------------------------------------------------------------------
2032 # API: measurements
2033 #------------------------------------------------------------------
2034 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
2036 """Retrieve data about test types for which this patient has results."""
2037
2038 cmd = u"""
2039 SELECT * FROM (
2040 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
2041 FROM clin.v_test_results
2042 WHERE pk_patient = %(pat)s
2043 ) AS foo
2044 ORDER BY clin_when desc, unified_name
2045 """
2046 args = {'pat': self.pk_patient}
2047 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2048 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
2049 #------------------------------------------------------------------
2051 """Retrieve details on tests grouped under unified names for this patient's results."""
2052 cmd = u"""
2053 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
2054 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
2055 from clin.v_test_results
2056 WHERE pk_patient = %(pat)s
2057 )
2058 order by unified_name"""
2059 args = {'pat': self.pk_patient}
2060 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2061 return rows, idx
2062 #------------------------------------------------------------------
2064 """Get the dates for which we have results."""
2065 cmd = u"""
2066 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2067 from clin.v_test_results
2068 WHERE pk_patient = %(pat)s
2069 order by cwhen desc"""
2070 args = {'pat': self.pk_patient}
2071 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2072 return rows
2073 #------------------------------------------------------------------
2075
2076 cmd = u"""
2077 SELECT *, xmin_test_result FROM clin.v_test_results
2078 WHERE pk_patient = %(pat)s
2079 order by clin_when desc, pk_episode, unified_name"""
2080 args = {'pat': self.pk_patient}
2081 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2082
2083 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2084
2085 if episodes is not None:
2086 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2087
2088 if encounter is not None:
2089 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2090
2091 return tests
2092 #------------------------------------------------------------------
2093 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2094
2095 try:
2096 epi = int(episode)
2097 except:
2098 epi = episode['pk_episode']
2099
2100 try:
2101 type = int(type)
2102 except:
2103 type = type['pk_test_type']
2104
2105 if intended_reviewer is None:
2106 intended_reviewer = _me['pk_staff']
2107
2108 tr = gmPathLab.create_test_result (
2109 encounter = self.current_encounter['pk_encounter'],
2110 episode = epi,
2111 type = type,
2112 intended_reviewer = intended_reviewer,
2113 val_num = val_num,
2114 val_alpha = val_alpha,
2115 unit = unit
2116 )
2117
2118 return tr
2119 #------------------------------------------------------------------
2121
2122 cfg_db = gmCfg.cCfgSQL()
2123
2124 mass_loincs = cfg_db.get2 (
2125 option = u'lab.body_mass_loincs',
2126 workplace = _here.active_workplace,
2127 bias = u'user',
2128 default = []
2129 )
2130
2131 height_loincs = cfg_db.get2 (
2132 option = u'lab.body_height_loincs',
2133 workplace = _here.active_workplace,
2134 bias = u'user',
2135 default = []
2136 )
2137
2138 return gmPathLab.calculate_bmi(mass = mass, height = height) # age = age
2139 #------------------------------------------------------------------
2140 #------------------------------------------------------------------
2141 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2142 """Retrieves lab result clinical items.
2143
2144 limit - maximum number of results to retrieve
2145 since - initial date
2146 until - final date
2147 encounters - list of encounters
2148 episodes - list of episodes
2149 issues - list of health issues
2150 """
2151 try:
2152 return self.__db_cache['lab results']
2153 except KeyError:
2154 pass
2155 self.__db_cache['lab results'] = []
2156 if limit is None:
2157 lim = ''
2158 else:
2159 # only use limit if all other constraints are None
2160 if since is None and until is None and encounters is None and episodes is None and issues is None:
2161 lim = "limit %s" % limit
2162 else:
2163 lim = ''
2164
2165 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2166 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2167 if rows is None:
2168 return False
2169 for row in rows:
2170 lab_row = {
2171 'pk_field': 'pk_result',
2172 'idx': idx,
2173 'data': row
2174 }
2175 lab_result = gmPathLab.cLabResult(row=lab_row)
2176 self.__db_cache['lab results'].append(lab_result)
2177
2178 # ok, let's constrain our list
2179 filtered_lab_results = []
2180 filtered_lab_results.extend(self.__db_cache['lab results'])
2181 if since is not None:
2182 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2183 if until is not None:
2184 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2185 if issues is not None:
2186 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2187 if episodes is not None:
2188 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2189 if encounters is not None:
2190 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2191 return filtered_lab_results
2192 #------------------------------------------------------------------
2194 # FIXME: verify that it is our patient ? ...
2195 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
2196 return req
2197 #------------------------------------------------------------------
2199 if encounter_id is None:
2200 encounter_id = self.current_encounter['pk_encounter']
2201 status, data = gmPathLab.create_lab_request(
2202 lab=lab,
2203 req_id=req_id,
2204 pat_id=self.pk_patient,
2205 encounter_id=encounter_id,
2206 episode_id=episode_id
2207 )
2208 if not status:
2209 _log.error(str(data))
2210 return None
2211 return data
2212 #============================================================
2213 # main
2214 #------------------------------------------------------------
2215 if __name__ == "__main__":
2216
2217 if len(sys.argv) == 1:
2218 sys.exit()
2219
2220 if sys.argv[1] != 'test':
2221 sys.exit()
2222
2223 from Gnumed.pycommon import gmLog2
2224 #-----------------------------------------
2226 emr = cClinicalRecord(aPKey=1)
2227 state = emr.allergy_state
2228 print "allergy state is:", state
2229
2230 print "setting state to 0"
2231 emr.allergy_state = 0
2232
2233 print "setting state to None"
2234 emr.allergy_state = None
2235
2236 print "setting state to 'abc'"
2237 emr.allergy_state = 'abc'
2238 #-----------------------------------------
2240 emr = cClinicalRecord(aPKey=12)
2241 rows = emr.get_test_types_for_results()
2242 print "test result names:"
2243 for row in rows:
2244 print row
2245 #-----------------------------------------
2247 emr = cClinicalRecord(aPKey=12)
2248 rows = emr.get_dates_for_results()
2249 print "test result dates:"
2250 for row in rows:
2251 print row
2252 #-----------------------------------------
2254 emr = cClinicalRecord(aPKey=12)
2255 rows, idx = emr.get_measurements_by_date()
2256 print "test results:"
2257 for row in rows:
2258 print row
2259 #-----------------------------------------
2261 emr = cClinicalRecord(aPKey=12)
2262 tests = emr.get_test_results_by_date()
2263 print "test results:"
2264 for test in tests:
2265 print test
2266 #-----------------------------------------
2268 emr = cClinicalRecord(aPKey=12)
2269 rows, idx = emr.get_test_types_details()
2270 print "test type details:"
2271 for row in rows:
2272 print row
2273 #-----------------------------------------
2275 emr = cClinicalRecord(aPKey=12)
2276 for key, item in emr.get_statistics().iteritems():
2277 print key, ":", item
2278 #-----------------------------------------
2280 emr = cClinicalRecord(aPKey=12)
2281
2282 probs = emr.get_problems()
2283 print "normal probs (%s):" % len(probs)
2284 for p in probs:
2285 print u'%s (%s)' % (p['problem'], p['type'])
2286
2287 probs = emr.get_problems(include_closed_episodes=True)
2288 print "probs + closed episodes (%s):" % len(probs)
2289 for p in probs:
2290 print u'%s (%s)' % (p['problem'], p['type'])
2291
2292 probs = emr.get_problems(include_irrelevant_issues=True)
2293 print "probs + issues (%s):" % len(probs)
2294 for p in probs:
2295 print u'%s (%s)' % (p['problem'], p['type'])
2296
2297 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2298 print "probs + issues + epis (%s):" % len(probs)
2299 for p in probs:
2300 print u'%s (%s)' % (p['problem'], p['type'])
2301 #-----------------------------------------
2303 emr = cClinicalRecord(aPKey=12)
2304 tr = emr.add_test_result (
2305 episode = 1,
2306 intended_reviewer = 1,
2307 type = 1,
2308 val_num = 75,
2309 val_alpha = u'somewhat obese',
2310 unit = u'kg'
2311 )
2312 print tr
2313 #-----------------------------------------
2317 #-----------------------------------------
2319 emr = cClinicalRecord(aPKey=12)
2320 print emr.get_last_encounter(issue_id=2)
2321 print emr.get_last_but_one_encounter(issue_id=2)
2322 #-----------------------------------------
2324 emr = cClinicalRecord(aPKey=12)
2325 for med in emr.get_current_substance_intake():
2326 print med
2327 #-----------------------------------------
2329 emr = cClinicalRecord(aPKey = 12)
2330 print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
2331 #-----------------------------------------
2333 emr = cClinicalRecord(aPKey = 12)
2334 for journal_line in emr.get_as_journal():
2335 #print journal_line.keys()
2336 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2337 print ""
2338 #-----------------------------------------
2339 #test_allergy_state()
2340 #test_is_allergic_to()
2341
2342 #test_get_test_names()
2343 #test_get_dates_for_results()
2344 #test_get_measurements()
2345 #test_get_test_results_by_date()
2346 #test_get_test_types_details()
2347 #test_get_statistics()
2348 #test_get_problems()
2349 #test_add_test_result()
2350 #test_get_most_recent_episode()
2351 #test_get_almost_recent_encounter()
2352 #test_get_meds()
2353 test_get_as_journal()
2354
2355 # emr = cClinicalRecord(aPKey = 12)
2356
2357 # # Vacc regimes
2358 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
2359 # print '\nVaccination regimes: '
2360 # for a_regime in vacc_regimes:
2361 # pass
2362 # #print a_regime
2363 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
2364 # #print vacc_regime
2365
2366 # # vaccination regimes and vaccinations for regimes
2367 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
2368 # print 'Vaccinations for the regime:'
2369 # for a_scheduled_vacc in scheduled_vaccs:
2370 # pass
2371 # #print ' %s' %(a_scheduled_vacc)
2372
2373 # # vaccination next shot and booster
2374 # vaccinations = emr.get_vaccinations()
2375 # for a_vacc in vaccinations:
2376 # 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'])
2377
2378 # # first and last encounters
2379 # first_encounter = emr.get_first_encounter(issue_id = 1)
2380 # print '\nFirst encounter: ' + str(first_encounter)
2381 # last_encounter = emr.get_last_encounter(episode_id = 1)
2382 # print '\nLast encounter: ' + str(last_encounter)
2383 # print ''
2384
2385 # # lab results
2386 # lab = emr.get_lab_results()
2387 # lab_file = open('lab-data.txt', 'wb')
2388 # for lab_result in lab:
2389 # lab_file.write(str(lab_result))
2390 # lab_file.write('\n')
2391 # lab_file.close()
2392
2393 #dump = record.get_missing_vaccinations()
2394 #f = open('vaccs.lst', 'wb')
2395 #if dump is not None:
2396 # print "=== due ==="
2397 # f.write("=== due ===\n")
2398 # for row in dump['due']:
2399 # print row
2400 # f.write(repr(row))
2401 # f.write('\n')
2402 # print "=== overdue ==="
2403 # f.write("=== overdue ===\n")
2404 # for row in dump['overdue']:
2405 # print row
2406 # f.write(repr(row))
2407 # f.write('\n')
2408 #f.close()
2409
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Feb 9 04:01:38 2012 | http://epydoc.sourceforge.net |