#!/usr/bin/env python3
"""Bank Batch 388: Rule 346 underrepresented bird-set-D singular/third perception."""

from __future__ import annotations

import datetime as dt
import hashlib
import json
import re
import sqlite3

import yaml

from process_ledger import ensure_schema, record_event

DB = "/mnt/donto-data/donto-resources/research/translation-training/kuku-yalanji-runpod-2026-06-30/synthetic/claude-synthetic-v1-2026-07-02/synthetic.db"
DICTIONARY = "/mnt/donto-data/workspace/mobtranslate.com/dictionaries/kuku_yalanji/dictionary.yaml"
GRAMMAR = "/mnt/donto-data/workspace/mobtranslate.com/dictionaries/kuku_yalanji/grammar.md"
LABEL = "batch-388"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-388-underrepresented-bird-set-d-singular-third-past-perception-after-rule345-process.md"
EXPECTED_HASHES = {
    DICTIONARY: "b575ab035ecc60f09f7bfc5537116555f3d290869fe75b2edf0fd8db60fe1b37",
    GRAMMAR: "5f21305f1fca9ee2cb64668e18156d1f322e3bd9e59850fda662bf0673714153",
}
TARGET_ORDER = [
    "diwalngku", "bujil-yiran", "karrmbil", "yiki", "bajal",
    "biru-biru", "jangkan", "kurranday", "ngarkay", "wakuka",
]
RECORD_COUNTS = {target: 2 for target in TARGET_ORDER}
PATZ_HIT_COUNTS = {target: (3 if target == "wakuka" else 0) for target in TARGET_ORDER}
TARGET_LABELS = {
    "diwalngku": "diwalngku bird", "bujil-yiran": "bujil-yiran ibis",
    "karrmbil": "karrmbil parakeet", "yiki": "yiki owl", "bajal": "bajal bower bird",
    "biru-biru": "biru-biru rainbow bird", "jangkan": "jangkan swift",
    "kurranday": "kurranday seagull", "ngarkay": "ngarkay waterhen", "wakuka": "wakuka bird",
}
TARGET_GLOSSES = {
    "diwalngku": "diwalngku.bird", "bujil-yiran": "bujil-yiran.ibis",
    "karrmbil": "karrmbil.parakeet", "yiki": "yiki.owl", "bajal": "bajal.bower-bird",
    "biru-biru": "biru-biru.rainbow-bird", "jangkan": "jangkan.swift",
    "kurranday": "kurranday.seagull", "ngarkay": "ngarkay.waterhen", "wakuka": "wakuka.bird",
}
TARGET_CONFIDENCE = {
    "diwalngku": "0.78", "bujil-yiran": "0.80", "karrmbil": "0.86", "yiki": "0.88",
    "bajal": "0.90", "biru-biru": "0.86", "jangkan": "0.90", "kurranday": "0.92",
    "ngarkay": "0.92", "wakuka": "0.76",
}
EXPECTED_TARGET_COVERAGE = {
    "diwalngku": 5, "bujil-yiran": 5, "karrmbil": 5, "yiki": 5, "bajal": 6,
    "biru-biru": 6, "jangkan": 6, "kurranday": 6, "ngarkay": 6, "wakuka": 7,
}
EXPECTED_SUPPORT_COVERAGE = {
    "ngayu": 1481, "yundu": 1077, "nyulu": 752, "bula": 548, "jana": 1253,
    "yaluy": 4532, "yinyay": 4704, "nyajin": 2350,
}
FRAME_SPECS = [
    ("ngayu", "yaluy", "I saw", "here", "1sg"),
    ("yundu", "yinyay", "You saw", "there", "2sg"),
    ("nyulu", "yaluy", "He saw", "here", "3sg"),
    ("bula", "yinyay", "The two of them saw", "there", "3du"),
    ("jana", "yaluy", "They saw", "here", "3pl"),
]
TARGET_EVIDENCE = {
    "diwalngku": "Dictionary yaml lines 6064-6088 contain two exact migratory-bird records and say the English species name is unknown. Rule 346 uses generic diwalngku bird only; record collapse, species identity, migration, season, route, habitat, source, possession and events remain closed.",
    "bujil-yiran": "Dictionary yaml lines 3688-3707 contain one no-definition cognition-emotion card proposing a bujil fear-base compound and one straw-necked-ibis card. Rule 346 selects generic bujil-yiran ibis only; card collapse, free components, fear/cognition, compositional analysis, taxonomy, source, possession and events remain closed.",
    "karrmbil": "Dictionary yaml lines 13670-13694 contain two exact parakeet records; commentary aligns blue mountain parakeet with rainbow lorikeet. Rule 346 uses generic karrmbil parakeet only; record/taxonomic resolution, colour, mountain/habitat, source, possession and events remain closed.",
    "yiki": "Dictionary yaml lines 29907-29929 contain owl and night-owl records. Rule 346 uses generic yiki owl only; record/species resolution, night/nocturnal behavior, sound, hunting, danger, source, possession and events remain closed.",
    "bajal": "Dictionary yaml lines 369-387 contain two bower-bird records. Rule 346 uses generic bajal bower bird only; duplicate collapse, sex, scrub/habitat, bower building, collecting, stealing, objects, source, possession and events remain closed.",
    "biru-biru": "Dictionary yaml lines 3227-3254 contain two full-reduplicated bee-eater/rainbow-bird records with base biru. Rule 346 preserves whole biru-biru rainbow bird; record/taxonomic resolution, free base, productive reduplication, colour, nesting, honey signs, source, possession and events remain closed.",
    "jangkan": "Dictionary yaml lines 7971-7993 contain swift/swift-bird records. Rule 346 uses generic jangkan swift only; duplicate collapse, speed/property inference, flight, season, source, possession and events remain closed.",
    "kurranday": "Dictionary yaml lines 16217-16239 contain two seagull records. Rule 346 uses generic kurranday seagull only; duplicate collapse, sea/shore habitat, weather warning, sound, feeding, flight, source, possession and events remain closed.",
    "ngarkay": "Dictionary yaml lines 22729-22750 contain waterhen/water-hen records. Rule 346 uses generic ngarkay waterhen only; duplicate collapse, swamp/water habitat, walking, feeding, hunting, source, possession and events remain closed.",
    "wakuka": "Dictionary yaml lines 25224-25248 conflict between crow and kookaburra records. Patz 713 gives a kookaburra messenger/death belief, 4026 a bird demonstrative, and 6903 Wakuka/Wakooka Station as a proper name. Rule 346 uses generic wakuka bird only; record/taxonomic resolution, messenger status, death belief, place-name transfer, sound, source, possession and events remain closed.",
}
SUPPORT_EVIDENCE = (
    "Exact Patz-form scan found wakuka only: bird contexts at 713 and 4026 plus unrelated Wakuka/Wakooka Station "
    "at 6903; the other nine zero-hit results are logged "
    "without treating them as negative lexical evidence because dictionary cards license the heads. Patz 2577-2584 "
    "sources bare nominative ngayu, yundu, nyulu, bula and plural-third-person paradigm with running jana; "
    "2834-2847 sources Yalanji yaluy/yinyay; 2021-2024, 2568-2571 and 3160-3162 license nyaji-ny; "
    "4256-4265 licenses nominative pronoun A with bare absolutive O in sensory perception. Dictionary "
    "23876-23883 and 23968-23976 contain running nyajin."
)
DENY_KUKU = {
    "ngali", "ngaliny", "yubal", "yurra", "nganjin", "ngana", "yulu", "iana", "biru", "bujil",
    "yiran", "yalay", "yala", "yanyu", "yinya", "nyajil", "nyaji-l", "nyaji-nka", "nyaji-l-nyaji",
    "milka", "baykal", "jalngkay",
}
DENY_ENGLISH = (
    "migratory", "species name", "fear", "cognition", "blue mountain", "rainbow lorikeet", "night owl",
    "nocturnal", "crow", "kookaburra", "messenger", "death", "herald", "totem", "build", "collect",
    "steal", "scrub", "bee eater", "productive reduplication", "free base", "speed", "fly", "flight",
    "habitat", "warning", "feed", "hunt", "sound", "source", "possess", "event",
)


