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