#!/usr/bin/env python3
"""Bank batch-289: Rule 247 dwelling/household complementary 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-289"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-289-dwelling-household-complementary-pronoun-expansion-after-rule246-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 = {
    "ngali": "1du.EXCL.NOM",
    "ngaliny": "1du.INCL.NOM",
    "yubal": "2du.NOM",
    "nganjin": "1pl.EXCL.NOM",
    "yurra": "2pl.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajil"}
DENY_KUKU = {
    # Rejected dwelling candidates, 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", "ngara", "yijarri",
    "jurrkiji", "kida", "ngunja",
    # Non-selected support bands and previous collision-aware supports.
    "yinya", "ngayu", "nyulu", "yulu", "yundu", "ngana", "bula", "jana", "nyajin",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "nganya", "nyunguny", "yununiny", "ngalingan", "ngalinyan", "yubalan",
    "nganjinan", "nganangan", "yurrangan", "ngalininy", "ngalinyiny", "yubaliny",
    "nganjininy", "ngananiny", "yurraniny", "ngalinka", "ngalinyanka", "yubalanka",
    "nganjinanka", "ngananka", "yurranka", "ngalinda", "ngalinyanda", "yubalanda",
    "nganjinanda", "ngananda", "yurranda", "ngalinji", "ngalinyanji", "yubalanji",
    "nganjinanji", "ngananji", "yurranji", "ngalinga", "ngalinyanga", "yubalanga",
    "nganjinanga", "yurranga", "yinyangka", "yinyayngka", "yinyayanka", "yinyamba",
    "yinyayanda", "yinyamuny", "yinyamundu", "yinyanji", "yinyarriny",
    "nyaji-nka", "nyaji-nyaku", "nyaji-l-nyaji", "nyajiny",
    # Recent semantic fields remain closed.
    "baya", "jimal", "jirramar", "kubu", "kunjin", "miyilkura", "ngalku",
    "ngaluri", "ngunjil", "wula", "bandin", "bari", "bibar", "birri", "jiba",
    "jandal", "jibar", "kangkul", "kuwinyu", "waba", "mabil", "karrirrangkal",
    "jalngarr", "jinali", "kangku", "yalmba", "kambal-kambal", "jalkar", "janku",
    "jamba", "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", "year", "years", "sheet",
)


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 lines 609-619: gloss/definition/translation 'ridge pole'; commentary identifies it as a structural element of a dwelling. Rule 247 selects ridge-pole perception/reference only; no construction, pole-material, or source-material row. Patz 3.5 Table 3.6 sources ngali/ngaliny/yubal/nganjin/yurra; Patz 3.7 sources yaluy/yinyay; Patz 3.8.3.2 sources nyaji-l 'see'.",
    "bayan": "Dictionary bayan noun lines 1965-1977: gloss 'house', definition 'house, camp, shelter'. Patz 3.1.2 also lists bayan as camp/house/shelter. Rule 247 selects house only; no camp setup, building, possession, locative, opium, particle, or yamba-kari row.",
    "bira": "Dictionary bira noun lines 3075-3084: gloss/definition/translation 'windbreak'. Rule 247 selects windbreak perception/reference only; no making, setting, weather, cold/rain, or material-source row.",
    "jiji": "Dictionary jiji noun lines 8896-8907: gloss 'roof', definitions roof of house and the top of anything. Rule 247 selects roof only and leaves top-of-anything and roofing-material prose closed.",
    "kilili": "Dictionary kilili noun lines 14259-14266: gloss/definition/translation 'windbreak'. Rule 247 selects windbreak perception/reference only; no construction, weather, source-material, or use row.",
    "wurun": "Dictionary wurun noun lines 28511-28519: gloss 'humpy', definitions/translations humpy and halfway. Rule 247 selects humpy only; halfway, building, sleeping, rain-shelter, and locative rows stay closed.",
    "jakway": "Dictionary jakway noun lines 7297-7307: gloss 'door', definition door/front, translations door/front. Rule 247 selects door only and leaves front/open/close/shut predicates closed.",
    "yulal": "Dictionary yulal noun lines 30747-30757: gloss 'board', definition board/plank with contrast to kimil round stick. Rule 247 selects board only; plank wording, round-stick contrast, nailing, and building prose stay closed.",
    "kararr": "Dictionary grammar-added kararr lines 13446-13461 plus Patz grammar line 848 and examples 153/416/563 support blanket/sheet/grass-bedding contexts. Rule 247 selects blanket only and leaves sheet, grass bedding, spreading, sleeping, covering, warming, privative, and year-counting closed.",
    "bilangkurr": "Dictionary bilangkurr noun lines 2489-2496: gloss/definition/translation 'blanket'. Rule 247 selects blanket perception/reference only and leaves clothing, sleeping, covering, warming, and ownership rows closed.",
}


ROWS = [
    row(1, "We two, not you, see the ridge pole here.", "Ngali bakarr yaluy nyajil.", "ngali bakarr yaluy nyajil | 1du.EXCL.NOM ridge.pole here see.NPST", "r247_1du_excl_dwelling_here_see_bakarr", ["ngali", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(2, "We two, not you, see the house here.", "Ngali bayan yaluy nyajil.", "ngali bayan yaluy nyajil | 1du.EXCL.NOM house here see.NPST", "r247_1du_excl_dwelling_here_see_bayan", ["ngali", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(3, "We two, not you, see the windbreak here.", "Ngali bira yaluy nyajil.", "ngali bira yaluy nyajil | 1du.EXCL.NOM windbreak here see.NPST", "r247_1du_excl_dwelling_here_see_bira", ["ngali", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(4, "We two, not you, see the roof here.", "Ngali jiji yaluy nyajil.", "ngali jiji yaluy nyajil | 1du.EXCL.NOM roof here see.NPST", "r247_1du_excl_dwelling_here_see_jiji", ["ngali", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(5, "We two, not you, see the windbreak here.", "Ngali kilili yaluy nyajil.", "ngali kilili yaluy nyajil | 1du.EXCL.NOM windbreak here see.NPST", "r247_1du_excl_dwelling_here_see_kilili", ["ngali", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(6, "We two, not you, see the humpy here.", "Ngali wurun yaluy nyajil.", "ngali wurun yaluy nyajil | 1du.EXCL.NOM humpy here see.NPST", "r247_1du_excl_dwelling_here_see_wurun", ["ngali", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(7, "We two, not you, see the door here.", "Ngali jakway yaluy nyajil.", "ngali jakway yaluy nyajil | 1du.EXCL.NOM door here see.NPST", "r247_1du_excl_dwelling_here_see_jakway", ["ngali", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(8, "We two, not you, see the board here.", "Ngali yulal yaluy nyajil.", "ngali yulal yaluy nyajil | 1du.EXCL.NOM board here see.NPST", "r247_1du_excl_dwelling_here_see_yulal", ["ngali", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(9, "We two, not you, see the blanket here.", "Ngali kararr yaluy nyajil.", "ngali kararr yaluy nyajil | 1du.EXCL.NOM blanket here see.NPST", "r247_1du_excl_dwelling_here_see_kararr", ["ngali", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(10, "We two, not you, see the blanket here.", "Ngali bilangkurr yaluy nyajil.", "ngali bilangkurr yaluy nyajil | 1du.EXCL.NOM blanket here see.NPST", "r247_1du_excl_dwelling_here_see_bilangkurr", ["ngali", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(11, "You and I see the ridge pole there.", "Ngaliny bakarr yinyay nyajil.", "ngaliny bakarr yinyay nyajil | 1du.INCL.NOM ridge.pole there see.NPST", "r247_1du_incl_dwelling_there_see_bakarr", ["ngaliny", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(12, "You and I see the house there.", "Ngaliny bayan yinyay nyajil.", "ngaliny bayan yinyay nyajil | 1du.INCL.NOM house there see.NPST", "r247_1du_incl_dwelling_there_see_bayan", ["ngaliny", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(13, "You and I see the windbreak there.", "Ngaliny bira yinyay nyajil.", "ngaliny bira yinyay nyajil | 1du.INCL.NOM windbreak there see.NPST", "r247_1du_incl_dwelling_there_see_bira", ["ngaliny", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(14, "You and I see the roof there.", "Ngaliny jiji yinyay nyajil.", "ngaliny jiji yinyay nyajil | 1du.INCL.NOM roof there see.NPST", "r247_1du_incl_dwelling_there_see_jiji", ["ngaliny", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(15, "You and I see the windbreak there.", "Ngaliny kilili yinyay nyajil.", "ngaliny kilili yinyay nyajil | 1du.INCL.NOM windbreak there see.NPST", "r247_1du_incl_dwelling_there_see_kilili", ["ngaliny", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(16, "You and I see the humpy there.", "Ngaliny wurun yinyay nyajil.", "ngaliny wurun yinyay nyajil | 1du.INCL.NOM humpy there see.NPST", "r247_1du_incl_dwelling_there_see_wurun", ["ngaliny", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(17, "You and I see the door there.", "Ngaliny jakway yinyay nyajil.", "ngaliny jakway yinyay nyajil | 1du.INCL.NOM door there see.NPST", "r247_1du_incl_dwelling_there_see_jakway", ["ngaliny", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(18, "You and I see the board there.", "Ngaliny yulal yinyay nyajil.", "ngaliny yulal yinyay nyajil | 1du.INCL.NOM board there see.NPST", "r247_1du_incl_dwelling_there_see_yulal", ["ngaliny", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(19, "You and I see the blanket there.", "Ngaliny kararr yinyay nyajil.", "ngaliny kararr yinyay nyajil | 1du.INCL.NOM blanket there see.NPST", "r247_1du_incl_dwelling_there_see_kararr", ["ngaliny", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(20, "You and I see the blanket there.", "Ngaliny bilangkurr yinyay nyajil.", "ngaliny bilangkurr yinyay nyajil | 1du.INCL.NOM blanket there see.NPST", "r247_1du_incl_dwelling_there_see_bilangkurr", ["ngaliny", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(21, "You two see the ridge pole here.", "Yubal bakarr yaluy nyajil.", "yubal bakarr yaluy nyajil | 2du.NOM ridge.pole here see.NPST", "r247_2du_dwelling_here_see_bakarr", ["yubal", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(22, "You two see the house here.", "Yubal bayan yaluy nyajil.", "yubal bayan yaluy nyajil | 2du.NOM house here see.NPST", "r247_2du_dwelling_here_see_bayan", ["yubal", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(23, "You two see the windbreak here.", "Yubal bira yaluy nyajil.", "yubal bira yaluy nyajil | 2du.NOM windbreak here see.NPST", "r247_2du_dwelling_here_see_bira", ["yubal", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(24, "You two see the roof here.", "Yubal jiji yaluy nyajil.", "yubal jiji yaluy nyajil | 2du.NOM roof here see.NPST", "r247_2du_dwelling_here_see_jiji", ["yubal", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(25, "You two see the windbreak here.", "Yubal kilili yaluy nyajil.", "yubal kilili yaluy nyajil | 2du.NOM windbreak here see.NPST", "r247_2du_dwelling_here_see_kilili", ["yubal", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(26, "You two see the humpy here.", "Yubal wurun yaluy nyajil.", "yubal wurun yaluy nyajil | 2du.NOM humpy here see.NPST", "r247_2du_dwelling_here_see_wurun", ["yubal", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(27, "You two see the door here.", "Yubal jakway yaluy nyajil.", "yubal jakway yaluy nyajil | 2du.NOM door here see.NPST", "r247_2du_dwelling_here_see_jakway", ["yubal", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(28, "You two see the board here.", "Yubal yulal yaluy nyajil.", "yubal yulal yaluy nyajil | 2du.NOM board here see.NPST", "r247_2du_dwelling_here_see_yulal", ["yubal", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(29, "You two see the blanket here.", "Yubal kararr yaluy nyajil.", "yubal kararr yaluy nyajil | 2du.NOM blanket here see.NPST", "r247_2du_dwelling_here_see_kararr", ["yubal", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(30, "You two see the blanket here.", "Yubal bilangkurr yaluy nyajil.", "yubal bilangkurr yaluy nyajil | 2du.NOM blanket here see.NPST", "r247_2du_dwelling_here_see_bilangkurr", ["yubal", "bilangkurr", "yaluy", "nyajil"], EVIDENCE["bilangkurr"]),
    row(31, "We all, not you, see the ridge pole there.", "Nganjin bakarr yinyay nyajil.", "nganjin bakarr yinyay nyajil | 1pl.EXCL.NOM ridge.pole there see.NPST", "r247_1pl_excl_dwelling_there_see_bakarr", ["nganjin", "bakarr", "yinyay", "nyajil"], EVIDENCE["bakarr"]),
    row(32, "We all, not you, see the house there.", "Nganjin bayan yinyay nyajil.", "nganjin bayan yinyay nyajil | 1pl.EXCL.NOM house there see.NPST", "r247_1pl_excl_dwelling_there_see_bayan", ["nganjin", "bayan", "yinyay", "nyajil"], EVIDENCE["bayan"]),
    row(33, "We all, not you, see the windbreak there.", "Nganjin bira yinyay nyajil.", "nganjin bira yinyay nyajil | 1pl.EXCL.NOM windbreak there see.NPST", "r247_1pl_excl_dwelling_there_see_bira", ["nganjin", "bira", "yinyay", "nyajil"], EVIDENCE["bira"]),
    row(34, "We all, not you, see the roof there.", "Nganjin jiji yinyay nyajil.", "nganjin jiji yinyay nyajil | 1pl.EXCL.NOM roof there see.NPST", "r247_1pl_excl_dwelling_there_see_jiji", ["nganjin", "jiji", "yinyay", "nyajil"], EVIDENCE["jiji"]),
    row(35, "We all, not you, see the windbreak there.", "Nganjin kilili yinyay nyajil.", "nganjin kilili yinyay nyajil | 1pl.EXCL.NOM windbreak there see.NPST", "r247_1pl_excl_dwelling_there_see_kilili", ["nganjin", "kilili", "yinyay", "nyajil"], EVIDENCE["kilili"]),
    row(36, "We all, not you, see the humpy there.", "Nganjin wurun yinyay nyajil.", "nganjin wurun yinyay nyajil | 1pl.EXCL.NOM humpy there see.NPST", "r247_1pl_excl_dwelling_there_see_wurun", ["nganjin", "wurun", "yinyay", "nyajil"], EVIDENCE["wurun"]),
    row(37, "We all, not you, see the door there.", "Nganjin jakway yinyay nyajil.", "nganjin jakway yinyay nyajil | 1pl.EXCL.NOM door there see.NPST", "r247_1pl_excl_dwelling_there_see_jakway", ["nganjin", "jakway", "yinyay", "nyajil"], EVIDENCE["jakway"]),
    row(38, "We all, not you, see the board there.", "Nganjin yulal yinyay nyajil.", "nganjin yulal yinyay nyajil | 1pl.EXCL.NOM board there see.NPST", "r247_1pl_excl_dwelling_there_see_yulal", ["nganjin", "yulal", "yinyay", "nyajil"], EVIDENCE["yulal"]),
    row(39, "We all, not you, see the blanket there.", "Nganjin kararr yinyay nyajil.", "nganjin kararr yinyay nyajil | 1pl.EXCL.NOM blanket there see.NPST", "r247_1pl_excl_dwelling_there_see_kararr", ["nganjin", "kararr", "yinyay", "nyajil"], EVIDENCE["kararr"]),
    row(40, "We all, not you, see the blanket there.", "Nganjin bilangkurr yinyay nyajil.", "nganjin bilangkurr yinyay nyajil | 1pl.EXCL.NOM blanket there see.NPST", "r247_1pl_excl_dwelling_there_see_bilangkurr", ["nganjin", "bilangkurr", "yinyay", "nyajil"], EVIDENCE["bilangkurr"]),
    row(41, "You all see the ridge pole here.", "Yurra bakarr yaluy nyajil.", "yurra bakarr yaluy nyajil | 2pl.NOM ridge.pole here see.NPST", "r247_2pl_dwelling_here_see_bakarr", ["yurra", "bakarr", "yaluy", "nyajil"], EVIDENCE["bakarr"]),
    row(42, "You all see the house here.", "Yurra bayan yaluy nyajil.", "yurra bayan yaluy nyajil | 2pl.NOM house here see.NPST", "r247_2pl_dwelling_here_see_bayan", ["yurra", "bayan", "yaluy", "nyajil"], EVIDENCE["bayan"]),
    row(43, "You all see the windbreak here.", "Yurra bira yaluy nyajil.", "yurra bira yaluy nyajil | 2pl.NOM windbreak here see.NPST", "r247_2pl_dwelling_here_see_bira", ["yurra", "bira", "yaluy", "nyajil"], EVIDENCE["bira"]),
    row(44, "You all see the roof here.", "Yurra jiji yaluy nyajil.", "yurra jiji yaluy nyajil | 2pl.NOM roof here see.NPST", "r247_2pl_dwelling_here_see_jiji", ["yurra", "jiji", "yaluy", "nyajil"], EVIDENCE["jiji"]),
    row(45, "You all see the windbreak here.", "Yurra kilili yaluy nyajil.", "yurra kilili yaluy nyajil | 2pl.NOM windbreak here see.NPST", "r247_2pl_dwelling_here_see_kilili", ["yurra", "kilili", "yaluy", "nyajil"], EVIDENCE["kilili"]),
    row(46, "You all see the humpy here.", "Yurra wurun yaluy nyajil.", "yurra wurun yaluy nyajil | 2pl.NOM humpy here see.NPST", "r247_2pl_dwelling_here_see_wurun", ["yurra", "wurun", "yaluy", "nyajil"], EVIDENCE["wurun"]),
    row(47, "You all see the door here.", "Yurra jakway yaluy nyajil.", "yurra jakway yaluy nyajil | 2pl.NOM door here see.NPST", "r247_2pl_dwelling_here_see_jakway", ["yurra", "jakway", "yaluy", "nyajil"], EVIDENCE["jakway"]),
    row(48, "You all see the board here.", "Yurra yulal yaluy nyajil.", "yurra yulal yaluy nyajil | 2pl.NOM board here see.NPST", "r247_2pl_dwelling_here_see_yulal", ["yurra", "yulal", "yaluy", "nyajil"], EVIDENCE["yulal"]),
    row(49, "You all see the blanket here.", "Yurra kararr yaluy nyajil.", "yurra kararr yaluy nyajil | 2pl.NOM blanket here see.NPST", "r247_2pl_dwelling_here_see_kararr", ["yurra", "kararr", "yaluy", "nyajil"], EVIDENCE["kararr"]),
    row(50, "You all see the blanket here.", "Yurra bilangkurr yaluy nyajil.", "yurra bilangkurr yaluy nyajil | 2pl.NOM blanket here see.NPST", "r247_2pl_dwelling_here_see_bilangkurr", ["yurra", "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("r247_"):
            issues.append(f"{label}: Rule 247 frame must start with r247_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface inside batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: loans_used must be empty")
        denied = [bad for bad in DENY_KUKU if has_token(low, bad)]
        if denied:
            issues.append(f"{label}: denied Kuku token(s) present: {denied}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 247 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("ngali ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngali TARGET yaluy nyajil")
        if 11 <= seq <= 20 and (not low.startswith("ngaliny ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Ngaliny TARGET yinyay nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yubal ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Yubal TARGET yaluy nyajil")
        if 31 <= seq <= 40 and (not low.startswith("nganjin ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Nganjin TARGET yinyay nyajil")
        if 41 <= seq <= 50 and (not low.startswith("yurra ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Yurra 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 247 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 247 source pass retained the ten Rule 238 dwelling/household target nouns; checked dictionary entries and line evidence for bakarr, bayan, bira, jiji, kilili, wurun, jakway, yulal, kararr, and bilangkurr; checked Patz 3.5 Table 3.6 for ngali, ngaliny, yubal, nganjin, and yurra; 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 247 dwelling/household complementary pronoun expansion after Rule 246: same ten Rule 238 target nouns in five zero-collision nominative-pronoun perception bands. No new dwelling lexemes, building, setting, spreading, entering, nailing, roofing, sleeping, covering, warming, front/open/shut, ownership, homograph, target case, pronoun case, or perception-compound semantics opened.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="coverage-audit",
        actor="claude-main",
        detail="Token-aware pre-batch coverage after Rule 246: bakarr 15, bayan 451, bira 16, jiji 15, kilili 15, wurun 38, jakway 25, yulal 25, kararr 32, bilangkurr 32; pronouns ngali 119, ngaliny 54, yubal 65, nganjin 71, yurra 113.",
        command="token-aware coverage audit over accepted/revised rows",
        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 247 dwelling/household complementary pronoun-expansion perception surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_289.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 247 target noun per row, exactly one selected bare nominative pronoun per row, no loans, no target suffixes, no rejected homographs/candidates, no excluded building/setting/spreading/entering/nailing/roofing/sleeping/covering/warming/front/open/shut/top/plank/source-prose/ownership drift, no pronoun case, no perception compound, and five planned frame bands only.",
        command="python3 batch_scripts/bank_289.py",
        artifact_path="batch_scripts/bank_289.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 247 dwelling/household complementary pronoun-expansion perception rows as under_review.",
        command="python3 batch_scripts/bank_289.py",
        artifact_path="batch_scripts/bank_289.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: {len(ROWS)} under_review rows")
    return 0


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