def count_words(db: sqlite3.Connection, words: list[str]) -> dict[str, int]:
    return {
        word: db.execute(
            """SELECT COUNT(*) FROM sentences s WHERE s.status IN ('accepted','revised')
               AND EXISTS (SELECT 1 FROM json_each(s.words_used) WHERE lower(value)=?)""",
            (word,),
        ).fetchone()[0]
        for word in words
    }


def row(seq: int, english: str, kuku: str, analysis: str, frame: str, words: list[str]) -> tuple:
    target = words[1]
    return (
        seq, english, kuku, analysis, frame, 3,
        json.dumps(words, ensure_ascii=False), json.dumps([], ensure_ascii=False),
        f"{TARGET_EVIDENCE[target]} {SUPPORT_EVIDENCE}", TARGET_CONFIDENCE[target],
    )


ROWS = [
    row(1, "I saw the diwalngku bird here.", "Ngayu diwalngku yaluy nyajin.", "ngayu diwalngku yaluy nyajin | 1sg.NOM diwalngku.bird here see.PST", "r346_1sg_here_bird_d_saw_diwalngku", ["ngayu", "diwalngku", "yaluy", "nyajin"]),
    row(2, "I saw the bujil-yiran ibis here.", "Ngayu bujil-yiran yaluy nyajin.", "ngayu bujil-yiran yaluy nyajin | 1sg.NOM bujil-yiran.ibis here see.PST", "r346_1sg_here_bird_d_saw_bujil_yiran", ["ngayu", "bujil-yiran", "yaluy", "nyajin"]),
    row(3, "I saw the karrmbil parakeet here.", "Ngayu karrmbil yaluy nyajin.", "ngayu karrmbil yaluy nyajin | 1sg.NOM karrmbil.parakeet here see.PST", "r346_1sg_here_bird_d_saw_karrmbil", ["ngayu", "karrmbil", "yaluy", "nyajin"]),
    row(4, "I saw the yiki owl here.", "Ngayu yiki yaluy nyajin.", "ngayu yiki yaluy nyajin | 1sg.NOM yiki.owl here see.PST", "r346_1sg_here_bird_d_saw_yiki", ["ngayu", "yiki", "yaluy", "nyajin"]),
    row(5, "I saw the bajal bower bird here.", "Ngayu bajal yaluy nyajin.", "ngayu bajal yaluy nyajin | 1sg.NOM bajal.bower-bird here see.PST", "r346_1sg_here_bird_d_saw_bajal", ["ngayu", "bajal", "yaluy", "nyajin"]),
    row(6, "I saw the biru-biru rainbow bird here.", "Ngayu biru-biru yaluy nyajin.", "ngayu biru-biru yaluy nyajin | 1sg.NOM biru-biru.rainbow-bird here see.PST", "r346_1sg_here_bird_d_saw_biru_biru", ["ngayu", "biru-biru", "yaluy", "nyajin"]),
    row(7, "I saw the jangkan swift here.", "Ngayu jangkan yaluy nyajin.", "ngayu jangkan yaluy nyajin | 1sg.NOM jangkan.swift here see.PST", "r346_1sg_here_bird_d_saw_jangkan", ["ngayu", "jangkan", "yaluy", "nyajin"]),
    row(8, "I saw the kurranday seagull here.", "Ngayu kurranday yaluy nyajin.", "ngayu kurranday yaluy nyajin | 1sg.NOM kurranday.seagull here see.PST", "r346_1sg_here_bird_d_saw_kurranday", ["ngayu", "kurranday", "yaluy", "nyajin"]),
    row(9, "I saw the ngarkay waterhen here.", "Ngayu ngarkay yaluy nyajin.", "ngayu ngarkay yaluy nyajin | 1sg.NOM ngarkay.waterhen here see.PST", "r346_1sg_here_bird_d_saw_ngarkay", ["ngayu", "ngarkay", "yaluy", "nyajin"]),
    row(10, "I saw the wakuka bird here.", "Ngayu wakuka yaluy nyajin.", "ngayu wakuka yaluy nyajin | 1sg.NOM wakuka.bird here see.PST", "r346_1sg_here_bird_d_saw_wakuka", ["ngayu", "wakuka", "yaluy", "nyajin"]),
    row(11, "You saw the diwalngku bird there.", "Yundu diwalngku yinyay nyajin.", "yundu diwalngku yinyay nyajin | 2sg.NOM diwalngku.bird there see.PST", "r346_2sg_there_bird_d_saw_diwalngku", ["yundu", "diwalngku", "yinyay", "nyajin"]),
    row(12, "You saw the bujil-yiran ibis there.", "Yundu bujil-yiran yinyay nyajin.", "yundu bujil-yiran yinyay nyajin | 2sg.NOM bujil-yiran.ibis there see.PST", "r346_2sg_there_bird_d_saw_bujil_yiran", ["yundu", "bujil-yiran", "yinyay", "nyajin"]),
    row(13, "You saw the karrmbil parakeet there.", "Yundu karrmbil yinyay nyajin.", "yundu karrmbil yinyay nyajin | 2sg.NOM karrmbil.parakeet there see.PST", "r346_2sg_there_bird_d_saw_karrmbil", ["yundu", "karrmbil", "yinyay", "nyajin"]),
    row(14, "You saw the yiki owl there.", "Yundu yiki yinyay nyajin.", "yundu yiki yinyay nyajin | 2sg.NOM yiki.owl there see.PST", "r346_2sg_there_bird_d_saw_yiki", ["yundu", "yiki", "yinyay", "nyajin"]),
    row(15, "You saw the bajal bower bird there.", "Yundu bajal yinyay nyajin.", "yundu bajal yinyay nyajin | 2sg.NOM bajal.bower-bird there see.PST", "r346_2sg_there_bird_d_saw_bajal", ["yundu", "bajal", "yinyay", "nyajin"]),
    row(16, "You saw the biru-biru rainbow bird there.", "Yundu biru-biru yinyay nyajin.", "yundu biru-biru yinyay nyajin | 2sg.NOM biru-biru.rainbow-bird there see.PST", "r346_2sg_there_bird_d_saw_biru_biru", ["yundu", "biru-biru", "yinyay", "nyajin"]),
    row(17, "You saw the jangkan swift there.", "Yundu jangkan yinyay nyajin.", "yundu jangkan yinyay nyajin | 2sg.NOM jangkan.swift there see.PST", "r346_2sg_there_bird_d_saw_jangkan", ["yundu", "jangkan", "yinyay", "nyajin"]),
    row(18, "You saw the kurranday seagull there.", "Yundu kurranday yinyay nyajin.", "yundu kurranday yinyay nyajin | 2sg.NOM kurranday.seagull there see.PST", "r346_2sg_there_bird_d_saw_kurranday", ["yundu", "kurranday", "yinyay", "nyajin"]),
    row(19, "You saw the ngarkay waterhen there.", "Yundu ngarkay yinyay nyajin.", "yundu ngarkay yinyay nyajin | 2sg.NOM ngarkay.waterhen there see.PST", "r346_2sg_there_bird_d_saw_ngarkay", ["yundu", "ngarkay", "yinyay", "nyajin"]),
    row(20, "You saw the wakuka bird there.", "Yundu wakuka yinyay nyajin.", "yundu wakuka yinyay nyajin | 2sg.NOM wakuka.bird there see.PST", "r346_2sg_there_bird_d_saw_wakuka", ["yundu", "wakuka", "yinyay", "nyajin"]),
    row(21, "He saw the diwalngku bird here.", "Nyulu diwalngku yaluy nyajin.", "nyulu diwalngku yaluy nyajin | 3sg.NOM diwalngku.bird here see.PST", "r346_3sg_here_bird_d_saw_diwalngku", ["nyulu", "diwalngku", "yaluy", "nyajin"]),
    row(22, "He saw the bujil-yiran ibis here.", "Nyulu bujil-yiran yaluy nyajin.", "nyulu bujil-yiran yaluy nyajin | 3sg.NOM bujil-yiran.ibis here see.PST", "r346_3sg_here_bird_d_saw_bujil_yiran", ["nyulu", "bujil-yiran", "yaluy", "nyajin"]),
    row(23, "He saw the karrmbil parakeet here.", "Nyulu karrmbil yaluy nyajin.", "nyulu karrmbil yaluy nyajin | 3sg.NOM karrmbil.parakeet here see.PST", "r346_3sg_here_bird_d_saw_karrmbil", ["nyulu", "karrmbil", "yaluy", "nyajin"]),
    row(24, "He saw the yiki owl here.", "Nyulu yiki yaluy nyajin.", "nyulu yiki yaluy nyajin | 3sg.NOM yiki.owl here see.PST", "r346_3sg_here_bird_d_saw_yiki", ["nyulu", "yiki", "yaluy", "nyajin"]),
    row(25, "He saw the bajal bower bird here.", "Nyulu bajal yaluy nyajin.", "nyulu bajal yaluy nyajin | 3sg.NOM bajal.bower-bird here see.PST", "r346_3sg_here_bird_d_saw_bajal", ["nyulu", "bajal", "yaluy", "nyajin"]),
    row(26, "He saw the biru-biru rainbow bird here.", "Nyulu biru-biru yaluy nyajin.", "nyulu biru-biru yaluy nyajin | 3sg.NOM biru-biru.rainbow-bird here see.PST", "r346_3sg_here_bird_d_saw_biru_biru", ["nyulu", "biru-biru", "yaluy", "nyajin"]),
    row(27, "He saw the jangkan swift here.", "Nyulu jangkan yaluy nyajin.", "nyulu jangkan yaluy nyajin | 3sg.NOM jangkan.swift here see.PST", "r346_3sg_here_bird_d_saw_jangkan", ["nyulu", "jangkan", "yaluy", "nyajin"]),
    row(28, "He saw the kurranday seagull here.", "Nyulu kurranday yaluy nyajin.", "nyulu kurranday yaluy nyajin | 3sg.NOM kurranday.seagull here see.PST", "r346_3sg_here_bird_d_saw_kurranday", ["nyulu", "kurranday", "yaluy", "nyajin"]),
    row(29, "He saw the ngarkay waterhen here.", "Nyulu ngarkay yaluy nyajin.", "nyulu ngarkay yaluy nyajin | 3sg.NOM ngarkay.waterhen here see.PST", "r346_3sg_here_bird_d_saw_ngarkay", ["nyulu", "ngarkay", "yaluy", "nyajin"]),
    row(30, "He saw the wakuka bird here.", "Nyulu wakuka yaluy nyajin.", "nyulu wakuka yaluy nyajin | 3sg.NOM wakuka.bird here see.PST", "r346_3sg_here_bird_d_saw_wakuka", ["nyulu", "wakuka", "yaluy", "nyajin"]),
    row(31, "The two of them saw the diwalngku bird there.", "Bula diwalngku yinyay nyajin.", "bula diwalngku yinyay nyajin | 3du.NOM diwalngku.bird there see.PST", "r346_3du_there_bird_d_saw_diwalngku", ["bula", "diwalngku", "yinyay", "nyajin"]),
    row(32, "The two of them saw the bujil-yiran ibis there.", "Bula bujil-yiran yinyay nyajin.", "bula bujil-yiran yinyay nyajin | 3du.NOM bujil-yiran.ibis there see.PST", "r346_3du_there_bird_d_saw_bujil_yiran", ["bula", "bujil-yiran", "yinyay", "nyajin"]),
    row(33, "The two of them saw the karrmbil parakeet there.", "Bula karrmbil yinyay nyajin.", "bula karrmbil yinyay nyajin | 3du.NOM karrmbil.parakeet there see.PST", "r346_3du_there_bird_d_saw_karrmbil", ["bula", "karrmbil", "yinyay", "nyajin"]),
    row(34, "The two of them saw the yiki owl there.", "Bula yiki yinyay nyajin.", "bula yiki yinyay nyajin | 3du.NOM yiki.owl there see.PST", "r346_3du_there_bird_d_saw_yiki", ["bula", "yiki", "yinyay", "nyajin"]),
    row(35, "The two of them saw the bajal bower bird there.", "Bula bajal yinyay nyajin.", "bula bajal yinyay nyajin | 3du.NOM bajal.bower-bird there see.PST", "r346_3du_there_bird_d_saw_bajal", ["bula", "bajal", "yinyay", "nyajin"]),
    row(36, "The two of them saw the biru-biru rainbow bird there.", "Bula biru-biru yinyay nyajin.", "bula biru-biru yinyay nyajin | 3du.NOM biru-biru.rainbow-bird there see.PST", "r346_3du_there_bird_d_saw_biru_biru", ["bula", "biru-biru", "yinyay", "nyajin"]),
    row(37, "The two of them saw the jangkan swift there.", "Bula jangkan yinyay nyajin.", "bula jangkan yinyay nyajin | 3du.NOM jangkan.swift there see.PST", "r346_3du_there_bird_d_saw_jangkan", ["bula", "jangkan", "yinyay", "nyajin"]),
    row(38, "The two of them saw the kurranday seagull there.", "Bula kurranday yinyay nyajin.", "bula kurranday yinyay nyajin | 3du.NOM kurranday.seagull there see.PST", "r346_3du_there_bird_d_saw_kurranday", ["bula", "kurranday", "yinyay", "nyajin"]),
    row(39, "The two of them saw the ngarkay waterhen there.", "Bula ngarkay yinyay nyajin.", "bula ngarkay yinyay nyajin | 3du.NOM ngarkay.waterhen there see.PST", "r346_3du_there_bird_d_saw_ngarkay", ["bula", "ngarkay", "yinyay", "nyajin"]),
    row(40, "The two of them saw the wakuka bird there.", "Bula wakuka yinyay nyajin.", "bula wakuka yinyay nyajin | 3du.NOM wakuka.bird there see.PST", "r346_3du_there_bird_d_saw_wakuka", ["bula", "wakuka", "yinyay", "nyajin"]),
    row(41, "They saw the diwalngku bird here.", "Jana diwalngku yaluy nyajin.", "jana diwalngku yaluy nyajin | 3pl.NOM diwalngku.bird here see.PST", "r346_3pl_here_bird_d_saw_diwalngku", ["jana", "diwalngku", "yaluy", "nyajin"]),
    row(42, "They saw the bujil-yiran ibis here.", "Jana bujil-yiran yaluy nyajin.", "jana bujil-yiran yaluy nyajin | 3pl.NOM bujil-yiran.ibis here see.PST", "r346_3pl_here_bird_d_saw_bujil_yiran", ["jana", "bujil-yiran", "yaluy", "nyajin"]),
    row(43, "They saw the karrmbil parakeet here.", "Jana karrmbil yaluy nyajin.", "jana karrmbil yaluy nyajin | 3pl.NOM karrmbil.parakeet here see.PST", "r346_3pl_here_bird_d_saw_karrmbil", ["jana", "karrmbil", "yaluy", "nyajin"]),
    row(44, "They saw the yiki owl here.", "Jana yiki yaluy nyajin.", "jana yiki yaluy nyajin | 3pl.NOM yiki.owl here see.PST", "r346_3pl_here_bird_d_saw_yiki", ["jana", "yiki", "yaluy", "nyajin"]),
    row(45, "They saw the bajal bower bird here.", "Jana bajal yaluy nyajin.", "jana bajal yaluy nyajin | 3pl.NOM bajal.bower-bird here see.PST", "r346_3pl_here_bird_d_saw_bajal", ["jana", "bajal", "yaluy", "nyajin"]),
    row(46, "They saw the biru-biru rainbow bird here.", "Jana biru-biru yaluy nyajin.", "jana biru-biru yaluy nyajin | 3pl.NOM biru-biru.rainbow-bird here see.PST", "r346_3pl_here_bird_d_saw_biru_biru", ["jana", "biru-biru", "yaluy", "nyajin"]),
    row(47, "They saw the jangkan swift here.", "Jana jangkan yaluy nyajin.", "jana jangkan yaluy nyajin | 3pl.NOM jangkan.swift here see.PST", "r346_3pl_here_bird_d_saw_jangkan", ["jana", "jangkan", "yaluy", "nyajin"]),
    row(48, "They saw the kurranday seagull here.", "Jana kurranday yaluy nyajin.", "jana kurranday yaluy nyajin | 3pl.NOM kurranday.seagull here see.PST", "r346_3pl_here_bird_d_saw_kurranday", ["jana", "kurranday", "yaluy", "nyajin"]),
    row(49, "They saw the ngarkay waterhen here.", "Jana ngarkay yaluy nyajin.", "jana ngarkay yaluy nyajin | 3pl.NOM ngarkay.waterhen here see.PST", "r346_3pl_here_bird_d_saw_ngarkay", ["jana", "ngarkay", "yaluy", "nyajin"]),
    row(50, "They saw the wakuka bird here.", "Jana wakuka yaluy nyajin.", "jana wakuka yaluy nyajin | 3pl.NOM wakuka.bird here see.PST", "r346_3pl_here_bird_d_saw_wakuka", ["jana", "wakuka", "yaluy", "nyajin"]),
]


