Entity resolution: pairwise(), crosswalk(), sign_edges()¶
The arche.resolve facade has two entry points by use-shape. Their scores are deliberately not comparable — different math for different jobs.
resolve.pairwise(a, b, *, entity="person", **kwargs)¶
"Are these two the same?" Dispatches on input shape — two Pipeline Results, two canonical References, or two raw strings (extract-then-resolve) — and returns a signable CoReferenceDecision (Fellegi–Sunter log-odds, exact-id gate, id-conflict veto, statute citations travelling with the evidence). Currently entity="person" only; place lists go through crosswalk.
from arche import resolve
decision = resolve.pairwise("Fatima Abdullahi, NIN 12345678901",
"Fatuma Abdullahi, NIN 12345678901")
decision.identity # "same_entity" | "review" | "different"
decision.action # what the gate decided to do about it
decision.score # 0.9999
decision.factors # per-field evidence
decision.decision_id # reproducible content address
Real output for the pair above:
identity = 'review'
action = 'no_op'
score = 0.9999
decision_id = 'dec:sha256:a5fde8c138c6157f00c0396ce63f6...'
identity, not decision
The verdict field is decision.identity, and its values are
same_entity / review / different. decision.decision does not
exist and raises AttributeError. The "match" / "review" /
"no_match" vocabulary belongs to crosswalk edges, which are a
different object — see resolve.crosswalk() below.
A high score is not a merge
Note the pair above scores 0.9999 and still returns review. Sharing
an exact national ID is strong evidence, but the gate requires
distinctive evidence before it will call two references the same
entity, and it abstains rather than guessing. review is the engine
working, not failing.
Signing a decision requires a keypair. Calling
arche.attest.attest() on a decision produced without an issuer_key raises,
deliberately:
a keyless reference_id is a hash of the person's attributes and can be
brute-forced back to the source record.
resolve.crosswalk(list_a, list_b, *, entity=None, comparators=None, tf=None, decl=None, **kwargs)¶
Link or dedupe two record lists at scale. Pass exactly one of:
entity=— a canned pack fromENTITY_PACKS("person","place","artist");decl=— aDeclaration: your fields, generated comparators, yourid_field, the pin entering every edge;comparators=— explicit specs (see kinds below).
Key keyword arguments (forwarded to reconcile):
| Argument | Default | Meaning |
|---|---|---|
threshold |
0.7 |
score at/above which a pair is match (gate permitting) |
review_margin |
0.15 |
[threshold − margin, threshold) is review; below is dropped |
id_field |
"id" |
record identifier column |
tf |
pack-dependent | a TokenFrequencyTable, "default" (person table), a domain name ("artist"), or None → self-calibrate over both lists |
block |
"union" |
"union" (H3 ∪ rare-token ∪ shared-id), "h3" (spatial only), or None (full cross-product) |
truth_pairs |
None |
labelled (a_id, b_id) true pairs → blocking["recall"] reported |
extra_pins |
None |
extra provenance hashed into every decision_id (e.g. a boundary-layer vintage) |
rerank |
False |
block-aware distinguishing-token reranker |
Comparator kinds: name, placename (lexicon-free), id, phone, email, address, date, tftoken (needs tf), geo (lat/lon/decay_km), containment (named admin hierarchy), type (domain= vocabulary; ships at weight 0 in the place pack).
Returns:
{
"matches": [{"a_id", "b_id", "score", "decision", # "match" | "review"
"evidence", # per-comparator similarities (+ distance_km)
"distinctive_max", "decision_id"}], # content hash over edge + pins
"count": int,
"pins": {...}, # engine, thresholds, block, tf provenance, declaration pin
"blocking": {"candidate_pairs", "reduction_ratio", "strategies", "recall"?},
}
Safety semantics: a match requires a distinctive comparator (name/placename/id/tftoken) at ≥ 0.75 — supporting signals (geo, containment, address) amplify but never manufacture a merge — and any containment conflict demotes a would-be match to review. Output decoding: read crosswalk output.
reconcile.sign_edges(result, *, private_key, kid, decisions=("match", "review"))¶
JWS-sign crosswalk edges. Each payload is the edge plus the run's pins under the arche.crosswalk_edge.v1 schema, so a verifier can recompute the decision_id from the signed payload (dropping the decision_id field first) and confirm nothing changed. Edges carry ids and numeric evidence only — never raw record values — so the signed artifact is as shareable as the crosswalk output itself.
from arche.resolve.reconcile import sign_edges
from arche.sign import generate_keypair, verify
kp = generate_keypair()
signed = sign_edges(result, private_key=kp.private_key, kid=kp.did_key)
verify(signed[0]["jws"], public_key=kp.public_key).valid # True
ENTITY_PACKS¶
The canned comparator specs (person, place, artist) — config over one engine, never a fork.