#!/usr/bin/env python3
"""Bank batch-273: Rule 231 perception consolidation after Rule 230."""

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-273"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-273-named-tree-perception-consolidation-after-rule230-process.md"

TARGET_HEADS = {
    "bingkajiri": "bingkajiri tree",
    "durral": "durral tree",
    "kalbariji": "kalbariji tree",
    "kalkuri": "kalkuri tree",
    "kuji-kuji": "kuji-kuji tree",
    "kuralbi": "kuralbi tree",
    "kuray-kuray": "kuray-kuray tree",
    "mingur": "mingur tree",
    "mukukana": "mukukana tree",
    "mungka-mungka": "mungka-mungka tree",
}
PRONOUNS = {"ngayu", "yundu", "nyulu", "ngana", "jana"}
LOCAL_SUPPORT = {"yaluy", "yinyay"}
PERCEPTION = {"nyajil", "nyajin"}
DENY_KUKU = {
    # Earlier flora/tree exact targets from Rules 163, 165, 167, 199, 208, 224, and 225.
    "buyukal", "dalkan", "dari", "dibul", "dilbal", "dili", "jabarr", "jakari", "jama", "jilbar",
    "babajaka", "balmbalka", "baway", "bijin", "dalban", "dingki", "dulmbil", "duwar", "kalal", "kumul",
    "jina-jina", "jarrangkal", "julbin", "kunjarri", "kurruy", "malurri", "mawar", "maya", "wangay", "yawarr",
    "jinjajalka", "kurunkal", "madu", "marngkil", "marrabal", "milbarakal", "mungari", "wurmbariji",
    "wuymbariji", "kurrun", "banday", "bijan", "bukunjarra", "bulngkarr", "wanakan", "warka", "wulngkurr",
    "yalngkal", "yarun", "yumba", "jundul", "kabariji", "kawunjil", "kayjurr", "kingal", "kulbar", "kumin",
    "marbaji", "milbanku", "mujarr", "bindi", "dakil-dakil", "duduy", "jumbay-jumbay", "kababina",
    "kalal-kalal", "karul-karul", "malari", "waji-waji", "wayul",
    # Rejected or deferred Rule 230 candidates.
    "baril-baril", "bikarrakul", "bilar", "bujabay", "bulbul", "jilanji", "kajul", "kakaji", "karranda",
    "kawarr", "marra", "marrakan", "ngalkarran", "ngurrkul", "yinkunji",
}
DENY_ENGLISH = (
    "fruit", "seed", "edible", "food", "eat", "eating", "ate", "roast", "roasted", "cook", "cooked",
    "blind", "blinding", "spear", "spears", "rope", "bark", "medicine", "poison", "poisonous", "sap",
    "timber", "wood", "material", "use", "used", "flower", "season", "ripe", "thorn", "thorns", "branch",
    "branches", "habitat", "ecology", "country", "story", "place", "water", "gum", "nut", "nuts",
)


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.87")


EVIDENCE = {
    "bingkajiri": "Dictionary bingkajiri noun gloss 'bingkajiri tree'. Rule 230 verified bare named-tree reference. Patz 3.5 sources pronouns, 3.7 local demonstratives, and Patz examples plus Rule 227 support nyajil/nyajin perception frames.",
    "durral": "Dictionary durral noun gloss 'durral tree'. Rule 230 closed fruit/spear/wood prose. Patz 3.5, 3.7, and nyaji-l perception examples support this construction.",
    "kalbariji": "Dictionary kalbariji noun flora-tree entry; Rule 230 verified kalbariji-tree reference only. Patz 3.5 pronouns, 3.7 local demonstratives, and nyaji-l perception law support the frame.",
    "kalkuri": "Dictionary kalkuri noun flora-tree entry; Rule 230 verified kalkuri-tree reference only. Patz 3.5, 3.7, and nyaji-l perception examples support the frame.",
    "kuji-kuji": "Dictionary kuji-kuji noun flora-tree entry, lexicalized hyphenated reduplication. Rule 230 verified whole-head reference. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
    "kuralbi": "Dictionary kuralbi noun gloss 'tree'. Rule 230 verified kuralbi-tree reference only and kept older bark-source rows closed. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
    "kuray-kuray": "Dictionary kuray-kuray noun flora-tree entry, lexicalized hyphenated reduplication. Rule 230 verified whole-head reference. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
    "mingur": "Dictionary mingur noun gloss 'tree'. Rule 230 verified mingur-tree reference only and closed oilnut prose. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
    "mukukana": "Dictionary mukukana noun gloss 'tree'. Rule 230 verified mukukana-tree reference only and closed edible-fruit prose. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
    "mungka-mungka": "Dictionary mungka-mungka noun flora-tree entry, lexicalized hyphenated reduplication. Rule 230 verified whole-head reference and closed thorn/branch prose. Patz 3.5, 3.7, and nyaji-l perception law support the frame.",
}