def source_audit() -> tuple[dict[str, int], dict[str, int]]:
    hashes = {path: hashlib.sha256(open(path, "rb").read()).hexdigest() for path in EXPECTED_HASHES}
    if hashes != EXPECTED_HASHES:
        raise SystemExit(f"source hash changed: {hashes}")
    entries = yaml.safe_load(open(DICTIONARY, encoding="utf-8"))["words"]
    counts = {target: sum(entry.get("word") == target for entry in entries) for target in TARGET_ORDER}
    if counts != RECORD_COUNTS:
        raise SystemExit(f"record inventory changed: {counts}")
    for target in TARGET_ORDER:
        records = [entry for entry in entries if entry.get("word") == target]
        if not any(entry.get("semantic_domain") == "fauna-bird" for entry in records):
            raise SystemExit(f"no fauna-bird record for {target}")
    domains = {entry.get("semantic_domain") for entry in entries if entry.get("word") == "bujil-yiran"}
    if domains != {"cognition-emotion", "fauna-bird"}:
        raise SystemExit(f"bujil-yiran cards changed: {domains}")
    wakuka_glosses = {entry.get("gloss") for entry in entries if entry.get("word") == "wakuka"}
    if wakuka_glosses != {"crow", "kookaburra"}:
        raise SystemExit(f"wakuka record conflict changed: {wakuka_glosses}")
    grammar = open(GRAMMAR, encoding="utf-8").read().lower()
    hit_counts = {
        target: len(re.findall(rf"(?<![a-z]){re.escape(target)}(?![a-z])", grammar))
        for target in TARGET_ORDER
    }
    if hit_counts != PATZ_HIT_COUNTS:
        raise SystemExit(f"Patz target-hit profile changed: {hit_counts}")
    return counts, hit_counts


