#!/usr/bin/env python3
"""Bank Batch 392: Rule 350 low-coverage bird-set-E singular/third past perception."""

from __future__ import annotations

import ast
import datetime as dt
import hashlib
import json
from pathlib import Path
import re
import sqlite3

import yaml

from process_ledger import ensure_schema, record_event

ROOT = "/mnt/donto-data/donto-resources/research/translation-training/kuku-yalanji-runpod-2026-06-30"
DB = f"{ROOT}/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-392"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-392-low-coverage-bird-set-e-singular-third-past-perception-after-rule349-process.md"
EXPECTED_HASHES = {
    DICTIONARY: "b575ab035ecc60f09f7bfc5537116555f3d290869fe75b2edf0fd8db60fe1b37",
    GRAMMAR: "5f21305f1fca9ee2cb64668e18156d1f322e3bd9e59850fda662bf0673714153",
}
TARGET_ORDER = [
    "dakwurr-dakwun", "kurkunbay", "kurrujuwa", "mirrku", "baringkan",
    "barinkan", "bilu-warra", "dukunjulu", "jiwurrmal", "kujuway",
]
EXPECTED_RECORD_COUNTS = {
    "dakwurr-dakwun": 2, "kurkunbay": 1, "kurrujuwa": 2, "mirrku": 1, "baringkan": 1,
    "barinkan": 1, "bilu-warra": 1, "dukunjulu": 1, "jiwurrmal": 2, "kujuway": 2,
}
REQUIRED_SOURCE_TERMS = {
    "dakwurr-dakwun": "hammer bird", "kurkunbay": "butcherbird", "kurrujuwa": "sentry bird",
    "mirrku": "kidney bird", "baringkan": "species of small bird", "barinkan": "scrub bird",
    "bilu-warra": "spoon bill", "dukunjulu": "golden whistler", "jiwurrmal": "pheasant cuckoo",
    "kujuway": "pigeon",
}
TARGET_LABELS = {
    "dakwurr-dakwun": "dakwurr-dakwun hammer bird", "kurkunbay": "kurkunbay butcherbird",
    "kurrujuwa": "kurrujuwa sentry bird", "mirrku": "mirrku kidney bird",
    "baringkan": "baringkan small bird", "barinkan": "barinkan scrub bird",
    "bilu-warra": "bilu-warra spoonbill", "dukunjulu": "dukunjulu golden whistler",
    "jiwurrmal": "jiwurrmal pheasant cuckoo", "kujuway": "kujuway pigeon",
}
TARGET_GLOSSES = {
    "dakwurr-dakwun": "dakwurr-dakwun.hammer-bird", "kurkunbay": "kurkunbay.butcherbird",
    "kurrujuwa": "kurrujuwa.sentry-bird", "mirrku": "mirrku.kidney-bird",
    "baringkan": "baringkan.small-bird", "barinkan": "barinkan.scrub-bird",
    "bilu-warra": "bilu-warra.spoonbill", "dukunjulu": "dukunjulu.golden-whistler",
    "jiwurrmal": "jiwurrmal.pheasant-cuckoo", "kujuway": "kujuway.pigeon",
}
TARGET_CONFIDENCE = {
    "dakwurr-dakwun": "0.88", "kurkunbay": "0.92", "kurrujuwa": "0.88", "mirrku": "0.86",
    "baringkan": "0.82", "barinkan": "0.90", "bilu-warra": "0.92", "dukunjulu": "0.92",
    "jiwurrmal": "0.88", "kujuway": "0.90",
}
EXPECTED_TARGET_COVERAGE = {
    "dakwurr-dakwun": 0, "kurkunbay": 5, "kurrujuwa": 5, "mirrku": 5, "baringkan": 5,
    "barinkan": 5, "bilu-warra": 5, "dukunjulu": 6, "jiwurrmal": 5, "kujuway": 5,
}
EXPECTED_SUPPORT_COVERAGE = {
    "ngayu": 1501, "yundu": 1097, "nyulu": 772, "bula": 568, "jana": 1273,
    "yaluy": 4632, "yinyay": 4804, "nyajin": 2550,
}
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 = {
    "dakwurr-dakwun": "Dictionary lines 4733-4760 contain two hammer-bird noun cards, one with walarr-moiety totem prose. Rule 350 uses the exact reduplicated whole head and common bird label only; totem, moiety, allomorphy generalization, source, possession and events remain closed.",
    "kurkunbay": "Dictionary lines 16134-16143 contain one butcherbird/stockwhip-bird noun card. Rule 350 uses butcherbird only; alternate label, call, hunting, source, possession and events remain closed.",
    "kurrujuwa": "Dictionary lines 16594-16617 contain two sentry-bird noun cards; one adds crocodile-warning, riverbank and dual-moiety prose. Rule 350 uses sentry bird only; warning function, crocodiles, river habitat, totem/moiety, source, possession and events remain closed.",
    "mirrku": "Dictionary lines 19802-19814 contain one kidney-bird noun card with dabu-moiety totem prose. Rule 350 uses kidney bird only; anatomical inference, totem/moiety, source, possession and events remain closed.",
    "baringkan": "Dictionary lines 1727-1735 contain one small-bird noun card without an English species identification. Rule 350 uses baringkan small bird only; guessed taxonomy, color, behavior, habitat, source, possession and events remain closed.",
    "barinkan": "Dictionary lines 1736-1745 define a scrub bird and include dark-grey/white-breast description. Rule 350 uses scrub bird only; color, anatomy, sex, habitat, source, possession and events remain closed.",
    "bilu-warra": "Dictionary lines 2595-2603 contain one spoonbill noun card. Rule 350 preserves the exact hyphenated whole head and spoonbill label; component analysis, feeding, water habitat, source, possession and events remain closed.",
    "dukunjulu": "Dictionary lines 6413-6422 contain one golden-whistler noun card. Rule 350 uses golden whistler only; song, color, sex, habitat, source, possession and events remain closed.",
    "jiwurrmal": "Dictionary lines 10085-10108 contain two pheasant-cuckoo/cuckoo-pheasant noun cards and an alternate-name note. Rule 350 normalizes the common English label to pheasant cuckoo only; alternate names, record collapse, song, source, possession and events remain closed.",
    "kujuway": "Dictionary lines 14860-14883 contain two pigeon noun cards with brown and brown-green descriptions. Rule 350 uses generic pigeon only; color, record identity, habitat, source, possession and events remain closed.",
}
SUPPORT_EVIDENCE = (
    "Exact Patz-form scan returned zero hits for all ten set-E heads; this is recorded as grammar coverage, not lexical "
    "absence, because the dictionary cards license each noun. Patz 2577-2584 sources bare nominative ngayu, yundu, "
    "nyulu, bula and plural third person 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", "yalay", "yala",
    "yanyu", "yinya", "nyajil", "nyaji-l", "nyaji-nka", "nyaji-l-nyaji", "dakwurr", "dakwun",
    "dawa", "bilu", "warra",
}
DENY_ENGLISH = (
    "totem", "moiety", "omen", "portent", "stranger", "died", "death", "singing", "sings", "message",
    "dark grey", "white breast", "brown and green", "alternate name", "bulbululul", "build", "collect",
    "display", "habitat", "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 source_audit() -> tuple[dict[str, int], dict[str, int]]:
    for path, expected in EXPECTED_HASHES.items():
        actual = hashlib.sha256(Path(path).read_bytes()).hexdigest()
        if actual != expected:
            raise SystemExit(f"source hash changed {path}: {actual}")
    records = yaml.safe_load(Path(DICTIONARY).read_text(encoding="utf-8"))["words"]
    counts: dict[str, int] = {}
    for target in TARGET_ORDER:
        selected = [record for record in records if record.get("word") == target]
        counts[target] = len(selected)
        if len(selected) != EXPECTED_RECORD_COUNTS[target]:
            raise SystemExit(f"record count changed {target}: {len(selected)}")
        if any(record.get("type") != "noun" or record.get("loanword") for record in selected):
            raise SystemExit(f"type/loan drift {target}")
        joined = json.dumps(selected, ensure_ascii=False).lower()
        if REQUIRED_SOURCE_TERMS[target] not in joined:
            raise SystemExit(f"source term missing {target}")
        if not any(record.get("semantic_domain") == "fauna-bird" for record in selected):
            raise SystemExit(f"bird domain missing {target}")
    grammar = Path(GRAMMAR).read_text(encoding="utf-8")
    hits = {
        target: len(re.findall(rf"(?<![A-Za-z-]){re.escape(target)}(?![A-Za-z-])", grammar, flags=re.IGNORECASE))
        for target in TARGET_ORDER
    }
    if any(hits.values()):
        raise SystemExit(f"Patz hit profile changed {hits}")
    return counts, hits


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 dakwurr-dakwun hammer bird here.", "Ngayu dakwurr-dakwun yaluy nyajin.", "ngayu dakwurr-dakwun yaluy nyajin | 1sg.NOM dakwurr-dakwun.hammer-bird here see.PST", "r350_1sg_here_bird_e_saw_dakwurr_dakwun", ["ngayu", "dakwurr-dakwun", "yaluy", "nyajin"]),
    row(2, "I saw the kurkunbay butcherbird here.", "Ngayu kurkunbay yaluy nyajin.", "ngayu kurkunbay yaluy nyajin | 1sg.NOM kurkunbay.butcherbird here see.PST", "r350_1sg_here_bird_e_saw_kurkunbay", ["ngayu", "kurkunbay", "yaluy", "nyajin"]),
    row(3, "I saw the kurrujuwa sentry bird here.", "Ngayu kurrujuwa yaluy nyajin.", "ngayu kurrujuwa yaluy nyajin | 1sg.NOM kurrujuwa.sentry-bird here see.PST", "r350_1sg_here_bird_e_saw_kurrujuwa", ["ngayu", "kurrujuwa", "yaluy", "nyajin"]),
    row(4, "I saw the mirrku kidney bird here.", "Ngayu mirrku yaluy nyajin.", "ngayu mirrku yaluy nyajin | 1sg.NOM mirrku.kidney-bird here see.PST", "r350_1sg_here_bird_e_saw_mirrku", ["ngayu", "mirrku", "yaluy", "nyajin"]),
    row(5, "I saw the baringkan small bird here.", "Ngayu baringkan yaluy nyajin.", "ngayu baringkan yaluy nyajin | 1sg.NOM baringkan.small-bird here see.PST", "r350_1sg_here_bird_e_saw_baringkan", ["ngayu", "baringkan", "yaluy", "nyajin"]),
    row(6, "I saw the barinkan scrub bird here.", "Ngayu barinkan yaluy nyajin.", "ngayu barinkan yaluy nyajin | 1sg.NOM barinkan.scrub-bird here see.PST", "r350_1sg_here_bird_e_saw_barinkan", ["ngayu", "barinkan", "yaluy", "nyajin"]),
    row(7, "I saw the bilu-warra spoonbill here.", "Ngayu bilu-warra yaluy nyajin.", "ngayu bilu-warra yaluy nyajin | 1sg.NOM bilu-warra.spoonbill here see.PST", "r350_1sg_here_bird_e_saw_bilu_warra", ["ngayu", "bilu-warra", "yaluy", "nyajin"]),
    row(8, "I saw the dukunjulu golden whistler here.", "Ngayu dukunjulu yaluy nyajin.", "ngayu dukunjulu yaluy nyajin | 1sg.NOM dukunjulu.golden-whistler here see.PST", "r350_1sg_here_bird_e_saw_dukunjulu", ["ngayu", "dukunjulu", "yaluy", "nyajin"]),
    row(9, "I saw the jiwurrmal pheasant cuckoo here.", "Ngayu jiwurrmal yaluy nyajin.", "ngayu jiwurrmal yaluy nyajin | 1sg.NOM jiwurrmal.pheasant-cuckoo here see.PST", "r350_1sg_here_bird_e_saw_jiwurrmal", ["ngayu", "jiwurrmal", "yaluy", "nyajin"]),
    row(10, "I saw the kujuway pigeon here.", "Ngayu kujuway yaluy nyajin.", "ngayu kujuway yaluy nyajin | 1sg.NOM kujuway.pigeon here see.PST", "r350_1sg_here_bird_e_saw_kujuway", ["ngayu", "kujuway", "yaluy", "nyajin"]),
    row(11, "You saw the dakwurr-dakwun hammer bird there.", "Yundu dakwurr-dakwun yinyay nyajin.", "yundu dakwurr-dakwun yinyay nyajin | 2sg.NOM dakwurr-dakwun.hammer-bird there see.PST", "r350_2sg_there_bird_e_saw_dakwurr_dakwun", ["yundu", "dakwurr-dakwun", "yinyay", "nyajin"]),
    row(12, "You saw the kurkunbay butcherbird there.", "Yundu kurkunbay yinyay nyajin.", "yundu kurkunbay yinyay nyajin | 2sg.NOM kurkunbay.butcherbird there see.PST", "r350_2sg_there_bird_e_saw_kurkunbay", ["yundu", "kurkunbay", "yinyay", "nyajin"]),
    row(13, "You saw the kurrujuwa sentry bird there.", "Yundu kurrujuwa yinyay nyajin.", "yundu kurrujuwa yinyay nyajin | 2sg.NOM kurrujuwa.sentry-bird there see.PST", "r350_2sg_there_bird_e_saw_kurrujuwa", ["yundu", "kurrujuwa", "yinyay", "nyajin"]),
    row(14, "You saw the mirrku kidney bird there.", "Yundu mirrku yinyay nyajin.", "yundu mirrku yinyay nyajin | 2sg.NOM mirrku.kidney-bird there see.PST", "r350_2sg_there_bird_e_saw_mirrku", ["yundu", "mirrku", "yinyay", "nyajin"]),
    row(15, "You saw the baringkan small bird there.", "Yundu baringkan yinyay nyajin.", "yundu baringkan yinyay nyajin | 2sg.NOM baringkan.small-bird there see.PST", "r350_2sg_there_bird_e_saw_baringkan", ["yundu", "baringkan", "yinyay", "nyajin"]),
    row(16, "You saw the barinkan scrub bird there.", "Yundu barinkan yinyay nyajin.", "yundu barinkan yinyay nyajin | 2sg.NOM barinkan.scrub-bird there see.PST", "r350_2sg_there_bird_e_saw_barinkan", ["yundu", "barinkan", "yinyay", "nyajin"]),
    row(17, "You saw the bilu-warra spoonbill there.", "Yundu bilu-warra yinyay nyajin.", "yundu bilu-warra yinyay nyajin | 2sg.NOM bilu-warra.spoonbill there see.PST", "r350_2sg_there_bird_e_saw_bilu_warra", ["yundu", "bilu-warra", "yinyay", "nyajin"]),
    row(18, "You saw the dukunjulu golden whistler there.", "Yundu dukunjulu yinyay nyajin.", "yundu dukunjulu yinyay nyajin | 2sg.NOM dukunjulu.golden-whistler there see.PST", "r350_2sg_there_bird_e_saw_dukunjulu", ["yundu", "dukunjulu", "yinyay", "nyajin"]),
    row(19, "You saw the jiwurrmal pheasant cuckoo there.", "Yundu jiwurrmal yinyay nyajin.", "yundu jiwurrmal yinyay nyajin | 2sg.NOM jiwurrmal.pheasant-cuckoo there see.PST", "r350_2sg_there_bird_e_saw_jiwurrmal", ["yundu", "jiwurrmal", "yinyay", "nyajin"]),
    row(20, "You saw the kujuway pigeon there.", "Yundu kujuway yinyay nyajin.", "yundu kujuway yinyay nyajin | 2sg.NOM kujuway.pigeon there see.PST", "r350_2sg_there_bird_e_saw_kujuway", ["yundu", "kujuway", "yinyay", "nyajin"]),
    row(21, "He saw the dakwurr-dakwun hammer bird here.", "Nyulu dakwurr-dakwun yaluy nyajin.", "nyulu dakwurr-dakwun yaluy nyajin | 3sg.NOM dakwurr-dakwun.hammer-bird here see.PST", "r350_3sg_here_bird_e_saw_dakwurr_dakwun", ["nyulu", "dakwurr-dakwun", "yaluy", "nyajin"]),
    row(22, "He saw the kurkunbay butcherbird here.", "Nyulu kurkunbay yaluy nyajin.", "nyulu kurkunbay yaluy nyajin | 3sg.NOM kurkunbay.butcherbird here see.PST", "r350_3sg_here_bird_e_saw_kurkunbay", ["nyulu", "kurkunbay", "yaluy", "nyajin"]),
    row(23, "He saw the kurrujuwa sentry bird here.", "Nyulu kurrujuwa yaluy nyajin.", "nyulu kurrujuwa yaluy nyajin | 3sg.NOM kurrujuwa.sentry-bird here see.PST", "r350_3sg_here_bird_e_saw_kurrujuwa", ["nyulu", "kurrujuwa", "yaluy", "nyajin"]),
    row(24, "He saw the mirrku kidney bird here.", "Nyulu mirrku yaluy nyajin.", "nyulu mirrku yaluy nyajin | 3sg.NOM mirrku.kidney-bird here see.PST", "r350_3sg_here_bird_e_saw_mirrku", ["nyulu", "mirrku", "yaluy", "nyajin"]),
    row(25, "He saw the baringkan small bird here.", "Nyulu baringkan yaluy nyajin.", "nyulu baringkan yaluy nyajin | 3sg.NOM baringkan.small-bird here see.PST", "r350_3sg_here_bird_e_saw_baringkan", ["nyulu", "baringkan", "yaluy", "nyajin"]),
    row(26, "He saw the barinkan scrub bird here.", "Nyulu barinkan yaluy nyajin.", "nyulu barinkan yaluy nyajin | 3sg.NOM barinkan.scrub-bird here see.PST", "r350_3sg_here_bird_e_saw_barinkan", ["nyulu", "barinkan", "yaluy", "nyajin"]),
    row(27, "He saw the bilu-warra spoonbill here.", "Nyulu bilu-warra yaluy nyajin.", "nyulu bilu-warra yaluy nyajin | 3sg.NOM bilu-warra.spoonbill here see.PST", "r350_3sg_here_bird_e_saw_bilu_warra", ["nyulu", "bilu-warra", "yaluy", "nyajin"]),
    row(28, "He saw the dukunjulu golden whistler here.", "Nyulu dukunjulu yaluy nyajin.", "nyulu dukunjulu yaluy nyajin | 3sg.NOM dukunjulu.golden-whistler here see.PST", "r350_3sg_here_bird_e_saw_dukunjulu", ["nyulu", "dukunjulu", "yaluy", "nyajin"]),
    row(29, "He saw the jiwurrmal pheasant cuckoo here.", "Nyulu jiwurrmal yaluy nyajin.", "nyulu jiwurrmal yaluy nyajin | 3sg.NOM jiwurrmal.pheasant-cuckoo here see.PST", "r350_3sg_here_bird_e_saw_jiwurrmal", ["nyulu", "jiwurrmal", "yaluy", "nyajin"]),
    row(30, "He saw the kujuway pigeon here.", "Nyulu kujuway yaluy nyajin.", "nyulu kujuway yaluy nyajin | 3sg.NOM kujuway.pigeon here see.PST", "r350_3sg_here_bird_e_saw_kujuway", ["nyulu", "kujuway", "yaluy", "nyajin"]),
    row(31, "The two of them saw the dakwurr-dakwun hammer bird there.", "Bula dakwurr-dakwun yinyay nyajin.", "bula dakwurr-dakwun yinyay nyajin | 3du.NOM dakwurr-dakwun.hammer-bird there see.PST", "r350_3du_there_bird_e_saw_dakwurr_dakwun", ["bula", "dakwurr-dakwun", "yinyay", "nyajin"]),
    row(32, "The two of them saw the kurkunbay butcherbird there.", "Bula kurkunbay yinyay nyajin.", "bula kurkunbay yinyay nyajin | 3du.NOM kurkunbay.butcherbird there see.PST", "r350_3du_there_bird_e_saw_kurkunbay", ["bula", "kurkunbay", "yinyay", "nyajin"]),
    row(33, "The two of them saw the kurrujuwa sentry bird there.", "Bula kurrujuwa yinyay nyajin.", "bula kurrujuwa yinyay nyajin | 3du.NOM kurrujuwa.sentry-bird there see.PST", "r350_3du_there_bird_e_saw_kurrujuwa", ["bula", "kurrujuwa", "yinyay", "nyajin"]),
    row(34, "The two of them saw the mirrku kidney bird there.", "Bula mirrku yinyay nyajin.", "bula mirrku yinyay nyajin | 3du.NOM mirrku.kidney-bird there see.PST", "r350_3du_there_bird_e_saw_mirrku", ["bula", "mirrku", "yinyay", "nyajin"]),
    row(35, "The two of them saw the baringkan small bird there.", "Bula baringkan yinyay nyajin.", "bula baringkan yinyay nyajin | 3du.NOM baringkan.small-bird there see.PST", "r350_3du_there_bird_e_saw_baringkan", ["bula", "baringkan", "yinyay", "nyajin"]),
    row(36, "The two of them saw the barinkan scrub bird there.", "Bula barinkan yinyay nyajin.", "bula barinkan yinyay nyajin | 3du.NOM barinkan.scrub-bird there see.PST", "r350_3du_there_bird_e_saw_barinkan", ["bula", "barinkan", "yinyay", "nyajin"]),
    row(37, "The two of them saw the bilu-warra spoonbill there.", "Bula bilu-warra yinyay nyajin.", "bula bilu-warra yinyay nyajin | 3du.NOM bilu-warra.spoonbill there see.PST", "r350_3du_there_bird_e_saw_bilu_warra", ["bula", "bilu-warra", "yinyay", "nyajin"]),
    row(38, "The two of them saw the dukunjulu golden whistler there.", "Bula dukunjulu yinyay nyajin.", "bula dukunjulu yinyay nyajin | 3du.NOM dukunjulu.golden-whistler there see.PST", "r350_3du_there_bird_e_saw_dukunjulu", ["bula", "dukunjulu", "yinyay", "nyajin"]),
    row(39, "The two of them saw the jiwurrmal pheasant cuckoo there.", "Bula jiwurrmal yinyay nyajin.", "bula jiwurrmal yinyay nyajin | 3du.NOM jiwurrmal.pheasant-cuckoo there see.PST", "r350_3du_there_bird_e_saw_jiwurrmal", ["bula", "jiwurrmal", "yinyay", "nyajin"]),
    row(40, "The two of them saw the kujuway pigeon there.", "Bula kujuway yinyay nyajin.", "bula kujuway yinyay nyajin | 3du.NOM kujuway.pigeon there see.PST", "r350_3du_there_bird_e_saw_kujuway", ["bula", "kujuway", "yinyay", "nyajin"]),
    row(41, "They saw the dakwurr-dakwun hammer bird here.", "Jana dakwurr-dakwun yaluy nyajin.", "jana dakwurr-dakwun yaluy nyajin | 3pl.NOM dakwurr-dakwun.hammer-bird here see.PST", "r350_3pl_here_bird_e_saw_dakwurr_dakwun", ["jana", "dakwurr-dakwun", "yaluy", "nyajin"]),
    row(42, "They saw the kurkunbay butcherbird here.", "Jana kurkunbay yaluy nyajin.", "jana kurkunbay yaluy nyajin | 3pl.NOM kurkunbay.butcherbird here see.PST", "r350_3pl_here_bird_e_saw_kurkunbay", ["jana", "kurkunbay", "yaluy", "nyajin"]),
    row(43, "They saw the kurrujuwa sentry bird here.", "Jana kurrujuwa yaluy nyajin.", "jana kurrujuwa yaluy nyajin | 3pl.NOM kurrujuwa.sentry-bird here see.PST", "r350_3pl_here_bird_e_saw_kurrujuwa", ["jana", "kurrujuwa", "yaluy", "nyajin"]),
    row(44, "They saw the mirrku kidney bird here.", "Jana mirrku yaluy nyajin.", "jana mirrku yaluy nyajin | 3pl.NOM mirrku.kidney-bird here see.PST", "r350_3pl_here_bird_e_saw_mirrku", ["jana", "mirrku", "yaluy", "nyajin"]),
    row(45, "They saw the baringkan small bird here.", "Jana baringkan yaluy nyajin.", "jana baringkan yaluy nyajin | 3pl.NOM baringkan.small-bird here see.PST", "r350_3pl_here_bird_e_saw_baringkan", ["jana", "baringkan", "yaluy", "nyajin"]),
    row(46, "They saw the barinkan scrub bird here.", "Jana barinkan yaluy nyajin.", "jana barinkan yaluy nyajin | 3pl.NOM barinkan.scrub-bird here see.PST", "r350_3pl_here_bird_e_saw_barinkan", ["jana", "barinkan", "yaluy", "nyajin"]),
    row(47, "They saw the bilu-warra spoonbill here.", "Jana bilu-warra yaluy nyajin.", "jana bilu-warra yaluy nyajin | 3pl.NOM bilu-warra.spoonbill here see.PST", "r350_3pl_here_bird_e_saw_bilu_warra", ["jana", "bilu-warra", "yaluy", "nyajin"]),
    row(48, "They saw the dukunjulu golden whistler here.", "Jana dukunjulu yaluy nyajin.", "jana dukunjulu yaluy nyajin | 3pl.NOM dukunjulu.golden-whistler here see.PST", "r350_3pl_here_bird_e_saw_dukunjulu", ["jana", "dukunjulu", "yaluy", "nyajin"]),
    row(49, "They saw the jiwurrmal pheasant cuckoo here.", "Jana jiwurrmal yaluy nyajin.", "jana jiwurrmal yaluy nyajin | 3pl.NOM jiwurrmal.pheasant-cuckoo here see.PST", "r350_3pl_here_bird_e_saw_jiwurrmal", ["jana", "jiwurrmal", "yaluy", "nyajin"]),
    row(50, "They saw the kujuway pigeon here.", "Jana kujuway yaluy nyajin.", "jana kujuway yaluy nyajin | 3pl.NOM kujuway.pigeon here see.PST", "r350_3pl_here_bird_e_saw_kujuway", ["jana", "kujuway", "yaluy", "nyajin"]),
]


