#!/usr/bin/env python3
"""Bank batch-314: Rule 272 fire-reference residual 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-314"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-314-fire-reference-residual-pronoun-perception-after-rule271-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",
    "nyulu": "3sg.NOM",
    "ngana": "1pl.INC.NOM",
    "bula": "3du.NOM",
    "jana": "3pl.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajil"}
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 from nearby perception grids.
    "ngali", "ngaliny", "yubal", "nganjin", "yurra", "yundu", "yulu", "nyajin",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "nganya", "nyunguny", "nganangan", "bulangan", "ianangan", "ngananiny", "bulaniny",
    "iananiny", "ngananka", "bulanka", "iananka", "ngananda", "bulanda", "iananda",
    "nganandamuny", "bulandamuny", "ianandamuny", "ngananji", "bulanji", "iananji",
    "ngananga", "bulanga", "iananga", "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", "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji", "nyajiny",
    # Recent closed 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", "listening", "hearing", "event", "use",
)


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 272 selects fire reference/perception only; no firewood, lighting, collecting, cooking, or use row. Patz lines 2577-2586 source bare nominative pronouns; lines 2682-2686 support ngana/jana analysis; lines 2838-2845 source yaluy/yinyay; 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 272 selects fire-stick perception/reference only.",
    "jirramar": "Dictionary lines 9863-9875 give jirramar as noun 'chips', translations chips/small sticks/twigs, with starting-fire prose. Rule 272 selects small-sticks 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 272 selects smoke perception/reference only.",
    "kunjin": "Dictionary lines 15880-15888 give kunjin as noun 'fire'. Nearby kunjirr purification/smoke prose stays closed. Rule 272 asserts no contrast with baya.",
    "miyilkura": "Dictionary lines 19985-19993 give miyilkura as noun 'smouldering log'. Rule 272 selects object 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 272 selects bush-fire reference only.",
    "ngaluri": "Dictionary lines 22014-22025 give ngaluri as noun 'flames', with usage 'The flames leap up from the fire'. Rule 272 selects flames 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 272 selects charcoal only and leaves hot-coals/hot-ashes extension closed.",
    "wula": "Dictionary lines 27719-27729 give wula as noun 'flames', synonym dalngarri. Rule 272 selects flames perception/reference only; dalngarri synonymy and nearby wulay verb drift remain closed.",
}

ROWS = [
    row(1, "I see the fire here.", "Ngayu baya yaluy nyajil.", "ngayu baya yaluy nyajil | 1sg.NOM fire here see.NPST", "r272_1sg_fire_here_see_baya", ["ngayu", "baya", "yaluy", "nyajil"], EVIDENCE["baya"]),
    row(2, "I see the fire stick here.", "Ngayu jimal yaluy nyajil.", "ngayu jimal yaluy nyajil | 1sg.NOM fire.stick here see.NPST", "r272_1sg_fire_here_see_jimal", ["ngayu", "jimal", "yaluy", "nyajil"], EVIDENCE["jimal"]),
    row(3, "I see the small sticks here.", "Ngayu jirramar yaluy nyajil.", "ngayu jirramar yaluy nyajil | 1sg.NOM small.sticks here see.NPST", "r272_1sg_fire_here_see_jirramar", ["ngayu", "jirramar", "yaluy", "nyajil"], EVIDENCE["jirramar"]),
    row(4, "I see the smoke here.", "Ngayu kubu yaluy nyajil.", "ngayu kubu yaluy nyajil | 1sg.NOM smoke here see.NPST", "r272_1sg_fire_here_see_kubu", ["ngayu", "kubu", "yaluy", "nyajil"], EVIDENCE["kubu"]),
    row(5, "I see the fire here.", "Ngayu kunjin yaluy nyajil.", "ngayu kunjin yaluy nyajil | 1sg.NOM fire here see.NPST", "r272_1sg_fire_here_see_kunjin", ["ngayu", "kunjin", "yaluy", "nyajil"], EVIDENCE["kunjin"]),
    row(6, "I see the smouldering log here.", "Ngayu miyilkura yaluy nyajil.", "ngayu miyilkura yaluy nyajil | 1sg.NOM smouldering.log here see.NPST", "r272_1sg_fire_here_see_miyilkura", ["ngayu", "miyilkura", "yaluy", "nyajil"], EVIDENCE["miyilkura"]),
    row(7, "I see the bush fire here.", "Ngayu ngalku yaluy nyajil.", "ngayu ngalku yaluy nyajil | 1sg.NOM bush.fire here see.NPST", "r272_1sg_fire_here_see_ngalku", ["ngayu", "ngalku", "yaluy", "nyajil"], EVIDENCE["ngalku"]),
    row(8, "I see the flames here.", "Ngayu ngaluri yaluy nyajil.", "ngayu ngaluri yaluy nyajil | 1sg.NOM flames here see.NPST", "r272_1sg_fire_here_see_ngaluri", ["ngayu", "ngaluri", "yaluy", "nyajil"], EVIDENCE["ngaluri"]),
    row(9, "I see the charcoal here.", "Ngayu ngunjil yaluy nyajil.", "ngayu ngunjil yaluy nyajil | 1sg.NOM charcoal here see.NPST", "r272_1sg_fire_here_see_ngunjil", ["ngayu", "ngunjil", "yaluy", "nyajil"], EVIDENCE["ngunjil"]),
    row(10, "I see the flames here.", "Ngayu wula yaluy nyajil.", "ngayu wula yaluy nyajil | 1sg.NOM flames here see.NPST", "r272_1sg_fire_here_see_wula", ["ngayu", "wula", "yaluy", "nyajil"], EVIDENCE["wula"]),
    row(11, "He sees the fire there.", "Nyulu baya yinyay nyajil.", "nyulu baya yinyay nyajil | 3sg.NOM fire there see.NPST", "r272_3sg_fire_there_see_baya", ["nyulu", "baya", "yinyay", "nyajil"], EVIDENCE["baya"]),
    row(12, "He sees the fire stick there.", "Nyulu jimal yinyay nyajil.", "nyulu jimal yinyay nyajil | 3sg.NOM fire.stick there see.NPST", "r272_3sg_fire_there_see_jimal", ["nyulu", "jimal", "yinyay", "nyajil"], EVIDENCE["jimal"]),
    row(13, "He sees the small sticks there.", "Nyulu jirramar yinyay nyajil.", "nyulu jirramar yinyay nyajil | 3sg.NOM small.sticks there see.NPST", "r272_3sg_fire_there_see_jirramar", ["nyulu", "jirramar", "yinyay", "nyajil"], EVIDENCE["jirramar"]),
    row(14, "He sees the smoke there.", "Nyulu kubu yinyay nyajil.", "nyulu kubu yinyay nyajil | 3sg.NOM smoke there see.NPST", "r272_3sg_fire_there_see_kubu", ["nyulu", "kubu", "yinyay", "nyajil"], EVIDENCE["kubu"]),
    row(15, "He sees the fire there.", "Nyulu kunjin yinyay nyajil.", "nyulu kunjin yinyay nyajil | 3sg.NOM fire there see.NPST", "r272_3sg_fire_there_see_kunjin", ["nyulu", "kunjin", "yinyay", "nyajil"], EVIDENCE["kunjin"]),
    row(16, "He sees the smouldering log there.", "Nyulu miyilkura yinyay nyajil.", "nyulu miyilkura yinyay nyajil | 3sg.NOM smouldering.log there see.NPST", "r272_3sg_fire_there_see_miyilkura", ["nyulu", "miyilkura", "yinyay", "nyajil"], EVIDENCE["miyilkura"]),
    row(17, "He sees the bush fire there.", "Nyulu ngalku yinyay nyajil.", "nyulu ngalku yinyay nyajil | 3sg.NOM bush.fire there see.NPST", "r272_3sg_fire_there_see_ngalku", ["nyulu", "ngalku", "yinyay", "nyajil"], EVIDENCE["ngalku"]),
    row(18, "He sees the flames there.", "Nyulu ngaluri yinyay nyajil.", "nyulu ngaluri yinyay nyajil | 3sg.NOM flames there see.NPST", "r272_3sg_fire_there_see_ngaluri", ["nyulu", "ngaluri", "yinyay", "nyajil"], EVIDENCE["ngaluri"]),
    row(19, "He sees the charcoal there.", "Nyulu ngunjil yinyay nyajil.", "nyulu ngunjil yinyay nyajil | 3sg.NOM charcoal there see.NPST", "r272_3sg_fire_there_see_ngunjil", ["nyulu", "ngunjil", "yinyay", "nyajil"], EVIDENCE["ngunjil"]),
    row(20, "He sees the flames there.", "Nyulu wula yinyay nyajil.", "nyulu wula yinyay nyajil | 3sg.NOM flames there see.NPST", "r272_3sg_fire_there_see_wula", ["nyulu", "wula", "yinyay", "nyajil"], EVIDENCE["wula"]),
    row(21, "We see the fire here.", "Ngana baya yaluy nyajil.", "ngana baya yaluy nyajil | 1pl.INC.NOM fire here see.NPST", "r272_1pl_incl_fire_here_see_baya", ["ngana", "baya", "yaluy", "nyajil"], EVIDENCE["baya"]),
    row(22, "We see the fire stick here.", "Ngana jimal yaluy nyajil.", "ngana jimal yaluy nyajil | 1pl.INC.NOM fire.stick here see.NPST", "r272_1pl_incl_fire_here_see_jimal", ["ngana", "jimal", "yaluy", "nyajil"], EVIDENCE["jimal"]),
    row(23, "We see the small sticks here.", "Ngana jirramar yaluy nyajil.", "ngana jirramar yaluy nyajil | 1pl.INC.NOM small.sticks here see.NPST", "r272_1pl_incl_fire_here_see_jirramar", ["ngana", "jirramar", "yaluy", "nyajil"], EVIDENCE["jirramar"]),
    row(24, "We see the smoke here.", "Ngana kubu yaluy nyajil.", "ngana kubu yaluy nyajil | 1pl.INC.NOM smoke here see.NPST", "r272_1pl_incl_fire_here_see_kubu", ["ngana", "kubu", "yaluy", "nyajil"], EVIDENCE["kubu"]),
    row(25, "We see the fire here.", "Ngana kunjin yaluy nyajil.", "ngana kunjin yaluy nyajil | 1pl.INC.NOM fire here see.NPST", "r272_1pl_incl_fire_here_see_kunjin", ["ngana", "kunjin", "yaluy", "nyajil"], EVIDENCE["kunjin"]),
    row(26, "We see the smouldering log here.", "Ngana miyilkura yaluy nyajil.", "ngana miyilkura yaluy nyajil | 1pl.INC.NOM smouldering.log here see.NPST", "r272_1pl_incl_fire_here_see_miyilkura", ["ngana", "miyilkura", "yaluy", "nyajil"], EVIDENCE["miyilkura"]),
    row(27, "We see the bush fire here.", "Ngana ngalku yaluy nyajil.", "ngana ngalku yaluy nyajil | 1pl.INC.NOM bush.fire here see.NPST", "r272_1pl_incl_fire_here_see_ngalku", ["ngana", "ngalku", "yaluy", "nyajil"], EVIDENCE["ngalku"]),
    row(28, "We see the flames here.", "Ngana ngaluri yaluy nyajil.", "ngana ngaluri yaluy nyajil | 1pl.INC.NOM flames here see.NPST", "r272_1pl_incl_fire_here_see_ngaluri", ["ngana", "ngaluri", "yaluy", "nyajil"], EVIDENCE["ngaluri"]),
    row(29, "We see the charcoal here.", "Ngana ngunjil yaluy nyajil.", "ngana ngunjil yaluy nyajil | 1pl.INC.NOM charcoal here see.NPST", "r272_1pl_incl_fire_here_see_ngunjil", ["ngana", "ngunjil", "yaluy", "nyajil"], EVIDENCE["ngunjil"]),
    row(30, "We see the flames here.", "Ngana wula yaluy nyajil.", "ngana wula yaluy nyajil | 1pl.INC.NOM flames here see.NPST", "r272_1pl_incl_fire_here_see_wula", ["ngana", "wula", "yaluy", "nyajil"], EVIDENCE["wula"]),
    row(31, "The two of them see the fire there.", "Bula baya yinyay nyajil.", "bula baya yinyay nyajil | 3du.NOM fire there see.NPST", "r272_3du_fire_there_see_baya", ["bula", "baya", "yinyay", "nyajil"], EVIDENCE["baya"]),
    row(32, "The two of them see the fire stick there.", "Bula jimal yinyay nyajil.", "bula jimal yinyay nyajil | 3du.NOM fire.stick there see.NPST", "r272_3du_fire_there_see_jimal", ["bula", "jimal", "yinyay", "nyajil"], EVIDENCE["jimal"]),
    row(33, "The two of them see the small sticks there.", "Bula jirramar yinyay nyajil.", "bula jirramar yinyay nyajil | 3du.NOM small.sticks there see.NPST", "r272_3du_fire_there_see_jirramar", ["bula", "jirramar", "yinyay", "nyajil"], EVIDENCE["jirramar"]),
    row(34, "The two of them see the smoke there.", "Bula kubu yinyay nyajil.", "bula kubu yinyay nyajil | 3du.NOM smoke there see.NPST", "r272_3du_fire_there_see_kubu", ["bula", "kubu", "yinyay", "nyajil"], EVIDENCE["kubu"]),
    row(35, "The two of them see the fire there.", "Bula kunjin yinyay nyajil.", "bula kunjin yinyay nyajil | 3du.NOM fire there see.NPST", "r272_3du_fire_there_see_kunjin", ["bula", "kunjin", "yinyay", "nyajil"], EVIDENCE["kunjin"]),
    row(36, "The two of them see the smouldering log there.", "Bula miyilkura yinyay nyajil.", "bula miyilkura yinyay nyajil | 3du.NOM smouldering.log there see.NPST", "r272_3du_fire_there_see_miyilkura", ["bula", "miyilkura", "yinyay", "nyajil"], EVIDENCE["miyilkura"]),
    row(37, "The two of them see the bush fire there.", "Bula ngalku yinyay nyajil.", "bula ngalku yinyay nyajil | 3du.NOM bush.fire there see.NPST", "r272_3du_fire_there_see_ngalku", ["bula", "ngalku", "yinyay", "nyajil"], EVIDENCE["ngalku"]),
    row(38, "The two of them see the flames there.", "Bula ngaluri yinyay nyajil.", "bula ngaluri yinyay nyajil | 3du.NOM flames there see.NPST", "r272_3du_fire_there_see_ngaluri", ["bula", "ngaluri", "yinyay", "nyajil"], EVIDENCE["ngaluri"]),
    row(39, "The two of them see the charcoal there.", "Bula ngunjil yinyay nyajil.", "bula ngunjil yinyay nyajil | 3du.NOM charcoal there see.NPST", "r272_3du_fire_there_see_ngunjil", ["bula", "ngunjil", "yinyay", "nyajil"], EVIDENCE["ngunjil"]),
    row(40, "The two of them see the flames there.", "Bula wula yinyay nyajil.", "bula wula yinyay nyajil | 3du.NOM flames there see.NPST", "r272_3du_fire_there_see_wula", ["bula", "wula", "yinyay", "nyajil"], EVIDENCE["wula"]),
    row(41, "They see the fire here.", "Jana baya yaluy nyajil.", "jana baya yaluy nyajil | 3pl.NOM fire here see.NPST", "r272_3pl_fire_here_see_baya", ["jana", "baya", "yaluy", "nyajil"], EVIDENCE["baya"]),
    row(42, "They see the fire stick here.", "Jana jimal yaluy nyajil.", "jana jimal yaluy nyajil | 3pl.NOM fire.stick here see.NPST", "r272_3pl_fire_here_see_jimal", ["jana", "jimal", "yaluy", "nyajil"], EVIDENCE["jimal"]),
    row(43, "They see the small sticks here.", "Jana jirramar yaluy nyajil.", "jana jirramar yaluy nyajil | 3pl.NOM small.sticks here see.NPST", "r272_3pl_fire_here_see_jirramar", ["jana", "jirramar", "yaluy", "nyajil"], EVIDENCE["jirramar"]),
    row(44, "They see the smoke here.", "Jana kubu yaluy nyajil.", "jana kubu yaluy nyajil | 3pl.NOM smoke here see.NPST", "r272_3pl_fire_here_see_kubu", ["jana", "kubu", "yaluy", "nyajil"], EVIDENCE["kubu"]),
    row(45, "They see the fire here.", "Jana kunjin yaluy nyajil.", "jana kunjin yaluy nyajil | 3pl.NOM fire here see.NPST", "r272_3pl_fire_here_see_kunjin", ["jana", "kunjin", "yaluy", "nyajil"], EVIDENCE["kunjin"]),
    row(46, "They see the smouldering log here.", "Jana miyilkura yaluy nyajil.", "jana miyilkura yaluy nyajil | 3pl.NOM smouldering.log here see.NPST", "r272_3pl_fire_here_see_miyilkura", ["jana", "miyilkura", "yaluy", "nyajil"], EVIDENCE["miyilkura"]),
    row(47, "They see the bush fire here.", "Jana ngalku yaluy nyajil.", "jana ngalku yaluy nyajil | 3pl.NOM bush.fire here see.NPST", "r272_3pl_fire_here_see_ngalku", ["jana", "ngalku", "yaluy", "nyajil"], EVIDENCE["ngalku"]),
    row(48, "They see the flames here.", "Jana ngaluri yaluy nyajil.", "jana ngaluri yaluy nyajil | 3pl.NOM flames here see.NPST", "r272_3pl_fire_here_see_ngaluri", ["jana", "ngaluri", "yaluy", "nyajil"], EVIDENCE["ngaluri"]),
    row(49, "They see the charcoal here.", "Jana ngunjil yaluy nyajil.", "jana ngunjil yaluy nyajil | 3pl.NOM charcoal here see.NPST", "r272_3pl_fire_here_see_ngunjil", ["jana", "ngunjil", "yaluy", "nyajil"], EVIDENCE["ngunjil"]),
    row(50, "They see the flames here.", "Jana wula yaluy nyajil.", "jana wula yaluy nyajil | 3pl.NOM flames here see.NPST", "r272_3pl_fire_here_see_wula", ["jana", "wula", "yaluy", "nyajil"], 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("r272_"):
            issues.append(f"{label}: Rule 272 frame must start with r272_")
        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 272 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 1 <= seq <= 10 and (not low.startswith("ngayu ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngayu TARGET yaluy nyajil")
        if 11 <= seq <= 20 and (not low.startswith("nyulu ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Nyulu TARGET yinyay nyajil")
        if 21 <= seq <= 30 and (not low.startswith("ngana ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Ngana TARGET yaluy nyajil")
        if 31 <= seq <= 40 and (not low.startswith("bula ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Bula TARGET yinyay nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yaluy nyajil")
        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 272 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 272 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 2682-2686 for bare nominative ngayu, nyulu, ngana, bula, and jana; lines 2838-2845 for yaluy/yinyay; 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 272 fire-reference residual pronoun perception sweep after Rule 271: ten checked Rule 236/246/250 fire-reference labels in five zero-collision bare-pronoun perception bands using ngayu, nyulu, ngana, bula, and jana. All fire-making/use/homograph/source-prose, target-case, pronoun-case, demonstrative-case, and perception-compound 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 271: baya 464, jimal 48, jirramar 20, kubu 74, kunjin 26, miyilkura 20, ngalku 20, ngaluri 20, ngunjil 83, wula 64; support ngayu 1310, nyulu 1794, ngana 978, bula 222, jana 1512, yaluy 2647, yinyay 2891, nyajil 3378.",
        command="sqlite3 synthetic.db token-aware coverage query",
        output_excerpt="Targets: baya 464, jimal 48, jirramar 20, kubu 74, kunjin 26, miyilkura 20, ngalku 20, ngaluri 20, ngunjil 83, wula 64. Support: ngayu 1310, nyulu 1794, ngana 978, bula 222, jana 1512, yaluy 2647, yinyay 2891, nyajil 3378.",
        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 272 fire-reference residual 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_314.py static guards passed: 50 rows, r272 frame prefix, one checked fire-reference target per row, exactly one selected bare nominative pronoun per row, expected residual here/there bands, no denied fire action/use/homograph/source-prose/case/perception-compound tokens, and no loans.",
        command="python3 -m py_compile batch_scripts/bank_314.py; import bank_314; bank_314.scan_static()",
        output_excerpt="issues 0",
        artifact_path="batch_scripts/bank_314.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Inserted 50 explicit Batch 314 / Rule 272 rows as under_review.",
        command="python3 batch_scripts/bank_314.py",
        output_excerpt="banked batch-314: 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())
