#!/usr/bin/env python3
"""Bank batch-282: Rule 240 fish-reference perception debt sweep."""

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-282"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-282-fish-reference-perception-debt-sweep-after-rule239-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 hard 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",
    "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji",
    # Recent dwelling/fire/body fields stay closed.
    "bakarr", "bayan", "bira", "jiji", "kilili", "wurun", "jakway", "yulal",
    "kararr", "bilangkurr", "baya", "jimal", "jirramar", "kubu", "kunjin",
    "miyilkura", "ngalku", "ngaluri", "ngunjil", "wula",
}
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 kubija has noun entries with glosses 'garfish' and 'barracuda'; Rule 240 selects the barracuda entry only. Patz 3.1.2 supports nouns for fauna; Patz 3.5 Table 3.6 sources bare nominative pronouns; Patz 3.7 sources yaluy/yinyay; Patz 3.8.3.2 sources nyaji-l 'see'. No garfish, catching, water, habitat, case, or compound row.",
    "mujarrka": "Dictionary mujarrka noun gloss 'archerfish'/'archer fish', marked Yalanji, with Nyungkul dalku and spotted-Jewfish/habitat prose only as source notes. Rule 240 selects archerfish reference/perception only. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the noun/perception frame.",
    "barrbal": "Dictionary barrbal noun gloss/definition/translation 'black bream'. Rule 240 selects black bream as a lexical species label only, not fishing, food, water, reef, colour-predicate, or habitat prose. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "wuyumbu": "Dictionary wuyumbu noun gloss 'toad fish' and second entry 'species of fish - Striped toby, also toadfish'. Rule 240 selects toadfish only; striped-toby and generic-fish prose stay closed. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the frame.",
    "bayil": "Dictionary bayil noun gloss 'perch'/'jungle perch'. Rule 240 selects perch as the English label and does not open freshwater, jungle, catching, food, or habitat prose. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "dalmbal": "Dictionary dalmbal noun gloss 'flathead', with synonym dukul-barangka only as a source note. Rule 240 selects flathead only and does not open synonym, catching, food, water, or target-compound rows. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the frame.",
    "balarri": "Dictionary balarri noun gloss/definition/translation 'queenfish'. Rule 240 uses queenfish reference/perception only. Patz 3.1.2 supports fauna nouns; Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "bilmbin": "Dictionary bilmbin noun gloss 'black-tip shark', with black-tip ray and river/mangrove habitat prose as alternate/source notes. Rule 240 selects black-tip shark only and does not open ray, habitat, danger, bite, or water rows. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the frame.",
    "jalkay": "Dictionary jalkay noun gloss/definition/translation 'salmon'. Rule 240 uses salmon reference/perception only, in new non-colliding perception frames after earlier bare-deictic rows. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "jijirr": "Dictionary jijirr noun gloss/definition/translation 'red bream'. Rule 240 selects red bream only, not colour-predicate, reef, habitat, catching, or food prose. Patz 3.1.2, 3.5, 3.7, and 3.8.3.2 source the frame.",
}


