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