def scan_static() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"row count {len(ROWS)} != 50")
    if [item[0] for item in ROWS] != list(range(1, 51)):
        issues.append("sequence is not 1..50")
    if len({item[1] for item in ROWS}) != 50 or len({item[2] for item in ROWS}) != 50:
        issues.append("duplicate authored surface")
    for seq, english, kuku, analysis, frame, _tier, words_json, loans_json, evidence, confidence in ROWS:
        band = (seq - 1) // 10
        target = TARGET_ORDER[(seq - 1) % 10]
        pronoun, demo, prefix, location, person = FRAME_SPECS[band]
        words = json.loads(words_json)
        expected_kuku = f"{pronoun.capitalize()} {target} {demo} nyajin."
        expected_english = f"{prefix} the {TARGET_LABELS[target]} {location}."
        expected_analysis = f"{pronoun} {target} {demo} nyajin | {person}.NOM {TARGET_GLOSSES[target]} {location} see.PST"
        if json.loads(loans_json):
            issues.append(f"{seq}: loans")
        denied = sorted(word for word in DENY_KUKU if re.search(rf"(^|\s){re.escape(word)}([\s\.]|$)", kuku.lower()))
        if denied:
            issues.append(f"{seq}: denied Kuku {denied}")
        drift = [term for term in DENY_ENGLISH if re.search(rf"\b{re.escape(term)}\b", english.lower())]
        if drift:
            issues.append(f"{seq}: English drift {drift}")
        if kuku != expected_kuku:
            issues.append(f"{seq}: Kuku mismatch")
        if english != expected_english:
            issues.append(f"{seq}: English mismatch")
        if analysis != expected_analysis:
            issues.append(f"{seq}: analysis mismatch")
        if words != [pronoun, target, demo, "nyajin"]:
            issues.append(f"{seq}: words mismatch")
        if kuku.lower().rstrip(".") != " ".join(words):
            issues.append(f"{seq}: surface/words mismatch")
        if not frame.startswith("r346_"):
            issues.append(f"{seq}: frame mismatch")
        if TARGET_EVIDENCE[target] not in evidence or SUPPORT_EVIDENCE not in evidence:
            issues.append(f"{seq}: evidence mismatch")
        if confidence != TARGET_CONFIDENCE[target]:
            issues.append(f"{seq}: confidence mismatch")
    return issues


