#!/usr/bin/env python3
"""Bank batch-279: Rule 237 fire-reference construction consolidation."""

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-279"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-279-fire-reference-construction-consolidation-after-rule236-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",
}
PLURAL_TARGETS = {"jirramar", "ngaluri", "wula"}
SUPPORT = {"yinya", "yaluy", "yinyay", "yundu", "nyulu", "ngana", "jana", "nyajil", "nyajin"}
PRONOUN_SUPPORT = {"yundu", "nyulu", "ngana", "jana"}
DENY_KUKU = {
    # Fire verbs, unselected fire nouns, loans, and homograph-heavy neighbours.
    "baya-wayu", "wayu", "waju", "dalngarri", "dama-murnil", "jakalambal", "jimal-damal",
    "jimalili", "maji", "minjiji", "nulung", "nyunil", "wulay",
    # Demonstrative/pronoun case, perception compounds, and support-inflection traps.
    "nganya", "yununy", "nyunguny", "yununiny", "nyunguniny", "nganangan", "ianangan",
    "yinyangka", "yinyayngka", "yinyayanka", "yinyamba", "yinyayanda", "yinyamuny",
    "yinyamundu", "yinyanji", "yinyarriny", "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji",
    # Recent body/landform targets and homographs stay closed.
    "bandin", "bari", "bibar", "birri", "jiba", "jandal", "jibar", "kangkul", "kuwinyu",
    "waba", "jibarr", "kangku", "mabil", "karrirrangkal", "jalngarr", "jinali", "yalmba",
    "kambal-kambal", "jalkar", "janku", "jamba",
}
DENY_ENGLISH = (
    "firewood", "wood", "light ", "lit ", "start", "started", "make", "made", "making", "twirl",
    "drill", "collect", "collected", "gather", "gathered", "cook", "cooked", "cooking", "food",
    "damper", "leap", "leapt", "shoot", "shooting", "spread", "burn", "burned", "burning", "clear",
    "clearing", "grassfire", "purify", "purified", "smoke out", "warm", "warmed", "urine", "pus",
    "sore", "body fluid", "ashes", "hot coals", "coals", "matches", "loan", "motor", "story",
    "my ", "your ", "his ", "her ", "their ", "possess", "owner", "case", "compound", "watch",
    "look", "listening", "hearing", "coming", "moving",
)


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 baya noun gloss 'fire', definitions 'flame, fire; fire ... light a fire; firewood'. Rule 237 retains the narrow Rule 236 fire reference only. Patz 3.5 sources nominative yundu/nyulu/ngana/jana; Patz 3.7 sources yinya/yaluy/yinyay; Patz 3.8.3.2 sources nyaji-l 'see'. Patz Text 12 includes baya/kubu with perception, but Rule 237 does not import coming, lighting, or burning event prose.",
    "jimal": "Dictionary jimal noun gloss 'fire stick', definition 'fire stick'. Rule 237 keeps object perception/reference only and does not open jimal damal fire-making. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "jirramar": "Dictionary jirramar noun gloss 'chips', definition 'chips, small sticks to start a fire'. Rule 237 selects small-sticks perception/reference only and does not open starting-fire use. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "kubu": "Dictionary kubu noun gloss 'smoke', definition 'smoke'. Rule 237 uses smoke perception/reference only, with no smoke-source or smoke-out prose. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "kunjin": "Dictionary kunjin noun gloss 'fire', definition 'fire'. Rule 237 uses fire perception/reference only and asserts no contrast with baya. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "miyilkura": "Dictionary miyilkura noun gloss 'smouldering log', definition 'smouldering log'. Rule 237 uses object perception/reference only, not burning/cooking prose. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "ngalku": "Dictionary ngalku noun gloss 'bush fire', definition 'bush fire'. Rule 237 uses bush-fire perception/reference only, not spreading/burning/clearing event prose. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "ngaluri": "Dictionary ngaluri noun gloss 'flames', definition 'flames'; usage has flames leaping up from fire. Rule 237 selects flames perception/reference only and does not open leaping/event prose. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "ngunjil": "Dictionary ngunjil noun gloss 'charcoal', definition 'charcoal; hot coals or hot ashes'. Rule 237 narrows to charcoal perception/reference only. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "wula": "Dictionary wula noun gloss 'flames', definition 'flames', synonym dalngarri. Rule 237 selects flames perception/reference only and does not admit dalngarri's urine homograph. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
}


