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