Entity Resolution
How raw observations become attached to the right Entity — the anti-fork problem — via a two-stage deterministic then probabilistic matcher.
How raw observations become attached to the right Entity (the "anti-fork" problem): never create a duplicate entity for something we already track, never wrongly merge two real entities. Product doc
02and06reference this; here is the runtime.
The two-stage matcher
observation ──► STAGE 1: deterministic match (strong keys)
│ mmsi / imo / lei / isin / exact gov id
├── hit ──► attach to existing entity (confidence 1.0)
└── miss ─► STAGE 2: probabilistic match (similarity)
score = weighted blend of:
name similarity (Jaro-Winkler / trigram)
geospatial proximity (within plausible track)
temporal plausibility
shared attributes (flag, type, owner)
├── score ≥ HIGH (auto) ──► attach
├── LOW ≤ score < HIGH ──► MERGE-REVIEW queue (human)
└── score < LOW ──► create new entity- Deterministic keys (
mmsi/imo/lei/isin/gov id) live inentity_aliaseswith a unique constraint, so a strong-key match is a single indexed lookup and can never fork.name_normand other probabilistic signals are NOT stored as unique aliases — a normalized name is not unique (two real entities can share one), so it is only a similarity feature in Stage 2, never a unique deterministic key. (This reconciles theentity_aliasesPK in20-canonical-model.md: only deterministic key types go there.) - Probabilistic scoring is a transparent weighted sum (not a black box) so every match is explainable — the analyst can see why two records were joined.
- The HIGH/LOW thresholds are tunable per entity type and per tenant.
Resolution timing (decided)
Stage 1 (deterministic) runs inline in the ingest transaction — it's a single indexed lookup, cheap, and lets the observation attach immediately. Stage 2 (probabilistic, candidate generation + scoring) runs asynchronously via the outbox → a resolution worker, because similarity search over many candidates is too heavy for the write path. This reconciles 10-system-architecture.md (which shows resolution after the observation insert): deterministic is synchronous, probabilistic is deferred.
The MERGE-REVIEW queue
Ambiguous matches (LOW ≤ score < HIGH) never auto-merge — they stage for a human, exactly the Q9/Q10 human-commit doctrine applied to data. A reviewer sees the two candidates side by side, the score breakdown, and the cited observations, then Accepts / Rejects / Splits. This is the same review pattern as the Proposal Inbox (product §13). Decisions are logged and feed back as labels to tune thresholds.
Merges and splits
- Merge: pick a surviving
entity_id; repoint observations/events/aliases; keep amerge_logrow recording the absorbed id, the actor, and the timestamp (reversible). - Split: if a merge was wrong, the
merge_loglets you reconstruct the original partition and re-resolve from the observations. - Because observations are immutable and carry their own ids, resolution is always replayable — you can re-run the matcher over history with new thresholds.
Implementation notes
- Run resolution synchronously in the ingest transaction for deterministic matches (cheap), and asynchronously (outbox → worker) for probabilistic scoring that needs similarity search over many candidates.
- Candidate generation for Stage 2: trigram index (
pg_trgm) oncanonical_name+ a geospatial pre-filter (PostGIS) to bound the candidate set before scoring. - Keep the matcher in
packages/canonicalso the same code resolves at ingest and during a backfill/replay.
Gaps to close
- Set per-type weights + HIGH/LOW thresholds and a labeling loop to tune them.
- Decide auto-merge audit retention and whether merges require a second approver for
designatedentities. - Define the candidate-generation budget (max candidates scored) for very large entity sets.
The Canonical Model (implementation)
The relational implementation of Source, Observation, Entity, Event, Case, Report — schema, bitemporality, provenance signing, and partitioning.
MongoDB & the Document / RAG Store
The non-relational half of the polyglot — raw payloads, documents, the RAG corpus, and agent transcripts, with Atlas Vector Search for semantic retrieval.