| 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 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/business/gmClinicalRecord.py,v $
12 # $Id: gmClinicalRecord.py,v 1.308 2009/12/03 17:44:18 ncq Exp $
13 __version__ = "$Revision: 1.308 $"
14 __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
15 __license__ = "GPL"
16
17 #===================================================
18 # TODO
19 # Basically we'll probably have to:
20 #
21 # a) serialize access to re-getting data from the cache so
22 # that later-but-concurrent cache accesses spin until
23 # the first one completes the refetch from the database
24 #
25 # b) serialize access to the cache per-se such that cache
26 # flushes vs. cache regets happen atomically (where
27 # flushes would abort/restart current regets)
28 #===================================================
29
30 # standard libs
31 import sys, string, time, copy, locale
32
33
34 # 3rd party
35 import mx.DateTime as mxDT, psycopg2, logging
36
37
38 if __name__ == '__main__':
39 sys.path.insert(0, '../../')
40 from Gnumed.pycommon import gmLog2, gmDateTime, gmI18N
41 gmI18N.activate_locale()
42 gmI18N.install_domain()
43 gmDateTime.init()
44 from Gnumed.pycommon import gmExceptions, gmPG2, gmDispatcher, gmI18N, gmCfg, gmTools
45 from Gnumed.business import gmAllergy, gmEMRStructItems, gmClinNarrative, gmPathLab, gmMedication
46
47
48 _log = logging.getLogger('gm.emr')
49 _log.debug(__version__)
50
51 _me = None
52 _here = None
53
54 _func_ask_user = None
55 #============================================================
57
58 _clin_root_item_children_union_query = None
59
61 """Fails if
62
63 - no connection to database possible
64 - patient referenced by aPKey does not exist
65 """
66 self.pk_patient = aPKey # == identity.pk == primary key
67
68 # log access to patient record (HIPAA, for example)
69 cmd = u'SELECT gm.log_access2emr(%(todo)s)'
70 args = {'todo': u'patient [%s]' % aPKey}
71 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
72
73 from Gnumed.business import gmSurgery, gmPerson
74 global _me
75 if _me is None:
76 _me = gmPerson.gmCurrentProvider()
77 global _here
78 if _here is None:
79 _here = gmSurgery.gmCurrentPractice()
80
81 # ...........................................
82 # this is a hack to speed up get_encounters()
83 clin_root_item_children = gmPG2.get_child_tables('clin', 'clin_root_item')
84 if cClinicalRecord._clin_root_item_children_union_query is None:
85 union_phrase = u"""
86 SELECT fk_encounter from
87 %s.%s cn
88 inner join
89 (SELECT pk FROM clin.episode ep WHERE ep.fk_health_issue in %%s) as epi
90 on (cn.fk_episode = epi.pk)
91 """
92 cClinicalRecord._clin_root_item_children_union_query = u'union\n'.join (
93 [ union_phrase % (child[0], child[1]) for child in clin_root_item_children ]
94 )
95 # ...........................................
96
97 self.__db_cache = {
98 'vaccinations': {}
99 }
100
101 # load current or create new encounter
102 self.remove_empty_encounters()
103 self.__encounter = None
104 if not self.__initiate_active_encounter():
105 raise gmExceptions.ConstructorError, "cannot activate an encounter for patient [%s]" % aPKey
106
107 gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
108
109 # register backend notification interests
110 # (keep this last so we won't hang on threads when
111 # failing this constructor for other reasons ...)
112 if not self._register_interests():
113 raise gmExceptions.ConstructorError, "cannot register signal interests"
114
115 _log.debug('Instantiated clinical record for patient [%s].' % self.pk_patient)
116 #--------------------------------------------------------
119 #--------------------------------------------------------
121 _log.debug('cleaning up after clinical record for patient [%s]' % self.pk_patient)
122
123 return True
124 #--------------------------------------------------------
125 # messaging
126 #--------------------------------------------------------
128 gmDispatcher.connect(signal = u'encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
129
130 return True
131 #--------------------------------------------------------
133 # get the current encounter as an extra instance
134 # from the database to check for changes
135 curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
136
137 # the encounter just retrieved and the active encounter
138 # have got the same transaction ID so there's no change
139 # in the database, there could be a local change in
140 # the active encounter but that doesn't matter
141 if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
142 return True
143
144 # there must have been a change to the active encounter
145 # committed to the database from elsewhere,
146 # we must fail propagating the change, however, if
147 # there are local changes
148 if self.current_encounter.is_modified():
149 _log.debug('unsaved changes in active encounter, cannot switch to another one')
150 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
151
152 # there was a change in the database from elsewhere,
153 # locally, however, we don't have any changes, therefore
154 # we can propagate the remote change locally without
155 # losing anything
156 _log.debug('active encounter modified remotely, reloading and announcing the modification')
157 self.current_encounter.refetch_payload()
158 gmDispatcher.send(u'current_encounter_modified')
159
160 return True
161 #--------------------------------------------------------
168 #--------------------------------------------------------
175 #--------------------------------------------------------
182 #--------------------------------------------------------
184 _log.debug('DB: clin_root_item modification')
185 #--------------------------------------------------------
186 # API: performed procedures
187 #--------------------------------------------------------
189
190 procs = gmEMRStructItems.get_performed_procedures(patient = self.pk_patient)
191
192 if episodes is not None:
193 procs = filter(lambda p: p['pk_episode'] in episodes, procs)
194
195 if issues is not None:
196 procs = filter(lambda p: p['pk_health_issue'] in issues, procs)
197
198 return procs
199 #--------------------------------------------------------
200 - def add_performed_procedure(self, episode=None, location=None, hospital_stay=None, procedure=None):
201 return gmEMRStructItems.create_performed_procedure (
202 encounter = self.current_encounter['pk_encounter'],
203 episode = episode,
204 location = location,
205 hospital_stay = hospital_stay,
206 procedure = procedure
207 )
208 #--------------------------------------------------------
209 # API: hospital stays
210 #--------------------------------------------------------
212
213 stays = gmEMRStructItems.get_patient_hospital_stays(patient = self.pk_patient)
214
215 if episodes is not None:
216 stays = filter(lambda s: s['pk_episode'] in episodes, stays)
217
218 if issues is not None:
219 stays = filter(lambda s: s['pk_health_issue'] in issues, stays)
220
221 return stays
222 #--------------------------------------------------------
223 # Narrative API
224 #--------------------------------------------------------
226
227 for note in notes:
228 success, data = gmClinNarrative.create_clin_narrative (
229 narrative = note[1],
230 soap_cat = note[0],
231 episode_id = episode,
232 encounter_id = self.current_encounter['pk_encounter']
233 )
234
235 return True
236 #--------------------------------------------------------
238 if note.strip() == '':
239 _log.info('will not create empty clinical note')
240 return None
241 status, data = gmClinNarrative.create_clin_narrative (
242 narrative = note,
243 soap_cat = soap_cat,
244 episode_id = episode['pk_episode'],
245 encounter_id = self.current_encounter['pk_encounter']
246 )
247 if not status:
248 _log.error(str(data))
249 return None
250 return data
251 #--------------------------------------------------------
252 - def get_clin_narrative(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
253 """Get SOAP notes pertinent to this encounter.
254
255 since
256 - initial date for narrative items
257 until
258 - final date for narrative items
259 encounters
260 - list of encounters whose narrative are to be retrieved
261 episodes
262 - list of episodes whose narrative are to be retrieved
263 issues
264 - list of health issues whose narrative are to be retrieved
265 soap_cats
266 - list of SOAP categories of the narrative to be retrieved
267 """
268 cmd = u"""
269 SELECT cvpn.*, (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = cvpn.soap_cat) as soap_rank
270 from clin.v_pat_narrative cvpn
271 WHERE pk_patient = %s
272 order by date, soap_rank
273 """
274
275 #xxxxxxxxxxxxxxx
276 # support row_version in narrative for display in tree
277
278 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
279
280 filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
281
282 if since is not None:
283 filtered_narrative = filter(lambda narr: narr['date'] >= since, filtered_narrative)
284
285 if until is not None:
286 filtered_narrative = filter(lambda narr: narr['date'] < until, filtered_narrative)
287
288 if issues is not None:
289 filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative)
290
291 if episodes is not None:
292 filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative)
293
294 if encounters is not None:
295 filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative)
296
297 if soap_cats is not None:
298 soap_cats = map(lambda c: c.lower(), soap_cats)
299 filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative)
300
301 if providers is not None:
302 filtered_narrative = filter(lambda narr: narr['provider'] in providers, filtered_narrative)
303
304 return filtered_narrative
305 #--------------------------------------------------------
307
308 search_term = search_term.strip()
309 if search_term == '':
310 return []
311
312 cmd = u"""
313 SELECT
314 *,
315 coalesce((SELECT description FROM clin.episode WHERE pk = vn4s.pk_episode), vn4s.src_table)
316 as episode,
317 coalesce((SELECT description FROM clin.health_issue WHERE pk = vn4s.pk_health_issue), vn4s.src_table)
318 as health_issue,
319 (SELECT started FROM clin.encounter WHERE pk = vn4s.pk_encounter)
320 as encounter_started,
321 (SELECT last_affirmed FROM clin.encounter WHERE pk = vn4s.pk_encounter)
322 as encounter_ended,
323 (SELECT _(description) FROM clin.encounter_type WHERE pk = (SELECT fk_type FROM clin.encounter WHERE pk = vn4s.pk_encounter))
324 as encounter_type
325 from clin.v_narrative4search vn4s
326 WHERE
327 pk_patient = %(pat)s and
328 vn4s.narrative ~ %(term)s
329 order by
330 encounter_started
331 """ # case sensitive
332 rows, idx = gmPG2.run_ro_queries(queries = [
333 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'term': search_term}}
334 ])
335 return rows
336 #--------------------------------------------------------
338 # don't know how to invalidate this by means of
339 # a notify without catching notifies from *all*
340 # child tables, the best solution would be if
341 # inserts in child tables would also fire triggers
342 # of ancestor tables, but oh well,
343 # until then the text dump will not be cached ...
344 try:
345 return self.__db_cache['text dump old']
346 except KeyError:
347 pass
348 # not cached so go get it
349 fields = [
350 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
351 'modified_by',
352 'clin_when',
353 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
354 'pk_item',
355 'pk_encounter',
356 'pk_episode',
357 'pk_health_issue',
358 'src_table'
359 ]
360 cmd = "SELECT %s FROM clin.v_pat_items WHERE pk_patient=%%s order by src_table, clin_when" % string.join(fields, ', ')
361 ro_conn = self._conn_pool.GetConnection('historica')
362 curs = ro_conn.cursor()
363 if not gmPG2.run_query(curs, None, cmd, self.pk_patient):
364 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
365 curs.close()
366 return None
367 rows = curs.fetchall()
368 view_col_idx = gmPG2.get_col_indices(curs)
369
370 # aggregate by src_table for item retrieval
371 items_by_table = {}
372 for item in rows:
373 src_table = item[view_col_idx['src_table']]
374 pk_item = item[view_col_idx['pk_item']]
375 if not items_by_table.has_key(src_table):
376 items_by_table[src_table] = {}
377 items_by_table[src_table][pk_item] = item
378
379 # get mapping for issue/episode IDs
380 issues = self.get_health_issues()
381 issue_map = {}
382 for issue in issues:
383 issue_map[issue['pk']] = issue['description']
384 episodes = self.get_episodes()
385 episode_map = {}
386 for episode in episodes:
387 episode_map[episode['pk_episode']] = episode['description']
388 emr_data = {}
389 # get item data from all source tables
390 for src_table in items_by_table.keys():
391 item_ids = items_by_table[src_table].keys()
392 # we don't know anything about the columns of
393 # the source tables but, hey, this is a dump
394 if len(item_ids) == 0:
395 _log.info('no items in table [%s] ?!?' % src_table)
396 continue
397 elif len(item_ids) == 1:
398 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
399 if not gmPG2.run_query(curs, None, cmd, item_ids[0]):
400 _log.error('cannot load items from table [%s]' % src_table)
401 # skip this table
402 continue
403 elif len(item_ids) > 1:
404 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
405 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
406 _log.error('cannot load items from table [%s]' % src_table)
407 # skip this table
408 continue
409 rows = curs.fetchall()
410 table_col_idx = gmPG.get_col_indices(curs)
411 # format per-table items
412 for row in rows:
413 # FIXME: make this get_pkey_name()
414 pk_item = row[table_col_idx['pk_item']]
415 view_row = items_by_table[src_table][pk_item]
416 age = view_row[view_col_idx['age']]
417 # format metadata
418 try:
419 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
420 except:
421 episode_name = view_row[view_col_idx['pk_episode']]
422 try:
423 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
424 except:
425 issue_name = view_row[view_col_idx['pk_health_issue']]
426
427 if not emr_data.has_key(age):
428 emr_data[age] = []
429
430 emr_data[age].append(
431 _('%s: encounter (%s)') % (
432 view_row[view_col_idx['clin_when']],
433 view_row[view_col_idx['pk_encounter']]
434 )
435 )
436 emr_data[age].append(_('health issue: %s') % issue_name)
437 emr_data[age].append(_('episode : %s') % episode_name)
438 # format table specific data columns
439 # - ignore those, they are metadata, some
440 # are in clin.v_pat_items data already
441 cols2ignore = [
442 'pk_audit', 'row_version', 'modified_when', 'modified_by',
443 'pk_item', 'id', 'fk_encounter', 'fk_episode'
444 ]
445 col_data = []
446 for col_name in table_col_idx.keys():
447 if col_name in cols2ignore:
448 continue
449 emr_data[age].append("=> %s:" % col_name)
450 emr_data[age].append(row[table_col_idx[col_name]])
451 emr_data[age].append("----------------------------------------------------")
452 emr_data[age].append("-- %s from table %s" % (
453 view_row[view_col_idx['modified_string']],
454 src_table
455 ))
456 emr_data[age].append("-- written %s by %s" % (
457 view_row[view_col_idx['modified_when']],
458 view_row[view_col_idx['modified_by']]
459 ))
460 emr_data[age].append("----------------------------------------------------")
461 curs.close()
462 self._conn_pool.ReleaseConnection('historica')
463 return emr_data
464 #--------------------------------------------------------
466 # don't know how to invalidate this by means of
467 # a notify without catching notifies from *all*
468 # child tables, the best solution would be if
469 # inserts in child tables would also fire triggers
470 # of ancestor tables, but oh well,
471 # until then the text dump will not be cached ...
472 try:
473 return self.__db_cache['text dump']
474 except KeyError:
475 pass
476 # not cached so go get it
477 # -- get the data --
478 fields = [
479 'age',
480 "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as modified_when",
481 'modified_by',
482 'clin_when',
483 "case is_modified when false then '%s' else '%s' end as modified_string" % (_('original entry'), _('modified entry')),
484 'pk_item',
485 'pk_encounter',
486 'pk_episode',
487 'pk_health_issue',
488 'src_table'
489 ]
490 select_from = "SELECT %s FROM clin.v_pat_items" % ', '.join(fields)
491 # handle constraint conditions
492 where_snippets = []
493 params = {}
494 where_snippets.append('pk_patient=%(pat_id)s')
495 params['pat_id'] = self.pk_patient
496 if not since is None:
497 where_snippets.append('clin_when >= %(since)s')
498 params['since'] = since
499 if not until is None:
500 where_snippets.append('clin_when <= %(until)s')
501 params['until'] = until
502 # FIXME: these are interrelated, eg if we constrain encounter
503 # we automatically constrain issue/episode, so handle that,
504 # encounters
505 if not encounters is None and len(encounters) > 0:
506 params['enc'] = encounters
507 if len(encounters) > 1:
508 where_snippets.append('fk_encounter in %(enc)s')
509 else:
510 where_snippets.append('fk_encounter=%(enc)s')
511 # episodes
512 if not episodes is None and len(episodes) > 0:
513 params['epi'] = episodes
514 if len(episodes) > 1:
515 where_snippets.append('fk_episode in %(epi)s')
516 else:
517 where_snippets.append('fk_episode=%(epi)s')
518 # health issues
519 if not issues is None and len(issues) > 0:
520 params['issue'] = issues
521 if len(issues) > 1:
522 where_snippets.append('fk_health_issue in %(issue)s')
523 else:
524 where_snippets.append('fk_health_issue=%(issue)s')
525
526 where_clause = ' and '.join(where_snippets)
527 order_by = 'order by src_table, age'
528 cmd = "%s WHERE %s %s" % (select_from, where_clause, order_by)
529
530 rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, params)
531 if rows is None:
532 _log.error('cannot load item links for patient [%s]' % self.pk_patient)
533 return None
534
535 # -- sort the data --
536 # FIXME: by issue/encounter/episode, eg formatting
537 # aggregate by src_table for item retrieval
538 items_by_table = {}
539 for item in rows:
540 src_table = item[view_col_idx['src_table']]
541 pk_item = item[view_col_idx['pk_item']]
542 if not items_by_table.has_key(src_table):
543 items_by_table[src_table] = {}
544 items_by_table[src_table][pk_item] = item
545
546 # get mapping for issue/episode IDs
547 issues = self.get_health_issues()
548 issue_map = {}
549 for issue in issues:
550 issue_map[issue['pk_health_issue']] = issue['description']
551 episodes = self.get_episodes()
552 episode_map = {}
553 for episode in episodes:
554 episode_map[episode['pk_episode']] = episode['description']
555 emr_data = {}
556 # get item data from all source tables
557 ro_conn = self._conn_pool.GetConnection('historica')
558 curs = ro_conn.cursor()
559 for src_table in items_by_table.keys():
560 item_ids = items_by_table[src_table].keys()
561 # we don't know anything about the columns of
562 # the source tables but, hey, this is a dump
563 if len(item_ids) == 0:
564 _log.info('no items in table [%s] ?!?' % src_table)
565 continue
566 elif len(item_ids) == 1:
567 cmd = "SELECT * FROM %s WHERE pk_item=%%s order by modified_when" % src_table
568 if not gmPG.run_query(curs, None, cmd, item_ids[0]):
569 _log.error('cannot load items from table [%s]' % src_table)
570 # skip this table
571 continue
572 elif len(item_ids) > 1:
573 cmd = "SELECT * FROM %s WHERE pk_item in %%s order by modified_when" % src_table
574 if not gmPG.run_query(curs, None, cmd, (tuple(item_ids),)):
575 _log.error('cannot load items from table [%s]' % src_table)
576 # skip this table
577 continue
578 rows = curs.fetchall()
579 table_col_idx = gmPG.get_col_indices(curs)
580 # format per-table items
581 for row in rows:
582 # FIXME: make this get_pkey_name()
583 pk_item = row[table_col_idx['pk_item']]
584 view_row = items_by_table[src_table][pk_item]
585 age = view_row[view_col_idx['age']]
586 # format metadata
587 try:
588 episode_name = episode_map[view_row[view_col_idx['pk_episode']]]
589 except:
590 episode_name = view_row[view_col_idx['pk_episode']]
591 try:
592 issue_name = issue_map[view_row[view_col_idx['pk_health_issue']]]
593 except:
594 issue_name = view_row[view_col_idx['pk_health_issue']]
595
596 if not emr_data.has_key(age):
597 emr_data[age] = []
598
599 emr_data[age].append(
600 _('%s: encounter (%s)') % (
601 view_row[view_col_idx['clin_when']],
602 view_row[view_col_idx['pk_encounter']]
603 )
604 )
605 emr_data[age].append(_('health issue: %s') % issue_name)
606 emr_data[age].append(_('episode : %s') % episode_name)
607 # format table specific data columns
608 # - ignore those, they are metadata, some
609 # are in clin.v_pat_items data already
610 cols2ignore = [
611 'pk_audit', 'row_version', 'modified_when', 'modified_by',
612 'pk_item', 'id', 'fk_encounter', 'fk_episode', 'pk'
613 ]
614 col_data = []
615 for col_name in table_col_idx.keys():
616 if col_name in cols2ignore:
617 continue
618 emr_data[age].append("=> %s: %s" % (col_name, row[table_col_idx[col_name]]))
619 emr_data[age].append("----------------------------------------------------")
620 emr_data[age].append("-- %s from table %s" % (
621 view_row[view_col_idx['modified_string']],
622 src_table
623 ))
624 emr_data[age].append("-- written %s by %s" % (
625 view_row[view_col_idx['modified_when']],
626 view_row[view_col_idx['modified_by']]
627 ))
628 emr_data[age].append("----------------------------------------------------")
629 curs.close()
630 return emr_data
631 #--------------------------------------------------------
634 #--------------------------------------------------------
636 union_query = u'\n union all\n'.join ([
637 u"""
638 SELECT ((
639 -- all relevant health issues + active episodes WITH health issue
640 SELECT COUNT(1)
641 FROM clin.v_problem_list
642 WHERE
643 pk_patient = %(pat)s
644 AND
645 pk_health_issue is not null
646 ) + (
647 -- active episodes WITHOUT health issue
648 SELECT COUNT(1)
649 FROM clin.v_problem_list
650 WHERE
651 pk_patient = %(pat)s
652 AND
653 pk_health_issue is null
654 ))""",
655 u'SELECT count(1) FROM clin.encounter WHERE fk_patient = %(pat)s',
656 u'SELECT count(1) FROM clin.v_pat_items WHERE pk_patient = %(pat)s',
657 u'SELECT count(1) FROM blobs.v_doc_med WHERE pk_patient = %(pat)s',
658 u'SELECT count(1) FROM clin.v_test_results WHERE pk_patient = %(pat)s',
659 u'SELECT count(1) FROM clin.v_pat_hospital_stays WHERE pk_patient = %(pat)s',
660 u'SELECT count(1) FROM clin.v_pat_procedures WHERE pk_patient = %(pat)s',
661 # active and approved substances == medication
662 u"""
663 SELECT count(1)
664 from clin.v_pat_substance_intake
665 WHERE
666 pk_patient = %(pat)s
667 and is_currently_active in (null, true)
668 and intake_is_approved_of in (null, true)"""
669 ])
670
671 rows, idx = gmPG2.run_ro_queries (
672 queries = [{'cmd': union_query, 'args': {'pat': self.pk_patient}}],
673 get_col_idx = False
674 )
675
676 stats = dict (
677 problems = rows[0][0],
678 encounters = rows[1][0],
679 items = rows[2][0],
680 documents = rows[3][0],
681 results = rows[4][0],
682 stays = rows[5][0],
683 procedures = rows[6][0],
684 active_drugs = rows[7][0]
685 )
686
687 return stats
688 #--------------------------------------------------------
690 return _("""Medical problems: %(problems)s
691 Total encounters: %(encounters)s
692 Total EMR entries: %(items)s
693 Active medications: %(active_drugs)s
694 Documents: %(documents)s
695 Test results: %(results)s
696 Hospital stays: %(stays)s
697 Procedures: %(procedures)s
698 """ ) % self.get_statistics()
699 #--------------------------------------------------------
701
702 stats = self.get_statistics()
703 first = self.get_first_encounter()
704 last = self.get_last_encounter()
705 probs = self.get_problems()
706
707 txt = _('EMR Statistics\n\n')
708 if len(probs) > 0:
709 txt += _(' %s known problems. Clinically relevant thereof:\n') % stats['problems']
710 else:
711 txt += _(' %s known problems\n') % stats['problems']
712 for prob in probs:
713 if not prob['clinically_relevant']:
714 continue
715 txt += u' \u00BB%s\u00AB (%s)\n' % (
716 prob['problem'],
717 gmTools.bool2subst(prob['problem_active'], _('active'), _('inactive'))
718 )
719 txt += _(' %s encounters from %s to %s\n') % (
720 stats['encounters'],
721 first['started'].strftime('%x'),
722 last['started'].strftime('%x')
723 )
724 txt += _(' %s active medications\n') % stats['active_drugs']
725 txt += _(' %s documents\n') % stats['documents']
726 txt += _(' %s test results\n') % stats['results']
727 txt += _(' %s hospital stays\n') % stats['stays']
728 txt += _(' %s performed procedures\n\n') % stats['procedures']
729
730 txt += _('Allergies and Intolerances\n\n')
731
732 allg_state = self.allergy_state
733 txt += (u' ' + allg_state.state_string)
734 if allg_state['last_confirmed'] is not None:
735 txt += (_(' (last confirmed %s)') % allg_state['last_confirmed'].strftime('%x'))
736 txt += u'\n'
737 txt += gmTools.coalesce(allg_state['comment'], u'', u' %s\n')
738 for allg in self.get_allergies():
739 txt += u' %s: %s\n' % (
740 allg['descriptor'],
741 gmTools.coalesce(allg['reaction'], _('unknown reaction'))
742 )
743
744 return txt
745 #--------------------------------------------------------
746 # allergy API
747 #--------------------------------------------------------
748 - def get_allergies(self, remove_sensitivities=False, since=None, until=None, encounters=None, episodes=None, issues=None, ID_list=None):
749 """Retrieves patient allergy items.
750
751 remove_sensitivities
752 - retrieve real allergies only, without sensitivities
753 since
754 - initial date for allergy items
755 until
756 - final date for allergy items
757 encounters
758 - list of encounters whose allergies are to be retrieved
759 episodes
760 - list of episodes whose allergies are to be retrieved
761 issues
762 - list of health issues whose allergies are to be retrieved
763 """
764 cmd = u"SELECT * FROM clin.v_pat_allergies WHERE pk_patient=%s order by descriptor"
765 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
766 allergies = []
767 for r in rows:
768 allergies.append(gmAllergy.cAllergy(row = {'data': r, 'idx': idx, 'pk_field': 'pk_allergy'}))
769
770 # ok, let's constrain our list
771 filtered_allergies = []
772 filtered_allergies.extend(allergies)
773
774 if ID_list is not None:
775 filtered_allergies = filter(lambda allg: allg['pk_allergy'] in ID_list, filtered_allergies)
776 if len(filtered_allergies) == 0:
777 _log.error('no allergies of list [%s] found for patient [%s]' % (str(ID_list), self.pk_patient))
778 # better fail here contrary to what we do elsewhere
779 return None
780 else:
781 return filtered_allergies
782
783 if remove_sensitivities:
784 filtered_allergies = filter(lambda allg: allg['type'] == 'allergy', filtered_allergies)
785 if since is not None:
786 filtered_allergies = filter(lambda allg: allg['date'] >= since, filtered_allergies)
787 if until is not None:
788 filtered_allergies = filter(lambda allg: allg['date'] < until, filtered_allergies)
789 if issues is not None:
790 filtered_allergies = filter(lambda allg: allg['pk_health_issue'] in issues, filtered_allergies)
791 if episodes is not None:
792 filtered_allergies = filter(lambda allg: allg['pk_episode'] in episodes, filtered_allergies)
793 if encounters is not None:
794 filtered_allergies = filter(lambda allg: allg['pk_encounter'] in encounters, filtered_allergies)
795
796 return filtered_allergies
797 #--------------------------------------------------------
799 if encounter_id is None:
800 encounter_id = self.current_encounter['pk_encounter']
801
802 if episode_id is None:
803 issue = self.add_health_issue(issue_name = _('allergies/intolerances'))
804 epi = self.add_episode(episode_name = substance, pk_health_issue = issue['pk_health_issue'])
805 episode_id = epi['pk_episode']
806
807 new_allergy = gmAllergy.create_allergy (
808 substance = substance,
809 allg_type = allg_type,
810 encounter_id = encounter_id,
811 episode_id = episode_id
812 )
813
814 return new_allergy
815 #--------------------------------------------------------
817 cmd = u'delete FROM clin.allergy WHERE pk=%(pk_allg)s'
818 args = {'pk_allg': pk_allergy}
819 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}])
820 #--------------------------------------------------------
822
823 if state not in gmAllergy.allergy_states:
824 raise ValueError('[%s].__set_allergy_state(): <state> must be one of %s' % (self.__class__.__name__, gmAllergy.allergy_states))
825
826 allg_state = gmAllergy.ensure_has_allergy_state(encounter = self.current_encounter['pk_encounter'])
827 allg_state['has_allergy'] = state
828 allg_state.save_payload()
829 return True
830
833
834 allergy_state = property(_get_allergy_state, _set_allergy_state)
835 #--------------------------------------------------------
836 # episodes API
837 #--------------------------------------------------------
839 """Fetches from backend patient episodes.
840
841 id_list - Episodes' PKs list
842 issues - Health issues' PKs list to filter episodes by
843 open_status - return all episodes, only open or closed one(s)
844 """
845 cmd = u"SELECT * FROM clin.v_pat_episodes WHERE pk_patient=%s"
846 rows, idx = gmPG2.run_ro_queries(queries=[{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
847 tmp = []
848 for r in rows:
849 tmp.append(gmEMRStructItems.cEpisode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_episode'}))
850
851 # now filter
852 if (id_list is None) and (issues is None) and (open_status is None):
853 return tmp
854
855 # ok, let's filter episode list
856 filtered_episodes = []
857 filtered_episodes.extend(tmp)
858 if open_status is not None:
859 filtered_episodes = filter(lambda epi: epi['episode_open'] == open_status, filtered_episodes)
860
861 if issues is not None:
862 filtered_episodes = filter(lambda epi: epi['pk_health_issue'] in issues, filtered_episodes)
863
864 if id_list is not None:
865 filtered_episodes = filter(lambda epi: epi['pk_episode'] in id_list, filtered_episodes)
866
867 return filtered_episodes
868 #------------------------------------------------------------------
870 cmd = u"""SELECT distinct pk_episode
871 from clin.v_pat_items
872 WHERE pk_encounter=%(enc)s and pk_patient=%(pat)s"""
873 args = {
874 'enc': gmTools.coalesce(pk_encounter, self.current_encounter['pk_encounter']),
875 'pat': self.pk_patient
876 }
877 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
878 if len(rows) == 0:
879 return []
880 epis = []
881 for row in rows:
882 epis.append(row[0])
883 return self.get_episodes(id_list=epis)
884 #------------------------------------------------------------------
886 """Add episode 'episode_name' for a patient's health issue.
887
888 - silently returns if episode already exists
889 """
890 episode = gmEMRStructItems.create_episode (
891 pk_health_issue = pk_health_issue,
892 episode_name = episode_name,
893 is_open = is_open,
894 encounter = self.current_encounter['pk_encounter']
895 )
896 return episode
897 #--------------------------------------------------------
899 # try to find the episode with the most recently modified clinical item
900
901 issue_where = gmTools.coalesce(issue, u'', u'and pk_health_issue = %(issue)s')
902
903 cmd = u"""
904 SELECT pk
905 from clin.episode
906 WHERE pk = (
907 SELECT distinct on(pk_episode) pk_episode
908 from clin.v_pat_items
909 WHERE
910 pk_patient = %%(pat)s
911 and
912 modified_when = (
913 SELECT max(vpi.modified_when)
914 from clin.v_pat_items vpi
915 WHERE vpi.pk_patient = %%(pat)s
916 )
917 %s
918 -- guard against several episodes created at the same moment of time
919 limit 1
920 )""" % issue_where
921 rows, idx = gmPG2.run_ro_queries(queries = [
922 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
923 ])
924 if len(rows) != 0:
925 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
926
927 # no clinical items recorded, so try to find
928 # the youngest episode for this patient
929 cmd = u"""
930 SELECT vpe0.pk_episode
931 from
932 clin.v_pat_episodes vpe0
933 WHERE
934 vpe0.pk_patient = %%(pat)s
935 and
936 vpe0.episode_modified_when = (
937 SELECT max(vpe1.episode_modified_when)
938 from clin.v_pat_episodes vpe1
939 WHERE vpe1.pk_episode = vpe0.pk_episode
940 )
941 %s""" % issue_where
942 rows, idx = gmPG2.run_ro_queries(queries = [
943 {'cmd': cmd, 'args': {'pat': self.pk_patient, 'issue': issue}}
944 ])
945 if len(rows) != 0:
946 return gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
947
948 return None
949 #--------------------------------------------------------
952 #--------------------------------------------------------
953 # problems API
954 #--------------------------------------------------------
955 - def get_problems(self, episodes=None, issues=None, include_closed_episodes=False, include_irrelevant_issues=False):
956 """Retrieve a patient's problems.
957
958 "Problems" are the UNION of:
959
960 - issues which are .clinically_relevant
961 - episodes which are .is_open
962
963 Therefore, both an issue and the open episode
964 thereof can each be listed as a problem.
965
966 include_closed_episodes/include_irrelevant_issues will
967 include those -- which departs from the definition of
968 the problem list being "active" items only ...
969
970 episodes - episodes' PKs to filter problems by
971 issues - health issues' PKs to filter problems by
972 """
973 # FIXME: this could use a good measure of streamlining, probably
974
975 args = {'pat': self.pk_patient}
976
977 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_problem_list WHERE pk_patient = %(pat)s"""
978 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
979
980 # Instantiate problem items
981 problems = []
982 for row in rows:
983 pk_args = {
984 u'pk_patient': self.pk_patient,
985 u'pk_health_issue': row['pk_health_issue'],
986 u'pk_episode': row['pk_episode']
987 }
988 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = False))
989
990 # include non-problems ?
991 other_rows = []
992 if include_closed_episodes:
993 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'episode'"""
994 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
995 other_rows.extend(rows)
996
997 if include_irrelevant_issues:
998 cmd = u"""SELECT pk_health_issue, pk_episode FROM clin.v_potential_problem_list WHERE pk_patient = %(pat)s and type = 'health issue'"""
999 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1000 other_rows.extend(rows)
1001
1002 if len(other_rows) > 0:
1003 for row in other_rows:
1004 pk_args = {
1005 u'pk_patient': self.pk_patient,
1006 u'pk_health_issue': row['pk_health_issue'],
1007 u'pk_episode': row['pk_episode']
1008 }
1009 problems.append(gmEMRStructItems.cProblem(aPK_obj = pk_args, try_potential_problems = True))
1010
1011 # filter ?
1012 if (episodes is None) and (issues is None):
1013 return problems
1014
1015 # filter
1016 if issues is not None:
1017 problems = filter(lambda epi: epi['pk_health_issue'] in issues, problems)
1018 if episodes is not None:
1019 problems = filter(lambda epi: epi['pk_episode'] in episodes, problems)
1020
1021 return problems
1022 #--------------------------------------------------------
1024 """
1025 Retrieve the cEpisode instance equivalent to the given problem.
1026 The problem's type attribute must be 'episode'
1027
1028 @param problem: The problem to retrieve its related episode for
1029 @type problem: A gmEMRStructItems.cProblem instance
1030 """
1031 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'episode'):
1032 return self.get_episodes(id_list=[problem['pk_episode']])[0]
1033
1034 if isinstance(problem, gmEMRStructItems.cEpisode):
1035 return problem
1036
1037 raise TypeError('cannot convert [%s] to episode' % problem)
1038 #--------------------------------------------------------
1040 """
1041 Retrieve the cIssue instance equivalent to the given problem.
1042 The problem's type attribute must be 'issue'.
1043
1044 @param problem: The problem to retrieve the corresponding issue for
1045 @type problem: A gmEMRStructItems.cProblem instance
1046 """
1047 if isinstance(problem, gmEMRStructItems.cProblem) and (problem['type'] == 'issue'):
1048 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0]
1049
1050 if isinstance(problem, gmEMRStructItems.cHealthIssue):
1051 return problem
1052
1053 raise TypeError('cannot convert [%s] to health issue' % problem)
1054 #--------------------------------------------------------
1056 """Transform given problem into either episode or health issue instance.
1057 """
1058 if not isinstance(problem, gmEMRStructItems.cProblem):
1059 _log.debug(str(problem))
1060 raise TypeError, 'cannot reclass [%s] instance to problem' % type(problem)
1061 if problem['type'] == 'episode':
1062 return self.get_episodes(id_list=[problem['pk_episode']])[0]
1063 if problem['type'] == 'issue':
1064 return self.get_health_issues(id_list=[problem['pk_health_issue']])[0]
1065 return None
1066 #--------------------------------------------------------
1067 # health issues API
1068 #--------------------------------------------------------
1070
1071 cmd = u"SELECT *, xmin_health_issue FROM clin.v_health_issues WHERE pk_patient=%(pat)s"
1072 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1073 issues = []
1074 for row in rows:
1075 r = {'idx': idx, 'data': row, 'pk_field': 'pk_health_issue'}
1076 issues.append(gmEMRStructItems.cHealthIssue(row=r))
1077
1078 if id_list is None:
1079 return issues
1080
1081 if len(id_list) == 0:
1082 raise ValueError('id_list to filter by is empty, most likely a programming error')
1083
1084 filtered_issues = []
1085 for issue in issues:
1086 if issue['pk_health_issue'] in id_list:
1087 filtered_issues.append(issue)
1088
1089 return filtered_issues
1090 #------------------------------------------------------------------
1092 """Adds patient health issue."""
1093 return gmEMRStructItems.create_health_issue (
1094 description = issue_name,
1095 encounter = self.current_encounter['pk_encounter'],
1096 patient = self.pk_patient
1097 )
1098 #--------------------------------------------------------
1101 #--------------------------------------------------------
1102 # API: substance intake
1103 #--------------------------------------------------------
1104 - def get_current_substance_intake(self, include_inactive=True, include_unapproved=False, order_by=None, episodes=None, issues=None):
1105
1106 where_parts = [u'pk_patient = %(pat)s']
1107
1108 if not include_inactive:
1109 where_parts.append(u'is_currently_active in (true, null)')
1110
1111 if not include_unapproved:
1112 where_parts.append(u'intake_is_approved_of in (true, null)')
1113
1114 if order_by is None:
1115 order_by = u''
1116 else:
1117 order_by = u'order by %s' % order_by
1118
1119 cmd = u"SELECT * FROM clin.v_pat_substance_intake WHERE %s %s" % (
1120 u'\nand '.join(where_parts),
1121 order_by
1122 )
1123
1124 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'pat': self.pk_patient}}], get_col_idx = True)
1125
1126 meds = [ gmMedication.cSubstanceIntakeEntry(row = {'idx': idx, 'data': r, 'pk_field': 'pk_substance_intake'}) for r in rows ]
1127
1128 if episodes is not None:
1129 meds = filter(lambda s: s['pk_episode'] in episodes, meds)
1130
1131 if issues is not None:
1132 meds = filter(lambda s: s['pk_health_issue'] in issues, meds)
1133
1134 return meds
1135 #--------------------------------------------------------
1137 return gmMedication.create_substance_intake (
1138 substance = substance,
1139 atc = atc,
1140 encounter = self.current_encounter['pk_encounter'],
1141 episode = episode,
1142 preparation = preparation
1143 )
1144 #--------------------------------------------------------
1145 # vaccinations API
1146 #--------------------------------------------------------
1148 """Retrieves vaccination regimes the patient is on.
1149
1150 optional:
1151 * ID - PK of the vaccination regime
1152 * indications - indications we want to retrieve vaccination
1153 regimes for, must be primary language, not l10n_indication
1154 """
1155 # FIXME: use course, not regime
1156 try:
1157 self.__db_cache['vaccinations']['scheduled regimes']
1158 except KeyError:
1159 # retrieve vaccination regimes definitions
1160 self.__db_cache['vaccinations']['scheduled regimes'] = []
1161 cmd = """SELECT distinct on(pk_course) pk_course
1162 FROM clin.v_vaccs_scheduled4pat
1163 WHERE pk_patient=%s"""
1164 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1165 if rows is None:
1166 _log.error('cannot retrieve scheduled vaccination courses')
1167 del self.__db_cache['vaccinations']['scheduled regimes']
1168 return None
1169 # Instantiate vaccination items and keep cache
1170 for row in rows:
1171 self.__db_cache['vaccinations']['scheduled regimes'].append(gmVaccination.cVaccinationCourse(aPK_obj=row[0]))
1172
1173 # ok, let's constrain our list
1174 filtered_regimes = []
1175 filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
1176 if ID is not None:
1177 filtered_regimes = filter(lambda regime: regime['pk_course'] == ID, filtered_regimes)
1178 if len(filtered_regimes) == 0:
1179 _log.error('no vaccination course [%s] found for patient [%s]' % (ID, self.pk_patient))
1180 return []
1181 else:
1182 return filtered_regimes[0]
1183 if indications is not None:
1184 filtered_regimes = filter(lambda regime: regime['indication'] in indications, filtered_regimes)
1185
1186 return filtered_regimes
1187 #--------------------------------------------------------
1189 """Retrieves patient vaccinated indications list.
1190
1191 Note that this does NOT rely on the patient being on
1192 some schedule or other but rather works with what the
1193 patient has ACTUALLY been vaccinated against. This is
1194 deliberate !
1195 """
1196 # most likely, vaccinations will be fetched close
1197 # by so it makes sense to count on the cache being
1198 # filled (or fill it for nearby use)
1199 vaccinations = self.get_vaccinations()
1200 if vaccinations is None:
1201 _log.error('cannot load vaccinated indications for patient [%s]' % self.pk_patient)
1202 return (False, [[_('ERROR: cannot retrieve vaccinated indications'), _('ERROR: cannot retrieve vaccinated indications')]])
1203 if len(vaccinations) == 0:
1204 return (True, [[_('no vaccinations recorded'), _('no vaccinations recorded')]])
1205 v_indications = []
1206 for vacc in vaccinations:
1207 tmp = [vacc['indication'], vacc['l10n_indication']]
1208 # remove duplicates
1209 if tmp in v_indications:
1210 continue
1211 v_indications.append(tmp)
1212 return (True, v_indications)
1213 #--------------------------------------------------------
1214 - def get_vaccinations(self, ID=None, indications=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1215 """Retrieves list of vaccinations the patient has received.
1216
1217 optional:
1218 * ID - PK of a vaccination
1219 * indications - indications we want to retrieve vaccination
1220 items for, must be primary language, not l10n_indication
1221 * since - initial date for allergy items
1222 * until - final date for allergy items
1223 * encounters - list of encounters whose allergies are to be retrieved
1224 * episodes - list of episodes whose allergies are to be retrieved
1225 * issues - list of health issues whose allergies are to be retrieved
1226 """
1227 try:
1228 self.__db_cache['vaccinations']['vaccinated']
1229 except KeyError:
1230 self.__db_cache['vaccinations']['vaccinated'] = []
1231 # Important fetch ordering by indication, date to know if a vaccination is booster
1232 cmd= """SELECT * FROM clin.v_pat_vaccinations4indication
1233 WHERE pk_patient=%s
1234 order by indication, date"""
1235 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1236 if rows is None:
1237 _log.error('cannot load given vaccinations for patient [%s]' % self.pk_patient)
1238 del self.__db_cache['vaccinations']['vaccinated']
1239 return None
1240 # Instantiate vaccination items
1241 vaccs_by_ind = {}
1242 for row in rows:
1243 vacc_row = {
1244 'pk_field': 'pk_vaccination',
1245 'idx': idx,
1246 'data': row
1247 }
1248 vacc = gmVaccination.cVaccination(row=vacc_row)
1249 self.__db_cache['vaccinations']['vaccinated'].append(vacc)
1250 # keep them, ordered by indication
1251 try:
1252 vaccs_by_ind[vacc['indication']].append(vacc)
1253 except KeyError:
1254 vaccs_by_ind[vacc['indication']] = [vacc]
1255
1256 # calculate sequence number and is_booster
1257 for ind in vaccs_by_ind.keys():
1258 vacc_regimes = self.get_scheduled_vaccination_regimes(indications = [ind])
1259 for vacc in vaccs_by_ind[ind]:
1260 # due to the "order by indication, date" the vaccinations are in the
1261 # right temporal order inside the indication-keyed dicts
1262 seq_no = vaccs_by_ind[ind].index(vacc) + 1
1263 vacc['seq_no'] = seq_no
1264 # if no active schedule for indication we cannot
1265 # check for booster status (eg. seq_no > max_shot)
1266 if (vacc_regimes is None) or (len(vacc_regimes) == 0):
1267 continue
1268 if seq_no > vacc_regimes[0]['shots']:
1269 vacc['is_booster'] = True
1270 del vaccs_by_ind
1271
1272 # ok, let's constrain our list
1273 filtered_shots = []
1274 filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
1275 if ID is not None:
1276 filtered_shots = filter(lambda shot: shot['pk_vaccination'] == ID, filtered_shots)
1277 if len(filtered_shots) == 0:
1278 _log.error('no vaccination [%s] found for patient [%s]' % (ID, self.pk_patient))
1279 return None
1280 else:
1281 return filtered_shots[0]
1282 if since is not None:
1283 filtered_shots = filter(lambda shot: shot['date'] >= since, filtered_shots)
1284 if until is not None:
1285 filtered_shots = filter(lambda shot: shot['date'] < until, filtered_shots)
1286 if issues is not None:
1287 filtered_shots = filter(lambda shot: shot['pk_health_issue'] in issues, filtered_shots)
1288 if episodes is not None:
1289 filtered_shots = filter(lambda shot: shot['pk_episode'] in episodes, filtered_shots)
1290 if encounters is not None:
1291 filtered_shots = filter(lambda shot: shot['pk_encounter'] in encounters, filtered_shots)
1292 if indications is not None:
1293 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1294 return filtered_shots
1295 #--------------------------------------------------------
1297 """Retrieves vaccinations scheduled for a regime a patient is on.
1298
1299 The regime is referenced by its indication (not l10n)
1300
1301 * indications - List of indications (not l10n) of regimes we want scheduled
1302 vaccinations to be fetched for
1303 """
1304 try:
1305 self.__db_cache['vaccinations']['scheduled']
1306 except KeyError:
1307 self.__db_cache['vaccinations']['scheduled'] = []
1308 cmd = """SELECT * FROM clin.v_vaccs_scheduled4pat WHERE pk_patient=%s"""
1309 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1310 if rows is None:
1311 _log.error('cannot load scheduled vaccinations for patient [%s]' % self.pk_patient)
1312 del self.__db_cache['vaccinations']['scheduled']
1313 return None
1314 # Instantiate vaccination items
1315 for row in rows:
1316 vacc_row = {
1317 'pk_field': 'pk_vacc_def',
1318 'idx': idx,
1319 'data': row
1320 }
1321 self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row = vacc_row))
1322
1323 # ok, let's constrain our list
1324 if indications is None:
1325 return self.__db_cache['vaccinations']['scheduled']
1326 filtered_shots = []
1327 filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
1328 filtered_shots = filter(lambda shot: shot['indication'] in indications, filtered_shots)
1329 return filtered_shots
1330 #--------------------------------------------------------
1332 try:
1333 self.__db_cache['vaccinations']['missing']
1334 except KeyError:
1335 self.__db_cache['vaccinations']['missing'] = {}
1336 # 1) non-booster
1337 self.__db_cache['vaccinations']['missing']['due'] = []
1338 # get list of (indication, seq_no) tuples
1339 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_vaccs WHERE pk_patient=%s"
1340 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1341 if rows is None:
1342 _log.error('error loading (indication, seq_no) for due/overdue vaccinations for patient [%s]' % self.pk_patient)
1343 return None
1344 pk_args = {'pat_id': self.pk_patient}
1345 if rows is not None:
1346 for row in rows:
1347 pk_args['indication'] = row[0]
1348 pk_args['seq_no'] = row[1]
1349 self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
1350
1351 # 2) boosters
1352 self.__db_cache['vaccinations']['missing']['boosters'] = []
1353 # get list of indications
1354 cmd = "SELECT indication, seq_no FROM clin.v_pat_missing_boosters WHERE pk_patient=%s"
1355 rows = gmPG.run_ro_query('historica', cmd, None, self.pk_patient)
1356 if rows is None:
1357 _log.error('error loading indications for missing boosters for patient [%s]' % self.pk_patient)
1358 return None
1359 pk_args = {'pat_id': self.pk_patient}
1360 if rows is not None:
1361 for row in rows:
1362 pk_args['indication'] = row[0]
1363 self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
1364
1365 # if any filters ...
1366 if indications is None:
1367 return self.__db_cache['vaccinations']['missing']
1368 if len(indications) == 0:
1369 return self.__db_cache['vaccinations']['missing']
1370 # ... apply them
1371 filtered_shots = {
1372 'due': [],
1373 'boosters': []
1374 }
1375 for due_shot in self.__db_cache['vaccinations']['missing']['due']:
1376 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['due']:
1377 filtered_shots['due'].append(due_shot)
1378 for due_shot in self.__db_cache['vaccinations']['missing']['boosters']:
1379 if due_shot['indication'] in indications: #and due_shot not in filtered_shots['boosters']:
1380 filtered_shots['boosters'].append(due_shot)
1381 return filtered_shots
1382 #--------------------------------------------------------
1384 """Creates a new vaccination entry in backend."""
1385 return gmVaccination.create_vaccination (
1386 patient_id = self.pk_patient,
1387 episode_id = episode['pk_episode'],
1388 encounter_id = self.current_encounter['pk_encounter'],
1389 staff_id = _me['pk_staff'],
1390 vaccine = vaccine
1391 )
1392 #------------------------------------------------------------------
1393 # API: encounters
1394 #------------------------------------------------------------------
1397
1399
1400 # first ever setting ?
1401 if self.__encounter is None:
1402 _log.debug('first setting of active encounter in this clinical record instance')
1403 else:
1404 _log.debug('switching of active encounter')
1405 # fail if the currently active encounter has unsaved changes
1406 if self.__encounter.is_modified():
1407 _log.debug('unsaved changes in active encounter, cannot switch to another one')
1408 raise ValueError('unsaved changes in active encounter, cannot switch to another one')
1409
1410 # set the currently active encounter and announce that change
1411 self.__encounter = encounter
1412 gmDispatcher.send(u'current_encounter_switched')
1413
1414 # this will trigger another signal "encounter_mod_db"
1415 self.__encounter.set_active(staff_id = _me['pk_staff'])
1416
1417 return True
1418
1419 current_encounter = property(_get_current_encounter, _set_current_encounter)
1420 active_encounter = property(_get_current_encounter, _set_current_encounter)
1421 #------------------------------------------------------------------
1423 # 1) "very recent" encounter recorded ?
1424 if self.__activate_very_recent_encounter():
1425 return True
1426 # 2) "fairly recent" encounter recorded ?
1427 if self.__activate_fairly_recent_encounter():
1428 return True
1429 self.start_new_encounter()
1430 return True
1431 #------------------------------------------------------------------
1433 """Try to attach to a "very recent" encounter if there is one.
1434
1435 returns:
1436 False: no "very recent" encounter, create new one
1437 True: success
1438 """
1439 cfg_db = gmCfg.cCfgSQL()
1440 min_ttl = cfg_db.get2 (
1441 option = u'encounter.minimum_ttl',
1442 workplace = _here.active_workplace,
1443 bias = u'user',
1444 default = u'1 hour 30 minutes'
1445 )
1446 cmd = u"""
1447 SELECT pk_encounter
1448 from clin.v_most_recent_encounters
1449 WHERE
1450 pk_patient = %s
1451 and
1452 last_affirmed > (now() - %s::interval)"""
1453 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, min_ttl]}])
1454 # none found
1455 if len(enc_rows) == 0:
1456 _log.debug('no <very recent> encounter (younger than [%s]) found' % min_ttl)
1457 return False
1458 # attach to existing
1459 self.current_encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1460 _log.debug('"very recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1461 return True
1462 #------------------------------------------------------------------
1464 """Try to attach to a "fairly recent" encounter if there is one.
1465
1466 returns:
1467 False: no "fairly recent" encounter, create new one
1468 True: success
1469 """
1470 cfg_db = gmCfg.cCfgSQL()
1471 min_ttl = cfg_db.get2 (
1472 option = u'encounter.minimum_ttl',
1473 workplace = _here.active_workplace,
1474 bias = u'user',
1475 default = u'1 hour 30 minutes'
1476 )
1477 max_ttl = cfg_db.get2 (
1478 option = u'encounter.maximum_ttl',
1479 workplace = _here.active_workplace,
1480 bias = u'user',
1481 default = u'6 hours'
1482 )
1483 cmd = u"""
1484 SELECT pk_encounter
1485 from clin.v_most_recent_encounters
1486 WHERE
1487 pk_patient=%s
1488 and
1489 last_affirmed between (now() - %s::interval) and (now() - %s::interval)"""
1490 enc_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient, max_ttl, min_ttl]}])
1491 # none found
1492 if len(enc_rows) == 0:
1493 _log.debug('no <fairly recent> encounter (between [%s] and [%s] old) found' % (min_ttl, max_ttl))
1494 return False
1495 encounter = gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
1496 # ask user whether to attach or not
1497 cmd = u"""
1498 SELECT title, firstnames, lastnames, gender, dob
1499 from dem.v_basic_person WHERE pk_identity=%s"""
1500 pats, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}])
1501 pat = pats[0]
1502 pat_str = u'%s %s %s (%s), %s [#%s]' % (
1503 gmTools.coalesce(pat[0], u'')[:5],
1504 pat[1][:15],
1505 pat[2][:15],
1506 pat[3],
1507 pat[4].strftime('%x'),
1508 self.pk_patient
1509 )
1510 enc = gmI18N.get_encoding()
1511 msg = _(
1512 '%s\n'
1513 '\n'
1514 "This patient's chart was worked on only recently:\n"
1515 '\n'
1516 ' %s %s - %s (%s)\n'
1517 '\n'
1518 ' Request: %s\n'
1519 ' Outcome: %s\n'
1520 '\n'
1521 'Do you want to continue that consultation\n'
1522 'or do you want to start a new one ?\n'
1523 ) % (
1524 pat_str,
1525 encounter['started'].strftime('%x').decode(enc),
1526 encounter['started'].strftime('%H:%M'), encounter['last_affirmed'].strftime('%H:%M'),
1527 encounter['l10n_type'],
1528 gmTools.coalesce(encounter['reason_for_encounter'], _('none given')),
1529 gmTools.coalesce(encounter['assessment_of_encounter'], _('none given')),
1530 )
1531 attach = False
1532 try:
1533 attach = _func_ask_user(msg = msg, caption = _('Starting patient encounter'), encounter = encounter)
1534 except:
1535 _log.exception('cannot ask user for guidance, not attaching to existing encounter')
1536 return False
1537 if not attach:
1538 return False
1539
1540 # attach to existing
1541 self.current_encounter = encounter
1542
1543 _log.debug('"fairly recent" encounter [%s] found and re-activated' % enc_rows[0][0])
1544 return True
1545 #------------------------------------------------------------------
1547 cfg_db = gmCfg.cCfgSQL()
1548 # FIXME: look for MRU/MCU encounter type config here
1549 enc_type = cfg_db.get2 (
1550 option = u'encounter.default_type',
1551 workplace = _here.active_workplace,
1552 bias = u'user',
1553 default = u'in surgery'
1554 )
1555 self.current_encounter = gmEMRStructItems.create_encounter(fk_patient = self.pk_patient, enc_type = enc_type)
1556 _log.debug('new encounter [%s] initiated' % self.current_encounter['pk_encounter'])
1557 #------------------------------------------------------------------
1559 """Retrieves patient's encounters.
1560
1561 id_list - PKs of encounters to fetch
1562 since - initial date for encounter items, DateTime instance
1563 until - final date for encounter items, DateTime instance
1564 episodes - PKs of the episodes the encounters belong to (many-to-many relation)
1565 issues - PKs of the health issues the encounters belong to (many-to-many relation)
1566
1567 NOTE: if you specify *both* issues and episodes
1568 you will get the *aggregate* of all encounters even
1569 if the episodes all belong to the health issues listed.
1570 IOW, the issues broaden the episode list rather than
1571 the episode list narrowing the episodes-from-issues
1572 list.
1573 Rationale: If it was the other way round it would be
1574 redundant to specify the list of issues at all.
1575 """
1576 # fetch all encounters for patient
1577 cmd = u"SELECT * FROM clin.v_pat_encounters WHERE pk_patient=%s order by started"
1578 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': [self.pk_patient]}], get_col_idx=True)
1579 encounters = []
1580 for r in rows:
1581 encounters.append(gmEMRStructItems.cEncounter(row={'data': r, 'idx': idx, 'pk_field': 'pk_encounter'}))
1582
1583 # we've got the encounters, start filtering
1584 filtered_encounters = []
1585 filtered_encounters.extend(encounters)
1586 if id_list is not None:
1587 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in id_list, filtered_encounters)
1588 if since is not None:
1589 filtered_encounters = filter(lambda enc: enc['started'] >= since, filtered_encounters)
1590 if until is not None:
1591 filtered_encounters = filter(lambda enc: enc['last_affirmed'] <= until, filtered_encounters)
1592
1593 if (issues is not None) and (len(issues) > 0):
1594
1595 issues = tuple(issues)
1596
1597 # Syan attests that an explicit union of child tables is way faster
1598 # as there seem to be problems with parent table expansion and use
1599 # of child table indexes, so if get_encounter() runs very slow on
1600 # your machine use the lines below
1601
1602 # rows = gmPG.run_ro_query('historica', cClinicalRecord._clin_root_item_children_union_query, None, (tuple(issues),))
1603 # if rows is None:
1604 # _log.error('cannot load encounters for issues [%s] (patient [%s])' % (str(issues), self.pk_patient))
1605 # else:
1606 # enc_ids = map(lambda x:x[0], rows)
1607 # filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1608
1609 # this problem seems fixed for us as of PostgreSQL 8.2 :-)
1610
1611 # however, this seems like the proper approach:
1612 # - find episodes corresponding to the health issues in question
1613 cmd = u"SELECT distinct pk FROM clin.episode WHERE fk_health_issue in %(issues)s"
1614 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'issues': issues}}])
1615 epi_ids = map(lambda x:x[0], rows)
1616 if episodes is None:
1617 episodes = []
1618 episodes.extend(epi_ids)
1619
1620 if (episodes is not None) and (len(episodes) > 0):
1621
1622 episodes = tuple(episodes)
1623
1624 # if the episodes to filter by belong to the patient in question so will
1625 # the encounters found with them - hence we don't need a WHERE on the patient ...
1626 cmd = u"SELECT distinct fk_encounter FROM clin.clin_root_item WHERE fk_episode in %(epis)s"
1627 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'epis': episodes}}])
1628 enc_ids = map(lambda x:x[0], rows)
1629 filtered_encounters = filter(lambda enc: enc['pk_encounter'] in enc_ids, filtered_encounters)
1630
1631 return filtered_encounters
1632 #--------------------------------------------------------
1634 """Retrieves first encounter for a particular issue and/or episode
1635
1636 issue_id - First encounter associated health issue
1637 episode - First encounter associated episode
1638 """
1639 # FIXME: use direct query
1640
1641 if issue_id is None:
1642 issues = None
1643 else:
1644 issues = [issue_id]
1645
1646 if episode_id is None:
1647 episodes = None
1648 else:
1649 episodes = [episode_id]
1650
1651 encounters = self.get_encounters(issues=issues, episodes=episodes)
1652 if len(encounters) == 0:
1653 return None
1654
1655 # FIXME: this does not scale particularly well, I assume
1656 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1657 return encounters[0]
1658 #--------------------------------------------------------
1660 """Retrieves last encounter for a concrete issue and/or episode
1661
1662 issue_id - Last encounter associated health issue
1663 episode_id - Last encounter associated episode
1664 """
1665 # FIXME: use direct query
1666
1667 if issue_id is None:
1668 issues = None
1669 else:
1670 issues = [issue_id]
1671
1672 if episode_id is None:
1673 episodes = None
1674 else:
1675 episodes = [episode_id]
1676
1677 encounters = self.get_encounters(issues=issues, episodes=episodes)
1678 if len(encounters) == 0:
1679 return None
1680
1681 # FIXME: this does not scale particularly well, I assume
1682 encounters.sort(lambda x,y: cmp(x['started'], y['started']))
1683 return encounters[-1]
1684 #------------------------------------------------------------------
1686
1687 args = {'pat': self.pk_patient}
1688
1689 if (issue_id is None) and (episode_id is None):
1690
1691 cmd = u"""
1692 SELECT * FROM clin.v_pat_encounters
1693 WHERE pk_patient = %(pat)s
1694 order by started desc
1695 limit 2
1696 """
1697 else:
1698 where_parts = []
1699
1700 if issue_id is not None:
1701 where_parts.append(u'pk_health_issue = %(issue)s')
1702 args['issue'] = issue_id
1703
1704 if episode_id is not None:
1705 where_parts.append(u'pk_episode = %(epi)s')
1706 args['epi'] = episode_id
1707
1708 cmd = u"""
1709 SELECT *
1710 from clin.v_pat_encounters
1711 WHERE
1712 pk_patient = %%(pat)s
1713 and
1714 pk_encounter in (
1715 SELECT distinct pk_encounter
1716 from clin.v_pat_narrative
1717 WHERE
1718 %s
1719 )
1720 order by started desc
1721 limit 2
1722 """ % u' and '.join(where_parts)
1723
1724 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1725
1726 if len(rows) == 0:
1727 return None
1728
1729 # just one encounter within the above limits
1730 if len(rows) == 1:
1731 # is it the current encounter ?
1732 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1733 # yes
1734 return None
1735 # no
1736 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1737
1738 # more than one encounter
1739 if rows[0]['pk_encounter'] == self.current_encounter['pk_encounter']:
1740 return gmEMRStructItems.cEncounter(row = {'data': rows[1], 'idx': idx, 'pk_field': 'pk_encounter'})
1741
1742 return gmEMRStructItems.cEncounter(row = {'data': rows[0], 'idx': idx, 'pk_field': 'pk_encounter'})
1743 #------------------------------------------------------------------
1745 cfg_db = gmCfg.cCfgSQL()
1746 ttl = cfg_db.get2 (
1747 option = u'encounter.ttl_if_empty',
1748 workplace = _here.active_workplace,
1749 bias = u'user',
1750 default = u'1 week'
1751 )
1752
1753 # FIXME: this should be done async
1754 cmd = u"""
1755 delete FROM clin.encounter
1756 WHERE
1757 clin.encounter.fk_patient = %(pat)s
1758 and
1759 age(clin.encounter.last_affirmed) > %(ttl)s::interval
1760 and
1761 not exists (SELECT 1 FROM clin.clin_root_item WHERE fk_encounter = clin.encounter.pk)
1762 and
1763 not exists (SELECT 1 FROM blobs.doc_med WHERE fk_encounter = clin.encounter.pk)
1764 and
1765 not exists (SELECT 1 FROM clin.episode WHERE fk_encounter = clin.encounter.pk)
1766 and
1767 not exists (SELECT 1 FROM clin.health_issue WHERE fk_encounter = clin.encounter.pk)
1768 and
1769 not exists (SELECT 1 FROM clin.operation WHERE fk_encounter = clin.encounter.pk)
1770 and
1771 not exists (SELECT 1 FROM clin.allergy_state WHERE fk_encounter = clin.encounter.pk)
1772 """
1773 try:
1774 rows, idx = gmPG2.run_rw_queries(queries = [{
1775 'cmd': cmd,
1776 'args': {'pat': self.pk_patient, 'ttl': ttl}
1777 }])
1778 except:
1779 _log.exception('error deleting empty encounters')
1780
1781 return True
1782 #------------------------------------------------------------------
1783 # measurements API
1784 #------------------------------------------------------------------
1785 # FIXME: use psyopg2 dbapi extension of named cursors - they are *server* side !
1787 """Retrieve data about test types for which this patient has results."""
1788
1789 cmd = u"""
1790 SELECT * FROM (
1791 SELECT DISTINCT ON (pk_test_type) pk_test_type, clin_when, unified_name
1792 FROM clin.v_test_results
1793 WHERE pk_patient = %(pat)s
1794 ) AS foo
1795 ORDER BY clin_when desc, unified_name
1796 """
1797 args = {'pat': self.pk_patient}
1798 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1799 return [ gmPathLab.cUnifiedTestType(aPK_obj = row['pk_test_type']) for row in rows ]
1800 #------------------------------------------------------------------
1802 """Retrieve details on tests grouped under unified names for this patient's results."""
1803 cmd = u"""
1804 SELECT * FROM clin.v_unified_test_types WHERE pk_test_type in (
1805 SELECT distinct on (unified_name, unified_abbrev) pk_test_type
1806 from clin.v_test_results
1807 WHERE pk_patient = %(pat)s
1808 )
1809 order by unified_name"""
1810 args = {'pat': self.pk_patient}
1811 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1812 return rows, idx
1813 #------------------------------------------------------------------
1815 """Get the dates for which we have results."""
1816 cmd = u"""
1817 SELECT distinct on (cwhen) date_trunc('day', clin_when) as cwhen
1818 from clin.v_test_results
1819 WHERE pk_patient = %(pat)s
1820 order by cwhen desc"""
1821 args = {'pat': self.pk_patient}
1822 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
1823 return rows
1824 #------------------------------------------------------------------
1826
1827 cmd = u"""
1828 SELECT *, xmin_test_result FROM clin.v_test_results
1829 WHERE pk_patient = %(pat)s
1830 order by clin_when desc, pk_episode, unified_name"""
1831 args = {'pat': self.pk_patient}
1832 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
1833
1834 tests = [ gmPathLab.cTestResult(row = {'pk_field': 'pk_test_result', 'idx': idx, 'data': r}) for r in rows ]
1835
1836 if episodes is not None:
1837 tests = [ t for t in tests if t['pk_episode'] in episodes ]
1838
1839 if encounter is not None:
1840 tests = [ t for t in tests if t['pk_encounter'] == encounter ]
1841
1842 return tests
1843 #------------------------------------------------------------------
1844 - def add_test_result(self, episode=None, type=None, intended_reviewer=None, val_num=None, val_alpha=None, unit=None):
1845
1846 try:
1847 epi = int(episode)
1848 except:
1849 epi = episode['pk_episode']
1850
1851 try:
1852 type = int(type)
1853 except:
1854 type = type['pk_test_type']
1855
1856 if intended_reviewer is None:
1857 from Gnumed.business import gmPerson
1858 intended_reviewer = _me['pk_staff']
1859
1860 tr = gmPathLab.create_test_result (
1861 encounter = self.current_encounter['pk_encounter'],
1862 episode = epi,
1863 type = type,
1864 intended_reviewer = intended_reviewer,
1865 val_num = val_num,
1866 val_alpha = val_alpha,
1867 unit = unit
1868 )
1869
1870 return tr
1871 #------------------------------------------------------------------
1872 #------------------------------------------------------------------
1873 - def get_lab_results(self, limit=None, since=None, until=None, encounters=None, episodes=None, issues=None):
1874 """Retrieves lab result clinical items.
1875
1876 limit - maximum number of results to retrieve
1877 since - initial date
1878 until - final date
1879 encounters - list of encounters
1880 episodes - list of episodes
1881 issues - list of health issues
1882 """
1883 try:
1884 return self.__db_cache['lab results']
1885 except KeyError:
1886 pass
1887 self.__db_cache['lab results'] = []
1888 if limit is None:
1889 lim = ''
1890 else:
1891 # only use limit if all other constraints are None
1892 if since is None and until is None and encounters is None and episodes is None and issues is None:
1893 lim = "limit %s" % limit
1894 else:
1895 lim = ''
1896
1897 cmd = """SELECT * FROM clin.v_results4lab_req WHERE pk_patient=%%s %s""" % lim
1898 rows, idx = gmPG.run_ro_query('historica', cmd, True, self.pk_patient)
1899 if rows is None:
1900 return False
1901 for row in rows:
1902 lab_row = {
1903 'pk_field': 'pk_result',
1904 'idx': idx,
1905 'data': row
1906 }
1907 lab_result = gmPathLab.cLabResult(row=lab_row)
1908 self.__db_cache['lab results'].append(lab_result)
1909
1910 # ok, let's constrain our list
1911 filtered_lab_results = []
1912 filtered_lab_results.extend(self.__db_cache['lab results'])
1913 if since is not None:
1914 filtered_lab_results = filter(lambda lres: lres['req_when'] >= since, filtered_lab_results)
1915 if until is not None:
1916 filtered_lab_results = filter(lambda lres: lres['req_when'] < until, filtered_lab_results)
1917 if issues is not None:
1918 filtered_lab_results = filter(lambda lres: lres['pk_health_issue'] in issues, filtered_lab_results)
1919 if episodes is not None:
1920 filtered_lab_results = filter(lambda lres: lres['pk_episode'] in episodes, filtered_lab_results)
1921 if encounters is not None:
1922 filtered_lab_results = filter(lambda lres: lres['pk_encounter'] in encounters, filtered_lab_results)
1923 return filtered_lab_results
1924 #------------------------------------------------------------------
1926 # FIXME: verify that it is our patient ? ...
1927 req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, lab=lab)
1928 return req
1929 #------------------------------------------------------------------
1931 if encounter_id is None:
1932 encounter_id = self.current_encounter['pk_encounter']
1933 status, data = gmPathLab.create_lab_request(
1934 lab=lab,
1935 req_id=req_id,
1936 pat_id=self.pk_patient,
1937 encounter_id=encounter_id,
1938 episode_id=episode_id
1939 )
1940 if not status:
1941 _log.error(str(data))
1942 return None
1943 return data
1944 #============================================================
1945 # convenience functions
1946 #------------------------------------------------------------
1951 #============================================================
1952 # main
1953 #------------------------------------------------------------
1954 if __name__ == "__main__":
1955
1956 from Gnumed.pycommon import gmLog2
1957
1958 #-----------------------------------------
1960 emr = cClinicalRecord(aPKey=1)
1961 state = emr.allergy_state
1962 print "allergy state is:", state
1963
1964 print "setting state to 0"
1965 emr.allergy_state = 0
1966
1967 print "setting state to None"
1968 emr.allergy_state = None
1969
1970 print "setting state to 'abc'"
1971 emr.allergy_state = 'abc'
1972 #-----------------------------------------
1974 emr = cClinicalRecord(aPKey=12)
1975 rows = emr.get_test_types_for_results()
1976 print "test result names:"
1977 for row in rows:
1978 print row
1979 #-----------------------------------------
1981 emr = cClinicalRecord(aPKey=12)
1982 rows = emr.get_dates_for_results()
1983 print "test result dates:"
1984 for row in rows:
1985 print row
1986 #-----------------------------------------
1988 emr = cClinicalRecord(aPKey=12)
1989 rows, idx = emr.get_measurements_by_date()
1990 print "test results:"
1991 for row in rows:
1992 print row
1993 #-----------------------------------------
1995 emr = cClinicalRecord(aPKey=12)
1996 tests = emr.get_test_results_by_date()
1997 print "test results:"
1998 for test in tests:
1999 print test
2000 #-----------------------------------------
2002 emr = cClinicalRecord(aPKey=12)
2003 rows, idx = emr.get_test_types_details()
2004 print "test type details:"
2005 for row in rows:
2006 print row
2007 #-----------------------------------------
2009 emr = cClinicalRecord(aPKey=12)
2010 for key, item in emr.get_statistics().iteritems():
2011 print key, ":", item
2012 #-----------------------------------------
2014 emr = cClinicalRecord(aPKey=12)
2015
2016 probs = emr.get_problems()
2017 print "normal probs (%s):" % len(probs)
2018 for p in probs:
2019 print u'%s (%s)' % (p['problem'], p['type'])
2020
2021 probs = emr.get_problems(include_closed_episodes=True)
2022 print "probs + closed episodes (%s):" % len(probs)
2023 for p in probs:
2024 print u'%s (%s)' % (p['problem'], p['type'])
2025
2026 probs = emr.get_problems(include_irrelevant_issues=True)
2027 print "probs + issues (%s):" % len(probs)
2028 for p in probs:
2029 print u'%s (%s)' % (p['problem'], p['type'])
2030
2031 probs = emr.get_problems(include_closed_episodes=True, include_irrelevant_issues=True)
2032 print "probs + issues + epis (%s):" % len(probs)
2033 for p in probs:
2034 print u'%s (%s)' % (p['problem'], p['type'])
2035 #-----------------------------------------
2037 emr = cClinicalRecord(aPKey=12)
2038 tr = emr.add_test_result (
2039 episode = 1,
2040 intended_reviewer = 1,
2041 type = 1,
2042 val_num = 75,
2043 val_alpha = u'somewhat obese',
2044 unit = u'kg'
2045 )
2046 print tr
2047 #-----------------------------------------
2051 #-----------------------------------------
2053 emr = cClinicalRecord(aPKey=12)
2054 print emr.get_last_encounter(issue_id=2)
2055 print emr.get_last_but_one_encounter(issue_id=2)
2056 #-----------------------------------------
2058 emr = cClinicalRecord(aPKey=12)
2059 for med in emr.get_current_substance_intake():
2060 print med
2061 #-----------------------------------------
2062 if (len(sys.argv) > 0) and (sys.argv[1] == 'test'):
2063 #test_allergy_state()
2064 test_get_test_names()
2065 #test_get_dates_for_results()
2066 #test_get_measurements()
2067 #test_get_test_results_by_date()
2068 #test_get_test_types_details()
2069 #test_get_statistics()
2070 #test_get_problems()
2071 #test_add_test_result()
2072 #test_get_most_recent_episode()
2073 #test_get_almost_recent_encounter()
2074 #test_get_meds()
2075
2076 # emr = cClinicalRecord(aPKey = 12)
2077
2078 # # Vacc regimes
2079 # vacc_regimes = emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
2080 # print '\nVaccination regimes: '
2081 # for a_regime in vacc_regimes:
2082 # pass
2083 # #print a_regime
2084 # vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)
2085 # #print vacc_regime
2086
2087 # # vaccination regimes and vaccinations for regimes
2088 # scheduled_vaccs = emr.get_scheduled_vaccinations(indications = ['tetanus'])
2089 # print 'Vaccinations for the regime:'
2090 # for a_scheduled_vacc in scheduled_vaccs:
2091 # pass
2092 # #print ' %s' %(a_scheduled_vacc)
2093
2094 # # vaccination next shot and booster
2095 # vaccinations = emr.get_vaccinations()
2096 # for a_vacc in vaccinations:
2097 # 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'])
2098
2099 # # first and last encounters
2100 # first_encounter = emr.get_first_encounter(issue_id = 1)
2101 # print '\nFirst encounter: ' + str(first_encounter)
2102 # last_encounter = emr.get_last_encounter(episode_id = 1)
2103 # print '\nLast encounter: ' + str(last_encounter)
2104 # print ''
2105
2106 # # lab results
2107 # lab = emr.get_lab_results()
2108 # lab_file = open('lab-data.txt', 'wb')
2109 # for lab_result in lab:
2110 # lab_file.write(str(lab_result))
2111 # lab_file.write('\n')
2112 # lab_file.close()
2113
2114 #dump = record.get_missing_vaccinations()
2115 #f = open('vaccs.lst', 'wb')
2116 #if dump is not None:
2117 # print "=== due ==="
2118 # f.write("=== due ===\n")
2119 # for row in dump['due']:
2120 # print row
2121 # f.write(repr(row))
2122 # f.write('\n')
2123 # print "=== overdue ==="
2124 # f.write("=== overdue ===\n")
2125 # for row in dump['overdue']:
2126 # print row
2127 # f.write(repr(row))
2128 # f.write('\n')
2129 #f.close()
2130 #============================================================
2131 # $Log: gmClinicalRecord.py,v $
2132 # Revision 1.308 2009/12/03 17:44:18 ncq
2133 # - rewrite get-test-types-for-results
2134 #
2135 # Revision 1.307 2009/12/01 21:47:52 ncq
2136 # - improved naming
2137 #
2138 # Revision 1.306 2009/11/28 18:21:51 ncq
2139 # - normalize turning things into problems
2140 #
2141 # Revision 1.305 2009/11/28 18:14:36 ncq
2142 # - lotsa SQL writ cleanup as suggested by David Merz
2143 # - fix statistics
2144 # - get-problems enhancement
2145 # - create-patient-consumed-substance -> create-substance-intake
2146 # - more tests
2147 #
2148 # Revision 1.304 2009/11/24 19:55:03 ncq
2149 # - cleanup
2150 # - much enhanced get-problems() but not done yet
2151 #
2152 # Revision 1.303 2009/11/15 01:04:08 ncq
2153 # - add missing self
2154 #
2155 # Revision 1.302 2009/11/14 22:46:31 ncq
2156 # - better naming
2157 #
2158 # Revision 1.301 2009/11/13 20:47:16 ncq
2159 # - add-performed-procedure
2160 #
2161 # Revision 1.300 2009/11/06 15:00:50 ncq
2162 # - add substances to stats
2163 # - enhance get-current-substance-intake
2164 # - add-consumed-substance
2165 #
2166 # Revision 1.299 2009/09/23 14:28:13 ncq
2167 # - support procedures
2168 # - create-health-issue can now take advantage of patient pk again
2169 #
2170 # Revision 1.298 2009/09/17 21:51:26 ncq
2171 # - add performed procedures support
2172 #
2173 # Revision 1.297 2009/09/15 15:26:06 ncq
2174 # - rework handling of setting active encounter and listening
2175 # to encounter changes in the DB
2176 # - no more get-active-encounter()
2177 #
2178 # Revision 1.296 2009/09/01 22:13:24 ncq
2179 # - cleanup
2180 #
2181 # Revision 1.295 2009/07/02 20:56:49 ncq
2182 # - cleanup
2183 #
2184 # Revision 1.294 2009/07/01 17:14:40 ncq
2185 # - cleanup
2186 #
2187 # Revision 1.293 2009/06/29 14:58:29 ncq
2188 # - fix emr stats getter which returned stats over *all* patients :-)
2189 #
2190 # Revision 1.292 2009/06/22 09:20:43 ncq
2191 # - problem statistics:
2192 # don't count open episode under active issue
2193 # again as per list discussion
2194 #
2195 # Revision 1.291 2009/06/04 14:29:18 ncq
2196 # - re-import lost adjustments
2197 #
2198 # Revision 1.291 2009/05/28 10:44:52 ncq
2199 # - adjust to test table changes
2200 #
2201 # Revision 1.290 2009/05/26 09:16:33 ncq
2202 # - comment on cursors
2203 #
2204 # Revision 1.289 2009/05/12 12:05:04 ncq
2205 # - start medication handling + test
2206 #
2207 # Revision 1.288 2009/04/16 12:47:00 ncq
2208 # - fix logic when getting last-but-one encounter
2209 #
2210 # Revision 1.287 2009/04/13 11:00:08 ncq
2211 # - proper property current_encounter/active_encounter and self-use it
2212 # - properly detect current encounter modification
2213 # - broadcast current encounter switching
2214 #
2215 # Revision 1.286 2009/04/03 11:06:07 ncq
2216 # - filter stays by issue
2217 # - include stays in summary and statistics
2218 #
2219 # Revision 1.285 2009/04/03 10:38:59 ncq
2220 # - allow filtering by episode when getting hospital stays
2221 #
2222 # Revision 1.284 2009/04/03 09:24:42 ncq
2223 # - start hospital stay API
2224 #
2225 # Revision 1.283 2009/02/20 15:41:42 ncq
2226 # - fix remove_empty_encounters
2227 #
2228 # Revision 1.282 2009/01/02 11:34:35 ncq
2229 # - cleanup
2230 # - pk_patient earlier in __init__
2231 # - listen to db encounter mods, check for current encounter mod,
2232 # send send specific signal
2233 #
2234 # Revision 1.281 2008/12/27 15:49:42 ncq
2235 # - add_notes
2236 #
2237 # Revision 1.280 2008/12/17 21:52:11 ncq
2238 # - filter narrative by provider
2239 #
2240 # Revision 1.279 2008/12/12 16:34:13 ncq
2241 # - HIPAA: log "access to the EMR"
2242 #
2243 # Revision 1.278 2008/12/09 23:19:15 ncq
2244 # - adjust to blobs.doc_med changes
2245 #
2246 # Revision 1.277 2008/11/24 11:06:24 ncq
2247 # - no more patient_id in create_episode
2248 #
2249 # Revision 1.276 2008/11/20 18:39:40 ncq
2250 # - get_last_but_one_encounter
2251 #
2252 # Revision 1.275 2008/11/03 10:27:41 ncq
2253 # - no more patient_id in create_health_issue
2254 #
2255 # Revision 1.274 2008/10/26 01:21:22 ncq
2256 # - improve EMR search result
2257 #
2258 # Revision 1.273 2008/10/22 12:04:21 ncq
2259 # - use %x in strftime
2260 #
2261 # Revision 1.272 2008/10/12 15:12:52 ncq
2262 # - adapt to reworked allergy support and adjust test
2263 # - statistics now says encounters, not visits
2264 # - improved wording for known problems/clinically relevant
2265 #
2266 # Revision 1.271 2008/09/02 18:58:26 ncq
2267 # - fk_patient dropped from clin.health_issue
2268 #
2269 # Revision 1.270 2008/08/15 15:55:41 ncq
2270 # - comment on row_version
2271 #
2272 # Revision 1.269 2008/07/12 15:19:16 ncq
2273 # - improve summary formatting
2274 # - fix get_most_recent_episode plus test
2275 #
2276 # Revision 1.268 2008/07/07 11:33:15 ncq
2277 # - improve "continue encounter ?" message
2278 #
2279 # Revision 1.267 2008/06/24 16:53:24 ncq
2280 # - enhance get_test_results_by_date to filter by episode/encounter
2281 #
2282 # Revision 1.266 2008/06/16 15:01:01 ncq
2283 # - test suite cleanup
2284 # - add_test_result
2285 #
2286 # Revision 1.265 2008/05/19 16:22:55 ncq
2287 # - format_statistics/summary()
2288 #
2289 # Revision 1.264 2008/05/19 15:43:17 ncq
2290 # - get_summary -> _statistics
2291 # - get all statistics in one database roundtrip
2292 # - fix test suite
2293 #
2294 # Revision 1.263 2008/04/22 21:12:08 ncq
2295 # - get_test_results_by_date and test
2296 #
2297 # Revision 1.262 2008/04/11 23:07:08 ncq
2298 # - fix remove_empty_encounters()
2299 #
2300 # Revision 1.261 2008/03/29 16:04:31 ncq
2301 # - retrieve test results ordered by day-truncated date
2302 #
2303 # Revision 1.260 2008/03/20 15:28:17 ncq
2304 # - get_test_types_details() w/ test
2305 #
2306 # Revision 1.259 2008/03/17 14:53:57 ncq
2307 # - improve deletion of empty encounters
2308 # - get_test_types_for_results()
2309 # - get_dates_for_results()
2310 # - get_measurements_by_date()
2311 # - improve tests
2312 #
2313 # Revision 1.258 2008/03/05 22:24:31 ncq
2314 # - support fk_encounter in issue and episode creation
2315 #
2316 # Revision 1.257 2008/02/25 16:58:03 ncq
2317 # - use new logging
2318 #
2319 # Revision 1.256 2008/01/30 13:34:49 ncq
2320 # - switch to std lib logging
2321 #
2322 # Revision 1.255 2008/01/16 19:43:28 ncq
2323 # - use backend configured default encounter type in create_encounter()
2324 # - configurable empty encounter removal age
2325 #
2326 # Revision 1.254 2007/12/26 18:31:53 ncq
2327 # - remove reference to old PG library
2328 #
2329 # Revision 1.253 2007/12/11 12:59:11 ncq
2330 # - cleanup and explicit signal handling
2331 #
2332 # Revision 1.252 2007/10/25 12:26:05 ncq
2333 # - cleanup
2334 #
2335 # Revision 1.251 2007/10/25 12:15:39 ncq
2336 # - we don't send allergy_updated() signal anymore, the backend does it for us
2337 #
2338 # Revision 1.250 2007/10/08 13:22:42 ncq
2339 # - avoid circular import
2340 #
2341 # Revision 1.249 2007/10/08 13:17:55 ncq
2342 # - missing gmPerson import
2343 #
2344 # Revision 1.248 2007/10/07 12:22:51 ncq
2345 # - workplace property now on gmSurgery.gmCurrentPractice() borg
2346 #
2347 # Revision 1.247 2007/09/24 22:07:32 ncq
2348 # - be careful about deleting empty encounters - make sure they are at least 1 week old
2349 #
2350 # Revision 1.246 2007/09/07 10:55:40 ncq
2351 # - order by in get_clin_narrative()
2352 #
2353 # Revision 1.245 2007/06/28 12:30:05 ncq
2354 # - uncache get_clin_narrative access
2355 #
2356 # Revision 1.244 2007/06/19 12:40:40 ncq
2357 # - cleanup
2358 #
2359 # Revision 1.243 2007/04/25 21:59:15 ncq
2360 # - improve message on very-recent-encounter
2361 #
2362 # Revision 1.242 2007/04/06 23:12:58 ncq
2363 # - move remove_empty_encounters() from cleanup() to init()
2364 #
2365 # Revision 1.241 2007/04/02 18:32:51 ncq
2366 # - start_new_encounter()
2367 #
2368 # Revision 1.240 2007/04/01 15:25:25 ncq
2369 # - safely get encoding
2370 #
2371 # Revision 1.239 2007/03/31 21:18:13 ncq
2372 # - fix get_episodes_by_encounter()
2373 #
2374 # Revision 1.238 2007/03/26 16:49:26 ncq
2375 # - settle on health issue/episode naming for newly added allergies
2376 #
2377 # Revision 1.237 2007/03/23 15:01:14 ncq
2378 # - cleanup get_allergies() API
2379 #
2380 # Revision 1.236 2007/03/21 08:12:14 ncq
2381 # - allergic_state property
2382 # - send allergy_modified() signal
2383 # - make cClinicalRecord a new-style class
2384 #
2385 # Revision 1.235 2007/03/18 13:01:16 ncq
2386 # - re-add lost 1.235
2387 # - add ensure_has_allergic_state()
2388 # - remove allergies cache
2389 # - add delete_allergy()
2390 #
2391 # Revision 1.235 2007/03/12 12:23:54 ncq
2392 # - create_allergy() now throws exceptions so deal with that
2393 #
2394 # Revision 1.234 2007/03/02 15:29:33 ncq
2395 # - need to decode() strftime() output to u''
2396 #
2397 # Revision 1.233 2007/02/19 14:06:56 ncq
2398 # - add_health_issue() should return True if health_issue already exists
2399 #
2400 # Revision 1.232 2007/02/17 14:08:52 ncq
2401 # - gmPerson.gmCurrentProvider.workplace now a property
2402 #
2403 # Revision 1.231 2007/02/09 14:58:43 ncq
2404 # - honor <pk_encounter> in get_episodes_by_encounter
2405 # instead of always assuming the current encounter
2406 #
2407 # Revision 1.230 2007/01/31 23:30:33 ncq
2408 # - fix __activate_fairly_recent_encounter()
2409 #
2410 # Revision 1.229 2007/01/29 11:58:53 ncq
2411 # - cleanup
2412 # - let _ask_user_func fail so programmers knows early
2413 #
2414 # Revision 1.228 2007/01/09 18:01:12 ncq
2415 # - error policy is now exceptions
2416 #
2417 # Revision 1.227 2007/01/09 12:55:29 ncq
2418 # - create_episode() now always takes patient fk
2419 #
2420 # Revision 1.226 2006/12/25 22:48:09 ncq
2421 # - comment on PG 8.2 fixing child table index scans for us
2422 #
2423 # Revision 1.225 2006/12/13 13:41:33 ncq
2424 # - add remove_empty_encounters() and call from cleanup()
2425 #
2426 # Revision 1.224 2006/12/13 00:30:43 ncq
2427 # - fix get_health_issues() id_list sanity check insanity
2428 #
2429 # Revision 1.223 2006/11/28 20:39:30 ncq
2430 # - improve problem2*()
2431 # - de-cache get_health_issues()
2432 #
2433 # Revision 1.222 2006/11/26 15:43:41 ncq
2434 # - keys in get_summary() shouldn't be _()
2435 #
2436 # Revision 1.221 2006/11/24 14:15:20 ncq
2437 # - u'' one query
2438 #
2439 # Revision 1.220 2006/11/20 18:22:39 ncq
2440 # - invalidate problem cache when health issues are updated, too
2441 # - do not use cache for now when getting problems/episodes
2442 #
2443 # Revision 1.219 2006/11/19 10:50:35 ncq
2444 # - fix get_episodes_by_encounter()
2445 #
2446 # Revision 1.218 2006/11/14 16:55:05 ncq
2447 # - make sure issues/episodes are tuple()s in get_encounters()
2448 #
2449 # Revision 1.217 2006/11/05 17:53:15 ncq
2450 # - one more get_col_idx
2451 #
2452 # Revision 1.216 2006/11/05 17:01:50 ncq
2453 # - fix some queries to produce proper rows
2454 # - var name fixes in get_encounters()
2455 #
2456 # Revision 1.215 2006/11/05 16:28:24 ncq
2457 # - fix a few double uses of variable row
2458 #
2459 # Revision 1.214 2006/11/05 16:20:49 ncq
2460 # - remove 2 printk()s
2461 #
2462 # Revision 1.213 2006/11/05 15:59:16 ncq
2463 # - make encounter ttl configurable
2464 # - audit clinical data retrieval and never appear to succeed if something fails
2465 # - this will show up some more exceptions which were thought harmless before and therefor masked out
2466 # - u'' some queries
2467 # - clarify get_encounters()
2468 # - remove cruft
2469 # - more gmPG -> gmPG2
2470 #
2471 # Revision 1.212 2006/10/28 15:01:21 ncq
2472 # - speed up allergy, encounter fetching
2473 # - unicode() queries
2474 #
2475 # Revision 1.211 2006/10/25 07:46:44 ncq
2476 # - Format() -> strftime() since datetime.datetime does not have .Format()
2477 #
2478 # Revision 1.210 2006/10/25 07:17:40 ncq
2479 # - no more gmPG
2480 # - no more cClinItem
2481 #
2482 # Revision 1.209 2006/10/24 13:14:07 ncq
2483 # - must import gmPG2, too, now
2484 #
2485 # Revision 1.208 2006/10/23 13:06:19 ncq
2486 # - don't import path lab/vaccs business objects, they are not converted yet
2487 # - use gmPG2 (not finished yet)
2488 # - comment out backend signal handling for now
2489 # - drop services support
2490 #
2491 # Revision 1.207 2006/07/19 20:25:00 ncq
2492 # - gmPyCompat.py is history
2493 #
2494 # Revision 1.206 2006/06/26 12:25:30 ncq
2495 # - cleanup
2496 #
2497 # Revision 1.205 2006/06/07 22:01:57 ncq
2498 # - cVaccinationRegime -> cVaccinationCourse
2499 #
2500 # Revision 1.204 2006/05/28 15:25:18 ncq
2501 # - ever better docs in get_encounters() just short of a proper fix
2502 #
2503 # Revision 1.203 2006/05/25 22:10:43 ncq
2504 # - improve comment in get_encounters()
2505 #
2506 # Revision 1.202 2006/05/14 21:44:22 ncq
2507 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof
2508 # - remove use of gmWhoAmI.py
2509 #
2510 # Revision 1.201 2006/05/12 13:54:26 ncq
2511 # - lazy import gmPerson
2512 #
2513 # Revision 1.200 2006/05/12 12:02:25 ncq
2514 # - use gmCurrentProvider()
2515 #
2516 # Revision 1.199 2006/05/06 18:53:56 ncq
2517 # - select age(...) <> ...; -> select ... <> now() - ...; as per Syan
2518 #
2519 # Revision 1.198 2006/05/04 18:01:39 ncq
2520 # - "properly" include Syan's hack to speed up get_encounters()
2521 # - not active but has comment on how and when to activate it
2522 # - programmatically finds clin_root_item child tables :-)
2523 # - vaccination regime -> course adjustments
2524 # - try yet another approach in get_encounters() which really
2525 # should speed things up, too, without resorting to brute-force
2526 # child table resolution just yet
2527 #
2528 # Revision 1.197 2006/04/23 16:49:03 ncq
2529 # - properly access encounters by health issue
2530 #
2531 # Revision 1.196 2006/04/23 16:46:28 ncq
2532 # - do not select age field from clin.v_pat_items since it doesn't exist anymore
2533 # - add get_summary()
2534 # - try faster get_encounters()
2535 #
2536 # Revision 1.195 2006/02/27 22:38:36 ncq
2537 # - spell out rfe/aoe as per Richard's request
2538 #
2539 # Revision 1.194 2006/01/07 13:00:19 ncq
2540 # - add some schema qualifiers
2541 #
2542 # Revision 1.193 2005/12/26 12:03:10 sjtan
2543 #
2544 # more schema matching. some delegation .
2545 #
2546 # Revision 1.192 2005/12/25 13:24:30 sjtan
2547 #
2548 # schema changes in names .
2549 #
2550 # Revision 1.191 2005/12/10 22:55:17 ncq
2551 # - fully log newly created encounters
2552 #
2553 # Revision 1.190 2005/12/06 14:24:14 ncq
2554 # - clin.clin_health_issue/episode -> clin.health_issue/episode
2555 #
2556 # Revision 1.189 2005/11/27 12:44:57 ncq
2557 # - clinical tables are in schema "clin" now
2558 #
2559 # Revision 1.188 2005/11/18 15:16:15 ncq
2560 # - add simple (non-context aware) search function
2561 #
2562 # Revision 1.187 2005/10/19 09:16:29 ncq
2563 # - cleanup, return to well-defined state re narrative
2564 # cache rebuild, to be fixed later
2565 #
2566 # Revision 1.186 2005/10/15 18:19:23 ncq
2567 # - cleanup
2568 # - improved logging when initiating active encounter
2569 #
2570 # Revision 1.185 2005/10/11 21:03:13 ncq
2571 # - a bit of cleanup re Syan's changes
2572 #
2573 # Revision 1.184 2005/10/08 12:33:08 sjtan
2574 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch.
2575 #
2576 # Revision 1.183 2005/10/04 19:31:45 sjtan
2577 # allow for flagged invalidation of cache and cache reloading.
2578 #
2579 # Revision 1.182 2005/09/22 15:45:11 ncq
2580 # - clin_encounter.fk_provider removed
2581 #
2582 # Revision 1.181 2005/09/12 15:05:44 ncq
2583 # - enhance get_episodes() to allow selection by is_open
2584 #
2585 # Revision 1.180 2005/09/11 17:21:55 ncq
2586 # - get_episodes_by_encounter()
2587 # - support is_open when adding episodes
2588 #
2589 # Revision 1.179 2005/09/09 13:49:25 ncq
2590 # - add instance casts from/to episode/issue/problem
2591 #
2592 # Revision 1.178 2005/09/05 16:26:36 ncq
2593 # - add reclass_problem()
2594 # - improve problem2(episode | issue)
2595 #
2596 # Revision 1.177 2005/08/14 15:34:32 ncq
2597 # - TODO item
2598 #
2599 # Revision 1.176 2005/08/06 16:03:31 ncq
2600 # - guard against concurrent cache flushing
2601 #
2602 # Revision 1.175 2005/05/08 21:40:27 ncq
2603 # - robustify get_first/last_encounter() as there may really be None
2604 #
2605 # Revision 1.174 2005/04/27 12:24:23 sjtan
2606 #
2607 # id_patient should be pk_patient ? changed a while ago. effect: enables emr_dump window to display.
2608 #
2609 # Revision 1.173 2005/04/24 14:41:04 ncq
2610 # - cleanup, fail in add_health_issue if issue exists
2611 #
2612 # Revision 1.172 2005/04/11 17:54:19 ncq
2613 # - cleanup
2614 #
2615 # Revision 1.171 2005/04/03 20:04:56 ncq
2616 # - remove __load_active_episode() sillyness and related stuff
2617 #
2618 # Revision 1.170 2005/04/03 09:26:48 ncq
2619 # - cleanup
2620 #
2621 # Revision 1.169 2005/03/30 22:07:35 ncq
2622 # - guard against several "latest episodes"
2623 # - maybe do away with the *explicit* "latest episode" stuff ?
2624 #
2625 # Revision 1.168 2005/03/23 18:30:40 ncq
2626 # - v_patient_items -> v_pat_items
2627 # - add problem2issue()
2628 #
2629 # Revision 1.167 2005/03/20 16:47:26 ncq
2630 # - cleanup
2631 #
2632 # Revision 1.166 2005/03/17 21:15:29 cfmoro
2633 # Added problem2episode cast method
2634 #
2635 # Revision 1.165 2005/03/14 18:16:52 cfmoro
2636 # Create episode according to only_standalone_epi_has_patient backend constraint
2637 #
2638 # Revision 1.164 2005/03/14 14:27:21 ncq
2639 # - id_patient -> pk_patient
2640 # - rewrite add_episode() work with simplified episode naming
2641 #
2642 # Revision 1.163 2005/03/10 19:49:34 cfmoro
2643 # Added episodes and issues constraints to get_problem
2644 #
2645 # Revision 1.162 2005/03/01 20:48:46 ncq
2646 # - rearrange __init__ such that encounter is set up before episode
2647 # - fix handling in __load_last_active_episode()
2648 #
2649 # Revision 1.161 2005/02/23 19:38:40 ncq
2650 # - listen to episode changes in DB, too
2651 #
2652 # Revision 1.160 2005/02/20 10:31:44 sjtan
2653 #
2654 # lazy initialize preconditions for create_episode.
2655 #
2656 # Revision 1.159 2005/02/13 15:44:52 ncq
2657 # - v_basic_person.i_pk -> pk_identity
2658 #
2659 # Revision 1.158 2005/02/12 13:52:45 ncq
2660 # - identity.id -> pk
2661 # - v_basic_person.i_id -> i_pk
2662 # - self.id_patient -> self.pk_patient
2663 #
2664 # Revision 1.157 2005/02/02 19:55:26 cfmoro
2665 # Delete problems cache on episodes modified callback method
2666 #
2667 # Revision 1.156 2005/01/31 20:25:07 ncq
2668 # - listen on episode changes, too
2669 #
2670 # Revision 1.155 2005/01/29 19:20:49 cfmoro
2671 # Commented out episode cache update after episode creation, should we use gmSignals?
2672 #
2673 # Revision 1.154 2005/01/29 19:14:00 cfmoro
2674 # Added newly created episode to episode cache
2675 #
2676 # Revision 1.153 2005/01/15 19:55:55 cfmoro
2677 # Added problem support to emr
2678 #
2679 # Revision 1.152 2004/12/18 15:57:57 ncq
2680 # - Syan found a logging bug, which is now fixed
2681 # - eventually fix bug in use of create_encounter() that
2682 # prevented gmSoapImporter from working properly
2683 #
2684 # Revision 1.151 2004/12/15 10:28:11 ncq
2685 # - fix create_episode() aka add_episode()
2686 #
2687 # Revision 1.150 2004/10/27 12:09:28 ncq
2688 # - properly set booster/seq_no in the face of the patient
2689 # not being on any vaccination schedule
2690 #
2691 # Revision 1.149 2004/10/26 12:51:24 ncq
2692 # - Carlos: bulk load lab results
2693 #
2694 # Revision 1.148 2004/10/20 21:50:29 ncq
2695 # - return [] on no vacc regimes found
2696 # - in get_vaccinations() handle case where patient is not on any schedule
2697 #
2698 # Revision 1.147 2004/10/20 12:28:25 ncq
2699 # - revert back to Carlos' bulk loading code
2700 # - keep some bits of what Syan added
2701 # - he likes to force overwriting other peoples' commits
2702 # - if that continues his CVS rights are at stake (to be discussed
2703 # on list when appropriate)
2704 #
2705 # Revision 1.144 2004/10/18 11:33:48 ncq
2706 # - more bulk loading
2707 #
2708 # Revision 1.143 2004/10/12 18:39:12 ncq
2709 # - first cut at using Carlos' bulk fetcher in get_vaccinations(),
2710 # seems to work fine so far ... please test and report lossage ...
2711 #
2712 # Revision 1.142 2004/10/12 11:14:51 ncq
2713 # - improve get_scheduled_vaccination_regimes/get_vaccinations, mostly by Carlos
2714 #
2715 # Revision 1.141 2004/10/11 19:50:15 ncq
2716 # - improve get_allergies()
2717 #
2718 # Revision 1.140 2004/09/28 12:19:15 ncq
2719 # - any vaccination related data now cached under 'vaccinations' so
2720 # all of it is flushed when any change to vaccinations occurs
2721 # - rewrite get_scheduled_vaccination_regimes() (Carlos)
2722 # - in get_vaccinations() compute seq_no and is_booster status
2723 #
2724 # Revision 1.139 2004/09/19 15:07:01 ncq
2725 # - we don't use a default health issue anymore
2726 # - remove duplicate existence checks
2727 # - cleanup, reformat/fix episode queries
2728 #
2729 # Revision 1.138 2004/09/13 19:07:00 ncq
2730 # - get_scheduled_vaccination_regimes() returns list of lists
2731 # (indication, l10n_indication, nr_of_shots) - this allows to
2732 # easily build table of given/missing vaccinations
2733 #
2734 # Revision 1.137 2004/09/06 18:54:31 ncq
2735 # - some cleanup
2736 # - in get_first/last_encounter we do need to check issue/episode for None so
2737 # we won't be applying the "one-item -> two-duplicate-items for IN query" trick
2738 # to "None" which would yield the wrong results
2739 #
2740 # Revision 1.136 2004/08/31 19:19:43 ncq
2741 # - Carlos added constraints to get_encounters()
2742 # - he also added get_first/last_encounter()
2743 #
2744 # Revision 1.135 2004/08/23 09:07:58 ncq
2745 # - removed unneeded get_vaccinated_regimes() - was faulty anyways
2746 #
2747 # Revision 1.134 2004/08/11 09:44:15 ncq
2748 # - gracefully continue loading clin_narrative items if one fails
2749 # - map soap_cats filter to lowercase in get_clin_narrative()
2750 #
2751 # Revision 1.133 2004/08/11 09:01:27 ncq
2752 # - Carlos-contributed get_clin_narrative() with usual filtering
2753 # and soap_cat filtering based on v_pat_narrative
2754 #
2755 # Revision 1.132 2004/07/17 21:08:51 ncq
2756 # - gmPG.run_query() now has a verbosity parameter, so use it
2757 #
2758 # Revision 1.131 2004/07/06 00:11:11 ncq
2759 # - make add_clin_narrative use gmClinNarrative.create_clin_narrative()
2760 #
2761 # Revision 1.130 2004/07/05 22:30:01 ncq
2762 # - improve get_text_dump()
2763 #
2764 # Revision 1.129 2004/07/05 22:23:38 ncq
2765 # - log some timings to find time sinks
2766 # - get_clinical_record now takes between 4 and 11 seconds
2767 # - record active episode at clinical record *cleanup* instead of startup !
2768 #
2769 # Revision 1.128 2004/07/02 00:20:54 ncq
2770 # - v_patient_items.id_item -> pk_item
2771 #
2772 # Revision 1.127 2004/06/30 20:33:40 ncq
2773 # - add_clinical_note() -> add_clin_narrative()
2774 #
2775 # Revision 1.126 2004/06/30 15:31:22 shilbert
2776 # - fk/pk issue fixed
2777 #
2778 # Revision 1.125 2004/06/28 16:05:42 ncq
2779 # - fix spurious 'id' for episode -> pk_episode
2780 #
2781 # Revision 1.124 2004/06/28 12:18:41 ncq
2782 # - more id_* -> fk_*
2783 #
2784 # Revision 1.123 2004/06/26 23:45:50 ncq
2785 # - cleanup, id_* -> fk/pk_*
2786 #
2787 # Revision 1.122 2004/06/26 07:33:55 ncq
2788 # - id_episode -> fk/pk_episode
2789 #
2790 # Revision 1.121 2004/06/20 18:39:30 ncq
2791 # - get_encounters() added by Carlos
2792 #
2793 # Revision 1.120 2004/06/17 21:30:53 ncq
2794 # - time range constraints in get()ters, by Carlos
2795 #
2796 # Revision 1.119 2004/06/15 19:08:15 ncq
2797 # - self._backend -> self._conn_pool
2798 # - remove instance level self._ro_conn_clin
2799 # - cleanup
2800 #
2801 # Revision 1.118 2004/06/14 06:36:51 ncq
2802 # - fix = -> == in filter(lambda ...)
2803 #
2804 # Revision 1.117 2004/06/13 08:03:07 ncq
2805 # - cleanup, better separate vaccination code from general EMR code
2806 #
2807 # Revision 1.116 2004/06/13 07:55:00 ncq
2808 # - create_allergy moved to gmAllergy
2809 # - get_indications moved to gmVaccinations
2810 # - many get_*()ers constrained by issue/episode/encounter
2811 # - code by Carlos
2812 #
2813 # Revision 1.115 2004/06/09 14:33:31 ncq
2814 # - cleanup
2815 # - rewrite _db_callback_allg_modified()
2816 #
2817 # Revision 1.114 2004/06/08 00:43:26 ncq
2818 # - add staff_id to add_allergy, unearthed by unittest
2819 #
2820 # Revision 1.113 2004/06/02 22:18:14 ncq
2821 # - fix my broken streamlining
2822 #
2823 # Revision 1.112 2004/06/02 22:11:47 ncq
2824 # - streamline Syan's check for failing create_episode() in __load_last_active_episode()
2825 #
2826 # Revision 1.111 2004/06/02 13:10:18 sjtan
2827 #
2828 # error handling , in case unsuccessful get_episodes.
2829 #
2830 # Revision 1.110 2004/06/01 23:51:33 ncq
2831 # - id_episode/pk_encounter
2832 #
2833 # Revision 1.109 2004/06/01 08:21:56 ncq
2834 # - default limit to all on get_lab_results()
2835 #
2836 # Revision 1.108 2004/06/01 08:20:14 ncq
2837 # - limit in get_lab_results
2838 #
2839 # Revision 1.107 2004/05/30 20:51:34 ncq
2840 # - verify provider in __init__, too
2841 #
2842 # Revision 1.106 2004/05/30 19:54:57 ncq
2843 # - comment out attach_to_encounter(), actually, all relevant
2844 # methods should have encounter_id, episode_id kwds that
2845 # default to self.__*['id']
2846 # - add_allergy()
2847 #
2848 # Revision 1.105 2004/05/28 15:46:28 ncq
2849 # - get_active_episode()
2850 #
2851 # Revision 1.104 2004/05/28 15:11:32 ncq
2852 # - get_active_encounter()
2853 #
2854 # Revision 1.103 2004/05/27 13:40:21 ihaywood
2855 # more work on referrals, still not there yet
2856 #
2857 # Revision 1.102 2004/05/24 21:13:33 ncq
2858 # - return better from add_lab_request()
2859 #
2860 # Revision 1.101 2004/05/24 20:52:18 ncq
2861 # - add_lab_request()
2862 #
2863 # Revision 1.100 2004/05/23 12:28:58 ncq
2864 # - fix missing : and episode reference before assignment
2865 #
2866 # Revision 1.99 2004/05/22 12:42:53 ncq
2867 # - add create_episode()
2868 # - cleanup add_episode()
2869 #
2870 # Revision 1.98 2004/05/22 11:46:15 ncq
2871 # - some cleanup re allergy signal handling
2872 # - get_health_issue_names doesn't exist anymore, so use get_health_issues
2873 #
2874 # Revision 1.97 2004/05/18 22:35:09 ncq
2875 # - readd set_active_episode()
2876 #
2877 # Revision 1.96 2004/05/18 20:33:40 ncq
2878 # - fix call to create_encounter() in __initiate_active_encounter()
2879 #
2880 # Revision 1.95 2004/05/17 19:01:40 ncq
2881 # - convert encounter API to use encounter class
2882 #
2883 # Revision 1.94 2004/05/16 15:47:27 ncq
2884 # - switch to use of episode class
2885 #
2886 # Revision 1.93 2004/05/16 14:34:45 ncq
2887 # - cleanup, small fix in patient xdb checking
2888 # - switch health issue handling to clin item class
2889 #
2890 # Revision 1.92 2004/05/14 13:16:34 ncq
2891 # - cleanup, remove dead code
2892 #
2893 # Revision 1.91 2004/05/12 14:33:42 ncq
2894 # - get_due_vaccinations -> get_missing_vaccinations + rewrite
2895 # thereof for value object use
2896 # - __activate_fairly_recent_encounter now fails right away if it
2897 # cannot talk to the user anyways
2898 #
2899 # Revision 1.90 2004/05/08 17:41:34 ncq
2900 # - due vaccs views are better now, so simplify get_due_vaccinations()
2901 #
2902 # Revision 1.89 2004/05/02 19:27:30 ncq
2903 # - simplify get_due_vaccinations
2904 #
2905 # Revision 1.88 2004/04/24 12:59:17 ncq
2906 # - all shiny and new, vastly improved vaccinations
2907 # handling via clinical item objects
2908 # - mainly thanks to Carlos Moro
2909 #
2910 # Revision 1.87 2004/04/20 12:56:58 ncq
2911 # - remove outdated get_due_vaccs(), use get_due_vaccinations() now
2912 #
2913 # Revision 1.86 2004/04/20 00:17:55 ncq
2914 # - allergies API revamped, kudos to Carlos
2915 #
2916 # Revision 1.85 2004/04/17 11:54:16 ncq
2917 # - v_patient_episodes -> v_pat_episodes
2918 #
2919 # Revision 1.84 2004/04/15 09:46:56 ncq
2920 # - cleanup, get_lab_data -> get_lab_results
2921 #
2922 # Revision 1.83 2004/04/14 21:06:10 ncq
2923 # - return cLabResult from get_lab_data()
2924 #
2925 # Revision 1.82 2004/03/27 22:18:43 ncq
2926 # - 7.4 doesn't allow aggregates in subselects which refer to outer-query
2927 # columns only, therefor use explicit inner query from list
2928 #
2929 # Revision 1.81 2004/03/25 11:00:19 ncq
2930 # test get_lab_data()
2931 #
2932 # Revision 1.80 2004/03/23 17:32:59 ncq
2933 # - support "unified" test code/name on get_lab_data()
2934 #
2935 # Revision 1.79 2004/03/23 15:04:59 ncq
2936 # - merge Carlos' constraints into get_text_dump
2937 # - add gmPatient.export_data()
2938 #
2939 # Revision 1.78 2004/03/23 02:29:24 ncq
2940 # - cleanup import/add pyCompat
2941 # - get_lab_data()
2942 # - unit test
2943 #
2944 # Revision 1.77 2004/03/20 19:41:59 ncq
2945 # - gmClin* cClin*
2946 #
2947 # Revision 1.76 2004/03/19 11:55:38 ncq
2948 # - in allergy.reaction -> allergy.narrative
2949 #
2950 # Revision 1.75 2004/03/04 19:35:01 ncq
2951 # - AU has rules on encounter timeout, so use them
2952 #
2953 # Revision 1.74 2004/02/25 09:46:19 ncq
2954 # - import from pycommon now, not python-common
2955 #
2956 # Revision 1.73 2004/02/18 15:25:20 ncq
2957 # - rewrote encounter support
2958 # - __init__() now initiates encounter
2959 # - _encounter_soft/hard_ttl now global mx.DateTime.TimeDelta
2960 # - added set_encounter_ttl()
2961 # - added set_func_ask_user() for UI callback on "fairly recent"
2962 # encounter detection
2963 #
2964 # Revision 1.72 2004/02/17 04:04:34 ihaywood
2965 # fixed patient creation refeential integrity error
2966 #
2967 # Revision 1.71 2004/02/14 00:37:10 ihaywood
2968 # Bugfixes
2969 # - weeks = days / 7
2970 # - create_new_patient to maintain xlnk_identity in historica
2971 #
2972 # Revision 1.70 2004/02/12 23:39:33 ihaywood
2973 # fixed parse errors on vaccine queries (I'm using postgres 7.3.3)
2974 #
2975 # Revision 1.69 2004/02/02 23:02:40 ncq
2976 # - it's personalia, not demographica
2977 #
2978 # Revision 1.68 2004/02/02 16:19:03 ncq
2979 # - rewrite get_due_vaccinations() taking advantage of indication-based tables
2980 #
2981 # Revision 1.67 2004/01/26 22:08:52 ncq
2982 # - gracefully handle failure to retrive vacc_ind
2983 #
2984 # Revision 1.66 2004/01/26 21:48:48 ncq
2985 # - v_patient_vacc4ind -> v_pat_vacc4ind
2986 #
2987 # Revision 1.65 2004/01/24 17:07:46 ncq
2988 # - fix insertion into xlnk_identity
2989 #
2990 # Revision 1.64 2004/01/21 16:52:02 ncq
2991 # - eventually do the right thing in get_vaccinations()
2992 #
2993 # Revision 1.63 2004/01/21 15:53:05 ncq
2994 # - use deepcopy when copying dict as to leave original intact in get_vaccinations()
2995 #
2996 # Revision 1.62 2004/01/19 13:41:15 ncq
2997 # - fix typos in SQL
2998 #
2999 # Revision 1.61 2004/01/19 13:30:46 ncq
3000 # - substantially smarten up __load_last_active_episode() after cleaning it up
3001 #
3002 # Revision 1.60 2004/01/18 21:41:49 ncq
3003 # - get_vaccinated_indications()
3004 # - make get_vaccinations() work against v_patient_vacc4ind
3005 # - don't store vacc_def/link on saving vaccination
3006 # - update_vaccination()
3007 #
3008 # Revision 1.59 2004/01/15 15:05:13 ncq
3009 # - verify patient id in xlnk_identity in _pkey_exists()
3010 # - make set_active_episode() logic more consistent - don't create default episode ...
3011 # - also, failing to record most_recently_used episode should prevent us
3012 # from still keeping things up
3013 #
3014 # Revision 1.58 2004/01/12 16:20:14 ncq
3015 # - set_active_episode() does not read rows from run_commit()
3016 # - need to check for argument aVacc keys *before* adding
3017 # corresponding snippets to where/cols clause else we would end
3018 # up with orphaned query parts
3019 # - also, aVacc will always have all keys, it's just that they may
3020 # be empty (because editarea.GetValue() will always return something)
3021 # - fix set_active_encounter: don't ask for column index
3022 #
3023 # Revision 1.57 2004/01/06 23:44:40 ncq
3024 # - __default__ -> xxxDEFAULTxxx
3025 #
3026 # Revision 1.56 2004/01/06 09:56:41 ncq
3027 # - default encounter name __default__ is nonsense, of course,
3028 # use mxDateTime.today().Format() instead
3029 # - consolidate API:
3030 # - load_most_recent_episode() -> load_last_active_episode()
3031 # - _get_* -> get_*
3032 # - sort methods
3033 # - convert more gmPG.run_query()
3034 # - handle health issue on episode change as they are tighthly coupled
3035 #
3036 # Revision 1.55 2003/12/29 16:13:51 uid66147
3037 # - listen to vaccination changes in DB
3038 # - allow filtering by ID in get_vaccinations()
3039 # - order get_due_vacc() by time_left/amount_overdue
3040 # - add add_vaccination()
3041 # - deal with provider in encounter handling
3042 #
3043 # Revision 1.54 2003/12/02 01:58:28 ncq
3044 # - make get_due_vaccinations return the right thing on empty lists
3045 #
3046 # Revision 1.53 2003/12/01 01:01:05 ncq
3047 # - add get_vaccinated_regimes()
3048 # - allow regime_list filter in get_vaccinations()
3049 # - handle empty lists better in get_due_vaccinations()
3050 #
3051 # Revision 1.52 2003/11/30 01:05:30 ncq
3052 # - improve get_vaccinations
3053 # - added get_vacc_regimes
3054 #
3055 # Revision 1.51 2003/11/28 10:06:18 ncq
3056 # - remove dead code
3057 #
3058 # Revision 1.50 2003/11/28 08:08:05 ncq
3059 # - improve get_due_vaccinations()
3060 #
3061 # Revision 1.49 2003/11/19 23:27:44 sjtan
3062 #
3063 # make _print() a dummy function , so that code reaching gmLog through this function works;
3064 #
3065 # Revision 1.48 2003/11/18 14:16:41 ncq
3066 # - cleanup
3067 # - intentionally comment out some methods and remove some code that
3068 # isn't fit for the main trunk such that it breaks and gets fixed
3069 # eventually
3070 #
3071 # Revision 1.47 2003/11/17 11:34:22 sjtan
3072 #
3073 # no ref to yaml
3074 #
3075 # Revision 1.46 2003/11/17 11:32:46 sjtan
3076 #
3077 # print ... -> _log.Log(gmLog.lInfo ...)
3078 #
3079 # Revision 1.45 2003/11/17 10:56:33 sjtan
3080 #
3081 # synced and commiting.
3082 #
3083 #
3084 #
3085 # uses gmDispatcher to send new currentPatient objects to toplevel gmGP_ widgets. Proprosal to use
3086 # yaml serializer to store editarea data in narrative text field of clin_root_item until
3087 # clin_root_item schema stabilizes.
3088 #
3089 # Revision 1.44 2003/11/16 19:30:26 ncq
3090 # - use clin_when in clin_root_item
3091 # - pretty _print(EMR text dump)
3092 #
3093 # Revision 1.43 2003/11/11 20:28:59 ncq
3094 # - get_allergy_names(), reimplemented
3095 #
3096 # Revision 1.42 2003/11/11 18:20:58 ncq
3097 # - fix get_text_dump() to actually do what it suggests
3098 #
3099 # Revision 1.41 2003/11/09 22:51:29 ncq
3100 # - don't close cursor prematurely in get_text_dump()
3101 #
3102 # Revision 1.40 2003/11/09 16:24:03 ncq
3103 # - typo fix
3104 #
3105 # Revision 1.39 2003/11/09 03:29:11 ncq
3106 # - API cleanup, __set/getitem__ deprecated
3107 #
3108 # Revision 1.38 2003/10/31 23:18:48 ncq
3109 # - improve encounter business
3110 #
3111 # Revision 1.37 2003/10/26 11:27:10 ihaywood
3112 # gmPatient is now the "patient stub", all demographics stuff in gmDemographics.
3113 #
3114 # manual edit areas modelled after r.terry's specs.
3115 #
3116 # Revision 1.36 2003/10/19 12:12:36 ncq
3117 # - remove obsolete code
3118 # - filter out sensitivities on get_allergy_names
3119 # - start get_vacc_status
3120 #
3121 # Revision 1.35 2003/09/30 19:11:58 ncq
3122 # - remove dead code
3123 #
3124 # Revision 1.34 2003/07/19 20:17:23 ncq
3125 # - code cleanup
3126 # - add cleanup()
3127 # - use signals better
3128 # - fix get_text_dump()
3129 #
3130 # Revision 1.33 2003/07/09 16:20:18 ncq
3131 # - remove dead code
3132 # - def_conn_ro -> ro_conn_clin
3133 # - check for patient existence in personalia, not historica
3134 # - listen to health issue changes, too
3135 # - add _get_health_issues
3136 #
3137 # Revision 1.32 2003/07/07 08:34:31 ihaywood
3138 # bugfixes on gmdrugs.sql for postgres 7.3
3139 #
3140 # Revision 1.31 2003/07/05 13:44:12 ncq
3141 # - modify -> modified
3142 #
3143 # Revision 1.30 2003/07/03 15:20:55 ncq
3144 # - lots od cleanup, some nice formatting for text dump of EMR
3145 #
3146 # Revision 1.29 2003/06/27 22:54:29 ncq
3147 # - improved _get_text_dump()
3148 # - added _get_episode/health_issue_names()
3149 # - remove old workaround code
3150 # - sort test output by age, oldest on top
3151 #
3152 # Revision 1.28 2003/06/27 16:03:50 ncq
3153 # - no need for ; in DB-API queries
3154 # - implement EMR text dump
3155 #
3156 # Revision 1.27 2003/06/26 21:24:49 ncq
3157 # - cleanup re quoting + ";" and (cmd, arg) style
3158 #
3159 # Revision 1.26 2003/06/26 06:05:38 ncq
3160 # - always add ; at end of sql queries but have space after %s
3161 #
3162 # Revision 1.25 2003/06/26 02:29:20 ihaywood
3163 # Bugfix for searching for pre-existing health issue records
3164 #
3165 # Revision 1.24 2003/06/24 12:55:08 ncq
3166 # - eventually make create_clinical_note() functional
3167 #
3168 # Revision 1.23 2003/06/23 22:28:22 ncq
3169 # - finish encounter handling for now, somewhat tested
3170 # - use gmPG.run_query changes where appropriate
3171 # - insert_clin_note() finished but untested
3172 # - cleanup
3173 #
3174 # Revision 1.22 2003/06/22 16:17:40 ncq
3175 # - start dealing with encounter initialization
3176 # - add create_clinical_note()
3177 # - cleanup
3178 #
3179 # Revision 1.21 2003/06/19 15:22:57 ncq
3180 # - fix spelling error in SQL in episode creation
3181 #
3182 # Revision 1.20 2003/06/03 14:05:05 ncq
3183 # - start listening threads last in __init__ so we won't hang
3184 # if anything else fails in the constructor
3185 #
3186 # Revision 1.19 2003/06/03 13:17:32 ncq
3187 # - finish default clinical episode/health issue handling, simple tests work
3188 # - clinical encounter handling still insufficient
3189 # - add some more comments to Syan's code
3190 #
3191 # Revision 1.18 2003/06/02 20:58:32 ncq
3192 # - nearly finished with default episode/health issue stuff
3193 #
3194 # Revision 1.17 2003/06/01 16:25:51 ncq
3195 # - preliminary code for episode handling
3196 #
3197 # Revision 1.16 2003/06/01 15:00:31 sjtan
3198 #
3199 # works with definite, maybe note definate.
3200 #
3201 # Revision 1.15 2003/06/01 14:45:31 sjtan
3202 #
3203 # definite and definate databases catered for, temporarily.
3204 #
3205 # Revision 1.14 2003/06/01 14:34:47 sjtan
3206 #
3207 # hopefully complies with temporary model; not using setData now ( but that did work).
3208 # Please leave a working and tested substitute (i.e. select a patient , allergy list
3209 # will change; check allergy panel allows update of allergy list), if still
3210 # not satisfied. I need a working model-view connection ; trying to get at least
3211 # a basically database updating version going .
3212 #
3213 # Revision 1.13 2003/06/01 14:15:48 ncq
3214 # - more comments
3215 #
3216 # Revision 1.12 2003/06/01 14:11:52 ncq
3217 # - added some comments
3218 #
3219 # Revision 1.11 2003/06/01 14:07:42 ncq
3220 # - "select into" is an update command, too
3221 #
3222 # Revision 1.10 2003/06/01 13:53:55 ncq
3223 # - typo fixes, cleanup, spelling definate -> definite
3224 # - fix my obsolote handling of patient allergies tables
3225 # - remove obsolete clin_transaction stuff
3226 #
3227 # Revision 1.9 2003/06/01 13:20:32 sjtan
3228 #
3229 # logging to data stream for debugging. Adding DEBUG tags when work out how to use vi
3230 # with regular expression groups (maybe never).
3231 #
3232 # Revision 1.8 2003/06/01 12:55:58 sjtan
3233 #
3234 # sql commit may cause PortalClose, whilst connection.commit() doesnt?
3235 #
3236 # Revision 1.7 2003/06/01 01:47:32 sjtan
3237 #
3238 # starting allergy connections.
3239 #
3240 # Revision 1.6 2003/05/17 17:23:43 ncq
3241 # - a little more testing in main()
3242 #
3243 # Revision 1.5 2003/05/05 00:06:32 ncq
3244 # - make allergies work again after EMR rework
3245 #
3246 # Revision 1.4 2003/05/03 14:11:22 ncq
3247 # - make allergy change signalling work properly
3248 #
3249 # Revision 1.3 2003/05/03 00:41:14 ncq
3250 # - fetchall() returns list, not dict, so handle accordingly in "allergy names"
3251 #
3252 # Revision 1.2 2003/05/01 14:59:24 ncq
3253 # - listen on allergy add/delete in backend, invalidate cache and notify frontend
3254 # - "allergies", "allergy names" getters
3255 #
3256 # Revision 1.1 2003/04/29 12:33:20 ncq
3257 # - first draft
3258 #
3259
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:08 2010 | http://epydoc.sourceforge.net |