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

Source Code for Module Gnumed.business.gmClinNarrative

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