#!/usr/bin/env python3
"""Bank batch-277: Rule 235 body-part 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-277"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-277-body-part-construction-consolidation-after-rule234-process.md"

TARGET_HEADS = {
    "bandin": "love handles",
    "bari": "chin",
    "bibar": "shin",
    "birri": "fingernail",
    "jiba": "liver",
    "jandal": "leg",
    "jibar": "stomach",
    "kangkul": "cheek",
    "kuwinyu": "posterior",
    "waba": "thigh",
}
PLURAL_TARGETS = {"bandin"}
SUPPORT = {"yinya", "yaluy", "yinyay", "yundu", "nyulu", "ngana", "jana", "nyajil", "nyajin"}
PRONOUN_SUPPORT = {"yundu", "nyulu", "ngana", "jana"}
DENY_KUKU = {
    # Rule 217 and Rule 226 exact body-part targets.
    "barrkar", "baru", "jaljan", "jamalka", "jinbar", "kabari", "kuburrubun", "makal",
    "mirrimar", "mudu", "bilirr", "durru", "jakuji", "jurrubiji", "murun", "ngungumurr",
    "nyidu", "nyumbul", "wulurringkal", "yimbi",
    # Earlier common body-part exact targets and rejected/deferred body-part candidates.
    "bajibay", "bangkarr", "bibi", "binda", "biyal", "dabul", "dabulkurra", "dakil",
    "dalkari", "diburr-miyil", "dirra", "dukul", "jadalangka", "jirril", "juju",
    "juka", "junkan", "jurru", "juwul", "kadan", "kali", "kanya", "kiru",
    "kunja", "kurrburrbun", "kurrul", "kuyi", "malbin", "manji", "manu", "mara",
    "mijan", "milka", "miyil", "muku", "muku-duku", "mula", "mungka", "mungka-dirka",
    "munju", "munyun", "murrka", "muyu", "nangki", "ngandal", "ngarran", "ngarri",
    "ngumanji", "nyabil", "nyinki", "nyirribulu", "nyumba", "waba-", "wakuy",
    "walarr", "walu", "waral", "wubul", "wubun", "wulbar", "wulu", "wurmbu",
    "wurra", "yijirr", "yiman", "yiman-bunjal", "yimarr-yiman", "yinkan", "yulban",
    # Pronoun case, demonstrative case, perception compounds, and nearby homograph traps.
    "nganya", "yununy", "nyunguny", "yununiny", "nyunguniny", "nganangan", "ianangan",
    "yinyangka", "yinyayngka", "yinyayanka", "yinyamba", "yinyayanda", "yinyamuny",
    "yinyamundu", "yinyanji", "yinyarriny", "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji",
    "jibabu", "jibabadi", "jiba-janay", "jiba-jurrkiji", "jiba-ngulkurmal", "jiba-wulay",
    "jiba-yararri", "jibarr", "kangku", "kangkul-kulbal",
}
DENY_ENGLISH = (
    "my ", "your ", "his ", "her ", "their ", "person's", "owner", "possession", "possessor",
    "pain", "painful", "sore", "hurt", "injury", "injured", "illness", "sick", "medicine", "medical",
    "healing", "heal", "body fluid", "fluid", "blood", "mucus", "saliva", "urine", "pus", "milk",
    "taboo", "scar", "scab", "initiation", "ritual", "witchcraft", "magic", "place", "story", "site",
    "animal", "beast", "turtle", "snake", "pig", "fish", "egg", "fur", "pelt", "dialect", "nyungkul",
    "cut", "pierced", "pierce", "stubbed", "touch", "touching", "hold", "holding", "carry", "carrying",
    "move", "moving", "walk", "walking", "tiptoe", "eat", "eating", "swallow", "speaking", "speech",
    "jaw", "ankle", "toenail", "insides", "guts", "organs", "emotion", "sad", "glad", "hip", "only",
)


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 = {
    "bandin": "Dictionary bandin noun gloss 'love handles', definition 'waist on the side above the hips; love handles'. Rule 235 keeps the Rule 234 body-part reference label only. Patz 3.1 nominal body-part source; Patz 3.5 nominative pronouns; Patz 3.7 yinya/yaluy/yinyay; Patz 3.8.3.2 nyaji-l 'see'.",
    "bari": "Dictionary bari noun gloss 'chin', definitions 'chin; jaw'. Rule 235 narrows to chin reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "bibar": "Dictionary bibar noun gloss 'shin', definitions 'shin, ankle'. Rule 235 narrows to shin reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "birri": "Dictionary birri noun gloss 'fingernail', definitions 'fingernail, toenail'. Rule 235 narrows to fingernail reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "jiba": "Dictionary jiba noun gloss 'liver', definitions 'liver; insides, guts'. Rule 235 narrows to liver reference only and does not open jiba compounds or emotion prose. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "jandal": "Dictionary jandal noun gloss 'leg', definition 'lower limb, leg'. Rule 235 uses leg reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "jibar": "Dictionary jibar noun gloss 'stomach'. Rule 235 keeps jibar distinct from locational jibarr 'hinterland'. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "kangkul": "Dictionary kangkul noun gloss 'cheek'. Rule 235 keeps it distinct from kangku 'valley' and kangkul kulbal 'slap'. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "kuwinyu": "Dictionary kuwinyu noun gloss 'posterior', definition 'posterior (not a rude term)'. Rule 235 uses posterior reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
    "waba": "Dictionary/grammar-added waba noun gloss 'thigh', definition 'thigh, hip'. Rule 235 narrows to thigh reference only. Patz 3.1, 3.5, 3.7, and 3.8.3.2.",
}


ROWS = [
    row(1, "Those love handles are here.", "Bandin yinya yaluy.", "bandin yinya yaluy | love.handles that here", "r235_body_part_that_here_bandin", ["bandin", "yinya", "yaluy"], EVIDENCE["bandin"]),
    row(2, "That chin is here.", "Bari yinya yaluy.", "bari yinya yaluy | chin that here", "r235_body_part_that_here_bari", ["bari", "yinya", "yaluy"], EVIDENCE["bari"]),
    row(3, "That shin is here.", "Bibar yinya yaluy.", "bibar yinya yaluy | shin that here", "r235_body_part_that_here_bibar", ["bibar", "yinya", "yaluy"], EVIDENCE["bibar"]),
    row(4, "That fingernail is here.", "Birri yinya yaluy.", "birri yinya yaluy | fingernail that here", "r235_body_part_that_here_birri", ["birri", "yinya", "yaluy"], EVIDENCE["birri"]),
    row(5, "That liver is here.", "Jiba yinya yaluy.", "jiba yinya yaluy | liver that here", "r235_body_part_that_here_jiba", ["jiba", "yinya", "yaluy"], EVIDENCE["jiba"]),
    row(6, "That leg is here.", "Jandal yinya yaluy.", "jandal yinya yaluy | leg that here", "r235_body_part_that_here_jandal", ["jandal", "yinya", "yaluy"], EVIDENCE["jandal"]),
    row(7, "That stomach is here.", "Jibar yinya yaluy.", "jibar yinya yaluy | stomach that here", "r235_body_part_that_here_jibar", ["jibar", "yinya", "yaluy"], EVIDENCE["jibar"]),
    row(8, "That cheek is here.", "Kangkul yinya yaluy.", "kangkul yinya yaluy | cheek that here", "r235_body_part_that_here_kangkul", ["kangkul", "yinya", "yaluy"], EVIDENCE["kangkul"]),
    row(9, "That posterior is here.", "Kuwinyu yinya yaluy.", "kuwinyu yinya yaluy | posterior that here", "r235_body_part_that_here_kuwinyu", ["kuwinyu", "yinya", "yaluy"], EVIDENCE["kuwinyu"]),
    row(10, "That thigh is here.", "Waba yinya yaluy.", "waba yinya yaluy | thigh that here", "r235_body_part_that_here_waba", ["waba", "yinya", "yaluy"], EVIDENCE["waba"]),
    row(11, "You see the love handles there.", "Yundu bandin yinyay nyajil.", "yundu bandin yinyay nyajil | 2sg.NOM love.handles there see.NPST", "r235_2sg_body_part_there_bandin", ["yundu", "bandin", "yinyay", "nyajil"], EVIDENCE["bandin"]),
    row(12, "You see the chin there.", "Yundu bari yinyay nyajil.", "yundu bari yinyay nyajil | 2sg.NOM chin there see.NPST", "r235_2sg_body_part_there_bari", ["yundu", "bari", "yinyay", "nyajil"], EVIDENCE["bari"]),
    row(13, "You see the shin there.", "Yundu bibar yinyay nyajil.", "yundu bibar yinyay nyajil | 2sg.NOM shin there see.NPST", "r235_2sg_body_part_there_bibar", ["yundu", "bibar", "yinyay", "nyajil"], EVIDENCE["bibar"]),
    row(14, "You see the fingernail there.", "Yundu birri yinyay nyajil.", "yundu birri yinyay nyajil | 2sg.NOM fingernail there see.NPST", "r235_2sg_body_part_there_birri", ["yundu", "birri", "yinyay", "nyajil"], EVIDENCE["birri"]),
    row(15, "You see the liver there.", "Yundu jiba yinyay nyajil.", "yundu jiba yinyay nyajil | 2sg.NOM liver there see.NPST", "r235_2sg_body_part_there_jiba", ["yundu", "jiba", "yinyay", "nyajil"], EVIDENCE["jiba"]),
    row(16, "You see the leg there.", "Yundu jandal yinyay nyajil.", "yundu jandal yinyay nyajil | 2sg.NOM leg there see.NPST", "r235_2sg_body_part_there_jandal", ["yundu", "jandal", "yinyay", "nyajil"], EVIDENCE["jandal"]),
    row(17, "You see the stomach there.", "Yundu jibar yinyay nyajil.", "yundu jibar yinyay nyajil | 2sg.NOM stomach there see.NPST", "r235_2sg_body_part_there_jibar", ["yundu", "jibar", "yinyay", "nyajil"], EVIDENCE["jibar"]),
    row(18, "You see the cheek there.", "Yundu kangkul yinyay nyajil.", "yundu kangkul yinyay nyajil | 2sg.NOM cheek there see.NPST", "r235_2sg_body_part_there_kangkul", ["yundu", "kangkul", "yinyay", "nyajil"], EVIDENCE["kangkul"]),
    row(19, "You see the posterior there.", "Yundu kuwinyu yinyay nyajil.", "yundu kuwinyu yinyay nyajil | 2sg.NOM posterior there see.NPST", "r235_2sg_body_part_there_kuwinyu", ["yundu", "kuwinyu", "yinyay", "nyajil"], EVIDENCE["kuwinyu"]),
    row(20, "You see the thigh there.", "Yundu waba yinyay nyajil.", "yundu waba yinyay nyajil | 2sg.NOM thigh there see.NPST", "r235_2sg_body_part_there_waba", ["yundu", "waba", "yinyay", "nyajil"], EVIDENCE["waba"]),
    row(21, "He sees the love handles here.", "Nyulu bandin yaluy nyajil.", "nyulu bandin yaluy nyajil | 3sg.NOM love.handles here see.NPST", "r235_3sg_body_part_here_bandin", ["nyulu", "bandin", "yaluy", "nyajil"], EVIDENCE["bandin"]),
    row(22, "He sees the chin here.", "Nyulu bari yaluy nyajil.", "nyulu bari yaluy nyajil | 3sg.NOM chin here see.NPST", "r235_3sg_body_part_here_bari", ["nyulu", "bari", "yaluy", "nyajil"], EVIDENCE["bari"]),
    row(23, "He sees the shin here.", "Nyulu bibar yaluy nyajil.", "nyulu bibar yaluy nyajil | 3sg.NOM shin here see.NPST", "r235_3sg_body_part_here_bibar", ["nyulu", "bibar", "yaluy", "nyajil"], EVIDENCE["bibar"]),
    row(24, "He sees the fingernail here.", "Nyulu birri yaluy nyajil.", "nyulu birri yaluy nyajil | 3sg.NOM fingernail here see.NPST", "r235_3sg_body_part_here_birri", ["nyulu", "birri", "yaluy", "nyajil"], EVIDENCE["birri"]),
    row(25, "He sees the liver here.", "Nyulu jiba yaluy nyajil.", "nyulu jiba yaluy nyajil | 3sg.NOM liver here see.NPST", "r235_3sg_body_part_here_jiba", ["nyulu", "jiba", "yaluy", "nyajil"], EVIDENCE["jiba"]),
    row(26, "He sees the leg here.", "Nyulu jandal yaluy nyajil.", "nyulu jandal yaluy nyajil | 3sg.NOM leg here see.NPST", "r235_3sg_body_part_here_jandal", ["nyulu", "jandal", "yaluy", "nyajil"], EVIDENCE["jandal"]),
    row(27, "He sees the stomach here.", "Nyulu jibar yaluy nyajil.", "nyulu jibar yaluy nyajil | 3sg.NOM stomach here see.NPST", "r235_3sg_body_part_here_jibar", ["nyulu", "jibar", "yaluy", "nyajil"], EVIDENCE["jibar"]),
    row(28, "He sees the cheek here.", "Nyulu kangkul yaluy nyajil.", "nyulu kangkul yaluy nyajil | 3sg.NOM cheek here see.NPST", "r235_3sg_body_part_here_kangkul", ["nyulu", "kangkul", "yaluy", "nyajil"], EVIDENCE["kangkul"]),
    row(29, "He sees the posterior here.", "Nyulu kuwinyu yaluy nyajil.", "nyulu kuwinyu yaluy nyajil | 3sg.NOM posterior here see.NPST", "r235_3sg_body_part_here_kuwinyu", ["nyulu", "kuwinyu", "yaluy", "nyajil"], EVIDENCE["kuwinyu"]),
    row(30, "He sees the thigh here.", "Nyulu waba yaluy nyajil.", "nyulu waba yaluy nyajil | 3sg.NOM thigh here see.NPST", "r235_3sg_body_part_here_waba", ["nyulu", "waba", "yaluy", "nyajil"], EVIDENCE["waba"]),
    row(31, "We see the love handles there.", "Ngana bandin yinyay nyajil.", "ngana bandin yinyay nyajil | 1pl.INC.NOM love.handles there see.NPST", "r235_1plinc_body_part_there_bandin", ["ngana", "bandin", "yinyay", "nyajil"], EVIDENCE["bandin"]),
    row(32, "We see the chin there.", "Ngana bari yinyay nyajil.", "ngana bari yinyay nyajil | 1pl.INC.NOM chin there see.NPST", "r235_1plinc_body_part_there_bari", ["ngana", "bari", "yinyay", "nyajil"], EVIDENCE["bari"]),
    row(33, "We see the shin there.", "Ngana bibar yinyay nyajil.", "ngana bibar yinyay nyajil | 1pl.INC.NOM shin there see.NPST", "r235_1plinc_body_part_there_bibar", ["ngana", "bibar", "yinyay", "nyajil"], EVIDENCE["bibar"]),
    row(34, "We see the fingernail there.", "Ngana birri yinyay nyajil.", "ngana birri yinyay nyajil | 1pl.INC.NOM fingernail there see.NPST", "r235_1plinc_body_part_there_birri", ["ngana", "birri", "yinyay", "nyajil"], EVIDENCE["birri"]),
    row(35, "We see the liver there.", "Ngana jiba yinyay nyajil.", "ngana jiba yinyay nyajil | 1pl.INC.NOM liver there see.NPST", "r235_1plinc_body_part_there_jiba", ["ngana", "jiba", "yinyay", "nyajil"], EVIDENCE["jiba"]),
    row(36, "We see the leg there.", "Ngana jandal yinyay nyajil.", "ngana jandal yinyay nyajil | 1pl.INC.NOM leg there see.NPST", "r235_1plinc_body_part_there_jandal", ["ngana", "jandal", "yinyay", "nyajil"], EVIDENCE["jandal"]),
    row(37, "We see the stomach there.", "Ngana jibar yinyay nyajil.", "ngana jibar yinyay nyajil | 1pl.INC.NOM stomach there see.NPST", "r235_1plinc_body_part_there_jibar", ["ngana", "jibar", "yinyay", "nyajil"], EVIDENCE["jibar"]),
    row(38, "We see the cheek there.", "Ngana kangkul yinyay nyajil.", "ngana kangkul yinyay nyajil | 1pl.INC.NOM cheek there see.NPST", "r235_1plinc_body_part_there_kangkul", ["ngana", "kangkul", "yinyay", "nyajil"], EVIDENCE["kangkul"]),
    row(39, "We see the posterior there.", "Ngana kuwinyu yinyay nyajil.", "ngana kuwinyu yinyay nyajil | 1pl.INC.NOM posterior there see.NPST", "r235_1plinc_body_part_there_kuwinyu", ["ngana", "kuwinyu", "yinyay", "nyajil"], EVIDENCE["kuwinyu"]),
    row(40, "We see the thigh there.", "Ngana waba yinyay nyajil.", "ngana waba yinyay nyajil | 1pl.INC.NOM thigh there see.NPST", "r235_1plinc_body_part_there_waba", ["ngana", "waba", "yinyay", "nyajil"], EVIDENCE["waba"]),
    row(41, "They saw the love handles here.", "Jana bandin yaluy nyajin.", "jana bandin yaluy nyajin | 3pl.NOM love.handles here see.PST", "r235_3pl_body_part_here_past_bandin", ["jana", "bandin", "yaluy", "nyajin"], EVIDENCE["bandin"]),
    row(42, "They saw the chin here.", "Jana bari yaluy nyajin.", "jana bari yaluy nyajin | 3pl.NOM chin here see.PST", "r235_3pl_body_part_here_past_bari", ["jana", "bari", "yaluy", "nyajin"], EVIDENCE["bari"]),
    row(43, "They saw the shin here.", "Jana bibar yaluy nyajin.", "jana bibar yaluy nyajin | 3pl.NOM shin here see.PST", "r235_3pl_body_part_here_past_bibar", ["jana", "bibar", "yaluy", "nyajin"], EVIDENCE["bibar"]),
    row(44, "They saw the fingernail here.", "Jana birri yaluy nyajin.", "jana birri yaluy nyajin | 3pl.NOM fingernail here see.PST", "r235_3pl_body_part_here_past_birri", ["jana", "birri", "yaluy", "nyajin"], EVIDENCE["birri"]),
    row(45, "They saw the liver here.", "Jana jiba yaluy nyajin.", "jana jiba yaluy nyajin | 3pl.NOM liver here see.PST", "r235_3pl_body_part_here_past_jiba", ["jana", "jiba", "yaluy", "nyajin"], EVIDENCE["jiba"]),
    row(46, "They saw the leg here.", "Jana jandal yaluy nyajin.", "jana jandal yaluy nyajin | 3pl.NOM leg here see.PST", "r235_3pl_body_part_here_past_jandal", ["jana", "jandal", "yaluy", "nyajin"], EVIDENCE["jandal"]),
    row(47, "They saw the stomach here.", "Jana jibar yaluy nyajin.", "jana jibar yaluy nyajin | 3pl.NOM stomach here see.PST", "r235_3pl_body_part_here_past_jibar", ["jana", "jibar", "yaluy", "nyajin"], EVIDENCE["jibar"]),
    row(48, "They saw the cheek here.", "Jana kangkul yaluy nyajin.", "jana kangkul yaluy nyajin | 3pl.NOM cheek here see.PST", "r235_3pl_body_part_here_past_kangkul", ["jana", "kangkul", "yaluy", "nyajin"], EVIDENCE["kangkul"]),
    row(49, "They saw the posterior here.", "Jana kuwinyu yaluy nyajin.", "jana kuwinyu yaluy nyajin | 3pl.NOM posterior here see.PST", "r235_3pl_body_part_here_past_kuwinyu", ["jana", "kuwinyu", "yaluy", "nyajin"], EVIDENCE["kuwinyu"]),
    row(50, "They saw the thigh here.", "Jana waba yaluy nyajin.", "jana waba yaluy nyajin | 3pl.NOM thigh here see.PST", "r235_3pl_body_part_here_past_waba", ["jana", "waba", "yaluy", "nyajin"], EVIDENCE["waba"]),
]


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("r235_"):
            issues.append(f"{label}: Rule 235 frame must start with r235_")
        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 235")
        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 235 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 not ("love handles" in english_low and "love.handles" in analysis):
                issues.append(f"{label}: plural lexical target {head} must stay lexical/contextual, not morphological")
        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 possession/body-state/medical/ritual/animal/event 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 235 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 235 reuses the ten Rule 234 dictionary-backed body-part heads. Patz 3.1 supplies nominal body-part domain; Patz 3.5 supplies bare nominative yundu/nyulu/ngana/jana; Patz 3.7 supplies yinya/yaluy/yinyay; Patz 3.8.3.2 supplies nyaji-l 'see'.",
        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 235 construction consolidation after Rule 234: same ten body-part target nouns, five frame bands, and no new lexical meanings.",
        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 235 Kuku surfaces returned zero accepted, revised, or under-review collisions.",
        command="sqlite3 synthetic.db WITH planned(kuku) AS (...) SELECT COUNT(*) ...",
        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 235 target per row, no loans, no target suffixes, no denied previous body-part targets, no pronoun case, no support inflection, no possession/body-state/medical/ritual/animal/movement/event English, and five planned frame bands only.",
        command="python3 batch_scripts/bank_277.py",
        artifact_path="batch_scripts/bank_277.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 235 body-part construction consolidation rows as under_review.",
        command="python3 batch_scripts/bank_277.py",
        artifact_path="batch_scripts/bank_277.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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