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