#!/usr/bin/env python3
"""Bank batch-285: Rule 243 dwelling/household 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-285"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-285-dwelling-household-pronoun-expansion-after-rule242-process.md"

TARGET_HEADS = {
    "bakarr": "ridge pole",
    "bayan": "house",
    "bira": "windbreak",
    "jiji": "roof",
    "kilili": "windbreak",
    "wurun": "humpy",
    "jakway": "door",
    "yulal": "board",
    "kararr": "blanket",
    "bilangkurr": "blanket",
}
PRONOUN_SUPPORT = {
    "yulu": "3sg.NOM",
    "ngali": "1du.EXCL.NOM",
    "ngaliny": "1du.INCL.NOM",
    "yubal": "2du.NOM",
    "nganjin": "1pl.EXCL.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajil"}
DENY_KUKU = {
    # Rejected homographs and source-prose traps.
    "yamba", "yamba-kari", "yamba-yamba", "jakal", "warrbi", "dubal", "kakarr",
    "karrngka", "dimbi", "wanjakan", "walal", "walan", "wadil", "ngaral",
    "ngaran", "yuru-dudal", "balanbalan", "balibali", "murba",
    "balkan", "buyku", "dumbul", "juku", "jikan", "kaji", "walngkan-daman",
    "walngkan", "nandan", "wunanka", "bundanday", "warngku", "wundi",
    # Rejected full-band pronouns for this collision-aware pass.
    "ngayu", "nyulu", "yundu", "ngana", "yurra", "bula", "jana",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "nganya", "nyunguny", "yununiny", "ngalingan", "ngalinyan", "yubalan",
    "bulangan", "nganjinan", "nganangan", "yurrangan", "ianangan",
    "ngalininy", "ngalinyiny", "yubaliny", "bulaniny", "nganjininy",
    "ngananiny", "yurraniny", "iananiny", "ngalinka", "ngalinyanka",
    "yubalanka", "bulanka", "nganjinanka", "ngananka", "yurranka", "iananka",
    "ngalinda", "ngalinyanda", "yubalanda", "bulanda", "nganjinanda",
    "ngananda", "yurranda", "iananda", "ngalinji", "ngalinyanji", "yubalanji",
    "bulanji", "nganjinanji", "ngananji", "yurranji", "iananji",
    "yinyangka", "yinyayngka", "yinyayanka", "yinyamba", "yinyayanda",
    "yinyamuny", "yinyamundu", "yinyanji", "yinyarriny",
    "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji", "nyajin", "nyajiny",
    # Recent semantic fields remain closed.
    "baya", "jimal", "jirramar", "kubu", "kunjin", "miyilkura", "ngalku",
    "ngaluri", "ngunjil", "wula", "kubija", "mujarrka", "barrbal", "wuyumbu",
    "bayil", "dalmbal", "balarri", "bilmbin", "jalkay", "jijirr", "birmba",
    "jamarbina", "nili-nili", "mabal", "wungku", "kuludu", "kururrungka",
    "karrambil", "yinjul", "kadanji",
}
DENY_ENGLISH = (
    "build", "built", "building", "make", "made", "making", "set up", "camp out",
    "camping", "sleep", "sleeping", "slept", "lie on", "lying", "spread", "spreading",
    "cover", "covered", "covering", "warm", "warming", "cold", "rain", "storm",
    "bark", "grass", "sapling", "timber", "wood", "roofing", "front", "open",
    "opened", "close", "closed", "shut", "nail", "nailed", "carpenter", "plank",
    "round stick", "opium", "none", "nothing", "mouse", "axe", "old house",
    "place name", "wall", "crack", "floor", "inside", "outside", "own", "my ",
    "your ", "his ", "her ", "their ", "possess", "owner", "case", "compound",
    "loan", "ceremony", "spirit", "dead", "corpse", "purify", "smoke out",
)


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


EVIDENCE = {
    "bakarr": "Dictionary bakarr noun gloss/translation 'ridge pole'. Rule 243 retains the Rule 238 target and changes only the bare nominative pronoun band. Patz 3.5 Table 3.6 sources yulu/ngali/ngaliny/yubal/nganjin; Patz 3.7 sources yaluy/yinyay; Patz 3.8.3.2 sources nyaji-l 'see'.",
    "bayan": "Dictionary bayan noun gloss 'house' with definition 'house, camp, shelter', plus Patz 3.1.2 bayan 'camp, house, shelter'. Rule 243 selects house only and changes only the pronoun band; no camping, building, possession, locative, opium, or particle row.",
    "bira": "Dictionary bira noun gloss/definition/translation 'windbreak'. Rule 243 retains windbreak reference/perception only and changes only the pronoun band. Patz 3.5, 3.7, and 3.8.3.2 source the support forms.",
    "jiji": "Dictionary jiji noun gloss 'roof' with definitions 'roof of house' and 'the top of anything'. Rule 243 selects roof only and leaves top-of-anything and roofing-material prose closed.",
    "kilili": "Dictionary kilili noun gloss/definition/translation 'windbreak'. Rule 243 retains windbreak reference/perception only and changes only the pronoun band.",
    "wurun": "Dictionary wurun noun gloss/definition/translation 'humpy'. Rule 243 selects humpy only and leaves halfway, building, sleeping, rain-shelter, and locative rows closed.",
    "jakway": "Dictionary jakway noun gloss 'door' with definition 'door, front'. Rule 243 selects door only and leaves front/open/close/shut predicates closed.",
    "yulal": "Dictionary yulal noun gloss 'board' with definition 'board, plank' and contrast with round stick. Rule 243 selects board only and leaves plank, round-stick contrast, nailing, and building prose closed.",
    "kararr": "Patz-backed grammar-added kararr 'blanket' and grammar evidence 'kararr grass, blanket to lie on'. Rule 243 selects blanket only and leaves sheet, grass bedding, spreading, sleeping, covering, warming, and year-counting closed.",
    "bilangkurr": "Dictionary bilangkurr noun gloss/definition/translation 'blanket'. Rule 243 selects blanket reference/perception only and leaves clothing, sleeping, covering, warming, and ownership rows closed.",
}


ROWS = [
    row(1, "He sees the ridge pole here.", "Yulu bakarr yaluy nyajil.", "yulu bakarr yaluy nyajil | 3sg.NOM ridge.pole here see.NPST", "r243_3sg_alt_target_here_see_bakarr", ["yulu", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(2, "He sees the house here.", "Yulu bayan yaluy nyajil.", "yulu bayan yaluy nyajil | 3sg.NOM house here see.NPST", "r243_3sg_alt_target_here_see_bayan", ["yulu", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(3, "He sees the windbreak here.", "Yulu bira yaluy nyajil.", "yulu bira yaluy nyajil | 3sg.NOM windbreak here see.NPST", "r243_3sg_alt_target_here_see_bira", ["yulu", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(4, "He sees the roof here.", "Yulu jiji yaluy nyajil.", "yulu jiji yaluy nyajil | 3sg.NOM roof here see.NPST", "r243_3sg_alt_target_here_see_jiji", ["yulu", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(5, "He sees the windbreak here.", "Yulu kilili yaluy nyajil.", "yulu kilili yaluy nyajil | 3sg.NOM windbreak here see.NPST", "r243_3sg_alt_target_here_see_kilili", ["yulu", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(6, "He sees the humpy here.", "Yulu wurun yaluy nyajil.", "yulu wurun yaluy nyajil | 3sg.NOM humpy here see.NPST", "r243_3sg_alt_target_here_see_wurun", ["yulu", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(7, "He sees the door here.", "Yulu jakway yaluy nyajil.", "yulu jakway yaluy nyajil | 3sg.NOM door here see.NPST", "r243_3sg_alt_target_here_see_jakway", ["yulu", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(8, "He sees the board here.", "Yulu yulal yaluy nyajil.", "yulu yulal yaluy nyajil | 3sg.NOM board here see.NPST", "r243_3sg_alt_target_here_see_yulal", ["yulu", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(9, "He sees the blanket here.", "Yulu kararr yaluy nyajil.", "yulu kararr yaluy nyajil | 3sg.NOM blanket here see.NPST", "r243_3sg_alt_target_here_see_kararr", ["yulu", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(10, "He sees the blanket here.", "Yulu bilangkurr yaluy nyajil.", "yulu bilangkurr yaluy nyajil | 3sg.NOM blanket here see.NPST", "r243_3sg_alt_target_here_see_bilangkurr", ["yulu", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(11, "We two, not you, see the ridge pole there.", "Ngali bakarr yinyay nyajil.", "ngali bakarr yinyay nyajil | 1du.EXCL.NOM ridge.pole there see.NPST", "r243_1du_excl_target_there_see_bakarr", ["ngali", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(12, "We two, not you, see the house there.", "Ngali bayan yinyay nyajil.", "ngali bayan yinyay nyajil | 1du.EXCL.NOM house there see.NPST", "r243_1du_excl_target_there_see_bayan", ["ngali", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(13, "We two, not you, see the windbreak there.", "Ngali bira yinyay nyajil.", "ngali bira yinyay nyajil | 1du.EXCL.NOM windbreak there see.NPST", "r243_1du_excl_target_there_see_bira", ["ngali", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(14, "We two, not you, see the roof there.", "Ngali jiji yinyay nyajil.", "ngali jiji yinyay nyajil | 1du.EXCL.NOM roof there see.NPST", "r243_1du_excl_target_there_see_jiji", ["ngali", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(15, "We two, not you, see the windbreak there.", "Ngali kilili yinyay nyajil.", "ngali kilili yinyay nyajil | 1du.EXCL.NOM windbreak there see.NPST", "r243_1du_excl_target_there_see_kilili", ["ngali", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(16, "We two, not you, see the humpy there.", "Ngali wurun yinyay nyajil.", "ngali wurun yinyay nyajil | 1du.EXCL.NOM humpy there see.NPST", "r243_1du_excl_target_there_see_wurun", ["ngali", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(17, "We two, not you, see the door there.", "Ngali jakway yinyay nyajil.", "ngali jakway yinyay nyajil | 1du.EXCL.NOM door there see.NPST", "r243_1du_excl_target_there_see_jakway", ["ngali", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(18, "We two, not you, see the board there.", "Ngali yulal yinyay nyajil.", "ngali yulal yinyay nyajil | 1du.EXCL.NOM board there see.NPST", "r243_1du_excl_target_there_see_yulal", ["ngali", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(19, "We two, not you, see the blanket there.", "Ngali kararr yinyay nyajil.", "ngali kararr yinyay nyajil | 1du.EXCL.NOM blanket there see.NPST", "r243_1du_excl_target_there_see_kararr", ["ngali", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(20, "We two, not you, see the blanket there.", "Ngali bilangkurr yinyay nyajil.", "ngali bilangkurr yinyay nyajil | 1du.EXCL.NOM blanket there see.NPST", "r243_1du_excl_target_there_see_bilangkurr", ["ngali", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(21, "You and I see the ridge pole here.", "Ngaliny bakarr yaluy nyajil.", "ngaliny bakarr yaluy nyajil | 1du.INCL.NOM ridge.pole here see.NPST", "r243_1du_incl_target_here_see_bakarr", ["ngaliny", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(22, "You and I see the house here.", "Ngaliny bayan yaluy nyajil.", "ngaliny bayan yaluy nyajil | 1du.INCL.NOM house here see.NPST", "r243_1du_incl_target_here_see_bayan", ["ngaliny", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(23, "You and I see the windbreak here.", "Ngaliny bira yaluy nyajil.", "ngaliny bira yaluy nyajil | 1du.INCL.NOM windbreak here see.NPST", "r243_1du_incl_target_here_see_bira", ["ngaliny", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(24, "You and I see the roof here.", "Ngaliny jiji yaluy nyajil.", "ngaliny jiji yaluy nyajil | 1du.INCL.NOM roof here see.NPST", "r243_1du_incl_target_here_see_jiji", ["ngaliny", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(25, "You and I see the windbreak here.", "Ngaliny kilili yaluy nyajil.", "ngaliny kilili yaluy nyajil | 1du.INCL.NOM windbreak here see.NPST", "r243_1du_incl_target_here_see_kilili", ["ngaliny", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(26, "You and I see the humpy here.", "Ngaliny wurun yaluy nyajil.", "ngaliny wurun yaluy nyajil | 1du.INCL.NOM humpy here see.NPST", "r243_1du_incl_target_here_see_wurun", ["ngaliny", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(27, "You and I see the door here.", "Ngaliny jakway yaluy nyajil.", "ngaliny jakway yaluy nyajil | 1du.INCL.NOM door here see.NPST", "r243_1du_incl_target_here_see_jakway", ["ngaliny", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(28, "You and I see the board here.", "Ngaliny yulal yaluy nyajil.", "ngaliny yulal yaluy nyajil | 1du.INCL.NOM board here see.NPST", "r243_1du_incl_target_here_see_yulal", ["ngaliny", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(29, "You and I see the blanket here.", "Ngaliny kararr yaluy nyajil.", "ngaliny kararr yaluy nyajil | 1du.INCL.NOM blanket here see.NPST", "r243_1du_incl_target_here_see_kararr", ["ngaliny", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(30, "You and I see the blanket here.", "Ngaliny bilangkurr yaluy nyajil.", "ngaliny bilangkurr yaluy nyajil | 1du.INCL.NOM blanket here see.NPST", "r243_1du_incl_target_here_see_bilangkurr", ["ngaliny", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(31, "You two see the ridge pole there.", "Yubal bakarr yinyay nyajil.", "yubal bakarr yinyay nyajil | 2du.NOM ridge.pole there see.NPST", "r243_2du_target_there_see_bakarr", ["yubal", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(32, "You two see the house there.", "Yubal bayan yinyay nyajil.", "yubal bayan yinyay nyajil | 2du.NOM house there see.NPST", "r243_2du_target_there_see_bayan", ["yubal", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(33, "You two see the windbreak there.", "Yubal bira yinyay nyajil.", "yubal bira yinyay nyajil | 2du.NOM windbreak there see.NPST", "r243_2du_target_there_see_bira", ["yubal", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(34, "You two see the roof there.", "Yubal jiji yinyay nyajil.", "yubal jiji yinyay nyajil | 2du.NOM roof there see.NPST", "r243_2du_target_there_see_jiji", ["yubal", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(35, "You two see the windbreak there.", "Yubal kilili yinyay nyajil.", "yubal kilili yinyay nyajil | 2du.NOM windbreak there see.NPST", "r243_2du_target_there_see_kilili", ["yubal", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(36, "You two see the humpy there.", "Yubal wurun yinyay nyajil.", "yubal wurun yinyay nyajil | 2du.NOM humpy there see.NPST", "r243_2du_target_there_see_wurun", ["yubal", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(37, "You two see the door there.", "Yubal jakway yinyay nyajil.", "yubal jakway yinyay nyajil | 2du.NOM door there see.NPST", "r243_2du_target_there_see_jakway", ["yubal", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(38, "You two see the board there.", "Yubal yulal yinyay nyajil.", "yubal yulal yinyay nyajil | 2du.NOM board there see.NPST", "r243_2du_target_there_see_yulal", ["yubal", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(39, "You two see the blanket there.", "Yubal kararr yinyay nyajil.", "yubal kararr yinyay nyajil | 2du.NOM blanket there see.NPST", "r243_2du_target_there_see_kararr", ["yubal", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(40, "You two see the blanket there.", "Yubal bilangkurr yinyay nyajil.", "yubal bilangkurr yinyay nyajil | 2du.NOM blanket there see.NPST", "r243_2du_target_there_see_bilangkurr", ["yubal", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(41, "We all, not you, see the ridge pole here.", "Nganjin bakarr yaluy nyajil.", "nganjin bakarr yaluy nyajil | 1pl.EXCL.NOM ridge.pole here see.NPST", "r243_1pl_excl_target_here_see_bakarr", ["nganjin", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(42, "We all, not you, see the house here.", "Nganjin bayan yaluy nyajil.", "nganjin bayan yaluy nyajil | 1pl.EXCL.NOM house here see.NPST", "r243_1pl_excl_target_here_see_bayan", ["nganjin", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(43, "We all, not you, see the windbreak here.", "Nganjin bira yaluy nyajil.", "nganjin bira yaluy nyajil | 1pl.EXCL.NOM windbreak here see.NPST", "r243_1pl_excl_target_here_see_bira", ["nganjin", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(44, "We all, not you, see the roof here.", "Nganjin jiji yaluy nyajil.", "nganjin jiji yaluy nyajil | 1pl.EXCL.NOM roof here see.NPST", "r243_1pl_excl_target_here_see_jiji", ["nganjin", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(45, "We all, not you, see the windbreak here.", "Nganjin kilili yaluy nyajil.", "nganjin kilili yaluy nyajil | 1pl.EXCL.NOM windbreak here see.NPST", "r243_1pl_excl_target_here_see_kilili", ["nganjin", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(46, "We all, not you, see the humpy here.", "Nganjin wurun yaluy nyajil.", "nganjin wurun yaluy nyajil | 1pl.EXCL.NOM humpy here see.NPST", "r243_1pl_excl_target_here_see_wurun", ["nganjin", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(47, "We all, not you, see the door here.", "Nganjin jakway yaluy nyajil.", "nganjin jakway yaluy nyajil | 1pl.EXCL.NOM door here see.NPST", "r243_1pl_excl_target_here_see_jakway", ["nganjin", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(48, "We all, not you, see the board here.", "Nganjin yulal yaluy nyajil.", "nganjin yulal yaluy nyajil | 1pl.EXCL.NOM board here see.NPST", "r243_1pl_excl_target_here_see_yulal", ["nganjin", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(49, "We all, not you, see the blanket here.", "Nganjin kararr yaluy nyajil.", "nganjin kararr yaluy nyajil | 1pl.EXCL.NOM blanket here see.NPST", "r243_1pl_excl_target_here_see_kararr", ["nganjin", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(50, "We all, not you, see the blanket here.", "Nganjin bilangkurr yaluy nyajil.", "nganjin bilangkurr yaluy nyajil | 1pl.EXCL.NOM blanket here see.NPST", "r243_1pl_excl_target_here_see_bilangkurr", ["nganjin", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
]


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("r243_"):
            issues.append(f"{label}: Rule 243 frame must start with r243_")
        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 243")
        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 243 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 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 selected bare nominative pronoun, found {pronouns}")
        if 1 <= seq <= 10 and (not low.startswith("yulu ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Yulu TARGET yaluy nyajil")
        if 11 <= seq <= 20 and (not low.startswith("ngali ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Ngali TARGET yinyay nyajil")
        if 21 <= seq <= 30 and (not low.startswith("ngaliny ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Ngaliny TARGET yaluy nyajil")
        if 31 <= seq <= 40 and (not low.startswith("yubal ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Yubal TARGET yinyay nyajil")
        if 41 <= seq <= 50 and (not low.startswith("nganjin ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Nganjin TARGET yaluy nyajil")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied dwelling action/use/homograph/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 243 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 243 source pass retained the ten Rule 238 dwelling/household target nouns; checked dictionary entries for bakarr, bayan, bira, jiji, kilili, wurun, jakway, yulal, and bilangkurr; retained Patz-backed kararr; checked Patz 3.5 Table 3.6 for yulu, ngali, ngaliny, yubal, and nganjin; 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="construction-scope",
        actor="claude-main",
        detail="Selected Rule 243 dwelling/household pronoun expansion after Rule 242: same ten dwelling/household nouns in five zero-collision nominative-pronoun perception bands. Rejected complementary Rule 239 grid, ngana bands, and yundu bands because exact surfaces collided on older accepted rows.",
        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 coverage after Rule 242: dwelling targets bakarr 10, jiji 10, kilili 10, bira 11, jakway 20, yulal 20, bilangkurr 27, kararr 27, wurun 33, bayan 436; pronouns ngaliny 13, yubal 24, nganjin 28, ngali 78, ngana 858, yundu 1174.",
        command="sqlite3 synthetic.db 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 243 pronoun-expansion perception surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_285.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 243 target noun per row, exactly one selected bare nominative pronoun per row, no loans, no target suffixes, no excluded homograph/action/source-prose target drift, no pronoun case, no perception compound, and five planned frame bands only.",
        command="python3 batch_scripts/bank_285.py",
        artifact_path="batch_scripts/bank_285.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 243 dwelling/household pronoun-expansion perception rows as under_review.",
        command="python3 batch_scripts/bank_285.py",
        artifact_path="batch_scripts/bank_285.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: 50 under_review rows")
    return 0


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