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