Package Gnumed :: Package business :: Module gmClinNarrative
[frames] | no frames]

Source Code for Module Gnumed.business.gmClinNarrative

  1  """GNUmed clinical narrative business object.""" 
  2  #============================================================ 
  3  __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
  4  __license__ = 'GPL v2 or later (for details see http://gnu.org)' 
  5   
  6  import sys 
  7  import logging 
  8   
  9   
 10  if __name__ == '__main__': 
 11          sys.path.insert(0, '../../') 
 12  from Gnumed.pycommon import gmPG2 
 13  from Gnumed.pycommon import gmBusinessDBObject 
 14  from Gnumed.pycommon import gmTools 
 15  from Gnumed.pycommon import gmDispatcher 
 16  from Gnumed.pycommon import gmHooks 
 17  from Gnumed.pycommon import gmDateTime 
 18   
 19  from Gnumed.business import gmCoding 
 20  from Gnumed.business import gmSoapDefs 
 21  from Gnumed.business import gmAutoHints 
 22   
 23   
 24  _log = logging.getLogger('gm.emr') 
 25   
 26  #============================================================ 
27 -def _on_soap_modified():
28 """Always relates to the active patient.""" 29 gmHooks.run_hook_script(hook = 'after_soap_modified')
30 31 gmDispatcher.connect(_on_soap_modified, 'clin.clin_narrative_mod_db') 32 33 #============================================================
34 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
35 """Represents one clinical free text entry.""" 36 37 _cmd_fetch_payload = "SELECT * FROM clin.v_narrative WHERE pk_narrative = %s" 38 _cmds_store_payload = [ 39 """update clin.clin_narrative set 40 narrative = %(narrative)s, 41 clin_when = %(date)s, 42 soap_cat = lower(%(soap_cat)s), 43 fk_encounter = %(pk_encounter)s, 44 fk_episode = %(pk_episode)s 45 WHERE 46 pk = %(pk_narrative)s 47 AND 48 xmin = %(xmin_clin_narrative)s 49 RETURNING 50 xmin AS xmin_clin_narrative""" 51 ] 52 53 _updatable_fields = [ 54 'narrative', 55 'date', 56 'soap_cat', 57 'pk_episode', 58 'pk_encounter' 59 ] 60 61 #--------------------------------------------------------
62 - def format_maximum_information(self, patient=None):
63 return self.format(fancy = True, width = 70).split('\n')
64 65 #--------------------------------------------------------
66 - def format(self, left_margin='', fancy=False, width=75):
67 68 if fancy: 69 txt = gmTools.wrap ( 70 text = _('%s: %s by %.8s (v%s)\n%s') % ( 71 self._payload[self._idx['date']].strftime('%x %H:%M'), 72 gmSoapDefs.soap_cat2l10n_str[self._payload[self._idx['soap_cat']]], 73 self._payload[self._idx['modified_by']], 74 self._payload[self._idx['row_version']], 75 self._payload[self._idx['narrative']] 76 ), 77 width = width, 78 initial_indent = '', 79 subsequent_indent = left_margin + ' ' 80 ) 81 else: 82 txt = '%s [%s]: %s (%.8s)' % ( 83 self._payload[self._idx['date']].strftime('%x %H:%M'), 84 gmSoapDefs.soap_cat2l10n[self._payload[self._idx['soap_cat']]], 85 self._payload[self._idx['narrative']], 86 self._payload[self._idx['modified_by']] 87 ) 88 if len(txt) > width: 89 txt = txt[:width] + gmTools.u_ellipsis 90 91 return txt
92 93 #--------------------------------------------------------
94 - def add_code(self, pk_code=None):
95 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 96 97 if pk_code in self._payload[self._idx['pk_generic_codes']]: 98 return 99 100 cmd = """ 101 INSERT INTO clin.lnk_code2narrative 102 (fk_item, fk_generic_code) 103 SELECT 104 %(item)s, 105 %(code)s 106 WHERE NOT EXISTS ( 107 SELECT 1 FROM clin.lnk_code2narrative 108 WHERE 109 fk_item = %(item)s 110 AND 111 fk_generic_code = %(code)s 112 )""" 113 args = { 114 'item': self._payload[self._idx['pk_narrative']], 115 'code': pk_code 116 } 117 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 118 return
119 120 #--------------------------------------------------------
121 - def remove_code(self, pk_code=None):
122 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 123 cmd = "DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 124 args = { 125 'item': self._payload[self._idx['pk_narrative']], 126 'code': pk_code 127 } 128 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 129 return True
130 131 #-------------------------------------------------------- 132 # properties 133 #--------------------------------------------------------
134 - def _get_generic_codes(self):
135 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 136 return [] 137 138 cmd = gmCoding._SQL_get_generic_linked_codes % 'pk_generic_code IN %(pks)s' 139 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 140 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 141 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
142
143 - def _set_generic_codes(self, pk_codes):
144 queries = [] 145 # remove all codes 146 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 147 queries.append ({ 148 'cmd': 'DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(narr)s AND fk_generic_code IN %(codes)s', 149 'args': { 150 'narr': self._payload[self._idx['pk_narrative']], 151 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 152 } 153 }) 154 # add new codes 155 for pk_code in pk_codes: 156 queries.append ({ 157 'cmd': 'INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) VALUES (%(narr)s, %(pk_code)s)', 158 'args': { 159 'narr': self._payload[self._idx['pk_narrative']], 160 'pk_code': pk_code 161 } 162 }) 163 if len(queries) == 0: 164 return 165 # run it all in one transaction 166 rows, idx = gmPG2.run_rw_queries(queries = queries) 167 return
168 169 generic_codes = property(_get_generic_codes, _set_generic_codes)
170 171 #============================================================
172 -def create_progress_note(soap=None, episode_id=None, encounter_id=None, link_obj=None):
173 """Create clinical narrative entries. 174 175 <soap> 176 must be a dict, the keys being SOAP categories (including U and 177 None=admin) and the values being text (possibly multi-line) 178 179 Existing but empty ('' or None) categories are skipped. 180 """ 181 if soap is None: 182 return True 183 184 if not gmSoapDefs.are_valid_soap_cats(soap.keys(), allow_upper = True): 185 raise ValueError('invalid SOAP category in <soap> dictionary: %s', soap) 186 187 if link_obj is None: 188 link_obj = gmPG2.get_connection(readonly = False) 189 conn_rollback = link_obj.rollback 190 conn_commit = link_obj.commit 191 conn_close = link_obj.close 192 else: 193 conn_rollback = lambda *x:None 194 conn_commit = lambda *x:None 195 conn_close = lambda *x:None 196 197 instances = {} 198 for cat in soap: 199 val = soap[cat] 200 if val is None: 201 continue 202 if ''.join([ v.strip() for v in val ]) == '': 203 continue 204 instance = create_narrative_item ( 205 narrative = '\n'.join([ v.strip() for v in val ]), 206 soap_cat = cat, 207 episode_id = episode_id, 208 encounter_id = encounter_id, 209 link_obj = link_obj 210 ) 211 if instance is None: 212 continue 213 instances[cat] = instance 214 215 conn_commit() 216 conn_close() 217 return instances
218 219 #============================================================
220 -def create_narrative_item(narrative=None, soap_cat=None, episode_id=None, encounter_id=None, link_obj=None):
221 """Creates a new clinical narrative entry 222 223 narrative - free text clinical narrative 224 soap_cat - soap category 225 episode_id - episodes's primary key 226 encounter_id - encounter's primary key 227 228 any of the args being None (except soap_cat) will fail the SQL code 229 """ 230 # silently do not insert empty narrative 231 narrative = narrative.strip() 232 if narrative == '': 233 return None 234 235 args = {'enc': encounter_id, 'epi': episode_id, 'soap': soap_cat, 'narr': narrative} 236 237 # insert new narrative 238 # but, also silently, do not insert true duplicates 239 # this should check for .provider = current_user but 240 # the view has provider mapped to their staff alias 241 cmd = """ 242 INSERT INTO clin.clin_narrative 243 (fk_encounter, fk_episode, narrative, soap_cat) 244 SELECT 245 %(enc)s, %(epi)s, %(narr)s, lower(%(soap)s) 246 WHERE NOT EXISTS ( 247 SELECT 1 FROM clin.v_narrative 248 WHERE 249 pk_encounter = %(enc)s 250 AND 251 pk_episode = %(epi)s 252 AND 253 soap_cat = lower(%(soap)s) 254 AND 255 narrative = %(narr)s 256 ) 257 RETURNING pk""" 258 rows, idx = gmPG2.run_rw_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 259 if len(rows) == 1: 260 # re-use same link_obj if given because when called from create_progress_note we won't yet see rows inside a new tx 261 return cNarrative(aPK_obj = rows[0]['pk'], link_obj = link_obj) 262 263 if len(rows) > 1: 264 raise Exception('more than one row returned from single-row INSERT') 265 266 # retrieve existing narrative 267 cmd = """ 268 SELECT * FROM clin.v_narrative 269 WHERE 270 pk_encounter = %(enc)s 271 AND 272 pk_episode = %(epi)s 273 AND 274 soap_cat = lower(%(soap)s) 275 AND 276 narrative = %(narr)s 277 """ 278 rows, idx = gmPG2.run_ro_queries(link_obj = link_obj, queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 279 if len(rows) == 1: 280 return cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx}) 281 282 raise Exception('retrieving known-to-exist narrative row returned 0 or >1 result: %s' % len(rows))
283 284 #------------------------------------------------------------
285 -def delete_clin_narrative(narrative=None):
286 """Deletes a clin.clin_narrative row by it's PK.""" 287 cmd = "DELETE FROM clin.clin_narrative WHERE pk=%s" 288 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [narrative]}]) 289 return True
290 291 #------------------------------------------------------------
292 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
293 """Get SOAP notes pertinent to this encounter. 294 295 since 296 - initial date for narrative items 297 until 298 - final date for narrative items 299 encounters 300 - list of encounters whose narrative are to be retrieved 301 episodes 302 - list of episodes whose narrative are to be retrieved 303 issues 304 - list of health issues whose narrative are to be retrieved 305 soap_cats 306 - list of SOAP categories of the narrative to be retrieved 307 """ 308 where_parts = ['TRUE'] 309 args = {} 310 311 if encounters is not None: 312 where_parts.append('pk_encounter IN %(encs)s') 313 args['encs'] = tuple(encounters) 314 315 if episodes is not None: 316 where_parts.append('pk_episode IN %(epis)s') 317 args['epis'] = tuple(episodes) 318 319 if issues is not None: 320 where_parts.append('pk_health_issue IN %(issues)s') 321 args['issues'] = tuple(issues) 322 323 if patient is not None: 324 where_parts.append('pk_patient = %(pat)s') 325 args['pat'] = patient 326 327 if soap_cats is not None: 328 where_parts.append('c_vn.soap_cat IN %(soap_cats)s') 329 args['soap_cats'] = tuple(soap_cats) 330 331 if order_by is None: 332 order_by = 'ORDER BY date, soap_rank' 333 else: 334 order_by = 'ORDER BY %s' % order_by 335 336 cmd = """ 337 SELECT 338 c_vn.*, 339 c_scr.rank AS soap_rank 340 FROM 341 clin.v_narrative c_vn 342 LEFT JOIN clin.soap_cat_ranks c_scr ON c_vn.soap_cat = c_scr.soap_cat 343 WHERE 344 %s 345 %s 346 """ % ( 347 ' AND '.join(where_parts), 348 order_by 349 ) 350 351 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 352 353 filtered_narrative = [ cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ] 354 355 if since is not None: 356 filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] >= since ] 357 358 if until is not None: 359 filtered_narrative = [ narr for narr in filtered_narrative if narr['date'] < until ] 360 361 if providers is not None: 362 filtered_narrative = [ narr for narr in filtered_narrative if narr['modified_by'] in providers ] 363 364 return filtered_narrative
365 366 # if issues is not None: 367 # filtered_narrative = (lambda narr: narr['pk_health_issue'] in issues, filtered_narrative) 368 # 369 # if episodes is not None: 370 # filtered_narrative = (lambda narr: narr['pk_episode'] in episodes, filtered_narrative) 371 # 372 # if encounters is not None: 373 # filtered_narrative = (lambda narr: narr['pk_encounter'] in encounters, filtered_narrative) 374 375 # if soap_cats is not None: 376 # soap_cats = map(lambda c: c.lower(), soap_cats) 377 # filtered_narrative = (lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative) 378 379 #------------------------------------------------------------
380 -def get_as_journal(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None, patient=None, active_encounter=None):
381 382 if (patient is None) and (episodes is None) and (issues is None) and (encounters is None): 383 raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None') 384 385 if order_by is None: 386 order_by = 'ORDER BY clin_when, pk_episode, scr, modified_when, src_table' 387 else: 388 order_by = 'ORDER BY %s' % order_by 389 390 where_parts = [] 391 args = {} 392 393 if patient is not None: 394 where_parts.append('c_vej.pk_patient = %(pat)s') 395 args['pat'] = patient 396 397 if soap_cats is not None: 398 # work around bug in psycopg2 not being able to properly 399 # adapt None to NULL inside tuples 400 if None in soap_cats: 401 where_parts.append('((c_vej.soap_cat IN %(soap_cat)s) OR (c_vej.soap_cat IS NULL))') 402 soap_cats.remove(None) 403 else: 404 where_parts.append('c_vej.soap_cat IN %(soap_cat)s') 405 args['soap_cat'] = tuple(soap_cats) 406 407 if time_range is not None: 408 where_parts.append("c_vej.clin_when > (now() - '%s days'::interval)" % time_range) 409 410 if episodes is not None: 411 where_parts.append("c_vej.pk_episode IN %(epis)s") 412 args['epis'] = tuple(episodes) 413 414 if issues is not None: 415 where_parts.append("c_vej.pk_health_issue IN %(issues)s") 416 args['issues'] = tuple(issues) 417 418 # FIXME: implement more constraints 419 420 cmd_journal = """ 421 SELECT 422 to_char(c_vej.clin_when, 'YYYY-MM-DD') AS date, 423 c_vej.clin_when, 424 coalesce(c_vej.soap_cat, '') as soap_cat, 425 c_vej.narrative, 426 c_vej.src_table, 427 c_scr.rank AS scr, 428 c_vej.modified_when, 429 to_char(c_vej.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified, 430 c_vej.modified_by, 431 c_vej.row_version, 432 c_vej.pk_episode, 433 c_vej.pk_encounter, 434 c_vej.soap_cat as real_soap_cat, 435 c_vej.src_pk, 436 c_vej.pk_health_issue, 437 c_vej.health_issue, 438 c_vej.episode, 439 c_vej.issue_active, 440 c_vej.issue_clinically_relevant, 441 c_vej.episode_open, 442 c_vej.encounter_started, 443 c_vej.encounter_last_affirmed, 444 c_vej.encounter_l10n_type, 445 c_vej.pk_patient 446 FROM 447 clin.v_emr_journal c_vej 448 join clin.soap_cat_ranks c_scr on (c_scr.soap_cat IS NOT DISTINCT FROM c_vej.soap_cat) 449 WHERE 450 %s 451 """ % '\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts) 452 453 if active_encounter is None: 454 cmd = cmd_journal + '\n ' + order_by 455 else: 456 args['pk_enc'] = active_encounter['pk_encounter'] 457 args['enc_start'] = active_encounter['started'] 458 args['enc_last_affirmed'] = active_encounter['last_affirmed'] 459 args['enc_type'] = active_encounter['l10n_type'] 460 args['enc_pat'] = active_encounter['pk_patient'] 461 cmd_hints = """ 462 SELECT 463 to_char(now(), 'YYYY-MM-DD') AS date, 464 now() as clin_when, 465 'u'::text as soap_cat, 466 hints.title || E'\n' || hints.hint 467 as narrative, 468 -- .src_table does not correspond with the 469 -- .src_pk column source because it is generated 470 -- from clin.get_hints_for_patient() 471 'ref.auto_hint'::text as src_table, 472 c_scr.rank AS scr, 473 now() as modified_when, 474 to_char(now(), 'YYYY-MM-DD HH24:MI') AS date_modified, 475 current_user as modified_by, 476 0::integer as row_version, 477 NULL::integer as pk_episode, 478 %(pk_enc)s as pk_encounter, 479 'u'::text as real_soap_cat, 480 hints.pk_auto_hint as src_pk, 481 NULL::integer as pk_health_issue, 482 ''::text as health_issue, 483 ''::text as episode, 484 False as issue_active, 485 False as issue_clinically_relevant, 486 False as episode_open, 487 %(enc_start)s as encounter_started, 488 %(enc_last_affirmed)s as encounter_last_affirmed, 489 %(enc_type)s as encounter_l10n_type, 490 %(enc_pat)s as pk_patient 491 FROM 492 clin.get_hints_for_patient(%(enc_pat)s) AS hints 493 JOIN clin.soap_cat_ranks c_scr ON (c_scr.soap_cat = 'u') 494 """ 495 cmd = cmd_journal + '\nUNION ALL\n' + cmd_hints + '\n' + order_by 496 497 journal_rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 498 499 return journal_rows
500 501 #============================================================ 502 # convenience functions 503 #============================================================ 504 __VIEW_DEF = """ 505 create temporary view v_narrative4search as 506 507 select * from ( 508 509 -- clin.clin_root_items 510 select 511 vpi.pk_patient as pk_patient, 512 vpi.soap_cat as soap_cat, 513 vpi.narrative as narrative, 514 vpi.pk_encounter as pk_encounter, 515 vpi.pk_episode as pk_episode, 516 vpi.pk_health_issue as pk_health_issue, 517 vpi.pk_item as src_pk, 518 vpi.src_table as src_table 519 from 520 clin.v_pat_items vpi 521 where 522 src_table not in ( 523 'clin.allergy', 524 'clin.test_result', 525 'clin.procedure', 526 'clin.substance_intake', 527 'clin.family_history' 528 ) 529 530 531 union all 532 533 select * from clin.v_subst_intake4narr_search 534 where gm.is_null_or_blank_string(narrative) is FALSE 535 536 537 union all 538 select -- clin.procedure 539 (select fk_patient from clin.encounter where pk = cpr.fk_encounter) 540 as pk_patient, 541 cpr.soap_cat 542 as soap_cat, 543 cpr.narrative 544 as narrative, 545 cpr.fk_encounter 546 as pk_encounter, 547 cpr.fk_episode 548 as pk_episode, 549 (select fk_health_issue from clin.episode where pk = cpr.fk_episode) 550 as pk_health_issue, 551 cpr.pk 552 as src_pk, 553 'clin.procedure' 554 as src_table 555 from 556 clin.procedure cpr 557 where 558 cpr.narrative is not NULL 559 560 561 union all 562 select -- test results 563 (select fk_patient from clin.encounter where pk = ctr.fk_encounter) 564 as pk_patient, 565 ctr.soap_cat 566 as soap_cat, 567 coalesce(ctr.narrative, '') 568 || coalesce(' ' || ctr.val_alpha, '') 569 || coalesce(' ' || ctr.val_unit, '') 570 || coalesce(' ' || ctr.val_normal_range, '') 571 || coalesce(' ' || ctr.val_target_range, '') 572 || coalesce(' ' || ctr.norm_ref_group, '') 573 || coalesce(' ' || ctr.note_test_org, '') 574 || coalesce(' ' || ctr.material, '') 575 || coalesce(' ' || ctr.material_detail, '') 576 as narrative, 577 ctr.fk_encounter 578 as pk_encounter, 579 ctr.fk_episode 580 as pk_episode, 581 (select fk_health_issue from clin.episode where pk = ctr.fk_episode) 582 as pk_health_issue, 583 ctr.pk 584 as src_pk, 585 'clin.test_result' 586 as src_table 587 from 588 clin.test_result ctr 589 590 591 union all -- test result reviews 592 select 593 (select fk_patient from clin.encounter where pk = 594 (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row) 595 ) 596 as pk_patient, 597 'o'::text 598 as soap_cat, 599 crtr.comment 600 as narrative, 601 (select fk_encounter from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row) 602 as pk_encounter, 603 (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row) 604 as pk_episode, 605 (select fk_health_issue from clin.episode where pk = 606 (select fk_episode from clin.test_result where clin.test_result.pk = crtr.fk_reviewed_row) 607 ) 608 as pk_health_issue, 609 crtr.pk 610 as src_pk, 611 'clin.reviewed_test_results' 612 as src_table 613 from 614 clin.reviewed_test_results crtr 615 616 617 union all 618 select -- allergy state 619 (select fk_patient from clin.encounter where pk = cas.fk_encounter) 620 as pk_patient, 621 'o'::text 622 as soap_cat, 623 cas.comment 624 as narrative, 625 cas.fk_encounter 626 as pk_encounter, 627 null 628 as pk_episode, 629 null 630 as pk_health_issue, 631 cas.pk 632 as src_pk, 633 'clin.allergy_state' 634 as src_table 635 from 636 clin.allergy_state cas 637 where 638 cas.comment is not NULL 639 640 641 union all 642 select -- allergies 643 (select fk_patient from clin.encounter where pk = ca.fk_encounter) 644 as pk_patient, 645 ca.soap_cat 646 as soap_cat, 647 coalesce(narrative, '') 648 || coalesce(' ' || substance, '') 649 || coalesce(' ' || substance_code, '') 650 || coalesce(' ' || generics, '') 651 || coalesce(' ' || allergene, '') 652 || coalesce(' ' || atc_code, '') 653 as narrative, 654 ca.fk_encounter 655 as pk_encounter, 656 ca.fk_episode 657 as pk_episode, 658 (select fk_health_issue from clin.episode where pk = ca.fk_episode) 659 as pk_health_issue, 660 ca.pk 661 as src_pk, 662 'clin.allergy' 663 as src_table 664 from 665 clin.allergy ca 666 667 668 union all -- health issues 669 select 670 (select fk_patient from clin.encounter where pk = chi.fk_encounter) 671 as pk_patient, 672 'a' as soap_cat, 673 chi.description 674 || coalesce(' ' || chi.summary, '') 675 as narrative, 676 chi.fk_encounter 677 as pk_encounter, 678 null 679 as pk_episode, 680 chi.pk 681 as pk_health_issue, 682 chi.pk 683 as src_pk, 684 'clin.health_issue' 685 as src_table 686 from 687 clin.health_issue chi 688 689 690 union all -- encounters 691 select 692 cenc.fk_patient as pk_patient, 693 's' as soap_cat, 694 coalesce(cenc.reason_for_encounter, '') 695 || coalesce(' ' || cenc.assessment_of_encounter, '') 696 as narrative, 697 cenc.pk as pk_encounter, 698 null as pk_episode, 699 null as pk_health_issue, 700 cenc.pk as src_pk, 701 'clin.encounter' as src_table 702 from 703 clin.encounter cenc 704 705 706 union all -- episodes 707 select 708 (select fk_patient from clin.encounter where pk = cep.fk_encounter) 709 as pk_patient, 710 's' as soap_cat, 711 cep.description 712 || coalesce(' ' || cep.summary, '') 713 as narrative, 714 cep.fk_encounter 715 as pk_encounter, 716 cep.pk 717 as pk_episode, 718 cep.fk_health_issue 719 as pk_health_issue, 720 cep.pk 721 as src_pk, 722 'clin.episode' 723 as src_table 724 from 725 clin.episode cep 726 727 728 union all -- family history 729 select 730 c_vfhx.pk_patient, 731 c_vfhx.soap_cat, 732 (c_vfhx.relation || ' / ' || c_vfhx.l10n_relation || ' ' 733 || c_vfhx.name_relative || ': ' 734 || c_vfhx.condition 735 ) as narrative, 736 c_vfhx.pk_encounter, 737 c_vfhx.pk_episode, 738 c_vfhx.pk_health_issue, 739 c_vfhx.pk_family_history as src_pk, 740 'clin.family_history' as src_table 741 from 742 clin.v_family_history c_vfhx 743 744 745 union all -- documents 746 select 747 vdm.pk_patient as pk_patient, 748 'o' as soap_cat, 749 (vdm.l10n_type || ' ' || 750 coalesce(vdm.ext_ref, '') || ' ' || 751 coalesce(vdm.comment, '') 752 ) as narrative, 753 vdm.pk_encounter as pk_encounter, 754 vdm.pk_episode as pk_episode, 755 vdm.pk_health_issue as pk_health_issue, 756 vdm.pk_doc as src_pk, 757 'blobs.doc_med' as src_table 758 from 759 blobs.v_doc_med vdm 760 761 762 union all -- document objects 763 select 764 vo4d.pk_patient as pk_patient, 765 'o' as soap_cat, 766 vo4d.obj_comment as narrative, 767 vo4d.pk_encounter as pk_encounter, 768 vo4d.pk_episode as pk_episode, 769 vo4d.pk_health_issue as pk_health_issue, 770 vo4d.pk_obj as src_pk, 771 'blobs.doc_obj' as src_table 772 from 773 blobs.v_obj4doc_no_data vo4d 774 775 776 union all -- document descriptions 777 select 778 vdd.pk_patient as pk_patient, 779 'o' as soap_cat, 780 vdd.description as narrative, 781 vdd.pk_encounter as pk_encounter, 782 vdd.pk_episode as pk_episode, 783 vdd.pk_health_issue as pk_health_issue, 784 vdd.pk_doc_desc as src_pk, 785 'blobs.doc_desc' as src_table 786 from 787 blobs.v_doc_desc vdd 788 789 790 union all -- reviewed documents 791 select 792 vrdo.pk_patient as pk_patient, 793 's' as soap_cat, 794 vrdo.comment as narrative, 795 null as pk_encounter, 796 vrdo.pk_episode as pk_episode, 797 vrdo.pk_health_issue as pk_health_issue, 798 vrdo.pk_review_root as src_pk, 799 'blobs.v_reviewed_doc_objects' as src_table 800 from 801 blobs.v_reviewed_doc_objects vrdo 802 803 804 union all -- patient tags 805 select 806 d_vit.pk_identity 807 as pk_patient, 808 's' as soap_cat, 809 d_vit.l10n_description 810 || coalesce(' ' || d_vit.comment, '') 811 as narrative, 812 null 813 as pk_encounter, 814 null 815 as pk_episode, 816 null 817 as pk_health_issue, 818 d_vit.pk_identity_tag 819 as src_pk, 820 'dem.v_identity_tags' 821 as src_table 822 from 823 dem.v_identity_tags d_vit 824 825 826 union all -- external care 827 select 828 c_vec.pk_identity 829 as pk_patient, 830 's' as soap_cat, 831 case 832 when c_vec.pk_health_issue is null then 833 coalesce(c_vec.issue, '') 834 || coalesce(' / ' || c_vec.provider, '') 835 || coalesce(' / ' || c_vec.comment, '') 836 else 837 coalesce(c_vec.provider, '') 838 || coalesce(' / ' || c_vec.comment, '') 839 end as narrative, 840 c_vec.pk_encounter 841 as pk_encounter, 842 null 843 as pk_episode, 844 c_vec.pk_health_issue 845 as pk_health_issue, 846 c_vec.pk_external_care 847 as src_pk, 848 'clin.v_external_care' 849 as src_table 850 from 851 clin.v_external_care c_vec 852 853 854 union all -- export items 855 select 856 c_vei.pk_identity 857 as pk_patient, 858 's' as soap_cat, 859 case 860 when c_vei.pk_doc_obj is null then 861 coalesce(c_vei.description, '') 862 || coalesce(' / ' || c_vei.filename, '') 863 else 864 coalesce(c_vei.description, '') 865 end as narrative, 866 null 867 as pk_encounter, 868 null 869 as pk_episode, 870 null 871 as pk_health_issue, 872 c_vei.pk_export_item 873 as src_pk, 874 'clin.v_export_items' 875 as src_table 876 from 877 clin.v_export_items c_vei 878 879 880 union all -- hint suppression rationale 881 select 882 (select fk_patient from clin.encounter where pk = c_sh.fk_encounter) 883 as pk_patient, 884 'p' as soap_cat, 885 c_sh.rationale 886 as narrative, 887 c_sh.fk_encounter 888 as pk_encounter, 889 null 890 as pk_episode, 891 null 892 as pk_health_issue, 893 c_sh.pk 894 as src_pk, 895 'clin.suppressed_hint' 896 as src_table 897 from 898 clin.suppressed_hint c_sh 899 900 ) as union_table 901 902 where 903 trim(coalesce(union_table.narrative, '')) != '' 904 ; 905 """ 906
907 -def search_text_across_emrs(search_term=None):
908 909 if search_term is None: 910 return [] 911 912 if search_term.strip() == '': 913 return [] 914 915 #cmd = u'select * from clin.v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000' 916 #rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'term': search_term}}], get_col_idx = False) 917 cmd = u'select * from v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000' 918 queries = [ 919 {'cmd': __VIEW_DEF}, 920 {'cmd': cmd, 'args': {'term': search_term}} 921 ] 922 rows, idx = gmPG2.run_rw_queries(queries = queries, get_col_idx = True, return_data = True) 923 return rows
924 925 #============================================================ 926 # main 927 #------------------------------------------------------------ 928 if __name__ == '__main__': 929 930 if len(sys.argv) < 2: 931 sys.exit() 932 933 if sys.argv[1] != 'test': 934 sys.exit() 935 936 from Gnumed.pycommon import gmI18N 937 gmI18N.activate_locale() 938 gmI18N.install_domain(domain = 'gnumed') 939 940 #-----------------------------------------
941 - def test_narrative():
942 print("\nnarrative test") 943 print("--------------") 944 narrative = cNarrative(aPK_obj=7) 945 fields = narrative.get_fields() 946 for field in fields: 947 print(field, ':', narrative[field]) 948 print("updatable:", narrative.get_updatable_fields()) 949 print("codes:", narrative.generic_codes)
950 #print "adding code..." 951 #narrative.add_code('Test code', 'Test coding system') 952 #print "codes:", diagnose.get_codes() 953 954 #print "creating narrative..." 955 #new_narrative = create_narrative_item(narrative = 'Test narrative', soap_cat = 'a', episode_id=1, encounter_id=2) 956 #print new_narrative 957 958 #-----------------------------------------
959 - def test_search_text_across_emrs():
960 results = search_text_across_emrs('cut') 961 for r in results: 962 print(r)
963 #----------------------------------------- 964 965 #test_search_text_across_emrs() 966 test_narrative() 967