#!/usr/bin/env python3
"""Bank batch-293: Rule 251 fish-reference complementary perception pronoun expansion."""

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-293"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-293-fish-reference-complementary-pronoun-expansion-after-rule250-process.md"

TARGET_HEADS = {
    "kubija": "barracuda",
    "mujarrka": "archerfish",
    "barrbal": "black bream",
    "wuyumbu": "toadfish",
    "bayil": "perch",
    "dalmbal": "flathead",
    "balarri": "queenfish",
    "bilmbin": "black-tip shark",
    "jalkay": "salmon",
    "jijirr": "red bream",
}
SUPPORT = {"ngayu", "nyulu", "yurra", "bula", "jana", "yaluy", "yinyay", "nyajil"}
PRONOUN_SUPPORT = {"ngayu", "nyulu", "yurra", "bula", "jana"}
DENY_KUKU = {
    # Rule 240/251 fish exclusions.
    "bayilbayil", "kijukum", "ngiwa", "yidilnyul", "yidi", "yaba", "yaburr", "walarrji",
    "dalku", "kudi", "dukul-barangka", "dukul-barangkal", "kubu-kubu",
    "kundurr-kundurr", "mangkurr-mangkurr", "walbul-walbul", "jalil-jalil",
    # Fish action/use/habitat/story/body-part drift.
    "manin", "mani", "daman", "dama", "karrba", "balbi", "dinda", "nuka",
    "badur", "kalka", "bana", "jalun", "wawubaja", "bubu", "baral", "juku",
    "mangrove", "walarr", "kanya", "munyun", "yijirr", "dukul", "murraja",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "nganya", "nyunguny", "yurrangan", "yurraniny", "yurranka", "yurranda",
    "yurranji", "yurranga", "bulangan", "bulaniny", "bulanka", "bulanda",
    "bulanji", "bulanga", "ianangan", "iananiny", "iananka", "iananda",
    "iananji", "iananga", "yinyangka", "yinyayngka", "yinyayanka", "yinyamba",
    "yinyayanda", "yinyamuny", "yinyamundu", "yinyanji", "yinyarriny",
    "yalungku", "yaluyngku", "yaluyunku", "yaluymbu", "yaluyundu", "yalamuny",
    "yalamundu", "yalanji", "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji",
    # Recent closed domains: dwelling, fire, body, landform/geography, bird.
    "bakarr", "bayan", "bira", "jiji", "kilili", "wurun", "jakway", "yulal",
    "kararr", "bilangkurr", "baya", "jimal", "jirramar", "kubu", "kunjin",
    "miyilkura", "ngalku", "ngaluri", "ngunjil", "wula", "bandin", "bari",
    "bibar", "birri", "jiba", "jina", "maka", "milka", "nguman", "bakarra",
    "baral", "binkimu", "dabul", "jalngkan", "jarramali", "jijar", "jimbil",
    "jinkal", "marrja", "wawuy",
}
DENY_ENGLISH = (
    "catch", "caught", "catching", "net", "netting", "hook", "line", "spear",
    "spearing", "eat", "eating", "ate", "cook", "cooking", "food", "bait",
    "taboo", "storm", "thunder", "story", "place", "water", "river", "sea",
    "reef", "habitat", "body", "part", "tail", "nose", "fin", "whisker",
    "scale", "poison", "danger", "dangerous", "bite", "biting", "injury",
    "mangrove", "freshwater", "saltwater", "garfish", "spotted jewfish",
    "striped toby", "black-tip ray", "ray", "colour", "red colour", "loan",
    "case", "compound", "reduplication", "story-site", "nyungkul",
)


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 = {
    "kubija": "Dictionary lines 14673-14682 give kubija as garfish/barracuda and lines 14683-14691 give the barracuda noun entry; Rule 251 selects barracuda only. Patz grammar lines 2553-2603 source bare nominative pronouns, lines 2838-2845 source yaluy/yinyay, and lines 3043-3049 source nyaji-l 'see'.",
    "mujarrka": "Dictionary lines 20152-20162 give mujarrka as Yalanji archerfish and lines 20163-20175 give archer fish with Nyungkul/habitat source prose; Rule 251 selects archerfish only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
    "barrbal": "Dictionary lines 1810-1818 and 1819-1826 give barrbal as black bream, semantic domain fauna-fish. Rule 251 selects black bream only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the pronoun, local-demonstrative, and perception support.",
    "wuyumbu": "Dictionary lines 28647-28655 give wuyumbu as toad fish and lines 28656-28663 give species/source prose including Striped toby; Rule 251 selects toadfish only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
    "bayil": "Dictionary lines 1992-2000 give bayil as perch and lines 2001-2008 give jungle perch; Rule 251 selects perch only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the frame.",
    "dalmbal": "Dictionary lines 4947-4957 and 4958-4966 give dalmbal as flathead; synonym/source prose stays closed. Rule 251 selects flathead only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
    "balarri": "Dictionary lines 842-850 and 851-858 give balarri as queenfish, semantic domain fauna-fish. Rule 251 selects queenfish only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the pronoun, local-demonstrative, and perception support.",
    "bilmbin": "Dictionary lines 2553-2565 give bilmbin as black-tip shark and lines 2566-2574 give an alternate black-tip ray entry; Rule 251 selects black-tip shark only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
    "jalkay": "Dictionary lines 7465-7473 and 7474-7481 give jalkay as salmon, semantic domain fauna-fish. Rule 251 selects salmon only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
    "jijirr": "Dictionary lines 8937-8945 and 8946-8953 give jijirr as red bream, semantic domain fauna-fish. Rule 251 selects red bream only. Patz grammar lines 2553-2603, 2838-2845, and 3043-3049 source the support forms.",
}


