def LF_relative(span): """Context includes any familial mention (e.g., mother father)""" left = get_left_span(span, span.sentence, window=6) right = get_right_span(span, span.sentence, window=6) left_trigger = match_regex(rgx_relatives, left) right_trigger = match_regex(rgx_relatives, right) return OTHER if left_trigger or right_trigger else PATIENT
def LF_social(span): rgx_social = re.compile(r'''\b(friend(s)*|roomate(s)*|passenger(s)*)\b''', re.I) left = get_left_span(span, span.sentence, window=6) right = get_right_span(span, span.sentence, window=6) left_trigger = match_regex(rgx_social, left) right_trigger = match_regex(rgx_social, right) return OTHER if left_trigger or right_trigger else ABSTAIN
def LF_pseudo_negation_exclusion(span): left_rgx = r'''(inadequate\s+to|does\s+not|cannot|can't)\s+exclude''' right_rgx = r'''(cannot\s+be|not\s+be|doesn't|not|to)\s+exclude[d]*''' left = get_left_span(span) trigger = match_regex(left_rgx, left) if trigger and token_distance(trigger, span) <= 3: return NON_NEGATED right = get_right_span(span) trigger = match_regex(right_rgx, right) if trigger and token_distance(trigger, span) <= 3: return NON_NEGATED return ABSTAIN
def LF_definite_left_0(span, negex): left = get_left_span(span, span.sentence, window=6) trigger = match_regex(negex.rgxs['definite']['left'], left) if not trigger: return ABSTAIN dist = token_distance(trigger, span) return NEGATED if dist == 0 else ABSTAIN
def LF_definite_right_expanded(span): right = get_right_span(span, span.sentence, window=6) rgx = r'''\b(not present|no evidence|is (absent|not seen)|ruled out|were negative)\b''' trigger = match_regex(rgx, right) if trigger and token_distance(trigger, span) <= 1: return NEGATED return ABSTAIN
def LF_left_context(span): left = get_left_span(span, span.sentence, window=6) # negated mentions neg_rgxes = [ r'''\b(no|did not have|neg(ative)* for) (mild|slight|minimal|severe|moderate|extensive|marked|extreme|significant|progressive)\b''', r'''\b(no|did not have|neg(ative)* for) (known|evidence of|evidence)\b''' ] neg_rgxes = [re.compile(rgx, re.I) for rgx in neg_rgxes] for rgx in neg_rgxes: trigger = match_regex(rgx, left) if trigger and token_distance(trigger, span) <= 2: return NEGATED # positive mentions pos_regxes = [ r'''(cannot exclude|does not become|may not|possible|evaluate for|suggests)''', # hedged r'''(mild|minimal|severe|moderate|extensive|coarse|marked|extreme|significant|trivial|progressive|slight)(ly)*''', # severity r'''(diagnosed with|known to have|known|non-specific|presented|secondary to|treated for|acute onset|improving|improved|improvement|involvement|resolved|consistent with|showed|presumed|suspicious for|check for|revealed|new onset|were noted|found to be|demonstrate(d)*)''', # present now r'''\b((in|de)creas(e|ed|ing)|up|down)\b''', # LF_change_words_left r'''(s/p|status[- ]post)''', ] pos_regxes = [re.compile(rgx, re.I) for rgx in pos_regxes] for rgx in pos_regxes: if rgx.search(span.text): return NON_NEGATED return ABSTAIN
def LF_pseudo_negation_rule_out(span): left_rgx = r'''(cannot|does not|doesn't) rule[s]* out''' left = get_left_span(span) trigger = match_regex(left_rgx, left) if not trigger or token_distance(trigger, span) > 5: return ABSTAIN return NON_NEGATED if re.search(r'''(cannot|does not|doesn't)''', trigger.text, re.I) else NEGATED
def LF_header(span, negex): """All spans under Family History are assumed to refer to family""" rgx = re.compile(r'''(family history[:]*|family hx)\b''', re.I) left = get_left_span(span, span.sentence, window=6) trigger = match_regex(rgx, left) if trigger: # check for negation ("no family history") neg = match_regex(negex.rgxs['definite']['left'], get_left_span(trigger, window=2)) return ABSTAIN if neg else OTHER if 'section' in span.props: header = span.props['section'] if header and rgx.search(header.text): return OTHER return ABSTAIN
def LF_positive_left(span): left = get_left_span(span, window=10) rgx = r'''\b(positive for|suggestive of|due to|shows)\b''' trigger = match_regex(rgx, left) if not trigger: return ABSTAIN left = get_left_span(trigger, window=50) m = re.search(r'''\b(no|not)\b''', left.text, re.I) return NON_NEGATED if not re.search(r'''\b(no|not)\b''', left.text, re.I) else ABSTAIN
def LF_header_break_negation(span, negex): left = get_left_span(span) trigger = match_regex(negex.rgxs['definite']['left'], left) if not trigger: return ABSTAIN btw = get_between_span(trigger, span) if not btw: return ABSTAIN rgx = r'''\s{2,}((?:(?:[A-Z][A-Za-z]+\s){1,4}(?:[A-Za-z]+))[:])''' return NON_NEGATED if re.search(rgx, btw.text) else ABSTAIN
def LF_probable_left_4_6(span, negex): left = get_left_span(span, span.sentence, window=6) trigger = match_regex(negex.rgxs['probable']['left'], left) if not trigger: return ABSTAIN dist = token_distance(trigger, span) right = get_right_span(trigger, window=2) if pseudo_negation.search(right.text): return ABSTAIN return NEGATED if (dist >= 4 and dist <= 6) else ABSTAIN
def LF_definite_left_7_10(span, negex): left = get_left_span(span, span.sentence) trigger = match_regex(negex.rgxs['definite']['left'], left) if not trigger: return ABSTAIN dist = token_distance(trigger, span) right = get_right_span(trigger, window=2) if pseudo_negation.search(right.text): return ABSTAIN return NEGATED if (dist >= 7 and dist <= 10) else ABSTAIN
def LF_definite_left_list(span, negex): left = get_left_span(span, span.sentence, window=100) trigger = match_regex(negex.rgxs['definite']['left'], left) if not trigger: return ABSTAIN dist = token_distance(trigger, span) btw = get_between_span(trigger, span) right = get_right_span(trigger, window=2) if pseudo_negation_rgx.search(right.text): return ABSTAIN return NEGATED if dist <= 10 and btw and len(re.findall(r'''[,;]''', btw.text)) > 1 else ABSTAIN
def LF_denies_list(span): """ Patient denies X,Y,Z. """ rgx = re.compile(r'''\b(den(ying|y|ies|ied))\b''', re.I) left = get_left_span(span, window=100) trigger = match_regex(rgx, left) if not trigger: return ABSTAIN btw = get_between_span(trigger, span) if not btw: return ABSTAIN n = len(re.findall(r'''[,;/]''', btw.text)) return NEGATED if n >= 1 else ABSTAIN
def LF_pseudo_left_expanded(span, negex): pseudo_rgxs = [ r'''\b((significant )*(change[s]* in|improvement))\b''', r'''\b((in|de)creas(e|ed|ing)|up|down)\b''' ] left = get_left_span(span) trigger = match_regex(negex.rgxs['definite']['left'], left) if not trigger or token_distance(trigger, span) > 20: return ABSTAIN btw = get_between_span(span, trigger) if not btw: return ABSTAIN for rgx in pseudo_rgxs: if re.search(rgx, btw.text, re.I): return NON_NEGATED return ABSTAIN
def LF_none(span): right_rgx = r'''\bnone\b''' right = get_right_span(span) trigger = match_regex(right_rgx, right) return NEGATED if trigger and token_distance(trigger, span) <= 2 else ABSTAIN