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, logging 
  7   
  8   
  9  if __name__ == '__main__': 
 10          sys.path.insert(0, '../../') 
 11  from Gnumed.pycommon import gmPG2, gmExceptions, gmBusinessDBObject, gmTools, gmDispatcher, gmHooks 
 12  from Gnumed.business import gmCoding 
 13   
 14   
 15  try: 
 16          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
 17  except NameError: 
 18          _ = lambda x:x 
 19   
 20   
 21  _log = logging.getLogger('gm.emr') 
 22   
 23   
 24  soap_cat2l10n = { 
 25          u's': _('soap_S').replace(u'soap_', u''), 
 26          u'o': _('soap_O').replace(u'soap_', u''), 
 27          u'a': _('soap_A').replace(u'soap_', u''), 
 28          u'p': _('soap_P').replace(u'soap_', u''), 
 29          u'u': _('soap_U').replace(u'soap_', u''), 
 30  #       u'u': u'?', 
 31          None: gmTools.u_ellipsis, 
 32          u'': gmTools.u_ellipsis 
 33  } 
 34   
 35  soap_cat2l10n_str = { 
 36          u's': _('soap_Subjective').replace(u'soap_', u''), 
 37          u'o': _('soap_Objective').replace(u'soap_', u''), 
 38          u'a': _('soap_Assessment').replace(u'soap_', u''), 
 39          u'p': _('soap_Plan').replace(u'soap_', u''), 
 40          u'u': _('soap_Unspecified').replace(u'soap_', u''), 
 41          None: _('soap_Administrative').replace(u'soap_', u'') 
 42  } 
 43   
 44  l10n2soap_cat = { 
 45          _('soap_S').replace(u'soap_', u''): u's', 
 46          _('soap_O').replace(u'soap_', u''): u'o', 
 47          _('soap_A').replace(u'soap_', u''): u'a', 
 48          _('soap_P').replace(u'soap_', u''): u'p', 
 49          _('soap_U').replace(u'soap_', u''): u'u', 
 50  #       u'?': u'u', 
 51          gmTools.u_ellipsis: None 
 52  } 
 53   
 54  #============================================================ 