def main() -> int:
    record_counts, patz_hits = source_audit()
    issues = scan_static()
    if issues:
        print("\n".join(issues))
        raise SystemExit(1)
    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys=ON")
    db.execute("BEGIN")
    ensure_schema(db)
    if db.execute("SELECT 1 FROM batches WHERE label=?", (LABEL,)).fetchone():
        raise SystemExit(f"{LABEL} exists")
    if db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]:
        raise SystemExit("another batch is under review")
    target_coverage = count_words(db, TARGET_ORDER)
    support_coverage = count_words(db, list(EXPECTED_SUPPORT_COVERAGE))
    if target_coverage != EXPECTED_TARGET_COVERAGE:
        raise SystemExit(f"target coverage changed: {target_coverage}")
    if support_coverage != EXPECTED_SUPPORT_COVERAGE:
        raise SystemExit(f"support coverage changed: {support_coverage}")
    collisions = []
    for pronoun in [spec[0] for spec in FRAME_SPECS]:
        for target in TARGET_ORDER:
            for demo in ("yaluy", "yinyay"):
                surface = f"{pronoun.capitalize()} {target} {demo} nyajin."
                hits = db.execute(
                    """SELECT b.label,s.seq FROM sentences s JOIN batches b ON b.id=s.batch_id
                       WHERE lower(s.kuku)=lower(?) AND s.status!='rejected' ORDER BY b.id,s.seq""",
                    (surface,),
                ).fetchall()
                if hits:
                    collisions.append((surface, hits))
    if collisions:
        raise SystemExit(f"100-cell matrix changed: {collisions}")
    verified, words = db.execute(
        """SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
           FROM sentences WHERE status IN ('accepted','revised')"""
    ).fetchone()
    if (verified, words) != (19447, 133147):
        raise SystemExit(f"baseline changed {verified}/{words}")
    notes = (
        "UNDERREPRESENTED BIRD LEXEME-SET-D SINGULAR/THIRD PAST PERCEPTION AFTER RULE 345 (Rule 346). "
        "50 under-review rows over ten 5-7-use heads. All targets have two exact cards; Patz exact target scan "
        "found wakuka only (3 hits: 2 bird, 1 place), without turning zero hits into negative lexical claims. Generic labels preserve "
        "unknown/conflicting taxonomy, empty cognition card, whole reduplication, cultural belief, property, habitat, "
        "source and event boundaries. All 100 cells empty; selected gap 172->162."
    )
    cur = db.execute(
        "INSERT INTO batches(label,cheatsheet_version,guide_version,notes,created_at) VALUES(?,?,?,?,?)",
        (LABEL, "Rule 346 under review", "operator-guide-2026-07-10", notes, NOW),
    )
    batch_id = cur.lastrowid
    db.executemany(
        """INSERT INTO sentences(
             batch_id,seq,english,kuku,analysis,frame,tier,words_used,loans_used,evidence,status,confidence,
             rights_status,author,created_at,updated_at
           ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
        [
            (
                batch_id, seq, english, kuku, analysis, frame, tier, words_json, loans_json, evidence,
                "under_review", confidence, "project_approved_synthetic_pending_elder_verification",
                "codex-main", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_json, loans_json, evidence, confidence in ROWS
        ],
    )
    events = [
        ("source", "source-hash-pass", f"Dictionary and Patz hashes match Rule 346 expectations: {EXPECTED_HASHES}."),
        ("source", "source-readback", f"Fresh exact-record inventory passed: {record_counts}; all ten target cards reread."),
        ("source", "patz-target-hit-audit", f"Exact Patz target-hit profile passed: {patz_hits}; zero hits recorded without negative lexical inference."),
        ("planning", "record-sense-audit", "Generic labels preserve diwalngku unknown species, bujil-yiran card split, karrmbil taxonomy, yiki/bajal duplicates, whole biru-biru, jangkan/kurranday/ngarkay duplicates and wakuka conflict/cultural belief."),
        ("planning", "construction-gap-audit", "All 100 singular/third-pronoun x target x deictic past cells absent; both orientations internally unique and collision-free."),
        ("planning", "deictic-balance-audit", "Before banking yaluy=4532 and yinyay=4704, gap 172; selected three-here/two-there projects 162; inverse 182."),
        ("preflight", "coverage-audit", f"Coverage before banking: targets={target_coverage}; support={support_coverage}."),
        ("preflight", "duplicate-preflight", "All 50 selected Kuku and English surfaces internally unique and absent; complete 100-cell matrix empty."),
        ("preflight", "static-guard-pass", "50 explicit Rule 346 rows; source, record/sense, morphology, frame, words, gloss, evidence, confidence, loan and exclusion contracts passed."),
    ]
    for phase, event_type, detail in events:
        record_event(db, batch_label=LABEL, phase=phase, event_type=event_type, actor="codex", detail=detail, artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(
        db, batch_label=LABEL, phase="bank", event_type="bank-success", actor="codex",
        detail=f"Banked 50 Rule 346 bird-set-D rows under_review; verified remains {verified}; words {words}.",
        command="python3 batch_scripts/bank_388.py", output_excerpt="50 under_review", artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    db.commit()
    print("banked batch-388: 50 rows under_review")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
