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