55 -def _on_soap_modified():
56 """Always relates to the active patient.""" 57 gmHooks.run_hook_script(hook = u'after_soap_modified')
58 59 gmDispatcher.connect(_on_soap_modified, u'clin.clin_narrative_mod_db') 60 61 #============================================================
62 -class cNarrative(gmBusinessDBObject.cBusinessDBObject):
63 """Represents one clinical free text entry. 64 """ 65 _cmd_fetch_payload = u"SELECT * FROM clin.v_narrative WHERE pk_narrative = %s" 66 _cmds_store_payload = [ 67 u"""update clin.clin_narrative set 68 narrative = %(narrative)s, 69 clin_when = %(date)s, 70 soap_cat = lower(%(soap_cat)s), 71 fk_encounter = %(pk_encounter)s, 72 fk_episode = %(pk_episode)s 73 WHERE 74 pk = %(pk_narrative)s 75 AND 76 xmin = %(xmin_clin_narrative)s 77 RETURNING 78 xmin AS xmin_clin_narrative""" 79 ] 80 81 _updatable_fields = [ 82 'narrative', 83 'date', 84 'soap_cat', 85 'pk_episode', 86 'pk_encounter' 87 ] 88 89 #--------------------------------------------------------
90 - def format(self, left_margin=u'', fancy=False, width=75):
91 92 if fancy: 93 # FIXME: add revision 94 txt = gmTools.wrap ( 95 text = _('%s: %s by %.8s (v%s)\n%s') % ( 96 self._payload[self._idx['date']].strftime('%x %H:%M'), 97 soap_cat2l10n_str[self._payload[self._idx['soap_cat']]], 98 self._payload[self._idx['modified_by']], 99 self._payload[self._idx['row_version']], 100 self._payload[self._idx['narrative']] 101 ), 102 width = width, 103 initial_indent = u'', 104 subsequent_indent = left_margin + u' ' 105 ) 106 else: 107 txt = u'%s [%s]: %s (%.8s)' % ( 108 self._payload[self._idx['date']].strftime('%x %H:%M'), 109 soap_cat2l10n[self._payload[self._idx['soap_cat']]], 110 self._payload[self._idx['narrative']], 111 self._payload[self._idx['modified_by']] 112 ) 113 if len(txt) > width: 114 txt = txt[:width] + gmTools.u_ellipsis 115 116 return txt
117 #--------------------------------------------------------
118 - def add_code(self, pk_code=None):
119 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 120 121 if pk_code in self._payload[self._idx['pk_generic_codes']]: 122 return 123 124 cmd = u""" 125 INSERT INTO clin.lnk_code2narrative 126 (fk_item, fk_generic_code) 127 SELECT 128 %(item)s, 129 %(code)s 130 WHERE NOT EXISTS ( 131 SELECT 1 FROM clin.lnk_code2narrative 132 WHERE 133 fk_item = %(item)s 134 AND 135 fk_generic_code = %(code)s 136 )""" 137 args = { 138 'item': self._payload[self._idx['pk_narrative']], 139 'code': pk_code 140 } 141 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 142 return
143 #--------------------------------------------------------
144 - def remove_code(self, pk_code=None):
145 """<pk_code> must be a value from ref.coding_system_root.pk_coding_system (clin.lnk_code2item_root.fk_generic_code)""" 146 cmd = u"DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(item)s AND fk_generic_code = %(code)s" 147 args = { 148 'item': self._payload[self._idx['pk_narrative']], 149 'code': pk_code 150 } 151 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 152 return True
153 #-------------------------------------------------------- 154 # properties 155 #--------------------------------------------------------
156 - def _get_generic_codes(self):
157 if len(self._payload[self._idx['pk_generic_codes']]) == 0: 158 return [] 159 160 cmd = gmCoding._SQL_get_generic_linked_codes % u'pk_generic_code IN %(pks)s' 161 args = {'pks': tuple(self._payload[self._idx['pk_generic_codes']])} 162 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 163 return [ gmCoding.cGenericLinkedCode(row = {'data': r, 'idx': idx, 'pk_field': 'pk_lnk_code2item'}) for r in rows ]
164
165 - def _set_generic_codes(self, pk_codes):
166 queries = [] 167 # remove all codes 168 if len(self._payload[self._idx['pk_generic_codes']]) > 0: 169 queries.append ({ 170 'cmd': u'DELETE FROM clin.lnk_code2narrative WHERE fk_item = %(narr)s AND fk_generic_code IN %(codes)s', 171 'args': { 172 'narr': self._payload[self._idx['pk_narrative']], 173 'codes': tuple(self._payload[self._idx['pk_generic_codes']]) 174 } 175 }) 176 # add new codes 177 for pk_code in pk_codes: 178 queries.append ({ 179 'cmd': u'INSERT INTO clin.lnk_code2narrative (fk_item, fk_generic_code) VALUES (%(narr)s, %(pk_code)s)', 180 'args': { 181 'narr': self._payload[self._idx['pk_narrative']], 182 'pk_code': pk_code 183 } 184 }) 185 if len(queries) == 0: 186 return 187 # run it all in one transaction 188 rows, idx = gmPG2.run_rw_queries(queries = queries) 189 return
190 191 generic_codes = property(_get_generic_codes, _set_generic_codes)
192 #============================================================ 193 # convenience functions 194 #============================================================
195 -def search_text_across_emrs(search_term=None):
196 197 if search_term is None: 198 return [] 199 200 if search_term.strip() == u'': 201 return [] 202 203 cmd = u'select * from clin.v_narrative4search where narrative ~* %(term)s order by pk_patient limit 1000' 204 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': {'term': search_term}}], get_col_idx = False) 205 206 return rows
207 #============================================================
208 -def create_clin_narrative(narrative=None, soap_cat=None, episode_id=None, encounter_id=None):
209 """Creates a new clinical narrative entry 210 211 narrative - free text clinical narrative 212 soap_cat - soap category 213 episode_id - episodes's primary key 214 encounter_id - encounter's primary key 215 """ 216 # any of the args being None (except soap_cat) should fail the SQL code 217 218 # sanity checks: 219 220 # 1) silently do not insert empty narrative 221 narrative = narrative.strip() 222 if narrative == u'': 223 return (True, None) 224 225 # 2) also, silently do not insert true duplicates 226 # FIXME: this should check for .provider = current_user but 227 # FIXME: the view has provider mapped to their staff alias 228 cmd = u""" 229 SELECT * FROM clin.v_narrative 230 WHERE 231 pk_encounter = %(enc)s 232 AND 233 pk_episode = %(epi)s 234 AND 235 soap_cat = %(soap)s 236 AND 237 narrative = %(narr)s 238 """ 239 args = { 240 'enc': encounter_id, 241 'epi': episode_id, 242 'soap': soap_cat, 243 'narr': narrative 244 } 245 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 246 if len(rows) == 1: 247 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'data': rows[0], 'idx': idx}) 248 return (True, narrative) 249 250 # insert new narrative 251 queries = [ 252 {'cmd': u""" 253 INSERT INTO clin.clin_narrative 254 (fk_encounter, fk_episode, narrative, soap_cat) 255 VALUES 256 (%s, %s, %s, lower(%s))""", 257 'args': [encounter_id, episode_id, narrative, soap_cat] 258 }, 259 {'cmd': u""" 260 SELECT * 261 FROM clin.v_narrative 262 WHERE 263 pk_narrative = currval(pg_get_serial_sequence('clin.clin_narrative', 'pk'))""" 264 } 265 ] 266 rows, idx = gmPG2.run_rw_queries(queries = queries, return_data = True, get_col_idx = True) 267 268 narrative = cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': rows[0]}) 269 return (True, narrative)
270 #------------------------------------------------------------
271 -def delete_clin_narrative(narrative=None):
272 """Deletes a clin.clin_narrative row by it's PK.""" 273 cmd = u"delete from clin.clin_narrative where pk=%s" 274 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': [narrative]}]) 275 return True
276 #------------------------------------------------------------
277 -def get_narrative(since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, patient=None, order_by=None):
278 """Get SOAP notes pertinent to this encounter. 279 280 since 281 - initial date for narrative items 282 until 283 - final date for narrative items 284 encounters 285 - list of encounters whose narrative are to be retrieved 286 episodes 287 - list of episodes whose narrative are to be retrieved 288 issues 289 - list of health issues whose narrative are to be retrieved 290 soap_cats 291 - list of SOAP categories of the narrative to be retrieved 292 """ 293 where_parts = [u'TRUE'] 294 args = {} 295 296 if encounters is not None: 297 where_parts.append(u'pk_encounter IN %(encs)s') 298 args['encs'] = tuple(encounters) 299 300 if episodes is not None: 301 where_parts.append(u'pk_episode IN %(epis)s') 302 args['epis'] = tuple(episodes) 303 304 if issues is not None: 305 where_parts.append(u'pk_health_issue IN %(issues)s') 306 args['issues'] = tuple(issues) 307 308 if patient is not None: 309 where_parts.append(u'pk_patient = %(pat)s') 310 args['pat'] = patient 311 312 if soap_cats is not None: 313 where_parts.append(u'c_vn.soap_cat IN %(soap_cats)s') 314 args['soap_cats'] = tuple(soap_cats) 315 316 if order_by is None: 317 order_by = u'ORDER BY date, soap_rank' 318 else: 319 order_by = u'ORDER BY %s' % order_by 320 321 cmd = u""" 322 SELECT 323 c_vn.*, 324 c_scr.rank AS soap_rank 325 FROM 326 clin.v_narrative c_vn 327 LEFT JOIN clin.soap_cat_ranks c_scr ON c_vn.soap_cat = c_scr.soap_cat 328 WHERE 329 %s 330 %s 331 """ % ( 332 u' AND '.join(where_parts), 333 order_by 334 ) 335 336 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 337 338 filtered_narrative = [ 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 # if issues is not None: 352 # filtered_narrative = filter(lambda narr: narr['pk_health_issue'] in issues, filtered_narrative) 353 # 354 # if episodes is not None: 355 # filtered_narrative = filter(lambda narr: narr['pk_episode'] in episodes, filtered_narrative) 356 # 357 # if encounters is not None: 358 # filtered_narrative = filter(lambda narr: narr['pk_encounter'] in encounters, filtered_narrative) 359 360 # if soap_cats is not None: 361 # soap_cats = map(lambda c: c.lower(), soap_cats) 362 # filtered_narrative = filter(lambda narr: narr['soap_cat'] in soap_cats, filtered_narrative) 363 364 #------------------------------------------------------------
365 -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):
366 367 if (patient is None) and (episodes is None) and (issues is None) and (encounters is None): 368 raise ValueError('at least one of <patient>, <episodes>, <issues>, <encounters> must not be None') 369 370 if order_by is None: 371 order_by = u'ORDER BY vemrj.clin_when, vemrj.pk_episode, scr, vemrj.src_table' 372 else: 373 order_by = u'ORDER BY %s' % order_by 374 375 where_parts = [] 376 args = {} 377 378 if patient is not None: 379 where_parts.append(u'pk_patient = %(pat)s') 380 args['pat'] = patient 381 382 if soap_cats is not None: 383 # work around bug in psycopg2 not being able to properly 384 # adapt None to NULL inside tuples 385 if None in soap_cats: 386 where_parts.append(u'((vemrj.soap_cat IN %(soap_cat)s) OR (vemrj.soap_cat IS NULL))') 387 soap_cats.remove(None) 388 else: 389 where_parts.append(u'vemrj.soap_cat IN %(soap_cat)s') 390 args['soap_cat'] = tuple(soap_cats) 391 392 if time_range is not None: 393 where_parts.append(u"vemrj.clin_when > (now() - '%s days'::interval)" % time_range) 394 395 if episodes is not None: 396 where_parts.append(u"vemrj.pk_episode IN %(epis)s") 397 args['epis'] = tuple(episodes) 398 399 if issues is not None: 400 where_parts.append(u"vemrj.pk_health_issue IN %(issues)s") 401 args['issues'] = tuple(issues) 402 403 # FIXME: implement more constraints 404 405 cmd = u""" 406 SELECT 407 to_char(vemrj.clin_when, 'YYYY-MM-DD') AS date, 408 vemrj.clin_when, 409 coalesce(vemrj.soap_cat, '') as soap_cat, 410 vemrj.narrative, 411 vemrj.src_table, 412 413 (SELECT rank FROM clin.soap_cat_ranks WHERE soap_cat = vemrj.soap_cat) AS scr, 414 415 vemrj.modified_when, 416 to_char(vemrj.modified_when, 'YYYY-MM-DD HH24:MI') AS date_modified, 417 vemrj.modified_by, 418 vemrj.row_version, 419 vemrj.pk_episode, 420 vemrj.pk_encounter, 421 vemrj.soap_cat as real_soap_cat 422 FROM clin.v_emr_journal vemrj 423 WHERE 424 %s 425 %s""" % ( 426 u'\n\t\t\t\t\tAND\n\t\t\t\t'.join(where_parts), 427 order_by 428 ) 429 430 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 431 return rows
432 #============================================================ 433 # main 434 #------------------------------------------------------------ 435 if __name__ == '__main__': 436 437 if len(sys.argv) < 2: 438 sys.exit() 439 440 if sys.argv[1] != 'test': 441 sys.exit() 442 443 from Gnumed.pycommon import gmI18N 444 gmI18N.activate_locale() 445 gmI18N.install_domain(domain = 'gnumed') 446 447 #-----------------------------------------
448 - def test_narrative():
449 print "\nnarrative test" 450 print "--------------" 451 narrative = cNarrative(aPK_obj=7) 452 fields = narrative.get_fields() 453 for field in fields: 454 print field, ':', narrative[field] 455 print "updatable:", narrative.get_updatable_fields() 456 print "codes:", narrative.generic_codes
457 #print "adding code..." 458 #narrative.add_code('Test code', 'Test coding system') 459 #print "codes:", diagnose.get_codes() 460 461 #print "creating narrative..." 462 #status, new_narrative = create_clin_narrative(narrative = 'Test narrative', soap_cat = 'a', episode_id=1, encounter_id=2) 463 #print new_narrative 464 465 #-----------------------------------------
466 - def test_search_text_across_emrs():
467 results = search_text_across_emrs('cut') 468 for r in results: 469 print r
470 #----------------------------------------- 471 472 #test_search_text_across_emrs() 473 test_narrative() 474 475 #============================================================ 476