ROWS = [
    row(1, "I see the barracuda here.", "Ngayu kubija yaluy nyajil.", "ngayu kubija yaluy nyajil | 1sg.NOM barracuda here see.NPST", "r251_1sg_target_here_see_kubija", ["ngayu", "kubija", "yaluy", "nyajil"], EVIDENCE["kubija"]),
    row(2, "I see the archerfish here.", "Ngayu mujarrka yaluy nyajil.", "ngayu mujarrka yaluy nyajil | 1sg.NOM archerfish here see.NPST", "r251_1sg_target_here_see_mujarrka", ["ngayu", "mujarrka", "yaluy", "nyajil"], EVIDENCE["mujarrka"]),
    row(3, "I see the black bream here.", "Ngayu barrbal yaluy nyajil.", "ngayu barrbal yaluy nyajil | 1sg.NOM black.bream here see.NPST", "r251_1sg_target_here_see_barrbal", ["ngayu", "barrbal", "yaluy", "nyajil"], EVIDENCE["barrbal"]),
    row(4, "I see the toadfish here.", "Ngayu wuyumbu yaluy nyajil.", "ngayu wuyumbu yaluy nyajil | 1sg.NOM toadfish here see.NPST", "r251_1sg_target_here_see_wuyumbu", ["ngayu", "wuyumbu", "yaluy", "nyajil"], EVIDENCE["wuyumbu"]),
    row(5, "I see the perch here.", "Ngayu bayil yaluy nyajil.", "ngayu bayil yaluy nyajil | 1sg.NOM perch here see.NPST", "r251_1sg_target_here_see_bayil", ["ngayu", "bayil", "yaluy", "nyajil"], EVIDENCE["bayil"]),
    row(6, "I see the flathead here.", "Ngayu dalmbal yaluy nyajil.", "ngayu dalmbal yaluy nyajil | 1sg.NOM flathead here see.NPST", "r251_1sg_target_here_see_dalmbal", ["ngayu", "dalmbal", "yaluy", "nyajil"], EVIDENCE["dalmbal"]),
    row(7, "I see the queenfish here.", "Ngayu balarri yaluy nyajil.", "ngayu balarri yaluy nyajil | 1sg.NOM queenfish here see.NPST", "r251_1sg_target_here_see_balarri", ["ngayu", "balarri", "yaluy", "nyajil"], EVIDENCE["balarri"]),
    row(8, "I see the black-tip shark here.", "Ngayu bilmbin yaluy nyajil.", "ngayu bilmbin yaluy nyajil | 1sg.NOM black.tip.shark here see.NPST", "r251_1sg_target_here_see_bilmbin", ["ngayu", "bilmbin", "yaluy", "nyajil"], EVIDENCE["bilmbin"]),
    row(9, "I see the salmon here.", "Ngayu jalkay yaluy nyajil.", "ngayu jalkay yaluy nyajil | 1sg.NOM salmon here see.NPST", "r251_1sg_target_here_see_jalkay", ["ngayu", "jalkay", "yaluy", "nyajil"], EVIDENCE["jalkay"]),
    row(10, "I see the red bream here.", "Ngayu jijirr yaluy nyajil.", "ngayu jijirr yaluy nyajil | 1sg.NOM red.bream here see.NPST", "r251_1sg_target_here_see_jijirr", ["ngayu", "jijirr", "yaluy", "nyajil"], EVIDENCE["jijirr"]),
    row(11, "He sees the barracuda there.", "Nyulu kubija yinyay nyajil.", "nyulu kubija yinyay nyajil | 3sg.NOM barracuda there see.NPST", "r251_3sg_target_there_see_kubija", ["nyulu", "kubija", "yinyay", "nyajil"], EVIDENCE["kubija"]),
    row(12, "He sees the archerfish there.", "Nyulu mujarrka yinyay nyajil.", "nyulu mujarrka yinyay nyajil | 3sg.NOM archerfish there see.NPST", "r251_3sg_target_there_see_mujarrka", ["nyulu", "mujarrka", "yinyay", "nyajil"], EVIDENCE["mujarrka"]),
    row(13, "He sees the black bream there.", "Nyulu barrbal yinyay nyajil.", "nyulu barrbal yinyay nyajil | 3sg.NOM black.bream there see.NPST", "r251_3sg_target_there_see_barrbal", ["nyulu", "barrbal", "yinyay", "nyajil"], EVIDENCE["barrbal"]),
    row(14, "He sees the toadfish there.", "Nyulu wuyumbu yinyay nyajil.", "nyulu wuyumbu yinyay nyajil | 3sg.NOM toadfish there see.NPST", "r251_3sg_target_there_see_wuyumbu", ["nyulu", "wuyumbu", "yinyay", "nyajil"], EVIDENCE["wuyumbu"]),
    row(15, "He sees the perch there.", "Nyulu bayil yinyay nyajil.", "nyulu bayil yinyay nyajil | 3sg.NOM perch there see.NPST", "r251_3sg_target_there_see_bayil", ["nyulu", "bayil", "yinyay", "nyajil"], EVIDENCE["bayil"]),
    row(16, "He sees the flathead there.", "Nyulu dalmbal yinyay nyajil.", "nyulu dalmbal yinyay nyajil | 3sg.NOM flathead there see.NPST", "r251_3sg_target_there_see_dalmbal", ["nyulu", "dalmbal", "yinyay", "nyajil"], EVIDENCE["dalmbal"]),
    row(17, "He sees the queenfish there.", "Nyulu balarri yinyay nyajil.", "nyulu balarri yinyay nyajil | 3sg.NOM queenfish there see.NPST", "r251_3sg_target_there_see_balarri", ["nyulu", "balarri", "yinyay", "nyajil"], EVIDENCE["balarri"]),
    row(18, "He sees the black-tip shark there.", "Nyulu bilmbin yinyay nyajil.", "nyulu bilmbin yinyay nyajil | 3sg.NOM black.tip.shark there see.NPST", "r251_3sg_target_there_see_bilmbin", ["nyulu", "bilmbin", "yinyay", "nyajil"], EVIDENCE["bilmbin"]),
    row(19, "He sees the salmon there.", "Nyulu jalkay yinyay nyajil.", "nyulu jalkay yinyay nyajil | 3sg.NOM salmon there see.NPST", "r251_3sg_target_there_see_jalkay", ["nyulu", "jalkay", "yinyay", "nyajil"], EVIDENCE["jalkay"]),
    row(20, "He sees the red bream there.", "Nyulu jijirr yinyay nyajil.", "nyulu jijirr yinyay nyajil | 3sg.NOM red.bream there see.NPST", "r251_3sg_target_there_see_jijirr", ["nyulu", "jijirr", "yinyay", "nyajil"], EVIDENCE["jijirr"]),
    row(21, "You all see the barracuda here.", "Yurra kubija yaluy nyajil.", "yurra kubija yaluy nyajil | 2pl.NOM barracuda here see.NPST", "r251_2pl_target_here_see_kubija", ["yurra", "kubija", "yaluy", "nyajil"], EVIDENCE["kubija"]),
    row(22, "You all see the archerfish here.", "Yurra mujarrka yaluy nyajil.", "yurra mujarrka yaluy nyajil | 2pl.NOM archerfish here see.NPST", "r251_2pl_target_here_see_mujarrka", ["yurra", "mujarrka", "yaluy", "nyajil"], EVIDENCE["mujarrka"]),
    row(23, "You all see the black bream here.", "Yurra barrbal yaluy nyajil.", "yurra barrbal yaluy nyajil | 2pl.NOM black.bream here see.NPST", "r251_2pl_target_here_see_barrbal", ["yurra", "barrbal", "yaluy", "nyajil"], EVIDENCE["barrbal"]),
    row(24, "You all see the toadfish here.", "Yurra wuyumbu yaluy nyajil.", "yurra wuyumbu yaluy nyajil | 2pl.NOM toadfish here see.NPST", "r251_2pl_target_here_see_wuyumbu", ["yurra", "wuyumbu", "yaluy", "nyajil"], EVIDENCE["wuyumbu"]),
    row(25, "You all see the perch here.", "Yurra bayil yaluy nyajil.", "yurra bayil yaluy nyajil | 2pl.NOM perch here see.NPST", "r251_2pl_target_here_see_bayil", ["yurra", "bayil", "yaluy", "nyajil"], EVIDENCE["bayil"]),
    row(26, "You all see the flathead here.", "Yurra dalmbal yaluy nyajil.", "yurra dalmbal yaluy nyajil | 2pl.NOM flathead here see.NPST", "r251_2pl_target_here_see_dalmbal", ["yurra", "dalmbal", "yaluy", "nyajil"], EVIDENCE["dalmbal"]),
    row(27, "You all see the queenfish here.", "Yurra balarri yaluy nyajil.", "yurra balarri yaluy nyajil | 2pl.NOM queenfish here see.NPST", "r251_2pl_target_here_see_balarri", ["yurra", "balarri", "yaluy", "nyajil"], EVIDENCE["balarri"]),
    row(28, "You all see the black-tip shark here.", "Yurra bilmbin yaluy nyajil.", "yurra bilmbin yaluy nyajil | 2pl.NOM black.tip.shark here see.NPST", "r251_2pl_target_here_see_bilmbin", ["yurra", "bilmbin", "yaluy", "nyajil"], EVIDENCE["bilmbin"]),
    row(29, "You all see the salmon here.", "Yurra jalkay yaluy nyajil.", "yurra jalkay yaluy nyajil | 2pl.NOM salmon here see.NPST", "r251_2pl_target_here_see_jalkay", ["yurra", "jalkay", "yaluy", "nyajil"], EVIDENCE["jalkay"]),
    row(30, "You all see the red bream here.", "Yurra jijirr yaluy nyajil.", "yurra jijirr yaluy nyajil | 2pl.NOM red.bream here see.NPST", "r251_2pl_target_here_see_jijirr", ["yurra", "jijirr", "yaluy", "nyajil"], EVIDENCE["jijirr"]),
    row(31, "The two of them see the barracuda there.", "Bula kubija yinyay nyajil.", "bula kubija yinyay nyajil | 3du.NOM barracuda there see.NPST", "r251_3du_target_there_see_kubija", ["bula", "kubija", "yinyay", "nyajil"], EVIDENCE["kubija"]),
    row(32, "The two of them see the archerfish there.", "Bula mujarrka yinyay nyajil.", "bula mujarrka yinyay nyajil | 3du.NOM archerfish there see.NPST", "r251_3du_target_there_see_mujarrka", ["bula", "mujarrka", "yinyay", "nyajil"], EVIDENCE["mujarrka"]),
    row(33, "The two of them see the black bream there.", "Bula barrbal yinyay nyajil.", "bula barrbal yinyay nyajil | 3du.NOM black.bream there see.NPST", "r251_3du_target_there_see_barrbal", ["bula", "barrbal", "yinyay", "nyajil"], EVIDENCE["barrbal"]),
    row(34, "The two of them see the toadfish there.", "Bula wuyumbu yinyay nyajil.", "bula wuyumbu yinyay nyajil | 3du.NOM toadfish there see.NPST", "r251_3du_target_there_see_wuyumbu", ["bula", "wuyumbu", "yinyay", "nyajil"], EVIDENCE["wuyumbu"]),
    row(35, "The two of them see the perch there.", "Bula bayil yinyay nyajil.", "bula bayil yinyay nyajil | 3du.NOM perch there see.NPST", "r251_3du_target_there_see_bayil", ["bula", "bayil", "yinyay", "nyajil"], EVIDENCE["bayil"]),
    row(36, "The two of them see the flathead there.", "Bula dalmbal yinyay nyajil.", "bula dalmbal yinyay nyajil | 3du.NOM flathead there see.NPST", "r251_3du_target_there_see_dalmbal", ["bula", "dalmbal", "yinyay", "nyajil"], EVIDENCE["dalmbal"]),
    row(37, "The two of them see the queenfish there.", "Bula balarri yinyay nyajil.", "bula balarri yinyay nyajil | 3du.NOM queenfish there see.NPST", "r251_3du_target_there_see_balarri", ["bula", "balarri", "yinyay", "nyajil"], EVIDENCE["balarri"]),
    row(38, "The two of them see the black-tip shark there.", "Bula bilmbin yinyay nyajil.", "bula bilmbin yinyay nyajil | 3du.NOM black.tip.shark there see.NPST", "r251_3du_target_there_see_bilmbin", ["bula", "bilmbin", "yinyay", "nyajil"], EVIDENCE["bilmbin"]),
    row(39, "The two of them see the salmon there.", "Bula jalkay yinyay nyajil.", "bula jalkay yinyay nyajil | 3du.NOM salmon there see.NPST", "r251_3du_target_there_see_jalkay", ["bula", "jalkay", "yinyay", "nyajil"], EVIDENCE["jalkay"]),
    row(40, "The two of them see the red bream there.", "Bula jijirr yinyay nyajil.", "bula jijirr yinyay nyajil | 3du.NOM red.bream there see.NPST", "r251_3du_target_there_see_jijirr", ["bula", "jijirr", "yinyay", "nyajil"], EVIDENCE["jijirr"]),
    row(41, "They see the barracuda here.", "Jana kubija yaluy nyajil.", "jana kubija yaluy nyajil | 3pl.NOM barracuda here see.NPST", "r251_3pl_target_here_see_kubija", ["jana", "kubija", "yaluy", "nyajil"], EVIDENCE["kubija"]),
    row(42, "They see the archerfish here.", "Jana mujarrka yaluy nyajil.", "jana mujarrka yaluy nyajil | 3pl.NOM archerfish here see.NPST", "r251_3pl_target_here_see_mujarrka", ["jana", "mujarrka", "yaluy", "nyajil"], EVIDENCE["mujarrka"]),
    row(43, "They see the black bream here.", "Jana barrbal yaluy nyajil.", "jana barrbal yaluy nyajil | 3pl.NOM black.bream here see.NPST", "r251_3pl_target_here_see_barrbal", ["jana", "barrbal", "yaluy", "nyajil"], EVIDENCE["barrbal"]),
    row(44, "They see the toadfish here.", "Jana wuyumbu yaluy nyajil.", "jana wuyumbu yaluy nyajil | 3pl.NOM toadfish here see.NPST", "r251_3pl_target_here_see_wuyumbu", ["jana", "wuyumbu", "yaluy", "nyajil"], EVIDENCE["wuyumbu"]),
    row(45, "They see the perch here.", "Jana bayil yaluy nyajil.", "jana bayil yaluy nyajil | 3pl.NOM perch here see.NPST", "r251_3pl_target_here_see_bayil", ["jana", "bayil", "yaluy", "nyajil"], EVIDENCE["bayil"]),
    row(46, "They see the flathead here.", "Jana dalmbal yaluy nyajil.", "jana dalmbal yaluy nyajil | 3pl.NOM flathead here see.NPST", "r251_3pl_target_here_see_dalmbal", ["jana", "dalmbal", "yaluy", "nyajil"], EVIDENCE["dalmbal"]),
    row(47, "They see the queenfish here.", "Jana balarri yaluy nyajil.", "jana balarri yaluy nyajil | 3pl.NOM queenfish here see.NPST", "r251_3pl_target_here_see_balarri", ["jana", "balarri", "yaluy", "nyajil"], EVIDENCE["balarri"]),
    row(48, "They see the black-tip shark here.", "Jana bilmbin yaluy nyajil.", "jana bilmbin yaluy nyajil | 3pl.NOM black.tip.shark here see.NPST", "r251_3pl_target_here_see_bilmbin", ["jana", "bilmbin", "yaluy", "nyajil"], EVIDENCE["bilmbin"]),
    row(49, "They see the salmon here.", "Jana jalkay yaluy nyajil.", "jana jalkay yaluy nyajil | 3pl.NOM salmon here see.NPST", "r251_3pl_target_here_see_jalkay", ["jana", "jalkay", "yaluy", "nyajil"], EVIDENCE["jalkay"]),
    row(50, "They see the red bream here.", "Jana jijirr yaluy nyajil.", "jana jijirr yaluy nyajil | 3pl.NOM red.bream here see.NPST", "r251_3pl_target_here_see_jijirr", ["jana", "jijirr", "yaluy", "nyajil"], EVIDENCE["jijirr"]),
]


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("r251_"):
            issues.append(f"{label}: Rule 251 frame must start with r251_")
        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 251")
        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 251 target fish 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 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("yurra ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Yurra 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 fish action/use/habitat/story/body-part/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 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 251 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 251 source pass checked exact dictionary lines for kubija barracuda, mujarrka archerfish, barrbal black bream, wuyumbu toadfish, bayil perch, dalmbal flathead, balarri queenfish, bilmbin black-tip shark, jalkay salmon, and jijirr red bream; Patz grammar lines 2553-2603 for bare nominative pronouns; 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="construction-scope",
        actor="claude-main",
        detail="Selected Rule 251 fish-reference complementary perception pronoun expansion after Rule 250: ten fish labels in five zero-collision bands, using Ngayu/Yurra/Jana with yaluy and Nyulu/Bula with yinyay. All fish action/use/habitat/story/body-part/source-prose 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="Pre-batch token-aware target coverage among accepted/revised rows after Rule 250: kubija 5, mujarrka 5, barrbal 11, wuyumbu 5, bayil 16, dalmbal 6, balarri 9, bilmbin 8, jalkay 11, and jijirr 18. Support coverage: ngayu 1236, nyulu 1757, yurra 170, bula 174, jana 2079, yaluy 2137, yinyay 2410, nyajil 2420.",
        command="python3 dictionary/corpus coverage audit",
        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 251 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_293.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 251 target fish noun per row, exactly one bare nominative pronoun per row, no loans, no target suffixes, no excluded fish candidate, no action/use/habitat/story/body-part/source-prose drift, no pronoun case, no demonstrative case, no perception compound, and five planned complementary frame bands only.",
        command="python3 batch_scripts/bank_293.py",
        artifact_path="batch_scripts/bank_293.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 explicit hand-authored Rule 251 fish-reference complementary perception pronoun rows as under_review.",
        command="python3 batch_scripts/bank_293.py",
        artifact_path="batch_scripts/bank_293.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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