| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed clinical patient record.
2
3 This is a clinical record object intended to let a useful
4 client-side API crystallize from actual use in true XP fashion.
5
6 Make sure to call set_func_ask_user() and set_encounter_ttl()
7 early on in your code (before cClinicalRecord.__init__() is
8 called for the first time).
9 """
10 #============================================================
11 __version__ = "$Revision: 1.308 $"
12 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
13 __license__ = "GPL"
14
15 #===================================================
16 # TODO
17 # Basically we'll probably have to:
18 #
19 # a) serialize access to re-getting data from the cache so
20 # that later-but-concurrent cache accesses spin until
21 # the first one completes the refetch from the database
22 #
23 # b) serialize access to the cache per-se such that cache
24 # flushes vs. cache regets happen atomically (where
25 # flushes would abort/restart current regets)
26 #===================================================
27
28 # standard libs
29 import sys, string, time, copy, locale
30
31
32 # 3rd party
33 import logging
34
35
36 if __name__ == '__main__':
37 sys.path.insert(0, '../../')
38 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
39 gmI18N.activate_locale()
40 gmI18N.install_domain()
41 gmDateTime.init()
42
43 from Gnumed.pycommon import gmExceptions, gmPG2, gmDispatcher, gmI18N, gmCfg, gmTools, gmDateTime
44 from Gnumed.business import gmAllergy, gmEMRStructItems, gmClinNarrative, gmPathLab
45 from Gnumed.business import gmMedication, gmVaccination
46
47
48 _log = logging.getLogger('gm.emr')
49 _log.debug(__version__)
50
51 _me = None
52 _here = None
53 #============================================================
54 # helper functions
55 #------------------------------------------------------------
56 _func_ask_user = None
57
59 if not callable(a_func):
60 _log.error('[%] not callable, not setting _func_ask_user', a_func)
61 return False
62
63 _log.debug('setting _func_ask_user to [%s]', a_func)
64
65 global _func_ask_user
66 _func_ask_user = a_func
67
68 #============================================================
70
71 _clin_root_item_children_union_query = None
72
74 """Fails if
75
76 - no connection to database possible
77 - patient referenced by aPKey does not exist
78 """
79 self.pk_patient = aPKey # == identity.pk == primary key
80
81 # log access to patient record (HIPAA, for example)
82 cmd = u'SELECT gm.log_access2emr(%(todo)s)'
83 args = {'todo': u'patient [%s]' % aPKey}
84 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
85
86 from Gnumed.business import gmSurgery, gmPerson
87 global _me
88 if _me is None:
89 _me = gmPerson.gmCurrentProvider()
90 global _here
91 if _here is None:
92 _here = gmSurgery.gmCurrentPractice()
93
94 # ...........................................
95 # this is a hack to speed up get_encounters()
96 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item')
97 if cClinicalRecord._clin_root_item_children_union_query is None:
98 union_phrase = u"""
99 SELECT fk_encounter from
100 %s.%s cn
101 inner join
102 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi
103 on (cn.fk_episode = epi.pk)
104 """
105 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join (
106 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ]
107 )
108 # ...........................................
109
110 self.__db_cache = {}
111
112 # load current or create new encounter
113 if _func_ask_user is None:
114 _log.error('[_func_ask_user] is None')
115 print "*** GNUmed [%s]: _func_ask_user is not set ***" % self.__class__.__name__
116 self.remove_empty_encounters()
117 self.__encounter = None
118 if not self.__initiate_active_encounter():
119 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey
120
121 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
122
123 # register backend notification interests
124 # (keep this last so we won't hang on threads when
125 # failing this constructor for other reasons ...)
126 if not self._register_interests():
127 raise gmExceptions.ConstructorError, "cannot register signal interests"
128
129 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
130 #--------------------------------------------------------
133 #--------------------------------------------------------
135 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
136
137 return True
138 #--------------------------------------------------------
139 # messaging
140 #--------------------------------------------------------
142 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
143
144 return True
145 #--------------------------------------------------------
147 # get the current encounter as an extra instance
148 # from the database to check for changes
149 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
150
151 # the encounter just retrieved and the active encounter
152 # have got the same transaction ID so there's no change
153 # in the database, there could be a local change in
154 # the active encounter but that doesn't matter
155 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
156 return True
157
158 # there must have been a change to the active encounter
159 # committed to the database from elsewhere,
160 # we must fail propagating the change, however, if
161 # there are local changes
162 if self.current_encounter.is_modified():
163 _log.debug('unsaved changes in active encounter, cannot switch to another one')
164 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
165
166 # there was a change in the database from elsewhere,
167 # locally, however, we don't have any changes, therefore
168 # we can propagate the remote change locally without
169 # losing anything
170 _log.debug('active encounter modified remotely, reloading and announcing the modification')
171 self.current_encounter.refetch_payload()
172 gmDispatcher.send(u'current_encounter_modified')
173
174 return True
175 #--------------------------------------------------------
178 #--------------------------------------------------------
185 #--------------------------------------------------------
192 #--------------------------------------------------------
194 _log.debug('DB: clin_root_item modification')
195 #--------------------------------------------------------
196 # API: performed procedures
197 #--------------------------------------------------------
199
200 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
201
202 if episodes is not None:
203 procs = filter(lambda p: p['pk_episode'] in episodes, procs)
204
205 if issues is not None:
206 procs = filter(lambda p: p['pk_health_issue'] in issues, procs)
207
208 return procs
209 #--------------------------------------------------------
210 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
211 return gmEMRStructItems.create_performed_procedure (
212 encounter = self.current_encounter['pk_encounter'],
213 episode = episode,
214 location = location,
215 hospital_stay = hospital_stay,
216 procedure = procedure
217 )
218 #--------------------------------------------------------
219 # API: hospital stays
220 #--------------------------------------------------------
222
223 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient)
224
225 if episodes is not None:
226 stays = filter(lambda s: s['pk_episode'] in episodes, stays)
227
228 if issues is not None:
229 stays = filter(lambda s: s['pk_health_issue'] in issues, stays)
230
231 return stays
232 #--------------------------------------------------------
234 return gmEMRStructItems.create_hospital_stay (
235 encounter = self.current_encounter['pk_encounter'],
236 episode = episode
237 )
238 #--------------------------------------------------------
239 # API: narrative
240 #--------------------------------------------------------
242
243 for note in notes:
244 success, data = gmClinNarrative.create_clin_narrative (
245 narrative = note[1],
246 soap_cat = note[0],
247 episode_id = episode,
248 encounter_id = self.current_encounter['pk_encounter']
249 )
250
251 return True
252 #--------------------------------------------------------
254 if note.strip() == '':
255 _log.info('will not create empty clinical note')
256 return None
257 status, data = gmClinNarrative.create_clin_narrative (
258 narrative = note,
259 soap_cat = soap_cat,
260 episode_id = episode['pk_episode'],
261 encounter_id = self.current_encounter['pk_encounter']
262 )
263 if not status:
264 _log.error(str(data))
265 return None
266 return data
267 #--------------------------------------------------------
268 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
269 """Get SOAP notes pertinent to this encounter.
270
271 since
272 - initial date for narrative items
273 until
274 - final date for narrative items
275 encounters
276 - list of encounters whose narrative are to be retrieved
277 episodes
278 - list of episodes whose narrative are to be retrieved
279 issues
280 - list of health issues whose narrative are to be retrieved
281 soap_cats
282 - list of SOAP categories of the narrative to be retrieved
283 """
284 cmd = u"""
285 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
286 from clin.v_pat_narrative cvpn
287 WHERE pk_patient = %s
288 order by date, soap_rank
289 """
290
291 #xxxxxxxxxxxxxxxx
292 # support row_version in narrative for display in tree
293
294 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
295
296 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
297
298 if since is not None:
299 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
300
301 if until is not None:
302 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
303
304 if issues is not None:
305 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
306
307 if episodes is not None:
308 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
309
310 if encounters is not None:
311 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
312
313 if soap_cats is not None:
314 soap_cats = map(lambda c: c.lower(), soap_cats)
315 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
316
317 if providers is not None:
318 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
319
320 return filtered_narrative
321 #--------------------------------------------------------
322 - 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):
323
324 if order_by is None:
325 order_by = u'ORDER BY vemrj.clin_when, vemrj.pk_episode, scr, vemrj.src_table'
326 else:
327 order_by = u'ORDER BY %s' % order_by
328
329 where_parts = [u'pk_patient = %(pat)s']
330 args = {'pat': self.pk_patient}
331
332 if soap_cats is not None:
333 # work around bug in psycopg2 not being able to properly
334 # adapt None to NULL inside tuples
335 if None in soap_cats:
336 where_parts.append(u'((vemrj.soap_cat IN %(soap_cat)s) OR (vemrj.soap_cat IS NULL))')
337 soap_cats.remove(None)
338 else:
339 where_parts.append(u'vemrj.soap_cat IN %(soap_cat)s')
340 args['soap_cat'] = tuple(soap_cats)
341
342 if time_range is not None:
343 where_parts.append(u"vemrj.clin_when > (now() - '%s days'::interval)" % time_range)
344
345 # FIXME: implement more constraints
346
347 cmd = u"""
348 SELECT
349 to_char(vemrj.clin_when, 'YYYY-MM-DD') AS date,
350 vemrj.clin_when,
351 coalesce(vemrj.soap_cat, '') as soap_cat,
352 vemrj.narrative,
353 vemrj.src_table,
354
355 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = vemrj.soap_cat) AS scr,
356
357 vemrj.modified_when,
358 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified,
359 vemrj.modified_by,
360 vemrj.row_version
361 FROM clin.v_emr_journal vemrj
362 WHERE
363 %s
364 %s""" % (
365 u'\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts),
366 order_by
367 )
368
369 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
370 return rows
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 _("""Medical problems: %(problems)s
759 Total encounters: %(encounters)s
760 Total EMR entries: %(items)s
761 Active medications: %(active_drugs)s
762 Documents: %(documents)s
763 Test results: %(results)s
764 Hospital stays: %(stays)s
765 Procedures: %(procedures)s
766 Vaccinations: %(vaccinations)s
767 """ ) % self.get_statistics()
768 #--------------------------------------------------------
770
771 stats = self.get_statistics()
772 first = self.get_first_encounter()
773 last = self.get_last_encounter()
774 probs = self.get_problems()
775
776 txt = _('EMR Statistics\n\n')
777 if len(probs) > 0:
778 txt += _(' %s known problems. Clinically relevant thereof:\n') % stats['problems']
779 else:
780 txt += _(' %s known problems\n') % stats['problems']
781 for prob in probs:
782 if not prob['clinically_relevant']:
783 continue
784 txt += u' \u00BB%s\u00AB (%s)\n' % (
785 prob['problem'],
786 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
787 )
788 txt += _(' %s encounters from %s to %s\n') % (
789 stats['encounters'],
790 first['started'].strftime('%x').decode(gmI18N.get_encoding()),
791 last['started'].strftime('%x').decode(gmI18N.get_encoding())
792 )
793 txt += _(' %s active medications\n') % stats['active_drugs']
794 txt += _(' %s documents\n') % stats['documents']
795 txt += _(' %s test results\n') % stats['results']
796 txt += _(' %s hospital stays\n') % stats['stays']
797 # FIXME: perhaps only count "ongoing ones"
798 txt += _(' %s performed procedures\n\n') % stats['procedures']
799
800 txt += _('Allergies and Intolerances\n\n')
801
802 allg_state = self.allergy_state
803 txt += (u' ' + allg_state.state_string)
804 if allg_state['last_confirmed'] is not None:
805 txt += (_(' (last confirmed %s)') % allg_state['last_confirmed'].strftime('%x').decode(gmI18N.get_encoding()))
806 txt += u'\n'
807 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n')
808 for allg in self.get_allergies():
809 txt += u' %s: %s\n' % (
810 allg['descriptor'],
811 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
812 )
813
814 txt += u'\n'
815 txt += _('Vaccinations')
816 txt += u'\n'
817
818 vaccs = self.get_latest_vaccinations()
819 inds = sorted(vaccs.keys())
820 for ind in inds:
821 ind_count, vacc = vaccs[ind]
822 txt += u' %s (%s%s): %s @ %s (%s %s%s%s)\n' % (
823 ind,
824 gmTools.u_sum,
825 ind_count,
826 vacc['date_given'].strftime('%b %Y').decode(gmI18N.get_encoding()),
827 gmDateTime.format_apparent_age_medically(gmDateTime.calculate_apparent_age (
828 start = dob,
829 end = vacc['date_given']
830 )),
831 vacc['vaccine'],
832 gmTools.u_left_double_angle_quote,
833 vacc['batch_no'],
834 gmTools.u_right_double_angle_quote
835 )
836
837 return txt
838 #--------------------------------------------------------
839 # allergy API
840 #--------------------------------------------------------
841 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
842 """Retrieves patient allergy items.
843
844 remove_sensitivities
845 - retrieve real allergies only, without sensitivities
846 since
847 - initial date for allergy items
848 until
849 - final date for allergy items
850 encounters
851 - list of encounters whose allergies are to be retrieved
852 episodes
853 - list of episodes whose allergies are to be retrieved
854 issues
855 - list of health issues whose allergies are to be retrieved
856 """
857 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
858 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx = True)
859 allergies = []
860 for r in rows:
861 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
862
863 # ok, let's constrain our list
864 filtered_allergies = []
865 filtered_allergies.extend(allergies)
866
867 if ID_list is not None:
868 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
869 if len(filtered_allergies) == 0:
870 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
871 # better fail here contrary to what we do elsewhere
872 return None
873 else:
874 return filtered_allergies
875
876 if remove_sensitivities:
877 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
878 if since is not None:
879 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
880 if until is not None:
881 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
882 if issues is not None:
883 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
884 if episodes is not None:
885 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
886 if encounters is not None:
887 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
888
889 return filtered_allergies
890 #--------------------------------------------------------
892 if encounter_id is None:
893 encounter_id = self.current_encounter['pk_encounter']
894
895 if episode_id is None:
896 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
897 epi = self.add_episode(episode_name = allergene, pk_health_issue = issue['pk_health_issue'])
898 episode_id = epi['pk_episode']
899
900 new_allergy = gmAllergy.create_allergy (
901 allergene = allergene,
902 allg_type = allg_type,
903 encounter_id = encounter_id,
904 episode_id = episode_id
905 )
906
907 return new_allergy
908 #--------------------------------------------------------
910 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
911 args = {'pk_allg': pk_allergy}
912 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
913 #--------------------------------------------------------
915 """Cave: only use with one potential allergic agent
916 otherwise you won't know which of the agents the allergy is to."""
917
918 # we don't know the state
919 if self.allergy_state is None:
920 return None
921
922 # we know there's no allergies
923 if self.allergy_state == 0:
924 return False
925
926 args = {
927 'atcs': atcs,
928 'inns': inns,
929 'brand': brand,
930 'pat': self.pk_patient
931 }
932 allergenes = []
933 where_parts = []
934
935 if len(atcs) == 0:
936 atcs = None
937 if atcs is not None:
938 where_parts.append(u'atc_code in %(atcs)s')
939 if len(inns) == 0:
940 inns = None
941 if inns is not None:
942 where_parts.append(u'generics in %(inns)s')
943 allergenes.extend(inns)
944 if brand is not None:
945 where_parts.append(u'substance = %(brand)s')
946 allergenes.append(brand)
947
948 if len(allergenes) != 0:
949 where_parts.append(u'allergene in %(allgs)s')
950 args['allgs'] = tuple(allergenes)
951
952 cmd = u"""
953 SELECT * FROM clin.v_pat_allergies
954 WHERE
955 pk_patient = %%(pat)s
956 AND ( %s )""" % u' OR '.join(where_parts)
957
958 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
959
960 if len(rows) == 0:
961 return False
962
963 return gmAllergy.cAllergy(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_allergy'})
964 #--------------------------------------------------------
966
967 if state not in gmAllergy.allergy_states:
968 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
969
970 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
971 allg_state['has_allergy'] = state
972 allg_state.save_payload()
973 return True
974
977
978 allergy_state = property(_get_allergy_state, _set_allergy_state)
979 #--------------------------------------------------------
980 # episodes API
981 #--------------------------------------------------------
983 """Fetches from backend patient episodes.
984
985 id_list - Episodes' PKs list
986 issues - Health issues' PKs list to filter episodes by
987 open_status - return all episodes, only open or closed one(s)
988 """
989 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
990 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
991 tmp = []
992 for r in rows:
993 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
994
995 # now filter
996 if (id_list is None) and (issues is None) and (open_status is None):
997 return tmp
998
999 # ok, let's filter episode list
1000 filtered_episodes = []
1001 filtered_episodes.extend(tmp)
1002 if open_status is not None:
1003 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
1004
1005 if issues is not None:
1006 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
1007
1008 if id_list is not None:
1009 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
1010
1011 return filtered_episodes
1012 #------------------------------------------------------------------
1014 cmd = u"""SELECT distinct pk_episode
1015 from clin.v_pat_items
1016 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
1017 args = {
1018 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
1019 'pat': self.pk_patient
1020 }
1021 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
1022 if len(rows) == 0:
1023 return []
1024 epis = []
1025 for row in rows:
1026 epis.append(row[0])
1027 return self.get_episodes(id_list=epis)
1028 #------------------------------------------------------------------
1030 """Add episode 'episode_name' for a patient's health issue.
1031
1032 - silently returns if episode already exists
1033 """
1034 episode = gmEMRStructItems.create_episode (
1035 pk_health_issue = pk_health_issue,
1036 episode_name = episode_name,
1037 is_open = is_open,
1038 encounter = self.current_encounter['pk_encounter']
1039 )
1040 return episode
1041 #--------------------------------------------------------
1043 # try to find the episode with the most recently modified clinical item
1044
1045 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
1046
1047 cmd = u"""
1048 SELECT pk
1049 from clin.episode
1050 WHERE pk = (
1051 SELECT distinct on(pk_episode) pk_episode
1052 from clin.v_pat_items
1053 WHERE
1054 pk_patient = %%(pat)s
1055 and
1056 modified_when = (
1057 SELECT max(vpi.modified_when)
1058 from clin.v_pat_items vpi
1059 WHERE vpi.pk_patient = %%(pat)s
1060 )
1061 %s
1062 -- guard against several episodes created at the same moment of time
1063 limit 1
1064 )""" % issue_where
1065 rows, idx = gmPG2.run_ro_queries(queries = [
1066 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1067 ])
1068 if len(rows) != 0:
1069 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1070
1071 # no clinical items recorded, so try to find
1072 # the youngest episode for this patient
1073 cmd = u"""
1074 SELECT vpe0.pk_episode
1075 from
1076 clin.v_pat_episodes vpe0
1077 WHERE
1078 vpe0.pk_patient = %%(pat)s
1079 and
1080 vpe0.episode_modified_when = (
1081 SELECT max(vpe1.episode_modified_when)
1082 from clin.v_pat_episodes vpe1
1083 WHERE vpe1.pk_episode = vpe0.pk_episode
1084 )
1085 %s""" % issue_where
1086 rows, idx = gmPG2.run_ro_queries(queries = [
1087 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
1088 ])
1089 if len(rows) != 0:
1090 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
1091
1092 return None
1093 #--------------------------------------------------------
1096 #--------------------------------------------------------
1097 # problems API
1098 #--------------------------------------------------------
1099 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
1100 """Retrieve a patient's problems.
1101
1102 "Problems" are the UNION of:
1103
1104 - issues which are .clinically_relevant
1105 - episodes which are .is_open
1106
1107 Therefore, both an issue and the open episode
1108 thereof can each be listed as a problem.
1109
1110 include_closed_episodes/include_irrelevant_issues will
1111 include those -- which departs from the definition of
1112 the problem list being "active" items only ...
1113
1114 episodes - episodes' PKs to filter problems by
1115 issues - health issues' PKs to filter problems by
1116 """
1117 # FIXME: this could use a good measure of streamlining, probably
1118
1119 args = {'pat': self.pk_patient}
1120
1121 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
1122 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1123
1124 # Instantiate problem items
1125 problems = []
1126 for row in rows:
1127 pk_args = {
1128 u'pk_patient': self.pk_patient,
1129 u'pk_health_issue': row['pk_health_issue'],
1130 u'pk_episode': row['pk_episode']
1131 }
1132 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
1133
1134 # include non-problems ?
1135 other_rows = []
1136 if include_closed_episodes:
1137 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
1138 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1139 other_rows.extend(rows)
1140
1141 if include_irrelevant_issues:
1142 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
1143 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1144 other_rows.extend(rows)
1145
1146 if len(other_rows) > 0:
1147 for row in other_rows:
1148 pk_args = {
1149 u'pk_patient': self.pk_patient,
1150 u'pk_health_issue': row['pk_health_issue'],
1151 u'pk_episode': row['pk_episode']
1152 }
1153 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1154
1155 # filter ?
1156 if (episodes is None) and (issues is None):
1157 return problems
1158
1159 # filter
1160 if issues is not None:
1161 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1162 if episodes is not None:
1163 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1164
1165 return problems
1166 #--------------------------------------------------------
1169 #--------------------------------------------------------
1172 #--------------------------------------------------------
1175 #--------------------------------------------------------
1176 # health issues API
1177 #--------------------------------------------------------
1179
1180 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1181 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1182 issues = []
1183 for row in rows:
1184 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1185 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1186
1187 if id_list is None:
1188 return issues
1189
1190 if len(id_list) == 0:
1191 raise ValueError('id_list to filter by is empty, most likely a programming error')
1192
1193 filtered_issues = []
1194 for issue in issues:
1195 if issue['pk_health_issue'] in id_list:
1196 filtered_issues.append(issue)
1197
1198 return filtered_issues
1199 #------------------------------------------------------------------
1201 """Adds patient health issue."""
1202 return gmEMRStructItems.create_health_issue (
1203 description = issue_name,
1204 encounter = self.current_encounter['pk_encounter'],
1205 patient = self.pk_patient
1206 )
1207 #--------------------------------------------------------
1210 #--------------------------------------------------------
1211 # API: substance intake
1212 #--------------------------------------------------------
1213 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1214
1215 where_parts = [u'pk_patient = %(pat)s']
1216
1217 if not include_inactive:
1218 where_parts.append(u'is_currently_active in (true, null)')
1219
1220 if not include_unapproved:
1221 where_parts.append(u'intake_is_approved_of in (true, null)')
1222
1223 if order_by is None:
1224 order_by = u''
1225 else:
1226 order_by = u'order by %s' % order_by
1227
1228 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1229 u'\nand '.join(where_parts),
1230 order_by
1231 )
1232
1233 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1234
1235 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1236
1237 if episodes is not None:
1238 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1239
1240 if issues is not None:
1241 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1242
1243 return meds
1244 #--------------------------------------------------------
1245 - def add_substance_intake(self, pk_substance=None, pk_component=None, episode=None, preparation=None):
1246 return gmMedication.create_substance_intake (
1247 pk_substance = pk_substance,
1248 pk_component = pk_component,
1249 encounter = self.current_encounter['pk_encounter'],
1250 episode = episode,
1251 preparation = preparation
1252 )
1253 #--------------------------------------------------------
1254 # vaccinations API
1255 #--------------------------------------------------------
1257 return gmVaccination.create_vaccination (
1258 encounter = self.current_encounter['pk_encounter'],
1259 episode = episode,
1260 vaccine = vaccine,
1261 batch_no = batch_no
1262 )
1263 #--------------------------------------------------------
1265 """Returns latest given vaccination for each vaccinated indication.
1266
1267 as a dict {'l10n_indication': cVaccination instance}
1268
1269 Note that this will produce duplicate vaccination instances on combi-indication vaccines !
1270 """
1271 # find the PKs
1272 args = {'pat': self.pk_patient}
1273 where_parts = [u'pk_patient = %(pat)s']
1274
1275 if (episodes is not None) and (len(episodes) > 0):
1276 where_parts.append(u'pk_episode IN %(epis)s')
1277 args['epis'] = tuple(episodes)
1278
1279 if (issues is not None) and (len(issues) > 0):
1280 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1281 args['issues'] = tuple(issues)
1282
1283 cmd = u'SELECT pk_vaccination, l10n_indication, indication_count FROM clin.v_pat_last_vacc4indication WHERE %s' % u'\nAND '.join(where_parts)
1284 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1285
1286 # none found
1287 if len(rows) == 0:
1288 return {}
1289
1290 vpks = [ ind['pk_vaccination'] for ind in rows ]
1291 vinds = [ ind['l10n_indication'] for ind in rows ]
1292 ind_counts = [ ind['indication_count'] for ind in rows ]
1293
1294 # turn them into vaccinations
1295 cmd = gmVaccination.sql_fetch_vaccination % u'pk_vaccination IN %(pks)s'
1296 args = {'pks': tuple(vpks)}
1297 rows, row_idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1298
1299 vaccs = {}
1300 for idx in range(len(vpks)):
1301 pk = vpks[idx]
1302 ind_count = ind_counts[idx]
1303 for r in rows:
1304 if r['pk_vaccination'] == pk:
1305 vaccs[vinds[idx]] = (ind_count, gmVaccination.cVaccination(row = {'idx': row_idx, 'data': r, 'pk_field': 'pk_vaccination'}))
1306
1307 return vaccs
1308 #--------------------------------------------------------
1310
1311 args = {'pat': self.pk_patient}
1312 where_parts = [u'pk_patient = %(pat)s']
1313
1314 if order_by is None:
1315 order_by = u''
1316 else:
1317 order_by = u'order by %s' % order_by
1318
1319 if (episodes is not None) and (len(episodes) > 0):
1320 where_parts.append(u'pk_episode IN %(epis)s')
1321 args['epis'] = tuple(episodes)
1322
1323 if (issues is not None) and (len(issues) > 0):
1324 where_parts.append(u'pk_episode IN (select pk from clin.episode where fk_health_issue IN %(issues)s)')
1325 args['issues'] = tuple(issues)
1326
1327 if (encounters is not None) and (len(encounters) > 0):
1328 where_parts.append(u'pk_encounter IN %(encs)s')
1329 args['encs'] = tuple(encounters)
1330
1331 cmd = u'%s %s' % (
1332 gmVaccination.sql_fetch_vaccination % u'\nAND '.join(where_parts),
1333 order_by
1334 )
1335 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1336 vaccs = [ gmVaccination.cVaccination(row = {'idx': idx, 'data': r, 'pk_field': 'pk_vaccination'}) for r in rows ]
1337
1338 return vaccs
1339 #--------------------------------------------------------
1340 # old/obsolete:
1341 #--------------------------------------------------------
1343 """Retrieves vaccination regimes the patient is on.
1344
1345 optional:
1346 * ID - PK of the vaccination regime
1347 * indications - indications we want to retrieve vaccination
1348 regimes for, must be primary language, not l10n_indication
1349 """
1350 # FIXME: use course, not regime
1351 try:
1352 self.__db_cache['vaccinations']['scheduled regimes']
1353 except KeyError:
1354 # retrieve vaccination regimes definitions
1355 self.__db_cache['vaccinations']['scheduled regimes'] = []
1356 cmd = """SELECT distinct on(pk_course) pk_course
1357 FROM clin.v_vaccs_scheduled4pat
1358 WHERE pk_patient=%s"""
1359 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1360 if rows is None:
1361 _log.error('cannot retrieve scheduled vaccination courses')
1362 del self.__db_cache['vaccinations']['scheduled regimes']
1363 return None
1364 # Instantiate vaccination items and keep cache
1365 for row in rows:
1366 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1367
1368 # ok, let's constrain our list
1369 filtered_regimes = []
1370 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1371 if ID is not None:
1372 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1373 if len(filtered_regimes) == 0:
1374 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1375 return []
1376 else:
1377 return filtered_regimes[0]
1378 if indications is not None:
1379 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1380
1381 return filtered_regimes
1382 #--------------------------------------------------------
1383 # def get_vaccinated_indications(self):
1384 # """Retrieves patient vaccinated indications list.
1385 #
1386 # Note that this does NOT rely on the patient being on
1387 # some schedule or other but rather works with what the
1388 # patient has ACTUALLY been vaccinated against. This is
1389 # deliberate !
1390 # """
1391 # # most likely, vaccinations will be fetched close
1392 # # by so it makes sense to count on the cache being
1393 # # filled (or fill it for nearby use)
1394 # vaccinations = self.get_vaccinations()
1395 # if vaccinations is None:
1396 # _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1397 # return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1398 # if len(vaccinations) == 0:
1399 # return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1400 # v_indications = []
1401 # for vacc in vaccinations:
1402 # tmp = [vacc['indication'], vacc['l10n_indication']]
1403 # # remove duplicates
1404 # if tmp in v_indications:
1405 # continue
1406 # v_indications.append(tmp)
1407 # return (True, v_indications)
1408 #--------------------------------------------------------
1409 - def get_vaccinations_old(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1410 """Retrieves list of vaccinations the patient has received.
1411
1412 optional:
1413 * ID - PK of a vaccination
1414 * indications - indications we want to retrieve vaccination
1415 items for, must be primary language, not l10n_indication
1416 * since - initial date for allergy items
1417 * until - final date for allergy items
1418 * encounters - list of encounters whose allergies are to be retrieved
1419 * episodes - list of episodes whose allergies are to be retrieved
1420 * issues - list of health issues whose allergies are to be retrieved
1421 """
1422 try:
1423 self.__db_cache['vaccinations']['vaccinated']
1424 except KeyError:
1425 self.__db_cache['vaccinations']['vaccinated'] = []
1426 # Important fetch ordering by indication, date to know if a vaccination is booster
1427 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1428 WHERE pk_patient=%s
1429 order by indication, date"""
1430 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1431 if rows is None:
1432 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1433 del self.__db_cache['vaccinations']['vaccinated']
1434 return None
1435 # Instantiate vaccination items
1436 vaccs_by_ind = {}
1437 for row in rows:
1438 vacc_row = {
1439 'pk_field': 'pk_vaccination',
1440 'idx': idx,
1441 'data': row
1442 }
1443 vacc = gmVaccination.cVaccination(row=vacc_row)
1444 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1445 # keep them, ordered by indication
1446 try:
1447 vaccs_by_ind[vacc['indication']].append(vacc)
1448 except KeyError:
1449 vaccs_by_ind[vacc['indication']] = [vacc]
1450
1451 # calculate sequence number and is_booster
1452 for ind in vaccs_by_ind.keys():
1453 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1454 for vacc in vaccs_by_ind[ind]:
1455 # due to the "order by indication, date" the vaccinations are in the
1456 # right temporal order inside the indication-keyed dicts
1457 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1458 vacc['seq_no'] = seq_no
1459 # if no active schedule for indication we cannot
1460 # check for booster status (eg. seq_no > max_shot)
1461 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1462 continue
1463 if seq_no > vacc_regimes[0]['shots']:
1464 vacc['is_booster'] = True
1465 del vaccs_by_ind
1466
1467 # ok, let's constrain our list
1468 filtered_shots = []
1469 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1470 if ID is not None:
1471 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1472 if len(filtered_shots) == 0:
1473 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1474 return None
1475 else:
1476 return filtered_shots[0]
1477 if since is not None:
1478 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1479 if until is not None:
1480 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1481 if issues is not None:
1482 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1483 if episodes is not None:
1484 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1485 if encounters is not None:
1486 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1487 if indications is not None:
1488 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1489 return filtered_shots
1490 #--------------------------------------------------------
1492 """Retrieves vaccinations scheduled for a regime a patient is on.
1493
1494 The regime is referenced by its indication (not l10n)
1495
1496 * indications - List of indications (not l10n) of regimes we want scheduled
1497 vaccinations to be fetched for
1498 """
1499 try:
1500 self.__db_cache['vaccinations']['scheduled']
1501 except KeyError:
1502 self.__db_cache['vaccinations']['scheduled'] = []
1503 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1504 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1505 if rows is None:
1506 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1507 del self.__db_cache['vaccinations']['scheduled']
1508 return None
1509 # Instantiate vaccination items
1510 for row in rows:
1511 vacc_row = {
1512 'pk_field': 'pk_vacc_def',
1513 'idx': idx,
1514 'data': row
1515 }
1516 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1517
1518 # ok, let's constrain our list
1519 if indications is None:
1520 return self.__db_cache['vaccinations']['scheduled']
1521 filtered_shots = []
1522 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1523 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1524 return filtered_shots
1525 #--------------------------------------------------------
1527 try:
1528 self.__db_cache['vaccinations']['missing']
1529 except KeyError:
1530 self.__db_cache['vaccinations']['missing'] = {}
1531 # 1) non-booster
1532 self.__db_cache['vaccinations']['missing']['due'] = []
1533 # get list of (indication, seq_no) tuples
1534 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1535 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1536 if rows is None:
1537 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1538 return None
1539 pk_args = {'pat_id': self.pk_patient}
1540 if rows is not None:
1541 for row in rows:
1542 pk_args['indication'] = row[0]
1543 pk_args['seq_no'] = row[1]
1544 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1545
1546 # 2) boosters
1547 self.__db_cache['vaccinations']['missing']['boosters'] = []
1548 # get list of indications
1549 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1550 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1551 if rows is None:
1552 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1553 return None
1554 pk_args = {'pat_id': self.pk_patient}
1555 if rows is not None:
1556 for row in rows:
1557 pk_args['indication'] = row[0]
1558 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1559
1560 # if any filters ...
1561 if indications is None:
1562 return self.__db_cache['vaccinations']['missing']
1563 if len(indications) == 0:
1564 return self.__db_cache['vaccinations']['missing']
1565 # ... apply them
1566 filtered_shots = {
1567 'due': [],
1568 'boosters': []
1569 }
1570 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1571 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
1572 filtered_shots['due'].append(due_shot)
1573 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1574 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
1575 filtered_shots['boosters'].append(due_shot)
1576 return filtered_shots
1577 #------------------------------------------------------------------
1578 # API: encounters
1579 #------------------------------------------------------------------
1582
1584
1585 # first ever setting ?
1586 if self.__encounter is None:
1587 _log.debug('first setting of active encounter in this clinical record instance')
1588 else:
1589 _log.debug('switching of active encounter')
1590 # fail if the currently active encounter has unsaved changes
1591 if self.__encounter.is_modified():
1592 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1593 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1594
1595 # set the currently active encounter and announce that change
1596 if encounter['started'].strftime('%Y-%m-%d %H:%M') == encounter['last_affirmed'].strftime('%Y-%m-%d %H:%M'):
1597 encounter['last_affirmed'] = gmDateTime.pydt_now_here() # this will trigger an "encounter_mod_db"
1598 encounter.save()
1599 self.__encounter = encounter
1600 gmDispatcher.send(u'current_encounter_switched')
1601
1602 return True
1603
1604 current_encounter = property(_get_current_encounter, _set_current_encounter)
1605 active_encounter = property(_get_current_encounter, _set_current_encounter)
1606 #------------------------------------------------------------------
1608
1609 # 1) "very recent" encounter recorded ?
1610 if self.__activate_very_recent_encounter():
1611 return True
1612
1613 # 2) "fairly recent" encounter recorded ?
1614 if self.__activate_fairly_recent_encounter():
1615 return True
1616
1617 # 3) start a completely new encounter
1618 self.start_new_encounter()
1619 return True
1620 #------------------------------------------------------------------
1622 """Try to attach to a "very recent" encounter if there is one.
1623
1624 returns:
1625 False: no "very recent" encounter, create new one
1626 True: success
1627 """
1628 cfg_db = gmCfg.cCfgSQL()
1629 min_ttl = cfg_db.get2 (
1630 option = u'encounter.minimum_ttl',
1631 workplace = _here.active_workplace,
1632 bias = u'user',
1633 default = u'1 hour 30 minutes'
1634 )
1635 cmd = u"""
1636 SELECT pk_encounter
1637 FROM clin.v_most_recent_encounters
1638 WHERE
1639 pk_patient = %s
1640 and
1641 last_affirmed > (now() - %s::interval)"""
1642 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1643 # none found
1644 if len(enc_rows) == 0:
1645 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1646 return False
1647 # attach to existing
1648 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1649 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1650 return True
1651 #------------------------------------------------------------------
1653 """Try to attach to a "fairly recent" encounter if there is one.
1654
1655 returns:
1656 False: no "fairly recent" encounter, create new one
1657 True: success
1658 """
1659 if _func_ask_user is None:
1660 _log.debug('cannot ask user for guidance, not looking for fairly recent encounter')
1661 return False
1662
1663 cfg_db = gmCfg.cCfgSQL()
1664 min_ttl = cfg_db.get2 (
1665 option = u'encounter.minimum_ttl',
1666 workplace = _here.active_workplace,
1667 bias = u'user',
1668 default = u'1 hour 30 minutes'
1669 )
1670 max_ttl = cfg_db.get2 (
1671 option = u'encounter.maximum_ttl',
1672 workplace = _here.active_workplace,
1673 bias = u'user',
1674 default = u'6 hours'
1675 )
1676 cmd = u"""
1677 SELECT pk_encounter
1678 FROM clin.v_most_recent_encounters
1679 WHERE
1680 pk_patient=%s
1681 AND
1682 last_affirmed BETWEEN (now() - %s::interval) AND (now() - %s::interval)"""
1683 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1684 # none found
1685 if len(enc_rows) == 0:
1686 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1687 return False
1688 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1689 # ask user whether to attach or not
1690 cmd = u"""
1691 SELECT title, firstnames, lastnames, gender, dob
1692 FROM dem.v_basic_person WHERE pk_identity=%s"""
1693 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1694 pat = pats[0]
1695 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1696 gmTools.coalesce(pat[0], u'')[:5],
1697 pat[1][:15],
1698 pat[2][:15],
1699 pat[3],
1700 pat[4].strftime('%x'),
1701 self.pk_patient
1702 )
1703 enc = gmI18N.get_encoding()
1704 msg = _(
1705 '%s\n'
1706 '\n'
1707 "This patient's chart was worked on only recently:\n"
1708 '\n'
1709 ' %s %s - %s (%s)\n'
1710 '\n'
1711 ' Request: %s\n'
1712 ' Outcome: %s\n'
1713 '\n'
1714 'Do you want to continue that consultation\n'
1715 'or do you want to start a new one ?\n'
1716 ) % (
1717 pat_str,
1718 encounter['started'].strftime('%x').decode(enc),
1719 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1720 encounter['l10n_type'],
1721 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1722 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1723 )
1724 attach = False
1725 try:
1726 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1727 except:
1728 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1729 return False
1730 if not attach:
1731 return False
1732
1733 # attach to existing
1734 self.current_encounter = encounter
1735
1736 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1737 return True
1738 #------------------------------------------------------------------
1740 cfg_db = gmCfg.cCfgSQL()
1741 # FIXME: look for MRU/MCU encounter type config here
1742 enc_type = cfg_db.get2 (
1743 option = u'encounter.default_type',
1744 workplace = _here.active_workplace,
1745 bias = u'user',
1746 default = u'in surgery'
1747 )
1748 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
1749 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1750 #------------------------------------------------------------------
1752 """Retrieves patient's encounters.
1753
1754 id_list - PKs of encounters to fetch
1755 since - initial date for encounter items, DateTime instance
1756 until - final date for encounter items, DateTime instance
1757 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1758 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1759
1760 NOTE: if you specify *both* issues and episodes
1761 you will get the *aggregate* of all encounters even
1762 if the episodes all belong to the health issues listed.
1763 IOW, the issues broaden the episode list rather than
1764 the episode list narrowing the episodes-from-issues
1765 list.
1766 Rationale: If it was the other way round it would be
1767 redundant to specify the list of issues at all.
1768 """
1769 # fetch all encounters for patient
1770 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1771 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1772 encounters = []
1773 for r in rows:
1774 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1775
1776 # we've got the encounters, start filtering
1777 filtered_encounters = []
1778 filtered_encounters.extend(encounters)
1779 if id_list is not None:
1780 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1781 if since is not None:
1782 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1783 if until is not None:
1784 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1785
1786 if (issues is not None) and (len(issues) > 0):
1787
1788 issues = tuple(issues)
1789
1790 # Syan attests that an explicit union of child tables is way faster
1791 # as there seem to be problems with parent table expansion and use
1792 # of child table indexes, so if get_encounter() runs very slow on
1793 # your machine use the lines below
1794
1795 # rows = gmPG.run_ro_query('historica', cClinicalRecord._clin_root_item_children_union_query, None, (tuple(issues),))
1796 # if rows is None:
1797 # _log.error('cannot load encounters for issues [%s] (patient [%s])' % (str(issues), self.pk_patient))
1798 # else:
1799 # enc_ids = map(lambda x:x[0], rows)
1800 # filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1801
1802 # this problem seems fixed for us as of PostgreSQL 8.2 :-)
1803
1804 # however, this seems like the proper approach:
1805 # - find episodes corresponding to the health issues in question
1806 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1807 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1808 epi_ids = map(lambda x:x[0], rows)
1809 if episodes is None:
1810 episodes = []
1811 episodes.extend(epi_ids)
1812
1813 if (episodes is not None) and (len(episodes) > 0):
1814
1815 episodes = tuple(episodes)
1816
1817 # if the episodes to filter by belong to the patient in question so will
1818 # the encounters found with them - hence we don't need a WHERE on the patient ...
1819 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1820 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
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 return filtered_encounters
1825 #--------------------------------------------------------
1827 """Retrieves first encounter for a particular issue and/or episode
1828
1829 issue_id - First encounter associated health issue
1830 episode - First encounter associated episode
1831 """
1832 # FIXME: use direct query
1833
1834 if issue_id is None:
1835 issues = None
1836 else:
1837 issues = [issue_id]
1838
1839 if episode_id is None:
1840 episodes = None
1841 else:
1842 episodes = [episode_id]
1843
1844 encounters = self.get_encounters(issues=issues, episodes=episodes)
1845 if len(encounters) == 0:
1846 return None
1847
1848 # FIXME: this does not scale particularly well, I assume
1849 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1850 return encounters[0]
1851 #--------------------------------------------------------
1853 """Retrieves last encounter for a concrete issue and/or episode
1854
1855 issue_id - Last encounter associated health issue
1856 episode_id - Last encounter associated episode
1857 """
1858 # FIXME: use direct query
1859
1860 if issue_id is None:
1861 issues = None
1862 else:
1863 issues = [issue_id]
1864
1865 if episode_id is None:
1866 episodes = None
1867 else:
1868 episodes = [episode_id]
1869
1870 encounters = self.get_encounters(issues=issues, episodes=episodes)
1871 if len(encounters) == 0:
1872 return None
1873
1874 # FIXME: this does not scale particularly well, I assume
1875 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1876 return encounters[-1]
1877 #------------------------------------------------------------------
1879
1880 args = {'pat': self.pk_patient}
1881
1882 if (issue_id is None) and (episode_id is None):
1883
1884 cmd = u"""
1885 SELECT * FROM clin.v_pat_encounters
1886 WHERE pk_patient = %(pat)s
1887 order by started desc
1888 limit 2
1889 """
1890 else:
1891 where_parts = []
1892
1893 if issue_id is not None:
1894 where_parts.append(u'pk_health_issue = %(issue)s')
1895 args['issue'] = issue_id
1896
1897 if episode_id is not None:
1898 where_parts.append(u'pk_episode = %(epi)s')
1899 args['epi'] = episode_id
1900
1901 cmd = u"""
1902 SELECT *
1903 from clin.v_pat_encounters
1904 WHERE
1905 pk_patient = %%(pat)s
1906 and
1907 pk_encounter in (
1908 SELECT distinct pk_encounter
1909 from clin.v_pat_narrative
1910 WHERE
1911 %s
1912 )
1913 order by started desc
1914 limit 2
1915 """ % u' and '.join(where_parts)
1916
1917 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1918
1919 if len(rows) == 0:
1920 return None
1921
1922 # just one encounter within the above limits
1923 if len(rows) == 1:
1924 # is it the current encounter ?
1925 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1926 # yes
1927 return None
1928 # no
1929 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1930
1931 # more than one encounter
1932 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1933 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1934
1935 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1936 #------------------------------------------------------------------
1938 cfg_db = gmCfg.cCfgSQL()
1939 ttl = cfg_db.get2 (
1940 option = u'encounter.ttl_if_empty',
1941 workplace = _here.active_workplace,
1942 bias = u'user',
1943 default = u'1 week'
1944 )
1945
1946 # FIXME: this should be done async
1947 cmd = u"""
1948 delete FROM clin.encounter
1949 WHERE
1950 clin.encounter.fk_patient = %(pat)s
1951 and
1952 age(clin.encounter.last_affirmed) > %(ttl)s::interval
1953 and
1954 not exists (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
1955 and
1956 not exists (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk)
1957 and
1958 not exists (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk)
1959 and
1960 not exists (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk)
1961 and
1962 not exists (SELECT 1 FROM clin.operation WHERE fk_encounter = clin.encounter.pk)
1963 and
1964 not exists (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk)
1965 """
1966 try:
1967 rows, idx = gmPG2.run_rw_queries(queries = [{
1968 'cmd': cmd,
1969 'args': {'pat': self.pk_patient, 'ttl': ttl}
1970 }])
1971 except:
1972 _log.exception('error deleting empty encounters')
1973
1974 return True
1975 #------------------------------------------------------------------
1976 # measurements API
1977 #------------------------------------------------------------------
1978 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
1980 """Retrieve data about test types for which this patient has results."""
1981
1982 cmd = u"""
1983 SELECT * FROM (
1984 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
1985 FROM clin.v_test_results
1986 WHERE pk_patient = %(pat)s
1987 ) AS foo
1988 ORDER BY clin_when desc, unified_name
1989 """
1990 args = {'pat': self.pk_patient}
1991 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1992 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1993 #------------------------------------------------------------------
1995 """Retrieve details on tests grouped under unified names for this patient's results."""
1996 cmd = u"""
1997 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
1998 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
1999 from clin.v_test_results
2000 WHERE pk_patient = %(pat)s
2001 )
2002 order by unified_name"""
2003 args = {'pat': self.pk_patient}
2004 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2005 return rows, idx
2006 #------------------------------------------------------------------
2008 """Get the dates for which we have results."""
2009 cmd = u"""
2010 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
2011 from clin.v_test_results
2012 WHERE pk_patient = %(pat)s
2013 order by cwhen desc"""
2014 args = {'pat': self.pk_patient}
2015 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
2016 return rows
2017 #------------------------------------------------------------------
2019
2020 cmd = u"""
2021 SELECT *, xmin_test_result FROM clin.v_test_results
2022 WHERE pk_patient = %(pat)s
2023 order by clin_when desc, pk_episode, unified_name"""
2024 args = {'pat': self.pk_patient}
2025 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
2026
2027 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
2028
2029 if episodes is not None:
2030 tests = [ t for t in tests if t['pk_episode'] in episodes ]
2031
2032 if encounter is not None:
2033 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
2034
2035 return tests
2036 #------------------------------------------------------------------
2037 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
2038
2039 try:
2040 epi = int(episode)
2041 except:
2042 epi = episode['pk_episode']
2043
2044 try:
2045 type = int(type)
2046 except:
2047 type = type['pk_test_type']
2048
2049 if intended_reviewer is None:
2050 from Gnumed.business import gmPerson
2051 intended_reviewer = _me['pk_staff']
2052
2053 tr = gmPathLab.create_test_result (
2054 encounter = self.current_encounter['pk_encounter'],
2055 episode = epi,
2056 type = type,
2057 intended_reviewer = intended_reviewer,
2058 val_num = val_num,
2059 val_alpha = val_alpha,
2060 unit = unit
2061 )
2062
2063 return tr
2064 #------------------------------------------------------------------
2065 #------------------------------------------------------------------
2066 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
2067 """Retrieves lab result clinical items.
2068
2069 limit - maximum number of results to retrieve
2070 since - initial date
2071 until - final date
2072 encounters - list of encounters
2073 episodes - list of episodes
2074 issues - list of health issues
2075 """
2076 try:
2077 return self.__db_cache['lab results']
2078 except KeyError:
2079 pass
2080 self.__db_cache['lab results'] = []
2081 if limit is None:
2082 lim = ''
2083 else:
2084 # only use limit if all other constraints are None
2085 if since is None and until is None and encounters is None and episodes is None and issues is None:
2086 lim = "limit %s" % limit
2087 else:
2088 lim = ''
2089
2090 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
2091 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
2092 if rows is None:
2093 return False
2094 for row in rows:
2095 lab_row = {
2096 'pk_field': 'pk_result',
2097 'idx': idx,
2098 'data': row
2099 }
2100 lab_result = gmPathLab.cLabResult(row=lab_row)
2101 self.__db_cache['lab results'].append(lab_result)
2102
2103 # ok, let's constrain our list
2104 filtered_lab_results = []
2105 filtered_lab_results.extend(self.__db_cache['lab results'])
2106 if since is not None:
2107 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
2108 if until is not None:
2109 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
2110 if issues is not None:
2111 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
2112 if episodes is not None:
2113 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
2114 if encounters is not None:
2115 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
2116 return filtered_lab_results
2117 #------------------------------------------------------------------
2119 # FIXME: verify that it is our patient ? ...
2120 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
2121 return req
2122 #------------------------------------------------------------------
2124 if encounter_id is None:
2125 encounter_id = self.current_encounter['pk_encounter']
2126 status, data = gmPathLab.create_lab_request(
2127 lab=lab,
2128 req_id=req_id,
2129 pat_id=self.pk_patient,
2130 encounter_id=encounter_id,
2131 episode_id=episode_id
2132 )
2133 if not status:
2134 _log.error(str(data))
2135 return None
2136 return data
2137 #============================================================
2138 # main
2139 #------------------------------------------------------------
2140 if __name__ == "__main__":
2141
2142 if len(sys.argv) == 1:
2143 sys.exit()
2144
2145 if sys.argv[1] != 'test':
2146 sys.exit()
2147
2148 from Gnumed.pycommon import gmLog2
2149 #-----------------------------------------
2151 emr = cClinicalRecord(aPKey=1)
2152 state = emr.allergy_state
2153 print "allergy state is:", state
2154
2155 print "setting state to 0"
2156 emr.allergy_state = 0
2157
2158 print "setting state to None"
2159 emr.allergy_state = None
2160
2161 print "setting state to 'abc'"
2162 emr.allergy_state = 'abc'
2163 #-----------------------------------------
2165 emr = cClinicalRecord(aPKey=12)
2166 rows = emr.get_test_types_for_results()
2167 print "test result names:"
2168 for row in rows:
2169 print row
2170 #-----------------------------------------
2172 emr = cClinicalRecord(aPKey=12)
2173 rows = emr.get_dates_for_results()
2174 print "test result dates:"
2175 for row in rows:
2176 print row
2177 #-----------------------------------------
2179 emr = cClinicalRecord(aPKey=12)
2180 rows, idx = emr.get_measurements_by_date()
2181 print "test results:"
2182 for row in rows:
2183 print row
2184 #-----------------------------------------
2186 emr = cClinicalRecord(aPKey=12)
2187 tests = emr.get_test_results_by_date()
2188 print "test results:"
2189 for test in tests:
2190 print test
2191 #-----------------------------------------
2193 emr = cClinicalRecord(aPKey=12)
2194 rows, idx = emr.get_test_types_details()
2195 print "test type details:"
2196 for row in rows:
2197 print row
2198 #-----------------------------------------
2200 emr = cClinicalRecord(aPKey=12)
2201 for key, item in emr.get_statistics().iteritems():
2202 print key, ":", item
2203 #-----------------------------------------
2205 emr = cClinicalRecord(aPKey=12)
2206
2207 probs = emr.get_problems()
2208 print "normal probs (%s):" % len(probs)
2209 for p in probs:
2210 print u'%s (%s)' % (p['problem'], p['type'])
2211
2212 probs = emr.get_problems(include_closed_episodes=True)
2213 print "probs + closed episodes (%s):" % len(probs)
2214 for p in probs:
2215 print u'%s (%s)' % (p['problem'], p['type'])
2216
2217 probs = emr.get_problems(include_irrelevant_issues=True)
2218 print "probs + issues (%s):" % len(probs)
2219 for p in probs:
2220 print u'%s (%s)' % (p['problem'], p['type'])
2221
2222 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2223 print "probs + issues + epis (%s):" % len(probs)
2224 for p in probs:
2225 print u'%s (%s)' % (p['problem'], p['type'])
2226 #-----------------------------------------
2228 emr = cClinicalRecord(aPKey=12)
2229 tr = emr.add_test_result (
2230 episode = 1,
2231 intended_reviewer = 1,
2232 type = 1,
2233 val_num = 75,
2234 val_alpha = u'somewhat obese',
2235 unit = u'kg'
2236 )
2237 print tr
2238 #-----------------------------------------
2242 #-----------------------------------------
2244 emr = cClinicalRecord(aPKey=12)
2245 print emr.get_last_encounter(issue_id=2)
2246 print emr.get_last_but_one_encounter(issue_id=2)
2247 #-----------------------------------------
2249 emr = cClinicalRecord(aPKey=12)
2250 for med in emr.get_current_substance_intake():
2251 print med
2252 #-----------------------------------------
2254 emr = cClinicalRecord(aPKey = 12)
2255 print emr.is_allergic_to(atcs = tuple(sys.argv[2:]), inns = tuple(sys.argv[2:]), brand = sys.argv[2])
2256 #-----------------------------------------
2258 emr = cClinicalRecord(aPKey = 12)
2259 for journal_line in emr.get_as_journal():
2260 #print journal_line.keys()
2261 print u'%(date)s %(modified_by)s %(soap_cat)s %(narrative)s' % journal_line
2262 print ""
2263 #-----------------------------------------
2264 #test_allergy_state()
2265 #test_is_allergic_to()
2266
2267 #test_get_test_names()
2268 #test_get_dates_for_results()
2269 #test_get_measurements()
2270 #test_get_test_results_by_date()
2271 #test_get_test_types_details()
2272 #test_get_statistics()
2273 #test_get_problems()
2274 #test_add_test_result()
2275 #test_get_most_recent_episode()
2276 #test_get_almost_recent_encounter()
2277 #test_get_meds()
2278 test_get_as_journal()
2279
2280 # emr = cClinicalRecord(aPKey = 12)
2281
2282 # # Vacc regimes
2283 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
2284 # print '\nVaccination regimes: '
2285 # for a_regime in vacc_regimes:
2286 # pass
2287 # #print a_regime
2288 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
2289 # #print vacc_regime
2290
2291 # # vaccination regimes and vaccinations for regimes
2292 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
2293 # print 'Vaccinations for the regime:'
2294 # for a_scheduled_vacc in scheduled_vaccs:
2295 # pass
2296 # #print ' %s' %(a_scheduled_vacc)
2297
2298 # # vaccination next shot and booster
2299 # vaccinations = emr.get_vaccinations()
2300 # for a_vacc in vaccinations:
2301 # 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'])
2302
2303 # # first and last encounters
2304 # first_encounter = emr.get_first_encounter(issue_id = 1)
2305 # print '\nFirst encounter: ' + str(first_encounter)
2306 # last_encounter = emr.get_last_encounter(episode_id = 1)
2307 # print '\nLast encounter: ' + str(last_encounter)
2308 # print ''
2309
2310 # # lab results
2311 # lab = emr.get_lab_results()
2312 # lab_file = open('lab-data.txt', 'wb')
2313 # for lab_result in lab:
2314 # lab_file.write(str(lab_result))
2315 # lab_file.write('\n')
2316 # lab_file.close()
2317
2318 #dump = record.get_missing_vaccinations()
2319 #f = open('vaccs.lst', 'wb')
2320 #if dump is not None:
2321 # print "=== due ==="
2322 # f.write("=== due ===\n")
2323 # for row in dump['due']:
2324 # print row
2325 # f.write(repr(row))
2326 # f.write('\n')
2327 # print "=== overdue ==="
2328 # f.write("=== overdue ===\n")
2329 # for row in dump['overdue']:
2330 # print row
2331 # f.write(repr(row))
2332 # f.write('\n')
2333 #f.close()
2334
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jan 10 03:56:48 2011 | http://epydoc.sourceforge.net |