def literal_row_guard() -> list[str]:
    module = ast.parse(Path(__file__).read_text(encoding="utf-8"))
    assignments = [node for node in module.body if isinstance(node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == "ROWS" for t in node.targets)]
    if len(assignments) != 1 or not isinstance(assignments[0].value, ast.List):
        return ["ROWS is not one literal list"]
    elements = assignments[0].value.elts
    issues = [] if len(elements) == 50 else [f"literal row count {len(elements)}"]
    for index, element in enumerate(elements, 1):
        if not isinstance(element, ast.Call) or not isinstance(element.func, ast.Name) or element.func.id != "row" or element.keywords:
            issues.append(f"{index}: not direct row call")
            continue
        try:
            values = [ast.literal_eval(arg) for arg in element.args]
        except (ValueError, TypeError):
            issues.append(f"{index}: nonliteral argument")
            continue
        if len(values) != 6 or values[0] != index:
            issues.append(f"{index}: argument/sequence mismatch")
    return issues


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


def main() -> int:
    record_counts, patz_hits = source_audit()
    issues = scan_static()
    if issues:
        raise SystemExit("\n".join(issues))
    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() or db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]:
        raise SystemExit("batch exists or another batch open")
    targets = count_words(db, TARGET_ORDER); support = count_words(db, list(EXPECTED_SUPPORT_COVERAGE))
    if targets != EXPECTED_TARGET_COVERAGE or support != EXPECTED_SUPPORT_COVERAGE:
        raise SystemExit(f"coverage changed {targets} {support}")
    participants = ["ngayu", "yundu", "nyulu", "bula", "jana", "ngali", "ngaliny", "yubal", "yurra", "nganjin"]
    collisions = []
    for participant in participants:
        for target in TARGET_ORDER:
            for demo in ("yaluy", "yinyay"):
                surface = f"{participant.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'", (surface,)).fetchall()
                if hits: collisions.append((surface, hits))
    if collisions:
        raise SystemExit(f"200-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) != (19647, 133947):
        raise SystemExit(f"baseline {verified}/{words}")
    notes = "LOW-COVERAGE BIRD SET-E SINGULAR/THIRD PAST PERCEPTION AFTER RULE 349 (Rule 350). 50 under-review rows; fresh hashes/cards/Patz and empty 200-cell audit passed; all cultural/taxonomic/property/event boundaries closed; gap 172->162."
    batch_id = db.execute("INSERT INTO batches(label,cheatsheet_version,guide_version,notes,created_at) VALUES(?,?,?,?,?)", (LABEL, "Rule 350 under review", "operator-guide-2026-07-10", notes, NOW)).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,en,ku,an,fr,tier,w,loans,ev,"under_review",conf,"project_approved_synthetic_pending_elder_verification","codex-main",NOW,NOW) for seq,en,ku,an,fr,tier,w,loans,ev,conf in ROWS])
    events = [
        ("source", "source-audit-pass", f"Hashes exact; record counts={record_counts}; Patz hits={patz_hits}."),
        ("planning", "sense-boundary-audit", "Common bird labels selected; totem/omen/color/alternate-name/song/behavior/habitat/source/event prose closed."),
        ("planning", "construction-gap-audit", "All 200 singular and non-singular participant x target x deictic past cells absent."),
        ("planning", "balance-audit", "Gap 172; selected three-here/two-there orientation projects 162."),
        ("preflight", "coverage-audit", f"targets={targets}; support={support}."),
        ("preflight", "duplicate-preflight", "50 selected surfaces unique and absent; full future four-batch matrix empty."),
        ("preflight", "static-guard-pass", "AST proved 50 direct literal row calls; all source/sense/frame/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 350 rows under_review; verified {verified}; words {words}.",command="python3 batch_scripts/bank_392.py",output_excerpt="50 under_review",artifact_path=PROCESS_LOG,created_at=NOW)
    db.commit(); print("banked batch-392: 50 rows under_review"); return 0


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