Edge /api/vehicles (RDW NL, live now) + /api/screen (OpenSanctions, live now), both defer to engine when ENGINE_URL set. Engine: /vehicles/{plate} + /screen endpoints; workers/resolve (blocking+fuzzy entity resolution, now used by pipeline); workers/queue (in-process job queue); sources/financials (Companies House accounts + XBRL path). py_compile + tsc + next build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
1.3 KiB
Python
34 lines
1.3 KiB
Python
"""
|
|
Entity resolution (docs/06): block, score, and link records across sources so the
|
|
same company from GLEIF + Brreg + CVR collapses to one record with corroborated
|
|
confidence. rapidfuzz only; deterministic; no external calls.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from rapidfuzz import fuzz
|
|
|
|
|
|
def block_key(rec: dict[str, Any]) -> tuple[str, str]:
|
|
"""Cheap blocking key — same market + first initial — to avoid O(n^2)."""
|
|
return (rec.get("market") or "", (rec.get("name") or "")[:1].upper())
|
|
|
|
|
|
def resolve(records: list[dict[str, Any]], threshold: int = 92) -> list[dict[str, Any]]:
|
|
out: list[dict[str, Any]] = []
|
|
for r in records:
|
|
match = None
|
|
for o in out:
|
|
if block_key(o) == block_key(r) and fuzz.token_sort_ratio(o.get("name", ""), r.get("name", "")) >= threshold:
|
|
match = o
|
|
break
|
|
if match:
|
|
# corroboration: independent source agreeing bumps confidence + level
|
|
match["confidence"] = min(0.99, match.get("confidence", 0.0) + 0.1)
|
|
match["agreed_sources"] = match.get("agreed_sources", 1) + 1
|
|
match.setdefault("merged_from", []).append(r.get("source"))
|
|
else:
|
|
out.append({**r, "agreed_sources": 1})
|
|
return out
|