ROWS = [
    row(1, "I see the bingkajiri tree here.", "Ngayu bingkajiri yaluy nyajil.", "ngayu bingkajiri yaluy nyajil | I bingkajiri.tree here perceive.NONPAST", "r231_1sg_tree_here_bingkajiri", ["ngayu", "bingkajiri", "yaluy", "nyajil"], EVIDENCE["bingkajiri"]),
    row(2, "I see the durral tree here.", "Ngayu durral yaluy nyajil.", "ngayu durral yaluy nyajil | I durral.tree here perceive.NONPAST", "r231_1sg_tree_here_durral", ["ngayu", "durral", "yaluy", "nyajil"], EVIDENCE["durral"]),
    row(3, "I see the kalbariji tree here.", "Ngayu kalbariji yaluy nyajil.", "ngayu kalbariji yaluy nyajil | I kalbariji.tree here perceive.NONPAST", "r231_1sg_tree_here_kalbariji", ["ngayu", "kalbariji", "yaluy", "nyajil"], EVIDENCE["kalbariji"]),
    row(4, "I see the kalkuri tree here.", "Ngayu kalkuri yaluy nyajil.", "ngayu kalkuri yaluy nyajil | I kalkuri.tree here perceive.NONPAST", "r231_1sg_tree_here_kalkuri", ["ngayu", "kalkuri", "yaluy", "nyajil"], EVIDENCE["kalkuri"]),
    row(5, "I see the kuji-kuji tree here.", "Ngayu kuji-kuji yaluy nyajil.", "ngayu kuji-kuji yaluy nyajil | I kuji.kuji.tree here perceive.NONPAST", "r231_1sg_tree_here_kuji_kuji", ["ngayu", "kuji-kuji", "yaluy", "nyajil"], EVIDENCE["kuji-kuji"]),
    row(6, "I see the kuralbi tree here.", "Ngayu kuralbi yaluy nyajil.", "ngayu kuralbi yaluy nyajil | I kuralbi.tree here perceive.NONPAST", "r231_1sg_tree_here_kuralbi", ["ngayu", "kuralbi", "yaluy", "nyajil"], EVIDENCE["kuralbi"]),
    row(7, "I see the kuray-kuray tree here.", "Ngayu kuray-kuray yaluy nyajil.", "ngayu kuray-kuray yaluy nyajil | I kuray.kuray.tree here perceive.NONPAST", "r231_1sg_tree_here_kuray_kuray", ["ngayu", "kuray-kuray", "yaluy", "nyajil"], EVIDENCE["kuray-kuray"]),
    row(8, "I see the mingur tree here.", "Ngayu mingur yaluy nyajil.", "ngayu mingur yaluy nyajil | I mingur.tree here perceive.NONPAST", "r231_1sg_tree_here_mingur", ["ngayu", "mingur", "yaluy", "nyajil"], EVIDENCE["mingur"]),
    row(9, "I see the mukukana tree here.", "Ngayu mukukana yaluy nyajil.", "ngayu mukukana yaluy nyajil | I mukukana.tree here perceive.NONPAST", "r231_1sg_tree_here_mukukana", ["ngayu", "mukukana", "yaluy", "nyajil"], EVIDENCE["mukukana"]),
    row(10, "I see the mungka-mungka tree here.", "Ngayu mungka-mungka yaluy nyajil.", "ngayu mungka-mungka yaluy nyajil | I mungka.mungka.tree here perceive.NONPAST", "r231_1sg_tree_here_mungka_mungka", ["ngayu", "mungka-mungka", "yaluy", "nyajil"], EVIDENCE["mungka-mungka"]),
    row(11, "You see the bingkajiri tree there.", "Yundu bingkajiri yinyay nyajil.", "yundu bingkajiri yinyay nyajil | you bingkajiri.tree there perceive.NONPAST", "r231_2sg_tree_there_bingkajiri", ["yundu", "bingkajiri", "yinyay", "nyajil"], EVIDENCE["bingkajiri"]),
    row(12, "You see the durral tree there.", "Yundu durral yinyay nyajil.", "yundu durral yinyay nyajil | you durral.tree there perceive.NONPAST", "r231_2sg_tree_there_durral", ["yundu", "durral", "yinyay", "nyajil"], EVIDENCE["durral"]),
    row(13, "You see the kalbariji tree there.", "Yundu kalbariji yinyay nyajil.", "yundu kalbariji yinyay nyajil | you kalbariji.tree there perceive.NONPAST", "r231_2sg_tree_there_kalbariji", ["yundu", "kalbariji", "yinyay", "nyajil"], EVIDENCE["kalbariji"]),
    row(14, "You see the kalkuri tree there.", "Yundu kalkuri yinyay nyajil.", "yundu kalkuri yinyay nyajil | you kalkuri.tree there perceive.NONPAST", "r231_2sg_tree_there_kalkuri", ["yundu", "kalkuri", "yinyay", "nyajil"], EVIDENCE["kalkuri"]),
    row(15, "You see the kuji-kuji tree there.", "Yundu kuji-kuji yinyay nyajil.", "yundu kuji-kuji yinyay nyajil | you kuji.kuji.tree there perceive.NONPAST", "r231_2sg_tree_there_kuji_kuji", ["yundu", "kuji-kuji", "yinyay", "nyajil"], EVIDENCE["kuji-kuji"]),
    row(16, "You see the kuralbi tree there.", "Yundu kuralbi yinyay nyajil.", "yundu kuralbi yinyay nyajil | you kuralbi.tree there perceive.NONPAST", "r231_2sg_tree_there_kuralbi", ["yundu", "kuralbi", "yinyay", "nyajil"], EVIDENCE["kuralbi"]),
    row(17, "You see the kuray-kuray tree there.", "Yundu kuray-kuray yinyay nyajil.", "yundu kuray-kuray yinyay nyajil | you kuray.kuray.tree there perceive.NONPAST", "r231_2sg_tree_there_kuray_kuray", ["yundu", "kuray-kuray", "yinyay", "nyajil"], EVIDENCE["kuray-kuray"]),
    row(18, "You see the mingur tree there.", "Yundu mingur yinyay nyajil.", "yundu mingur yinyay nyajil | you mingur.tree there perceive.NONPAST", "r231_2sg_tree_there_mingur", ["yundu", "mingur", "yinyay", "nyajil"], EVIDENCE["mingur"]),
    row(19, "You see the mukukana tree there.", "Yundu mukukana yinyay nyajil.", "yundu mukukana yinyay nyajil | you mukukana.tree there perceive.NONPAST", "r231_2sg_tree_there_mukukana", ["yundu", "mukukana", "yinyay", "nyajil"], EVIDENCE["mukukana"]),
    row(20, "You see the mungka-mungka tree there.", "Yundu mungka-mungka yinyay nyajil.", "yundu mungka-mungka yinyay nyajil | you mungka.mungka.tree there perceive.NONPAST", "r231_2sg_tree_there_mungka_mungka", ["yundu", "mungka-mungka", "yinyay", "nyajil"], EVIDENCE["mungka-mungka"]),
    row(21, "He sees the bingkajiri tree there.", "Nyulu bingkajiri yinyay nyajil.", "nyulu bingkajiri yinyay nyajil | he bingkajiri.tree there perceive.NONPAST", "r231_3sg_tree_there_bingkajiri", ["nyulu", "bingkajiri", "yinyay", "nyajil"], EVIDENCE["bingkajiri"]),
    row(22, "He sees the durral tree there.", "Nyulu durral yinyay nyajil.", "nyulu durral yinyay nyajil | he durral.tree there perceive.NONPAST", "r231_3sg_tree_there_durral", ["nyulu", "durral", "yinyay", "nyajil"], EVIDENCE["durral"]),
    row(23, "He sees the kalbariji tree there.", "Nyulu kalbariji yinyay nyajil.", "nyulu kalbariji yinyay nyajil | he kalbariji.tree there perceive.NONPAST", "r231_3sg_tree_there_kalbariji", ["nyulu", "kalbariji", "yinyay", "nyajil"], EVIDENCE["kalbariji"]),
    row(24, "He sees the kalkuri tree there.", "Nyulu kalkuri yinyay nyajil.", "nyulu kalkuri yinyay nyajil | he kalkuri.tree there perceive.NONPAST", "r231_3sg_tree_there_kalkuri", ["nyulu", "kalkuri", "yinyay", "nyajil"], EVIDENCE["kalkuri"]),
    row(25, "He sees the kuji-kuji tree there.", "Nyulu kuji-kuji yinyay nyajil.", "nyulu kuji-kuji yinyay nyajil | he kuji.kuji.tree there perceive.NONPAST", "r231_3sg_tree_there_kuji_kuji", ["nyulu", "kuji-kuji", "yinyay", "nyajil"], EVIDENCE["kuji-kuji"]),
    row(26, "He sees the kuralbi tree there.", "Nyulu kuralbi yinyay nyajil.", "nyulu kuralbi yinyay nyajil | he kuralbi.tree there perceive.NONPAST", "r231_3sg_tree_there_kuralbi", ["nyulu", "kuralbi", "yinyay", "nyajil"], EVIDENCE["kuralbi"]),
    row(27, "He sees the kuray-kuray tree there.", "Nyulu kuray-kuray yinyay nyajil.", "nyulu kuray-kuray yinyay nyajil | he kuray.kuray.tree there perceive.NONPAST", "r231_3sg_tree_there_kuray_kuray", ["nyulu", "kuray-kuray", "yinyay", "nyajil"], EVIDENCE["kuray-kuray"]),
    row(28, "He sees the mingur tree there.", "Nyulu mingur yinyay nyajil.", "nyulu mingur yinyay nyajil | he mingur.tree there perceive.NONPAST", "r231_3sg_tree_there_mingur", ["nyulu", "mingur", "yinyay", "nyajil"], EVIDENCE["mingur"]),
    row(29, "He sees the mukukana tree there.", "Nyulu mukukana yinyay nyajil.", "nyulu mukukana yinyay nyajil | he mukukana.tree there perceive.NONPAST", "r231_3sg_tree_there_mukukana", ["nyulu", "mukukana", "yinyay", "nyajil"], EVIDENCE["mukukana"]),
    row(30, "He sees the mungka-mungka tree there.", "Nyulu mungka-mungka yinyay nyajil.", "nyulu mungka-mungka yinyay nyajil | he mungka.mungka.tree there perceive.NONPAST", "r231_3sg_tree_there_mungka_mungka", ["nyulu", "mungka-mungka", "yinyay", "nyajil"], EVIDENCE["mungka-mungka"]),
    row(31, "We see the bingkajiri tree here.", "Ngana bingkajiri yaluy nyajil.", "ngana bingkajiri yaluy nyajil | we.INC bingkajiri.tree here perceive.NONPAST", "r231_1plinc_tree_here_bingkajiri", ["ngana", "bingkajiri", "yaluy", "nyajil"], EVIDENCE["bingkajiri"]),
    row(32, "We see the durral tree here.", "Ngana durral yaluy nyajil.", "ngana durral yaluy nyajil | we.INC durral.tree here perceive.NONPAST", "r231_1plinc_tree_here_durral", ["ngana", "durral", "yaluy", "nyajil"], EVIDENCE["durral"]),
    row(33, "We see the kalbariji tree here.", "Ngana kalbariji yaluy nyajil.", "ngana kalbariji yaluy nyajil | we.INC kalbariji.tree here perceive.NONPAST", "r231_1plinc_tree_here_kalbariji", ["ngana", "kalbariji", "yaluy", "nyajil"], EVIDENCE["kalbariji"]),
    row(34, "We see the kalkuri tree here.", "Ngana kalkuri yaluy nyajil.", "ngana kalkuri yaluy nyajil | we.INC kalkuri.tree here perceive.NONPAST", "r231_1plinc_tree_here_kalkuri", ["ngana", "kalkuri", "yaluy", "nyajil"], EVIDENCE["kalkuri"]),
    row(35, "We see the kuji-kuji tree here.", "Ngana kuji-kuji yaluy nyajil.", "ngana kuji-kuji yaluy nyajil | we.INC kuji.kuji.tree here perceive.NONPAST", "r231_1plinc_tree_here_kuji_kuji", ["ngana", "kuji-kuji", "yaluy", "nyajil"], EVIDENCE["kuji-kuji"]),
    row(36, "We see the kuralbi tree here.", "Ngana kuralbi yaluy nyajil.", "ngana kuralbi yaluy nyajil | we.INC kuralbi.tree here perceive.NONPAST", "r231_1plinc_tree_here_kuralbi", ["ngana", "kuralbi", "yaluy", "nyajil"], EVIDENCE["kuralbi"]),
    row(37, "We see the kuray-kuray tree here.", "Ngana kuray-kuray yaluy nyajil.", "ngana kuray-kuray yaluy nyajil | we.INC kuray.kuray.tree here perceive.NONPAST", "r231_1plinc_tree_here_kuray_kuray", ["ngana", "kuray-kuray", "yaluy", "nyajil"], EVIDENCE["kuray-kuray"]),
    row(38, "We see the mingur tree here.", "Ngana mingur yaluy nyajil.", "ngana mingur yaluy nyajil | we.INC mingur.tree here perceive.NONPAST", "r231_1plinc_tree_here_mingur", ["ngana", "mingur", "yaluy", "nyajil"], EVIDENCE["mingur"]),
    row(39, "We see the mukukana tree here.", "Ngana mukukana yaluy nyajil.", "ngana mukukana yaluy nyajil | we.INC mukukana.tree here perceive.NONPAST", "r231_1plinc_tree_here_mukukana", ["ngana", "mukukana", "yaluy", "nyajil"], EVIDENCE["mukukana"]),
    row(40, "We see the mungka-mungka tree here.", "Ngana mungka-mungka yaluy nyajil.", "ngana mungka-mungka yaluy nyajil | we.INC mungka.mungka.tree here perceive.NONPAST", "r231_1plinc_tree_here_mungka_mungka", ["ngana", "mungka-mungka", "yaluy", "nyajil"], EVIDENCE["mungka-mungka"]),
    row(41, "They saw the bingkajiri tree there.", "Jana bingkajiri yinyay nyajin.", "jana bingkajiri yinyay nyajin | they bingkajiri.tree there perceive.PAST", "r231_3pl_tree_there_past_bingkajiri", ["jana", "bingkajiri", "yinyay", "nyajin"], EVIDENCE["bingkajiri"]),
    row(42, "They saw the durral tree there.", "Jana durral yinyay nyajin.", "jana durral yinyay nyajin | they durral.tree there perceive.PAST", "r231_3pl_tree_there_past_durral", ["jana", "durral", "yinyay", "nyajin"], EVIDENCE["durral"]),
    row(43, "They saw the kalbariji tree there.", "Jana kalbariji yinyay nyajin.", "jana kalbariji yinyay nyajin | they kalbariji.tree there perceive.PAST", "r231_3pl_tree_there_past_kalbariji", ["jana", "kalbariji", "yinyay", "nyajin"], EVIDENCE["kalbariji"]),
    row(44, "They saw the kalkuri tree there.", "Jana kalkuri yinyay nyajin.", "jana kalkuri yinyay nyajin | they kalkuri.tree there perceive.PAST", "r231_3pl_tree_there_past_kalkuri", ["jana", "kalkuri", "yinyay", "nyajin"], EVIDENCE["kalkuri"]),
    row(45, "They saw the kuji-kuji tree there.", "Jana kuji-kuji yinyay nyajin.", "jana kuji-kuji yinyay nyajin | they kuji.kuji.tree there perceive.PAST", "r231_3pl_tree_there_past_kuji_kuji", ["jana", "kuji-kuji", "yinyay", "nyajin"], EVIDENCE["kuji-kuji"]),
    row(46, "They saw the kuralbi tree there.", "Jana kuralbi yinyay nyajin.", "jana kuralbi yinyay nyajin | they kuralbi.tree there perceive.PAST", "r231_3pl_tree_there_past_kuralbi", ["jana", "kuralbi", "yinyay", "nyajin"], EVIDENCE["kuralbi"]),
    row(47, "They saw the kuray-kuray tree there.", "Jana kuray-kuray yinyay nyajin.", "jana kuray-kuray yinyay nyajin | they kuray.kuray.tree there perceive.PAST", "r231_3pl_tree_there_past_kuray_kuray", ["jana", "kuray-kuray", "yinyay", "nyajin"], EVIDENCE["kuray-kuray"]),
    row(48, "They saw the mingur tree there.", "Jana mingur yinyay nyajin.", "jana mingur yinyay nyajin | they mingur.tree there perceive.PAST", "r231_3pl_tree_there_past_mingur", ["jana", "mingur", "yinyay", "nyajin"], EVIDENCE["mingur"]),
    row(49, "They saw the mukukana tree there.", "Jana mukukana yinyay nyajin.", "jana mukukana yinyay nyajin | they mukukana.tree there perceive.PAST", "r231_3pl_tree_there_past_mukukana", ["jana", "mukukana", "yinyay", "nyajin"], EVIDENCE["mukukana"]),
    row(50, "They saw the mungka-mungka tree there.", "Jana mungka-mungka yinyay nyajin.", "jana mungka-mungka yinyay nyajin | they mungka.mungka.tree there perceive.PAST", "r231_3pl_tree_there_past_mungka_mungka", ["jana", "mungka-mungka", "yinyay", "nyajin"], EVIDENCE["mungka-mungka"]),
]


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("r231_"):
            issues.append(f"{label}: Rule 231 frame must start with r231_")
        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 231")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied earlier flora/tree 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 230 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 PRONOUNS | LOCAL_SUPPORT | PERCEPTION:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if 1 <= seq <= 10 and (not low.startswith("ngayu ") or not low.endswith(" yaluy nyajil.") or has_token(low, "yinyay") 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("yundu ") or not low.endswith(" yinyay nyajil.") or has_token(low, "yaluy") 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(" yinyay nyajil.") or has_token(low, "yaluy") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Nyulu TARGET yinyay nyajil")
        if 31 <= seq <= 40 and (not low.startswith("ngana ") or not low.endswith(" yaluy nyajil.") or has_token(low, "yinyay") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Ngana TARGET yaluy nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yinyay nyajin.") or has_token(low, "yaluy") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yinyay nyajin")
        if "saw" in english_low and not has_token(low, "nyajin"):
            issues.append(f"{label}: English past perception requires nyajin")
        if "see" in english_low and not has_token(low, "nyajil"):
            issues.append(f"{label}: English nonpast perception requires nyajil")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied tree-source-prose 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 sentences WHERE batch_id=?", (batch_id,))
    else:
        db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 231 draft", "operator-guide-2026-07-08", "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:02d} {kuku}: {found}")
    if collisions:
        raise SystemExit("duplicate preflight failed:\n" + "\n".join(collisions))

    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-pass",
        actor="claude-main",
        detail="Checked Patz section 3.5 personal-pronoun nominative forms, Patz section 3.7 yaluy/yinyay local demonstratives, Patz nyaji-l perception examples, Rule 227 nyajil/nyajin corpus law, Rule 230 target tree entries, and all ten dictionary entries for the named-tree heads.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="construction-scope",
        actor="claude-main",
        detail="Rule 231 reuses the ten Rule 230 named-tree heads but changes the construction to visual-perception frames with bare pronoun A, bare tree object, yaluy/yinyay local support, nonpast nyajil, and past nyajin. No new tree lexeme or tree-source prose is admitted.",
        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 231 Kuku surfaces returned no accepted, revised, or under-review collisions.",
        command="sqlite3 exact lower(kuku) collision scan for 50 planned surfaces",
        output_excerpt="collisions=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="bank_273.py static guards passed: 50 explicit hand-authored rows, exactly one Rule 230 target tree noun per row, no loans, no target morphology, no earlier flora/tree target token, no tree source-prose English, five sourced perception frame bands, and correct nyajil/nyajin tense matching.",
        artifact_path="batch_scripts/bank_273.py",
        created_at=NOW,
    )

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, created_at, updated_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (batch_id, seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, "under_review", confidence, NOW, NOW)
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    db.commit()

    n = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status='under_review'", (batch_id,)).fetchone()[0]
    print(f"banked {LABEL}: {n} under_review rows")
    return 0


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