import re import decode as D TITLE_BEFORE = re.compile(r'(?:\b(?:Mr|Mrs|Ms|Miss|Mx|Dr|Prof|Professor|Sir|Dame|Lord|Lady|Cllr|Councillor)\.?\s+)$') TITLE = re.compile(r'^(?:(?:mr|mrs|ms|miss|mx|dr|prof|professor|sir|dame|lord|lady|cllr|councillor)\.?\s+)+', re.I) PARTICLES = {'van', 'von', 'der', 'den', 'de', 'la', 'le', 'du', 'da', 'dos', 'di', 'del', 'al', 'el', 'bin', 'bint', 'ibn', 'ap', 'mac', 'st'} ORG_TYPES = set('''council borough district county city metropolitan royal unitary parish town combined authority nhs foundation trust trusts university hospital hospitals college school academy police fire rescue service services constabulary commissioner board agency department ministry office health care integrated partnership housing transport commission ombudsman regulator executive committee institute library libraries museum museums group association'''.split()) WORD = r"[^\W\d_]+(?:[’'\-][^\W\d_]+)*" def flexible(name): return r'\s+'.join(map(re.escape, name.split())) def authority_ranges(text, authority): if not authority or len(authority.split()) < 2: return [] out = [(m.start(), m.end()) for m in re.finditer(r'(? p and text[q - 1].isspace(): q -= 1 if p < q: out.append([p, q]) return out def requester_parts(name): name = TITLE.sub('', name.strip()).strip() parts = re.findall(WORD, name) if len(parts) < 2: return name, [], None k = len(parts) - 1 while k > 1 and parts[k - 1].casefold() in PARTICLES: k -= 1 variants = {parts[0], parts[-1]} if k < len(parts) - 1: variants.add(' '.join(parts[k:])) variants = [v for v in variants if len(v) >= 3 and v.casefold() not in PARTICLES] initials = ''.join(p[0] for p in parts if p.casefold() not in PARTICLES).upper() return name, variants, initials if 2 <= len(initials) <= 4 else None def merge(spans): out = [] for a, b in sorted(map(list, spans)): if out and a <= out[-1][1]: out[-1][1] = max(out[-1][1], b) else: out.append([a, b]) return out def apply(text, spans, rec, requester=None, authority=None, tau=.5, gate=.1): protect = authority_ranges(text, authority) spans = subtract(text, spans, protect) if not requester or not requester.strip(): return merge(spans) scores = None def support(a, b): nonlocal scores if scores is None: scores = D.char_scores(text, rec['offsets'], rec['probs']) return max(scores[a:b], default=0.0) name, variants, initials = requester_parts(requester) add = [] if name and len(name.split()) >= 2: add += [[m.start(), m.end()] for m in re.finditer(r'(?= gate] for v in variants: for m in re.finditer(r"(?= gate: add.append([a, b]) if initials: for m in re.finditer(r'(?= gate: add.append([a, b]) return merge(spans + add)