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