ROWS = [
    row(1, "That fire is here.", "Baya yinya yaluy.", "baya yinya yaluy | fire that here", "r237_fire_that_here_baya", ["baya", "yinya", "yaluy"], EVIDENCE["baya"]),
    row(2, "That fire stick is here.", "Jimal yinya yaluy.", "jimal yinya yaluy | fire.stick that here", "r237_fire_that_here_jimal", ["jimal", "yinya", "yaluy"], EVIDENCE["jimal"]),
    row(3, "Those small sticks are here.", "Jirramar yinya yaluy.", "jirramar yinya yaluy | small.sticks that here", "r237_fire_that_here_jirramar", ["jirramar", "yinya", "yaluy"], EVIDENCE["jirramar"]),
    row(4, "That smoke is here.", "Kubu yinya yaluy.", "kubu yinya yaluy | smoke that here", "r237_fire_that_here_kubu", ["kubu", "yinya", "yaluy"], EVIDENCE["kubu"]),
    row(5, "That fire is here.", "Kunjin yinya yaluy.", "kunjin yinya yaluy | fire that here", "r237_fire_that_here_kunjin", ["kunjin", "yinya", "yaluy"], EVIDENCE["kunjin"]),
    row(6, "That smouldering log is here.", "Miyilkura yinya yaluy.", "miyilkura yinya yaluy | smouldering.log that here", "r237_fire_that_here_miyilkura", ["miyilkura", "yinya", "yaluy"], EVIDENCE["miyilkura"]),
    row(7, "That bush fire is here.", "Ngalku yinya yaluy.", "ngalku yinya yaluy | bush.fire that here", "r237_fire_that_here_ngalku", ["ngalku", "yinya", "yaluy"], EVIDENCE["ngalku"]),
    row(8, "Those flames are here.", "Ngaluri yinya yaluy.", "ngaluri yinya yaluy | flames that here", "r237_fire_that_here_ngaluri", ["ngaluri", "yinya", "yaluy"], EVIDENCE["ngaluri"]),
    row(9, "That charcoal is here.", "Ngunjil yinya yaluy.", "ngunjil yinya yaluy | charcoal that here", "r237_fire_that_here_ngunjil", ["ngunjil", "yinya", "yaluy"], EVIDENCE["ngunjil"]),
    row(10, "Those flames are here.", "Wula yinya yaluy.", "wula yinya yaluy | flames that here", "r237_fire_that_here_wula", ["wula", "yinya", "yaluy"], EVIDENCE["wula"]),
    row(11, "You see the fire there.", "Yundu baya yinyay nyajil.", "yundu baya yinyay nyajil | 2sg.NOM fire there see.NPST", "r237_2sg_fire_there_baya", ["yundu", "baya", "yinyay", "nyajil"], EVIDENCE["baya"]),
    row(12, "You see the fire stick there.", "Yundu jimal yinyay nyajil.", "yundu jimal yinyay nyajil | 2sg.NOM fire.stick there see.NPST", "r237_2sg_fire_there_jimal", ["yundu", "jimal", "yinyay", "nyajil"], EVIDENCE["jimal"]),
    row(13, "You see the small sticks there.", "Yundu jirramar yinyay nyajil.", "yundu jirramar yinyay nyajil | 2sg.NOM small.sticks there see.NPST", "r237_2sg_fire_there_jirramar", ["yundu", "jirramar", "yinyay", "nyajil"], EVIDENCE["jirramar"]),
    row(14, "You see the smoke there.", "Yundu kubu yinyay nyajil.", "yundu kubu yinyay nyajil | 2sg.NOM smoke there see.NPST", "r237_2sg_fire_there_kubu", ["yundu", "kubu", "yinyay", "nyajil"], EVIDENCE["kubu"]),
    row(15, "You see the fire there.", "Yundu kunjin yinyay nyajil.", "yundu kunjin yinyay nyajil | 2sg.NOM fire there see.NPST", "r237_2sg_fire_there_kunjin", ["yundu", "kunjin", "yinyay", "nyajil"], EVIDENCE["kunjin"]),
    row(16, "You see the smouldering log there.", "Yundu miyilkura yinyay nyajil.", "yundu miyilkura yinyay nyajil | 2sg.NOM smouldering.log there see.NPST", "r237_2sg_fire_there_miyilkura", ["yundu", "miyilkura", "yinyay", "nyajil"], EVIDENCE["miyilkura"]),
    row(17, "You see the bush fire there.", "Yundu ngalku yinyay nyajil.", "yundu ngalku yinyay nyajil | 2sg.NOM bush.fire there see.NPST", "r237_2sg_fire_there_ngalku", ["yundu", "ngalku", "yinyay", "nyajil"], EVIDENCE["ngalku"]),
    row(18, "You see the flames there.", "Yundu ngaluri yinyay nyajil.", "yundu ngaluri yinyay nyajil | 2sg.NOM flames there see.NPST", "r237_2sg_fire_there_ngaluri", ["yundu", "ngaluri", "yinyay", "nyajil"], EVIDENCE["ngaluri"]),
    row(19, "You see the charcoal there.", "Yundu ngunjil yinyay nyajil.", "yundu ngunjil yinyay nyajil | 2sg.NOM charcoal there see.NPST", "r237_2sg_fire_there_ngunjil", ["yundu", "ngunjil", "yinyay", "nyajil"], EVIDENCE["ngunjil"]),
    row(20, "You see the flames there.", "Yundu wula yinyay nyajil.", "yundu wula yinyay nyajil | 2sg.NOM flames there see.NPST", "r237_2sg_fire_there_wula", ["yundu", "wula", "yinyay", "nyajil"], EVIDENCE["wula"]),
    row(21, "He sees the fire here.", "Nyulu baya yaluy nyajil.", "nyulu baya yaluy nyajil | 3sg.NOM fire here see.NPST", "r237_3sg_fire_here_baya", ["nyulu", "baya", "yaluy", "nyajil"], EVIDENCE["baya"]),
    row(22, "He sees the fire stick here.", "Nyulu jimal yaluy nyajil.", "nyulu jimal yaluy nyajil | 3sg.NOM fire.stick here see.NPST", "r237_3sg_fire_here_jimal", ["nyulu", "jimal", "yaluy", "nyajil"], EVIDENCE["jimal"]),
    row(23, "He sees the small sticks here.", "Nyulu jirramar yaluy nyajil.", "nyulu jirramar yaluy nyajil | 3sg.NOM small.sticks here see.NPST", "r237_3sg_fire_here_jirramar", ["nyulu", "jirramar", "yaluy", "nyajil"], EVIDENCE["jirramar"]),
    row(24, "He sees the smoke here.", "Nyulu kubu yaluy nyajil.", "nyulu kubu yaluy nyajil | 3sg.NOM smoke here see.NPST", "r237_3sg_fire_here_kubu", ["nyulu", "kubu", "yaluy", "nyajil"], EVIDENCE["kubu"]),
    row(25, "He sees the fire here.", "Nyulu kunjin yaluy nyajil.", "nyulu kunjin yaluy nyajil | 3sg.NOM fire here see.NPST", "r237_3sg_fire_here_kunjin", ["nyulu", "kunjin", "yaluy", "nyajil"], EVIDENCE["kunjin"]),
    row(26, "He sees the smouldering log here.", "Nyulu miyilkura yaluy nyajil.", "nyulu miyilkura yaluy nyajil | 3sg.NOM smouldering.log here see.NPST", "r237_3sg_fire_here_miyilkura", ["nyulu", "miyilkura", "yaluy", "nyajil"], EVIDENCE["miyilkura"]),
    row(27, "He sees the bush fire here.", "Nyulu ngalku yaluy nyajil.", "nyulu ngalku yaluy nyajil | 3sg.NOM bush.fire here see.NPST", "r237_3sg_fire_here_ngalku", ["nyulu", "ngalku", "yaluy", "nyajil"], EVIDENCE["ngalku"]),
    row(28, "He sees the flames here.", "Nyulu ngaluri yaluy nyajil.", "nyulu ngaluri yaluy nyajil | 3sg.NOM flames here see.NPST", "r237_3sg_fire_here_ngaluri", ["nyulu", "ngaluri", "yaluy", "nyajil"], EVIDENCE["ngaluri"]),
    row(29, "He sees the charcoal here.", "Nyulu ngunjil yaluy nyajil.", "nyulu ngunjil yaluy nyajil | 3sg.NOM charcoal here see.NPST", "r237_3sg_fire_here_ngunjil", ["nyulu", "ngunjil", "yaluy", "nyajil"], EVIDENCE["ngunjil"]),
    row(30, "He sees the flames here.", "Nyulu wula yaluy nyajil.", "nyulu wula yaluy nyajil | 3sg.NOM flames here see.NPST", "r237_3sg_fire_here_wula", ["nyulu", "wula", "yaluy", "nyajil"], EVIDENCE["wula"]),
    row(31, "We see the fire there.", "Ngana baya yinyay nyajil.", "ngana baya yinyay nyajil | 1pl.INC.NOM fire there see.NPST", "r237_1plinc_fire_there_baya", ["ngana", "baya", "yinyay", "nyajil"], EVIDENCE["baya"]),
    row(32, "We see the fire stick there.", "Ngana jimal yinyay nyajil.", "ngana jimal yinyay nyajil | 1pl.INC.NOM fire.stick there see.NPST", "r237_1plinc_fire_there_jimal", ["ngana", "jimal", "yinyay", "nyajil"], EVIDENCE["jimal"]),
    row(33, "We see the small sticks there.", "Ngana jirramar yinyay nyajil.", "ngana jirramar yinyay nyajil | 1pl.INC.NOM small.sticks there see.NPST", "r237_1plinc_fire_there_jirramar", ["ngana", "jirramar", "yinyay", "nyajil"], EVIDENCE["jirramar"]),
    row(34, "We see the smoke there.", "Ngana kubu yinyay nyajil.", "ngana kubu yinyay nyajil | 1pl.INC.NOM smoke there see.NPST", "r237_1plinc_fire_there_kubu", ["ngana", "kubu", "yinyay", "nyajil"], EVIDENCE["kubu"]),
    row(35, "We see the fire there.", "Ngana kunjin yinyay nyajil.", "ngana kunjin yinyay nyajil | 1pl.INC.NOM fire there see.NPST", "r237_1plinc_fire_there_kunjin", ["ngana", "kunjin", "yinyay", "nyajil"], EVIDENCE["kunjin"]),
    row(36, "We see the smouldering log there.", "Ngana miyilkura yinyay nyajil.", "ngana miyilkura yinyay nyajil | 1pl.INC.NOM smouldering.log there see.NPST", "r237_1plinc_fire_there_miyilkura", ["ngana", "miyilkura", "yinyay", "nyajil"], EVIDENCE["miyilkura"]),
    row(37, "We see the bush fire there.", "Ngana ngalku yinyay nyajil.", "ngana ngalku yinyay nyajil | 1pl.INC.NOM bush.fire there see.NPST", "r237_1plinc_fire_there_ngalku", ["ngana", "ngalku", "yinyay", "nyajil"], EVIDENCE["ngalku"]),
    row(38, "We see the flames there.", "Ngana ngaluri yinyay nyajil.", "ngana ngaluri yinyay nyajil | 1pl.INC.NOM flames there see.NPST", "r237_1plinc_fire_there_ngaluri", ["ngana", "ngaluri", "yinyay", "nyajil"], EVIDENCE["ngaluri"]),
    row(39, "We see the charcoal there.", "Ngana ngunjil yinyay nyajil.", "ngana ngunjil yinyay nyajil | 1pl.INC.NOM charcoal there see.NPST", "r237_1plinc_fire_there_ngunjil", ["ngana", "ngunjil", "yinyay", "nyajil"], EVIDENCE["ngunjil"]),
    row(40, "We see the flames there.", "Ngana wula yinyay nyajil.", "ngana wula yinyay nyajil | 1pl.INC.NOM flames there see.NPST", "r237_1plinc_fire_there_wula", ["ngana", "wula", "yinyay", "nyajil"], EVIDENCE["wula"]),
    row(41, "They saw the fire here.", "Jana baya yaluy nyajin.", "jana baya yaluy nyajin | 3pl.NOM fire here see.PST", "r237_3pl_fire_here_past_baya", ["jana", "baya", "yaluy", "nyajin"], EVIDENCE["baya"]),
    row(42, "They saw the fire stick here.", "Jana jimal yaluy nyajin.", "jana jimal yaluy nyajin | 3pl.NOM fire.stick here see.PST", "r237_3pl_fire_here_past_jimal", ["jana", "jimal", "yaluy", "nyajin"], EVIDENCE["jimal"]),
    row(43, "They saw the small sticks here.", "Jana jirramar yaluy nyajin.", "jana jirramar yaluy nyajin | 3pl.NOM small.sticks here see.PST", "r237_3pl_fire_here_past_jirramar", ["jana", "jirramar", "yaluy", "nyajin"], EVIDENCE["jirramar"]),
    row(44, "They saw the smoke here.", "Jana kubu yaluy nyajin.", "jana kubu yaluy nyajin | 3pl.NOM smoke here see.PST", "r237_3pl_fire_here_past_kubu", ["jana", "kubu", "yaluy", "nyajin"], EVIDENCE["kubu"]),
    row(45, "They saw the fire here.", "Jana kunjin yaluy nyajin.", "jana kunjin yaluy nyajin | 3pl.NOM fire here see.PST", "r237_3pl_fire_here_past_kunjin", ["jana", "kunjin", "yaluy", "nyajin"], EVIDENCE["kunjin"]),
    row(46, "They saw the smouldering log here.", "Jana miyilkura yaluy nyajin.", "jana miyilkura yaluy nyajin | 3pl.NOM smouldering.log here see.PST", "r237_3pl_fire_here_past_miyilkura", ["jana", "miyilkura", "yaluy", "nyajin"], EVIDENCE["miyilkura"]),
    row(47, "They saw the bush fire here.", "Jana ngalku yaluy nyajin.", "jana ngalku yaluy nyajin | 3pl.NOM bush.fire here see.PST", "r237_3pl_fire_here_past_ngalku", ["jana", "ngalku", "yaluy", "nyajin"], EVIDENCE["ngalku"]),
    row(48, "They saw the flames here.", "Jana ngaluri yaluy nyajin.", "jana ngaluri yaluy nyajin | 3pl.NOM flames here see.PST", "r237_3pl_fire_here_past_ngaluri", ["jana", "ngaluri", "yaluy", "nyajin"], EVIDENCE["ngaluri"]),
    row(49, "They saw the charcoal here.", "Jana ngunjil yaluy nyajin.", "jana ngunjil yaluy nyajin | 3pl.NOM charcoal here see.PST", "r237_3pl_fire_here_past_ngunjil", ["jana", "ngunjil", "yaluy", "nyajin"], EVIDENCE["ngunjil"]),
    row(50, "They saw the flames here.", "Jana wula yaluy nyajin.", "jana wula yaluy nyajin | 3pl.NOM flames here see.PST", "r237_3pl_fire_here_past_wula", ["jana", "wula", "yaluy", "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("r237_"):
            issues.append(f"{label}: Rule 237 frame must start with r237_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: no loans are admitted in Rule 237")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied carry-forward token {denied!r}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 237 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")
            if head in PLURAL_TARGETS and selected not in english_low:
                issues.append(f"{label}: plural lexical target {head} must stay lexical/contextual")
        for support in SUPPORT:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if any(has_token(low, pronoun) for pronoun in PRONOUN_SUPPORT):
            pronouns = [pronoun for pronoun in PRONOUN_SUPPORT if has_token(low, pronoun)]
            if len(pronouns) != 1:
                issues.append(f"{label}: pronoun/perception rows require exactly one nominative pronoun, found {pronouns}")
        if 1 <= seq <= 10 and (not low.endswith(" yinya yaluy.") or low.startswith("yinya ") or len(low.split()) != 3):
            issues.append(f"{label}: rows 1-10 require TARGET yinya yaluy")
        if 11 <= seq <= 20 and (not low.startswith("yundu ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Yundu TARGET yinyay nyajil")
        if 21 <= seq <= 30 and (not low.startswith("nyulu ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Nyulu TARGET yaluy nyajil")
        if 31 <= seq <= 40 and (not low.startswith("ngana ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Ngana TARGET yinyay nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yaluy nyajin.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yaluy 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,))
    else:
        db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 237 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 237 source pass retained Rule 236 dictionary fire nouns, checked Patz 3.5 for nominative yundu/nyulu/ngana/jana, Patz 3.7 for yinya/yaluy/yinyay, Patz 3.8.3.2 for nyaji-l 'see', and Patz Text 12 fire/smoke perception examples without importing their event prose.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="planning",
        event_type="construction-scope",
        actor="claude-main",
        detail="Selected Rule 237 fire-reference construction consolidation after Rule 236: same ten fire-domain nouns in one postposed-demonstrative band and four pronoun-perception bands. No fire-making, lighting, firewood, cooking, burning, clearing, spreading, leaping-flame, hot-coal/ash, homograph, loan, pronoun-case, target-case, or support-inflection semantics admitted.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="coverage-audit",
        actor="claude-main",
        detail="Pre-batch token-boundary coverage among accepted/revised rows: baya 455, jimal 33, jirramar 5, kubu 61, kunjin 11, miyilkura 5, ngalku 5, ngaluri 5, ngunjil 68, wula 59.",
        command="sqlite3 synthetic.db WITH norm AS (...) SELECT h, SUM(token_match) ...",
        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 all 50 planned Rule 237 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_279.py internal collision scan",
        output_excerpt="0",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="Static guards passed: 50 rows, exactly one Rule 237 target per row, no loans, no target suffixes, no denied recent target drift, no fire action/use/homograph English, no pronoun case, no perception compound, and five planned construction bands only.",
        command="python3 batch_scripts/bank_279.py",
        artifact_path="batch_scripts/bank_279.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 hand-authored Rule 237 fire-reference construction rows as under_review.",
        command="python3 batch_scripts/bank_279.py",
        artifact_path="batch_scripts/bank_279.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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