ROWS = [
    row(1, "I see the barracuda there.", "Ngayu kubija yinyay nyajil.", "ngayu kubija yinyay nyajil | 1sg.NOM barracuda there see.NPST", "r240_1sg_target_there_see_kubija", ["ngayu", "kubija", "yinyay", "nyajil"], EVIDENCE["kubija"]),
    row(2, "I see the archerfish there.", "Ngayu mujarrka yinyay nyajil.", "ngayu mujarrka yinyay nyajil | 1sg.NOM archerfish there see.NPST", "r240_1sg_target_there_see_mujarrka", ["ngayu", "mujarrka", "yinyay", "nyajil"], EVIDENCE["mujarrka"]),
    row(3, "I see the black bream there.", "Ngayu barrbal yinyay nyajil.", "ngayu barrbal yinyay nyajil | 1sg.NOM black.bream there see.NPST", "r240_1sg_target_there_see_barrbal", ["ngayu", "barrbal", "yinyay", "nyajil"], EVIDENCE["barrbal"]),
    row(4, "I see the toadfish there.", "Ngayu wuyumbu yinyay nyajil.", "ngayu wuyumbu yinyay nyajil | 1sg.NOM toadfish there see.NPST", "r240_1sg_target_there_see_wuyumbu", ["ngayu", "wuyumbu", "yinyay", "nyajil"], EVIDENCE["wuyumbu"]),
    row(5, "I see the perch there.", "Ngayu bayil yinyay nyajil.", "ngayu bayil yinyay nyajil | 1sg.NOM perch there see.NPST", "r240_1sg_target_there_see_bayil", ["ngayu", "bayil", "yinyay", "nyajil"], EVIDENCE["bayil"]),
    row(6, "I see the flathead there.", "Ngayu dalmbal yinyay nyajil.", "ngayu dalmbal yinyay nyajil | 1sg.NOM flathead there see.NPST", "r240_1sg_target_there_see_dalmbal", ["ngayu", "dalmbal", "yinyay", "nyajil"], EVIDENCE["dalmbal"]),
    row(7, "I see the queenfish there.", "Ngayu balarri yinyay nyajil.", "ngayu balarri yinyay nyajil | 1sg.NOM queenfish there see.NPST", "r240_1sg_target_there_see_balarri", ["ngayu", "balarri", "yinyay", "nyajil"], EVIDENCE["balarri"]),
    row(8, "I see the black-tip shark there.", "Ngayu bilmbin yinyay nyajil.", "ngayu bilmbin yinyay nyajil | 1sg.NOM black.tip.shark there see.NPST", "r240_1sg_target_there_see_bilmbin", ["ngayu", "bilmbin", "yinyay", "nyajil"], EVIDENCE["bilmbin"]),
    row(9, "I see the salmon there.", "Ngayu jalkay yinyay nyajil.", "ngayu jalkay yinyay nyajil | 1sg.NOM salmon there see.NPST", "r240_1sg_target_there_see_jalkay", ["ngayu", "jalkay", "yinyay", "nyajil"], EVIDENCE["jalkay"]),
    row(10, "I see the red bream there.", "Ngayu jijirr yinyay nyajil.", "ngayu jijirr yinyay nyajil | 1sg.NOM red.bream there see.NPST", "r240_1sg_target_there_see_jijirr", ["ngayu", "jijirr", "yinyay", "nyajil"], EVIDENCE["jijirr"]),
    row(11, "He sees the barracuda here.", "Nyulu kubija yaluy nyajil.", "nyulu kubija yaluy nyajil | 3sg.NOM barracuda here see.NPST", "r240_3sg_target_here_see_kubija", ["nyulu", "kubija", "yaluy", "nyajil"], EVIDENCE["kubija"]),
    row(12, "He sees the archerfish here.", "Nyulu mujarrka yaluy nyajil.", "nyulu mujarrka yaluy nyajil | 3sg.NOM archerfish here see.NPST", "r240_3sg_target_here_see_mujarrka", ["nyulu", "mujarrka", "yaluy", "nyajil"], EVIDENCE["mujarrka"]),
    row(13, "He sees the black bream here.", "Nyulu barrbal yaluy nyajil.", "nyulu barrbal yaluy nyajil | 3sg.NOM black.bream here see.NPST", "r240_3sg_target_here_see_barrbal", ["nyulu", "barrbal", "yaluy", "nyajil"], EVIDENCE["barrbal"]),
    row(14, "He sees the toadfish here.", "Nyulu wuyumbu yaluy nyajil.", "nyulu wuyumbu yaluy nyajil | 3sg.NOM toadfish here see.NPST", "r240_3sg_target_here_see_wuyumbu", ["nyulu", "wuyumbu", "yaluy", "nyajil"], EVIDENCE["wuyumbu"]),
    row(15, "He sees the perch here.", "Nyulu bayil yaluy nyajil.", "nyulu bayil yaluy nyajil | 3sg.NOM perch here see.NPST", "r240_3sg_target_here_see_bayil", ["nyulu", "bayil", "yaluy", "nyajil"], EVIDENCE["bayil"]),
    row(16, "He sees the flathead here.", "Nyulu dalmbal yaluy nyajil.", "nyulu dalmbal yaluy nyajil | 3sg.NOM flathead here see.NPST", "r240_3sg_target_here_see_dalmbal", ["nyulu", "dalmbal", "yaluy", "nyajil"], EVIDENCE["dalmbal"]),
    row(17, "He sees the queenfish here.", "Nyulu balarri yaluy nyajil.", "nyulu balarri yaluy nyajil | 3sg.NOM queenfish here see.NPST", "r240_3sg_target_here_see_balarri", ["nyulu", "balarri", "yaluy", "nyajil"], EVIDENCE["balarri"]),
    row(18, "He sees the black-tip shark here.", "Nyulu bilmbin yaluy nyajil.", "nyulu bilmbin yaluy nyajil | 3sg.NOM black.tip.shark here see.NPST", "r240_3sg_target_here_see_bilmbin", ["nyulu", "bilmbin", "yaluy", "nyajil"], EVIDENCE["bilmbin"]),
    row(19, "He sees the salmon here.", "Nyulu jalkay yaluy nyajil.", "nyulu jalkay yaluy nyajil | 3sg.NOM salmon here see.NPST", "r240_3sg_target_here_see_jalkay", ["nyulu", "jalkay", "yaluy", "nyajil"], EVIDENCE["jalkay"]),
    row(20, "He sees the red bream here.", "Nyulu jijirr yaluy nyajil.", "nyulu jijirr yaluy nyajil | 3sg.NOM red.bream here see.NPST", "r240_3sg_target_here_see_jijirr", ["nyulu", "jijirr", "yaluy", "nyajil"], EVIDENCE["jijirr"]),
    row(21, "You all see the barracuda there.", "Yurra kubija yinyay nyajil.", "yurra kubija yinyay nyajil | 2pl.NOM barracuda there see.NPST", "r240_2pl_target_there_see_kubija", ["yurra", "kubija", "yinyay", "nyajil"], EVIDENCE["kubija"]),
    row(22, "You all see the archerfish there.", "Yurra mujarrka yinyay nyajil.", "yurra mujarrka yinyay nyajil | 2pl.NOM archerfish there see.NPST", "r240_2pl_target_there_see_mujarrka", ["yurra", "mujarrka", "yinyay", "nyajil"], EVIDENCE["mujarrka"]),
    row(23, "You all see the black bream there.", "Yurra barrbal yinyay nyajil.", "yurra barrbal yinyay nyajil | 2pl.NOM black.bream there see.NPST", "r240_2pl_target_there_see_barrbal", ["yurra", "barrbal", "yinyay", "nyajil"], EVIDENCE["barrbal"]),
    row(24, "You all see the toadfish there.", "Yurra wuyumbu yinyay nyajil.", "yurra wuyumbu yinyay nyajil | 2pl.NOM toadfish there see.NPST", "r240_2pl_target_there_see_wuyumbu", ["yurra", "wuyumbu", "yinyay", "nyajil"], EVIDENCE["wuyumbu"]),
    row(25, "You all see the perch there.", "Yurra bayil yinyay nyajil.", "yurra bayil yinyay nyajil | 2pl.NOM perch there see.NPST", "r240_2pl_target_there_see_bayil", ["yurra", "bayil", "yinyay", "nyajil"], EVIDENCE["bayil"]),
    row(26, "You all see the flathead there.", "Yurra dalmbal yinyay nyajil.", "yurra dalmbal yinyay nyajil | 2pl.NOM flathead there see.NPST", "r240_2pl_target_there_see_dalmbal", ["yurra", "dalmbal", "yinyay", "nyajil"], EVIDENCE["dalmbal"]),
    row(27, "You all see the queenfish there.", "Yurra balarri yinyay nyajil.", "yurra balarri yinyay nyajil | 2pl.NOM queenfish there see.NPST", "r240_2pl_target_there_see_balarri", ["yurra", "balarri", "yinyay", "nyajil"], EVIDENCE["balarri"]),
    row(28, "You all see the black-tip shark there.", "Yurra bilmbin yinyay nyajil.", "yurra bilmbin yinyay nyajil | 2pl.NOM black.tip.shark there see.NPST", "r240_2pl_target_there_see_bilmbin", ["yurra", "bilmbin", "yinyay", "nyajil"], EVIDENCE["bilmbin"]),
    row(29, "You all see the salmon there.", "Yurra jalkay yinyay nyajil.", "yurra jalkay yinyay nyajil | 2pl.NOM salmon there see.NPST", "r240_2pl_target_there_see_jalkay", ["yurra", "jalkay", "yinyay", "nyajil"], EVIDENCE["jalkay"]),
    row(30, "You all see the red bream there.", "Yurra jijirr yinyay nyajil.", "yurra jijirr yinyay nyajil | 2pl.NOM red.bream there see.NPST", "r240_2pl_target_there_see_jijirr", ["yurra", "jijirr", "yinyay", "nyajil"], EVIDENCE["jijirr"]),
    row(31, "The two of them see the barracuda here.", "Bula kubija yaluy nyajil.", "bula kubija yaluy nyajil | 3du.NOM barracuda here see.NPST", "r240_3du_target_here_see_kubija", ["bula", "kubija", "yaluy", "nyajil"], EVIDENCE["kubija"]),
    row(32, "The two of them see the archerfish here.", "Bula mujarrka yaluy nyajil.", "bula mujarrka yaluy nyajil | 3du.NOM archerfish here see.NPST", "r240_3du_target_here_see_mujarrka", ["bula", "mujarrka", "yaluy", "nyajil"], EVIDENCE["mujarrka"]),
    row(33, "The two of them see the black bream here.", "Bula barrbal yaluy nyajil.", "bula barrbal yaluy nyajil | 3du.NOM black.bream here see.NPST", "r240_3du_target_here_see_barrbal", ["bula", "barrbal", "yaluy", "nyajil"], EVIDENCE["barrbal"]),
    row(34, "The two of them see the toadfish here.", "Bula wuyumbu yaluy nyajil.", "bula wuyumbu yaluy nyajil | 3du.NOM toadfish here see.NPST", "r240_3du_target_here_see_wuyumbu", ["bula", "wuyumbu", "yaluy", "nyajil"], EVIDENCE["wuyumbu"]),
    row(35, "The two of them see the perch here.", "Bula bayil yaluy nyajil.", "bula bayil yaluy nyajil | 3du.NOM perch here see.NPST", "r240_3du_target_here_see_bayil", ["bula", "bayil", "yaluy", "nyajil"], EVIDENCE["bayil"]),
    row(36, "The two of them see the flathead here.", "Bula dalmbal yaluy nyajil.", "bula dalmbal yaluy nyajil | 3du.NOM flathead here see.NPST", "r240_3du_target_here_see_dalmbal", ["bula", "dalmbal", "yaluy", "nyajil"], EVIDENCE["dalmbal"]),
    row(37, "The two of them see the queenfish here.", "Bula balarri yaluy nyajil.", "bula balarri yaluy nyajil | 3du.NOM queenfish here see.NPST", "r240_3du_target_here_see_balarri", ["bula", "balarri", "yaluy", "nyajil"], EVIDENCE["balarri"]),
    row(38, "The two of them see the black-tip shark here.", "Bula bilmbin yaluy nyajil.", "bula bilmbin yaluy nyajil | 3du.NOM black.tip.shark here see.NPST", "r240_3du_target_here_see_bilmbin", ["bula", "bilmbin", "yaluy", "nyajil"], EVIDENCE["bilmbin"]),
    row(39, "The two of them see the salmon here.", "Bula jalkay yaluy nyajil.", "bula jalkay yaluy nyajil | 3du.NOM salmon here see.NPST", "r240_3du_target_here_see_jalkay", ["bula", "jalkay", "yaluy", "nyajil"], EVIDENCE["jalkay"]),
    row(40, "The two of them see the red bream here.", "Bula jijirr yaluy nyajil.", "bula jijirr yaluy nyajil | 3du.NOM red.bream here see.NPST", "r240_3du_target_here_see_jijirr", ["bula", "jijirr", "yaluy", "nyajil"], EVIDENCE["jijirr"]),
    row(41, "They see the barracuda there.", "Jana kubija yinyay nyajil.", "jana kubija yinyay nyajil | 3pl.NOM barracuda there see.NPST", "r240_3pl_target_there_see_kubija", ["jana", "kubija", "yinyay", "nyajil"], EVIDENCE["kubija"]),
    row(42, "They see the archerfish there.", "Jana mujarrka yinyay nyajil.", "jana mujarrka yinyay nyajil | 3pl.NOM archerfish there see.NPST", "r240_3pl_target_there_see_mujarrka", ["jana", "mujarrka", "yinyay", "nyajil"], EVIDENCE["mujarrka"]),
    row(43, "They see the black bream there.", "Jana barrbal yinyay nyajil.", "jana barrbal yinyay nyajil | 3pl.NOM black.bream there see.NPST", "r240_3pl_target_there_see_barrbal", ["jana", "barrbal", "yinyay", "nyajil"], EVIDENCE["barrbal"]),
    row(44, "They see the toadfish there.", "Jana wuyumbu yinyay nyajil.", "jana wuyumbu yinyay nyajil | 3pl.NOM toadfish there see.NPST", "r240_3pl_target_there_see_wuyumbu", ["jana", "wuyumbu", "yinyay", "nyajil"], EVIDENCE["wuyumbu"]),
    row(45, "They see the perch there.", "Jana bayil yinyay nyajil.", "jana bayil yinyay nyajil | 3pl.NOM perch there see.NPST", "r240_3pl_target_there_see_bayil", ["jana", "bayil", "yinyay", "nyajil"], EVIDENCE["bayil"]),
    row(46, "They see the flathead there.", "Jana dalmbal yinyay nyajil.", "jana dalmbal yinyay nyajil | 3pl.NOM flathead there see.NPST", "r240_3pl_target_there_see_dalmbal", ["jana", "dalmbal", "yinyay", "nyajil"], EVIDENCE["dalmbal"]),
    row(47, "They see the queenfish there.", "Jana balarri yinyay nyajil.", "jana balarri yinyay nyajil | 3pl.NOM queenfish there see.NPST", "r240_3pl_target_there_see_balarri", ["jana", "balarri", "yinyay", "nyajil"], EVIDENCE["balarri"]),
    row(48, "They see the black-tip shark there.", "Jana bilmbin yinyay nyajil.", "jana bilmbin yinyay nyajil | 3pl.NOM black.tip.shark there see.NPST", "r240_3pl_target_there_see_bilmbin", ["jana", "bilmbin", "yinyay", "nyajil"], EVIDENCE["bilmbin"]),
    row(49, "They see the salmon there.", "Jana jalkay yinyay nyajil.", "jana jalkay yinyay nyajil | 3pl.NOM salmon there see.NPST", "r240_3pl_target_there_see_jalkay", ["jana", "jalkay", "yinyay", "nyajil"], EVIDENCE["jalkay"]),
    row(50, "They see the red bream there.", "Jana jijirr yinyay nyajil.", "jana jijirr yinyay nyajil | 3pl.NOM red.bream there see.NPST", "r240_3pl_target_there_see_jijirr", ["jana", "jijirr", "yinyay", "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("r240_"):
            issues.append(f"{label}: Rule 240 frame must start with r240_")
        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 240")
        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 240 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(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngayu TARGET yinyay nyajil")
        if 11 <= seq <= 20 and (not low.startswith("nyulu ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Nyulu TARGET yaluy nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yurra ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Yurra TARGET yinyay nyajil")
        if 31 <= seq <= 40 and (not low.startswith("bula ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Bula TARGET yaluy nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yinyay 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 240 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 240 source pass checked dictionary entries 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 3.1.2 for fauna nouns; Patz 3.5 Table 3.6 for bare nominative pronouns; Patz 3.7 for yaluy/yinyay; and Patz 3.8.3.2 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 240 fish-reference perception debt sweep after Rule 239: ten low-coverage fish labels in five zero-collision nominative-pronoun perception bands. Excluded bayilbayil, kijukum, ngiwa, yidilnyul, walarrji after lint correction, and all catching/eating/habitat/story/body-part/source-prose semantics.",
        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: kubija 0, mujarrka 0, barrbal 6, wuyumbu 0, bayil 1, dalmbal 1, balarri 3, bilmbin 3, jalkay 6, and jijirr 6.",
        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 240 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_282.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 240 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 perception compound, and five planned frame bands only.",
        command="python3 batch_scripts/bank_282.py",
        artifact_path="batch_scripts/bank_282.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 240 fish-reference perception rows as under_review.",
        command="python3 batch_scripts/bank_282.py",
        artifact_path="batch_scripts/bank_282.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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