#!/usr/bin/env python3
"""Bank batch-315: Rule 273 fire-reference past pronoun perception."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3

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"
LABEL = "batch-315"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-315-fire-reference-past-pronoun-perception-after-rule272-process.md"

TARGET_HEADS = {
    "baya": "fire",
    "jimal": "fire stick",
    "jirramar": "small sticks",
    "kubu": "smoke",
    "kunjin": "fire",
    "miyilkura": "smouldering log",
    "ngalku": "bush fire",
    "ngaluri": "flames",
    "ngunjil": "charcoal",
    "wula": "flames",
}
PRONOUN_SUPPORT = {
    "ngayu": "1sg.NOM",
    "yundu": "2sg.NOM",
    "nyulu": "3sg.NOM",
    "bula": "3du.NOM",
    "jana": "3pl.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajin"}
DENY_KUKU = {
    # Fire-use/action/homograph traps.
    "baya-wayu", "wayu", "waju", "dalngarri", "dama-murnil", "jakalambal", "jimal damal",
    "jimal-damal", "jimalili", "maji", "minjiji", "nulung", "nyunil", "wulay", "kubu-kubu",
    "kunjirr", "ngalkun", "ngalkunbu", "ngamar", "ngamba",
    # Non-selected supports and perception morphology.
    "ngali", "ngaliny", "yubal", "nganjin", "ngana", "yurra", "yulu", "nyajil",
    "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji", "nyajiny",
    # Pronoun/demonstrative case and support inflection.
    "nganya", "yununy", "yununiny", "nyunguny", "nyunguniny", "bulangan", "ianangan",
    "nganangan", "nganjinan", "yurrangan", "ngaykunku", "yununku", "nyungunku", "ngaykundu",
    "yunundu", "nyungundu", "ngaykundumuny", "yunundumuny", "nyungundumuny", "ngaykunji",
    "yununji", "nyungunji", "ngayku", "yunu", "nyunju", "yinyangka", "yinyayngka",
    "yinyayanka", "yinyamba", "yinyayanda", "yinyamuny", "yinyamundu", "yinyanji",
    "yinyarriny", "yalungku", "yaluyngku", "yaluyunku", "yaluymbu", "yaluyundu", "yalamuny",
    "yalamundu", "yalanji",
    # Recent closed lexical fields remain closed.
    "balarr", "dijaraban", "kalburiji", "mawal", "munudumbun", "munyurri", "nakawarra",
    "ngalmnbu", "yarrka", "yiljun", "kubija", "mujarrka", "barrbal", "wuyumbu", "bayil",
    "dalmbal", "balarri", "bilmbin", "jalkay", "jijirr", "birmba", "jamarbina", "nili-nili",
    "mabal", "wungku", "kuludu", "kururrungka", "karrambil", "yinjul", "kadanji", "bajuy",
    "bilngkumu", "binanjal", "bindimu", "bubal", "jijirrbay", "kabul", "kalajarr", "kundurr",
    "yuday", "bala", "darrba", "jarrabina", "jayju", "jungarr", "kukur", "mali", "murramu",
    "ngumbi", "yawa",
}
DENY_ENGLISH = (
    "firewood", "light ", "lit ", "lighting", "start", "started", "starting", "make", "made",
    "making", "twirl", "drill", "collect", "collected", "gather", "gathered", "cook", "cooked",
    "cooking", "food", "damper", "leap", "leapt", "leaping", "shoot", "shooting", "spread",
    "spreading", "burn", "burned", "burning", "clear", "clearing", "grassfire", "danger", "warning",
    "smoke out", "smoking out", "source", "motion", "move", "moving", "warm", "warmed", "warming",
    "urine", "pus", "body fluid", "ashes", "hot ashes", "hot coals", "coals", "matches", "motor",
    "loan", "story", "ritual", "ceremony", "purify", "purified", "mullet", "place", "tadpole",
    "my ", "your ", "his ", "her ", "their ", "possess", "owner", "case", "compound", "watch",
    "look", "looks", "looking", "listening", "hearing", "event", "use", "uses", "used",
)


def js(values: list[str]) -> str:
    return json.dumps(values, ensure_ascii=False)


def row(
    seq: int,
    english: str,
    kuku: str,
    analysis: str,
    frame: str,
    words: list[str],
    evidence: str,
) -> tuple:
    return (seq, english, kuku, analysis, frame, 3, js(words), js([]), evidence, "0.86")


EVIDENCE = {
    "baya": "Dictionary lines 1948-1962 give baya as noun 'fire', definitions flame/fire, baya wayu light a fire, and firewood. Rule 273 selects fire reference/perception only; no firewood, lighting, collecting, cooking, or use row. Patz lines 2577-2586 and 2592-2603 source the bare nominative pronouns; lines 2838-2845 source yaluy/yinyay; lines 2568-2571 give nyaji-ny see-PAST; and lines 3043-3049 source nyaji-l 'see'.",
    "jimal": "Dictionary lines 9258-9268 give jimal as noun 'fire stick'. Nearby jimal damal and jimalili stay closed. Rule 273 selects fire-stick past perception/reference only.",
    "jirramar": "Dictionary lines 9863-9875 give jirramar as noun 'chips', translations chips/small sticks/twigs, with starting-fire prose. Rule 273 selects small-sticks past perception/reference only and leaves starting-fire/kindling-use prose closed.",
    "kubu": "Dictionary lines 14707-14715 give kubu as noun 'smoke'. Nearby kubu-kubu tadpole stays closed. Rule 273 selects smoke past perception/reference only.",
    "kunjin": "Dictionary lines 15880-15888 give kunjin as noun 'fire'. Nearby kunjirr purification/smoke prose stays closed. Rule 273 asserts no contrast with baya.",
    "miyilkura": "Dictionary lines 19985-19993 give miyilkura as noun 'smouldering log'. Rule 273 selects object past perception/reference only; no burning, cooking, warming, ash, or eye/miyil drift.",
    "ngalku": "Dictionary lines 21918-21926 give ngalku as noun 'bush fire'. Nearby ngalkun mullet stays closed. Rule 273 selects bush-fire past reference only.",
    "ngaluri": "Dictionary lines 22014-22025 give ngaluri as noun 'flames', with usage 'The flames leap up from the fire'. Rule 273 selects flames past perception/reference only and leaves leap-up event prose closed.",
    "ngunjil": "Dictionary lines 23679-23688 give ngunjil as noun 'charcoal', definition 'charcoal; hot coals or hot ashes'. Rule 273 selects charcoal only and leaves hot-coals/hot-ashes extension closed.",
    "wula": "Dictionary lines 27719-27729 give wula as noun 'flames', synonym dalngarri. Rule 273 selects flames past perception/reference only; dalngarri synonymy and nearby wulay verb drift remain closed.",
}

ROWS = [
    row(1, "I saw the fire there.", "Ngayu baya yinyay nyajin.", "ngayu baya yinyay nyajin | 1sg.NOM fire there see.PAST", "r273_1sg_fire_there_saw_baya", ["ngayu", "baya", "yinyay", "nyajin"], EVIDENCE["baya"]),
    row(2, "I saw the fire stick there.", "Ngayu jimal yinyay nyajin.", "ngayu jimal yinyay nyajin | 1sg.NOM fire.stick there see.PAST", "r273_1sg_fire_there_saw_jimal", ["ngayu", "jimal", "yinyay", "nyajin"], EVIDENCE["jimal"]),
    row(3, "I saw the small sticks there.", "Ngayu jirramar yinyay nyajin.", "ngayu jirramar yinyay nyajin | 1sg.NOM small.sticks there see.PAST", "r273_1sg_fire_there_saw_jirramar", ["ngayu", "jirramar", "yinyay", "nyajin"], EVIDENCE["jirramar"]),
    row(4, "I saw the smoke there.", "Ngayu kubu yinyay nyajin.", "ngayu kubu yinyay nyajin | 1sg.NOM smoke there see.PAST", "r273_1sg_fire_there_saw_kubu", ["ngayu", "kubu", "yinyay", "nyajin"], EVIDENCE["kubu"]),
    row(5, "I saw the fire there.", "Ngayu kunjin yinyay nyajin.", "ngayu kunjin yinyay nyajin | 1sg.NOM fire there see.PAST", "r273_1sg_fire_there_saw_kunjin", ["ngayu", "kunjin", "yinyay", "nyajin"], EVIDENCE["kunjin"]),
    row(6, "I saw the smouldering log there.", "Ngayu miyilkura yinyay nyajin.", "ngayu miyilkura yinyay nyajin | 1sg.NOM smouldering.log there see.PAST", "r273_1sg_fire_there_saw_miyilkura", ["ngayu", "miyilkura", "yinyay", "nyajin"], EVIDENCE["miyilkura"]),
    row(7, "I saw the bush fire there.", "Ngayu ngalku yinyay nyajin.", "ngayu ngalku yinyay nyajin | 1sg.NOM bush.fire there see.PAST", "r273_1sg_fire_there_saw_ngalku", ["ngayu", "ngalku", "yinyay", "nyajin"], EVIDENCE["ngalku"]),
    row(8, "I saw the flames there.", "Ngayu ngaluri yinyay nyajin.", "ngayu ngaluri yinyay nyajin | 1sg.NOM flames there see.PAST", "r273_1sg_fire_there_saw_ngaluri", ["ngayu", "ngaluri", "yinyay", "nyajin"], EVIDENCE["ngaluri"]),
    row(9, "I saw the charcoal there.", "Ngayu ngunjil yinyay nyajin.", "ngayu ngunjil yinyay nyajin | 1sg.NOM charcoal there see.PAST", "r273_1sg_fire_there_saw_ngunjil", ["ngayu", "ngunjil", "yinyay", "nyajin"], EVIDENCE["ngunjil"]),
    row(10, "I saw the flames there.", "Ngayu wula yinyay nyajin.", "ngayu wula yinyay nyajin | 1sg.NOM flames there see.PAST", "r273_1sg_fire_there_saw_wula", ["ngayu", "wula", "yinyay", "nyajin"], EVIDENCE["wula"]),
    row(11, "You saw the fire here.", "Yundu baya yaluy nyajin.", "yundu baya yaluy nyajin | 2sg.NOM fire here see.PAST", "r273_2sg_fire_here_saw_baya", ["yundu", "baya", "yaluy", "nyajin"], EVIDENCE["baya"]),
    row(12, "You saw the fire stick here.", "Yundu jimal yaluy nyajin.", "yundu jimal yaluy nyajin | 2sg.NOM fire.stick here see.PAST", "r273_2sg_fire_here_saw_jimal", ["yundu", "jimal", "yaluy", "nyajin"], EVIDENCE["jimal"]),
    row(13, "You saw the small sticks here.", "Yundu jirramar yaluy nyajin.", "yundu jirramar yaluy nyajin | 2sg.NOM small.sticks here see.PAST", "r273_2sg_fire_here_saw_jirramar", ["yundu", "jirramar", "yaluy", "nyajin"], EVIDENCE["jirramar"]),
    row(14, "You saw the smoke here.", "Yundu kubu yaluy nyajin.", "yundu kubu yaluy nyajin | 2sg.NOM smoke here see.PAST", "r273_2sg_fire_here_saw_kubu", ["yundu", "kubu", "yaluy", "nyajin"], EVIDENCE["kubu"]),
    row(15, "You saw the fire here.", "Yundu kunjin yaluy nyajin.", "yundu kunjin yaluy nyajin | 2sg.NOM fire here see.PAST", "r273_2sg_fire_here_saw_kunjin", ["yundu", "kunjin", "yaluy", "nyajin"], EVIDENCE["kunjin"]),
    row(16, "You saw the smouldering log here.", "Yundu miyilkura yaluy nyajin.", "yundu miyilkura yaluy nyajin | 2sg.NOM smouldering.log here see.PAST", "r273_2sg_fire_here_saw_miyilkura", ["yundu", "miyilkura", "yaluy", "nyajin"], EVIDENCE["miyilkura"]),
    row(17, "You saw the bush fire here.", "Yundu ngalku yaluy nyajin.", "yundu ngalku yaluy nyajin | 2sg.NOM bush.fire here see.PAST", "r273_2sg_fire_here_saw_ngalku", ["yundu", "ngalku", "yaluy", "nyajin"], EVIDENCE["ngalku"]),
    row(18, "You saw the flames here.", "Yundu ngaluri yaluy nyajin.", "yundu ngaluri yaluy nyajin | 2sg.NOM flames here see.PAST", "r273_2sg_fire_here_saw_ngaluri", ["yundu", "ngaluri", "yaluy", "nyajin"], EVIDENCE["ngaluri"]),
    row(19, "You saw the charcoal here.", "Yundu ngunjil yaluy nyajin.", "yundu ngunjil yaluy nyajin | 2sg.NOM charcoal here see.PAST", "r273_2sg_fire_here_saw_ngunjil", ["yundu", "ngunjil", "yaluy", "nyajin"], EVIDENCE["ngunjil"]),
    row(20, "You saw the flames here.", "Yundu wula yaluy nyajin.", "yundu wula yaluy nyajin | 2sg.NOM flames here see.PAST", "r273_2sg_fire_here_saw_wula", ["yundu", "wula", "yaluy", "nyajin"], EVIDENCE["wula"]),
    row(21, "He saw the fire there.", "Nyulu baya yinyay nyajin.", "nyulu baya yinyay nyajin | 3sg.NOM fire there see.PAST", "r273_3sg_fire_there_saw_baya", ["nyulu", "baya", "yinyay", "nyajin"], EVIDENCE["baya"]),
    row(22, "He saw the fire stick there.", "Nyulu jimal yinyay nyajin.", "nyulu jimal yinyay nyajin | 3sg.NOM fire.stick there see.PAST", "r273_3sg_fire_there_saw_jimal", ["nyulu", "jimal", "yinyay", "nyajin"], EVIDENCE["jimal"]),
    row(23, "He saw the small sticks there.", "Nyulu jirramar yinyay nyajin.", "nyulu jirramar yinyay nyajin | 3sg.NOM small.sticks there see.PAST", "r273_3sg_fire_there_saw_jirramar", ["nyulu", "jirramar", "yinyay", "nyajin"], EVIDENCE["jirramar"]),
    row(24, "He saw the smoke there.", "Nyulu kubu yinyay nyajin.", "nyulu kubu yinyay nyajin | 3sg.NOM smoke there see.PAST", "r273_3sg_fire_there_saw_kubu", ["nyulu", "kubu", "yinyay", "nyajin"], EVIDENCE["kubu"]),
    row(25, "He saw the fire there.", "Nyulu kunjin yinyay nyajin.", "nyulu kunjin yinyay nyajin | 3sg.NOM fire there see.PAST", "r273_3sg_fire_there_saw_kunjin", ["nyulu", "kunjin", "yinyay", "nyajin"], EVIDENCE["kunjin"]),
    row(26, "He saw the smouldering log there.", "Nyulu miyilkura yinyay nyajin.", "nyulu miyilkura yinyay nyajin | 3sg.NOM smouldering.log there see.PAST", "r273_3sg_fire_there_saw_miyilkura", ["nyulu", "miyilkura", "yinyay", "nyajin"], EVIDENCE["miyilkura"]),
    row(27, "He saw the bush fire there.", "Nyulu ngalku yinyay nyajin.", "nyulu ngalku yinyay nyajin | 3sg.NOM bush.fire there see.PAST", "r273_3sg_fire_there_saw_ngalku", ["nyulu", "ngalku", "yinyay", "nyajin"], EVIDENCE["ngalku"]),
    row(28, "He saw the flames there.", "Nyulu ngaluri yinyay nyajin.", "nyulu ngaluri yinyay nyajin | 3sg.NOM flames there see.PAST", "r273_3sg_fire_there_saw_ngaluri", ["nyulu", "ngaluri", "yinyay", "nyajin"], EVIDENCE["ngaluri"]),
    row(29, "He saw the charcoal there.", "Nyulu ngunjil yinyay nyajin.", "nyulu ngunjil yinyay nyajin | 3sg.NOM charcoal there see.PAST", "r273_3sg_fire_there_saw_ngunjil", ["nyulu", "ngunjil", "yinyay", "nyajin"], EVIDENCE["ngunjil"]),
    row(30, "He saw the flames there.", "Nyulu wula yinyay nyajin.", "nyulu wula yinyay nyajin | 3sg.NOM flames there see.PAST", "r273_3sg_fire_there_saw_wula", ["nyulu", "wula", "yinyay", "nyajin"], EVIDENCE["wula"]),
    row(31, "The two of them saw the fire here.", "Bula baya yaluy nyajin.", "bula baya yaluy nyajin | 3du.NOM fire here see.PAST", "r273_3du_fire_here_saw_baya", ["bula", "baya", "yaluy", "nyajin"], EVIDENCE["baya"]),
    row(32, "The two of them saw the fire stick here.", "Bula jimal yaluy nyajin.", "bula jimal yaluy nyajin | 3du.NOM fire.stick here see.PAST", "r273_3du_fire_here_saw_jimal", ["bula", "jimal", "yaluy", "nyajin"], EVIDENCE["jimal"]),
    row(33, "The two of them saw the small sticks here.", "Bula jirramar yaluy nyajin.", "bula jirramar yaluy nyajin | 3du.NOM small.sticks here see.PAST", "r273_3du_fire_here_saw_jirramar", ["bula", "jirramar", "yaluy", "nyajin"], EVIDENCE["jirramar"]),
    row(34, "The two of them saw the smoke here.", "Bula kubu yaluy nyajin.", "bula kubu yaluy nyajin | 3du.NOM smoke here see.PAST", "r273_3du_fire_here_saw_kubu", ["bula", "kubu", "yaluy", "nyajin"], EVIDENCE["kubu"]),
    row(35, "The two of them saw the fire here.", "Bula kunjin yaluy nyajin.", "bula kunjin yaluy nyajin | 3du.NOM fire here see.PAST", "r273_3du_fire_here_saw_kunjin", ["bula", "kunjin", "yaluy", "nyajin"], EVIDENCE["kunjin"]),
    row(36, "The two of them saw the smouldering log here.", "Bula miyilkura yaluy nyajin.", "bula miyilkura yaluy nyajin | 3du.NOM smouldering.log here see.PAST", "r273_3du_fire_here_saw_miyilkura", ["bula", "miyilkura", "yaluy", "nyajin"], EVIDENCE["miyilkura"]),
    row(37, "The two of them saw the bush fire here.", "Bula ngalku yaluy nyajin.", "bula ngalku yaluy nyajin | 3du.NOM bush.fire here see.PAST", "r273_3du_fire_here_saw_ngalku", ["bula", "ngalku", "yaluy", "nyajin"], EVIDENCE["ngalku"]),
    row(38, "The two of them saw the flames here.", "Bula ngaluri yaluy nyajin.", "bula ngaluri yaluy nyajin | 3du.NOM flames here see.PAST", "r273_3du_fire_here_saw_ngaluri", ["bula", "ngaluri", "yaluy", "nyajin"], EVIDENCE["ngaluri"]),
    row(39, "The two of them saw the charcoal here.", "Bula ngunjil yaluy nyajin.", "bula ngunjil yaluy nyajin | 3du.NOM charcoal here see.PAST", "r273_3du_fire_here_saw_ngunjil", ["bula", "ngunjil", "yaluy", "nyajin"], EVIDENCE["ngunjil"]),
    row(40, "The two of them saw the flames here.", "Bula wula yaluy nyajin.", "bula wula yaluy nyajin | 3du.NOM flames here see.PAST", "r273_3du_fire_here_saw_wula", ["bula", "wula", "yaluy", "nyajin"], EVIDENCE["wula"]),
    row(41, "They saw the fire there.", "Jana baya yinyay nyajin.", "jana baya yinyay nyajin | 3pl.NOM fire there see.PAST", "r273_3pl_fire_there_saw_baya", ["jana", "baya", "yinyay", "nyajin"], EVIDENCE["baya"]),
    row(42, "They saw the fire stick there.", "Jana jimal yinyay nyajin.", "jana jimal yinyay nyajin | 3pl.NOM fire.stick there see.PAST", "r273_3pl_fire_there_saw_jimal", ["jana", "jimal", "yinyay", "nyajin"], EVIDENCE["jimal"]),
    row(43, "They saw the small sticks there.", "Jana jirramar yinyay nyajin.", "jana jirramar yinyay nyajin | 3pl.NOM small.sticks there see.PAST", "r273_3pl_fire_there_saw_jirramar", ["jana", "jirramar", "yinyay", "nyajin"], EVIDENCE["jirramar"]),
    row(44, "They saw the smoke there.", "Jana kubu yinyay nyajin.", "jana kubu yinyay nyajin | 3pl.NOM smoke there see.PAST", "r273_3pl_fire_there_saw_kubu", ["jana", "kubu", "yinyay", "nyajin"], EVIDENCE["kubu"]),
    row(45, "They saw the fire there.", "Jana kunjin yinyay nyajin.", "jana kunjin yinyay nyajin | 3pl.NOM fire there see.PAST", "r273_3pl_fire_there_saw_kunjin", ["jana", "kunjin", "yinyay", "nyajin"], EVIDENCE["kunjin"]),
    row(46, "They saw the smouldering log there.", "Jana miyilkura yinyay nyajin.", "jana miyilkura yinyay nyajin | 3pl.NOM smouldering.log there see.PAST", "r273_3pl_fire_there_saw_miyilkura", ["jana", "miyilkura", "yinyay", "nyajin"], EVIDENCE["miyilkura"]),
    row(47, "They saw the bush fire there.", "Jana ngalku yinyay nyajin.", "jana ngalku yinyay nyajin | 3pl.NOM bush.fire there see.PAST", "r273_3pl_fire_there_saw_ngalku", ["jana", "ngalku", "yinyay", "nyajin"], EVIDENCE["ngalku"]),
    row(48, "They saw the flames there.", "Jana ngaluri yinyay nyajin.", "jana ngaluri yinyay nyajin | 3pl.NOM flames there see.PAST", "r273_3pl_fire_there_saw_ngaluri", ["jana", "ngaluri", "yinyay", "nyajin"], EVIDENCE["ngaluri"]),
    row(49, "They saw the charcoal there.", "Jana ngunjil yinyay nyajin.", "jana ngunjil yinyay nyajin | 3pl.NOM charcoal there see.PAST", "r273_3pl_fire_there_saw_ngunjil", ["jana", "ngunjil", "yinyay", "nyajin"], EVIDENCE["ngunjil"]),
    row(50, "They saw the flames there.", "Jana wula yinyay nyajin.", "jana wula yinyay nyajin | 3pl.NOM flames there see.PAST", "r273_3pl_fire_there_saw_wula", ["jana", "wula", "yinyay", "nyajin"], EVIDENCE["wula"]),
]


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


def has_target_morphology(low: str, head: str) -> bool:
    suffixes = "ngka|ngku|angka|nga|ngu|ba|mba|mbu|muny|darr|burr|bu|ka|nka|nku|ji|jirr|ku|mundu|amu|mu|iji|nda|ndu|anda|undu|unji|nji|kari|baka|baja|bungal|bungan|mal|man|rriny|karra|manda|bulal"
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:{suffixes})\b", low) is not None


def english_has_denied(english_low: str, denied: str) -> bool:
    return denied in english_low if " " in denied else re.search(rf"\b{re.escape(denied.strip())}\b", english_low) is not None


def scan_static() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"{LABEL}: expected 50 rows, found {len(ROWS)}")
    seen_ku: set[str] = set()
    for expected, row_data in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row_data
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        english_low = english.lower()
        words = json.loads(words_used)
        if seq != expected:
            issues.append(f"{label}: sequence mismatch; expected {expected}")
        if not english[:1].isupper() or english[-1:] not in ".!?":
            issues.append(f"{label}: English punctuation/capitalization failure")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        if not frame.startswith("r273_"):
            issues.append(f"{label}: Rule 273 frame must start with r273_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface inside batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: loans_used must be empty")
        denied = [bad for bad in DENY_KUKU if has_token(low, bad)]
        if denied:
            issues.append(f"{label}: denied Kuku token(s) present: {denied}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 273 target noun, found {targets}")
        for head in targets:
            selected = TARGET_HEADS[head]
            dotted = selected.replace("-", ".").replace(" ", ".")
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if selected not in english_low:
                issues.append(f"{label}: English must include selected sense {selected!r}")
            if selected not in analysis and dotted not in analysis:
                issues.append(f"{label}: analysis must gloss {head} as {selected}")
            if has_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        for support in SUPPORT:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        pronouns = [pronoun for pronoun in PRONOUN_SUPPORT if has_token(low, pronoun)]
        if len(pronouns) != 1:
            issues.append(f"{label}: rows require exactly one selected bare nominative pronoun, found {pronouns}")
        if " saw " not in f" {english_low} ":
            issues.append(f"{label}: English must be past-perception prose with 'saw'")
        if "nyajin" not in words or not low.endswith(" nyajin.") or "see.PAST" not in analysis:
            issues.append(f"{label}: row must use nyajin as see.PAST")
        if 1 <= seq <= 10 and (not low.startswith("ngayu ") or not low.endswith(" yinyay nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngayu TARGET yinyay nyajin")
        if 11 <= seq <= 20 and (not low.startswith("yundu ") or not low.endswith(" yaluy nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Yundu TARGET yaluy nyajin")
        if 21 <= seq <= 30 and (not low.startswith("nyulu ") or not low.endswith(" yinyay nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Nyulu TARGET yinyay nyajin")
        if 31 <= seq <= 40 and (not low.startswith("bula ") or not low.endswith(" yaluy nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Bula TARGET yaluy nyajin")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yinyay nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yinyay nyajin")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied fire action/use/homograph/pronoun-case field in English")
        if not evidence or not confidence:
            issues.append(f"{label}: missing evidence/confidence")
    return issues


def main() -> int:
    issues = scan_static()
    if issues:
        print("pre-bank scan failed:")
        for issue in issues:
            print(f"  - {issue}")
        return 1

    db = sqlite3.connect(DB)
    db.execute("PRAGMA foreign_keys = ON")
    db.execute("BEGIN")
    ensure_schema(db)
    existing = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()
    if existing:
        batch_id = existing[0]
        accepted = db.execute(
            "SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status IN ('accepted','revised')",
            (batch_id,),
        ).fetchone()[0]
        if accepted:
            raise SystemExit(f"{LABEL} already has accepted/revised rows; refusing to overwrite")
        db.execute("DELETE FROM reviews WHERE sentence_id IN (SELECT id FROM sentences WHERE batch_id=?)", (batch_id,))
        db.execute("DELETE FROM lessons WHERE batch_id=?", (batch_id,))
        db.execute("DELETE FROM sentences WHERE batch_id=?", (batch_id,))
        db.execute("UPDATE batches SET notes=?, created_at=? WHERE id=?", ("rebanked under_review", NOW, batch_id))
    else:
        db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 273 draft", "operator-guide-2026-07-09", "draft under review", NOW),
        )
        batch_id = db.execute("SELECT id FROM batches WHERE label=?", (LABEL,)).fetchone()[0]

    collisions: list[str] = []
    for seq, _english, kuku, *_rest in ROWS:
        found = db.execute(
            """
            SELECT b.label, s.seq, s.status
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(s.kuku)=lower(?) AND s.status IN ('accepted','revised','under_review')
            """,
            (kuku,),
        ).fetchall()
        if found:
            collisions.append(f"{seq}: {kuku} -> {found}")
    if collisions:
        raise SystemExit("collision scan failed:\n" + "\n".join(collisions))

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, created_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (
                batch_id,
                seq,
                english,
                kuku,
                analysis,
                frame,
                tier,
                words_used,
                loans_used,
                evidence,
                "under_review",
                confidence,
                NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-readback",
        actor="claude-main",
        detail="Rule 273 source pass checked dictionary lines for baya fire, jimal fire stick, jirramar small sticks, kubu smoke, kunjin fire, miyilkura smouldering log, ngalku bush fire, ngaluri flames, ngunjil charcoal, and wula flames plus nearby trap entries; checked Patz lines 2577-2586 and 2592-2603 for bare nominative ngayu, yundu, nyulu, bula, and jana; lines 2682-2686 for modern jana/bula discussion; lines 2838-2845 for yaluy/yinyay; lines 2568-2571 for nyaji-ny see-PAST; and lines 3043-3049 for nyaji-l.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="planning",
        event_type="debt-sweep-scope",
        actor="claude-main",
        detail="Selected Rule 273 fire-reference past pronoun perception sweep after Rule 272: ten checked fire-reference labels in five zero-collision bare-pronoun past-perception bands using ngayu, yundu, nyulu, bula, and jana. All fire-making/use/homograph/source-prose, target-case, pronoun-case, demonstrative-case, perception-compound, and support-inflection semantics remain closed.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="coverage-audit",
        actor="claude-main",
        detail="Coverage after Rule 272: baya 478, jimal 53, jirramar 25, kubu 81, kunjin 31, miyilkura 25, ngalku 25, ngaluri 25, ngunjil 88, wula 79; support ngayu 1331, yundu 1191, nyulu 1806, bula 234, jana 1527, yaluy 2684, yinyay 2918, nyajin 1402.",
        command="sqlite3 synthetic.db token-aware coverage query",
        output_excerpt="Targets: baya 478, jimal 53, jirramar 25, kubu 81, kunjin 31, miyilkura 25, ngalku 25, ngaluri 25, ngunjil 88, wula 79. Support: ngayu 1331, yundu 1191, nyulu 1806, bula 234, jana 1527, yaluy 2684, yinyay 2918, nyajin 1402.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="duplicate-preflight",
        actor="claude-main",
        detail="Exact duplicate preflight over the 50 final Rule 273 fire-reference past pronoun perception surfaces found no accepted, revised, or under-review collisions.",
        command="sqlite3 synthetic.db planned-surface duplicate query",
        output_excerpt="0 rows returned.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="bank_315.py static guards passed: 50 rows, r273 frame prefix, one checked fire-reference target per row, exactly one selected bare nominative pronoun per row, expected past here/there bands, nyajin see.PAST only, no denied fire action/use/homograph/source-prose/case/perception-compound tokens, and no loans.",
        command="python3 -m py_compile batch_scripts/bank_315.py; import bank_315; bank_315.scan_static()",
        output_excerpt="issues 0",
        artifact_path="batch_scripts/bank_315.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Inserted 50 explicit Batch 315 / Rule 273 rows as under_review.",
        command="python3 batch_scripts/bank_315.py",
        output_excerpt="banked batch-315: 50 rows under_review",
        artifact_path="synthetic.db",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 rows under_review")
    return 0


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