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

TARGET_HEADS = {
    "mabil": "swamp",
    "karrirrangkal": "coral",
    "jalngarr": "flat rock",
    "jinali": "smooth rock",
    "kangku": "valley",
    "yalmba": "seashore",
    "kambal-kambal": "mud",
    "jalkar": "high bank",
    "janku": "hole",
    "jamba": "hillside",
}
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 = {
    # Earlier landform targets/rejected candidates and source-prose traps.
    "jalkaraburr", "yikin", "binda-dijarr", "duli", "jamar", "kabay", "karrangkal",
    "kuri", "ngamar", "nganyay", "ngidin", "warrmba", "damu", "naka", "kurrkal",
    "ngunnga", "nurru", "nyamun", "walba-murru", "bana", "yilki", "walngka",
    "yalmbar", "jalkar-jalkar", "kambal", "kangkul", "karrngka",
    # Non-selected supports and older Rule 232/233/244 frames.
    "yinya", "ngayu", "yundu", "nyulu", "ngana", "jana", "bula", "yulu", "nyajin",
    # Pronoun/demonstrative case, perception compounds, and support inflection.
    "ngalingan", "ngalinyan", "yubalan", "nganjinan", "yurrangan", "ngalininy",
    "ngalinyiny", "yubaliny", "nganjininy", "yurraniny", "ngalinka", "ngalinyanka",
    "yubalanka", "nganjinanka", "yurranka", "ngalinda", "ngalinyanda", "yubalanda",
    "nganjinanda", "yurranda", "ngalinji", "ngalinyanji", "yubalanji", "nganjinanji",
    "yurranji", "ngalinga", "ngalinyanga", "yubalanga", "nganjinanga", "yurranga",
    "yinyangka", "yinyayngka", "yinyayanka", "yinyamba", "yinyayanda", "yinyamuny",
    "yinyamundu", "yinyanji", "yinyarriny", "nyaji-nka", "nyaji-nyaku",
    "nyaji-l-nyaji", "nyajiny",
    # Recent fields remain closed.
    "bakarr", "bayan", "bira", "jiji", "kilili", "wurun", "jakway", "yulal",
    "kararr", "bilangkurr", "baya", "jimal", "jirramar", "kubu", "kunjin",
    "miyilkura", "ngalku", "ngaluri", "ngunjil", "wula", "bandin", "bari", "bibar",
    "birri", "jiba", "jandal", "jibar", "kuwinyu", "waba", "kubija", "mujarrka",
    "barrbal", "wuyumbu", "bayil", "dalmbal", "balarri", "bilmbin", "jalkay",
    "jijirr", "birmba", "jamarbina", "nili-nili", "mabal", "wungku", "kuludu",
    "kururrungka", "karrambil", "yinjul", "kadanji",
}
DENY_ENGLISH = (
    "route", "travel", "traveled", "travelled", "go", "went", "come", "came",
    "cross", "crossing", "path", "track", "road", "river", "creek", "water",
    "waterhole", "reef", "sea", "tide", "beach", "collect", "collecting", "gather",
    "lime", "material", "source", "use", "using", "burn", "burning", "clear",
    "clearing", "live ant", "insect", "habitat", "ecology", "climb", "climbing",
    "fall", "falling", "danger", "dangerous", "own", "owner", "ownership",
    "dispute", "legal", "law", "social", "find", "finding", "peep", "causative",
    "rubbish", "debris", "leaf mould", "grow", "growing", "smooth out", "smooths",
    "steep", "mountain pass", "cheek", "body", "turtle", "food", "warning",
    "burrow", "nest", "dig", "digging", "ground oven", "root", "under tree",
    "case", "compound", "loan", "place name",
)


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 = {
    "mabil": "Dictionary mabil noun lines 17030-17038: gloss/definition/translation 'swamp'. Rule 248 keeps swamp reference/perception only. 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'.",
    "karrirrangkal": "Dictionary karrirrangkal noun lines 13615-13624: gloss 'coral', definition 'coral crushed for lime', translations coral/lime. Rule 248 selects coral only; lime/source-material, reef/sea, collecting, and use prose stay closed.",
    "jalngarr": "Dictionary jalngarr noun lines 7498-7506: gloss/definition/translation 'flat rock'. Rule 248 selects flat-rock reference/perception only; no sitting, creek, water, route, or event row.",
    "jinali": "Dictionary jinali noun lines 9391-9399: gloss/definition/translation 'smooth rock'. Rule 248 selects smooth-rock reference/perception only; no smooth-property predicate or smoothing-use row.",
    "kangku": "Dictionary kangku noun lines 13266-13275: gloss 'valley', definition 'low gap in mountain, mountain pass', translations valley/mountain pass. Rule 248 selects valley only and does not open route/travel or cheek/body-part drift.",
    "yalmba": "Dictionary yalmba noun lines 29078-29087: gloss 'seashore', definition 'seashore, beach', translations seashore/beach. Rule 248 selects seashore only; beach-collecting, sea/tide, turtle, food, and warning rows stay closed.",
    "kambal-kambal": "Dictionary kambal-kambal noun lines 12962-12975: gloss/definition/translation 'mud', lexical reduplicated form. Rule 248 keeps it as a whole headword and does not license productive reduplication from kambal.",
    "jalkar": "Dictionary jalkar noun lines 7429-7439: gloss/definition/translation 'high bank', with commentary cf. jalkar-jalkar 'steep'. Rule 248 selects high-bank reference only and leaves steep-property, route, river, and danger rows closed.",
    "janku": "Dictionary janku noun lines 8119-8127: gloss 'hole', definition 'hole in the ground', translation hole. Rule 248 selects hole only and leaves waterhole, burrow, nest, digging, ground-oven, and event rows closed.",
    "jamba": "Dictionary jamba noun lines 7690-7703: gloss/definition/translation 'hillside', with locative usage examples. Rule 248 selects the bare hillside head only and does not import locative case, root/under-tree, burning, social-metaphor, or event readings.",
}


ROWS = [
    row(1, "We two, not you, see the swamp there.", "Ngali mabil yinyay nyajil.", "ngali mabil yinyay nyajil | 1du.EXCL.NOM swamp there see.NPST", "r248_1du_excl_landform_there_see_mabil", ["ngali", "mabil", "yinyay", "nyajil"], EVIDENCE["mabil"]),
    row(2, "We two, not you, see the coral there.", "Ngali karrirrangkal yinyay nyajil.", "ngali karrirrangkal yinyay nyajil | 1du.EXCL.NOM coral there see.NPST", "r248_1du_excl_landform_there_see_karrirrangkal", ["ngali", "karrirrangkal", "yinyay", "nyajil"], EVIDENCE["karrirrangkal"]),
    row(3, "We two, not you, see the flat rock there.", "Ngali jalngarr yinyay nyajil.", "ngali jalngarr yinyay nyajil | 1du.EXCL.NOM flat.rock there see.NPST", "r248_1du_excl_landform_there_see_jalngarr", ["ngali", "jalngarr", "yinyay", "nyajil"], EVIDENCE["jalngarr"]),
    row(4, "We two, not you, see the smooth rock there.", "Ngali jinali yinyay nyajil.", "ngali jinali yinyay nyajil | 1du.EXCL.NOM smooth.rock there see.NPST", "r248_1du_excl_landform_there_see_jinali", ["ngali", "jinali", "yinyay", "nyajil"], EVIDENCE["jinali"]),
    row(5, "We two, not you, see the valley there.", "Ngali kangku yinyay nyajil.", "ngali kangku yinyay nyajil | 1du.EXCL.NOM valley there see.NPST", "r248_1du_excl_landform_there_see_kangku", ["ngali", "kangku", "yinyay", "nyajil"], EVIDENCE["kangku"]),
    row(6, "We two, not you, see the seashore there.", "Ngali yalmba yinyay nyajil.", "ngali yalmba yinyay nyajil | 1du.EXCL.NOM seashore there see.NPST", "r248_1du_excl_landform_there_see_yalmba", ["ngali", "yalmba", "yinyay", "nyajil"], EVIDENCE["yalmba"]),
    row(7, "We two, not you, see the mud there.", "Ngali kambal-kambal yinyay nyajil.", "ngali kambal-kambal yinyay nyajil | 1du.EXCL.NOM mud there see.NPST", "r248_1du_excl_landform_there_see_kambal_kambal", ["ngali", "kambal-kambal", "yinyay", "nyajil"], EVIDENCE["kambal-kambal"]),
    row(8, "We two, not you, see the high bank there.", "Ngali jalkar yinyay nyajil.", "ngali jalkar yinyay nyajil | 1du.EXCL.NOM high.bank there see.NPST", "r248_1du_excl_landform_there_see_jalkar", ["ngali", "jalkar", "yinyay", "nyajil"], EVIDENCE["jalkar"]),
    row(9, "We two, not you, see the hole there.", "Ngali janku yinyay nyajil.", "ngali janku yinyay nyajil | 1du.EXCL.NOM hole there see.NPST", "r248_1du_excl_landform_there_see_janku", ["ngali", "janku", "yinyay", "nyajil"], EVIDENCE["janku"]),
    row(10, "We two, not you, see the hillside there.", "Ngali jamba yinyay nyajil.", "ngali jamba yinyay nyajil | 1du.EXCL.NOM hillside there see.NPST", "r248_1du_excl_landform_there_see_jamba", ["ngali", "jamba", "yinyay", "nyajil"], EVIDENCE["jamba"]),
    row(11, "You and I see the swamp here.", "Ngaliny mabil yaluy nyajil.", "ngaliny mabil yaluy nyajil | 1du.INCL.NOM swamp here see.NPST", "r248_1du_incl_landform_here_see_mabil", ["ngaliny", "mabil", "yaluy", "nyajil"], EVIDENCE["mabil"]),
    row(12, "You and I see the coral here.", "Ngaliny karrirrangkal yaluy nyajil.", "ngaliny karrirrangkal yaluy nyajil | 1du.INCL.NOM coral here see.NPST", "r248_1du_incl_landform_here_see_karrirrangkal", ["ngaliny", "karrirrangkal", "yaluy", "nyajil"], EVIDENCE["karrirrangkal"]),
    row(13, "You and I see the flat rock here.", "Ngaliny jalngarr yaluy nyajil.", "ngaliny jalngarr yaluy nyajil | 1du.INCL.NOM flat.rock here see.NPST", "r248_1du_incl_landform_here_see_jalngarr", ["ngaliny", "jalngarr", "yaluy", "nyajil"], EVIDENCE["jalngarr"]),
    row(14, "You and I see the smooth rock here.", "Ngaliny jinali yaluy nyajil.", "ngaliny jinali yaluy nyajil | 1du.INCL.NOM smooth.rock here see.NPST", "r248_1du_incl_landform_here_see_jinali", ["ngaliny", "jinali", "yaluy", "nyajil"], EVIDENCE["jinali"]),
    row(15, "You and I see the valley here.", "Ngaliny kangku yaluy nyajil.", "ngaliny kangku yaluy nyajil | 1du.INCL.NOM valley here see.NPST", "r248_1du_incl_landform_here_see_kangku", ["ngaliny", "kangku", "yaluy", "nyajil"], EVIDENCE["kangku"]),
    row(16, "You and I see the seashore here.", "Ngaliny yalmba yaluy nyajil.", "ngaliny yalmba yaluy nyajil | 1du.INCL.NOM seashore here see.NPST", "r248_1du_incl_landform_here_see_yalmba", ["ngaliny", "yalmba", "yaluy", "nyajil"], EVIDENCE["yalmba"]),
    row(17, "You and I see the mud here.", "Ngaliny kambal-kambal yaluy nyajil.", "ngaliny kambal-kambal yaluy nyajil | 1du.INCL.NOM mud here see.NPST", "r248_1du_incl_landform_here_see_kambal_kambal", ["ngaliny", "kambal-kambal", "yaluy", "nyajil"], EVIDENCE["kambal-kambal"]),
    row(18, "You and I see the high bank here.", "Ngaliny jalkar yaluy nyajil.", "ngaliny jalkar yaluy nyajil | 1du.INCL.NOM high.bank here see.NPST", "r248_1du_incl_landform_here_see_jalkar", ["ngaliny", "jalkar", "yaluy", "nyajil"], EVIDENCE["jalkar"]),
    row(19, "You and I see the hole here.", "Ngaliny janku yaluy nyajil.", "ngaliny janku yaluy nyajil | 1du.INCL.NOM hole here see.NPST", "r248_1du_incl_landform_here_see_janku", ["ngaliny", "janku", "yaluy", "nyajil"], EVIDENCE["janku"]),
    row(20, "You and I see the hillside here.", "Ngaliny jamba yaluy nyajil.", "ngaliny jamba yaluy nyajil | 1du.INCL.NOM hillside here see.NPST", "r248_1du_incl_landform_here_see_jamba", ["ngaliny", "jamba", "yaluy", "nyajil"], EVIDENCE["jamba"]),
    row(21, "You two see the swamp there.", "Yubal mabil yinyay nyajil.", "yubal mabil yinyay nyajil | 2du.NOM swamp there see.NPST", "r248_2du_landform_there_see_mabil", ["yubal", "mabil", "yinyay", "nyajil"], EVIDENCE["mabil"]),
    row(22, "You two see the coral there.", "Yubal karrirrangkal yinyay nyajil.", "yubal karrirrangkal yinyay nyajil | 2du.NOM coral there see.NPST", "r248_2du_landform_there_see_karrirrangkal", ["yubal", "karrirrangkal", "yinyay", "nyajil"], EVIDENCE["karrirrangkal"]),
    row(23, "You two see the flat rock there.", "Yubal jalngarr yinyay nyajil.", "yubal jalngarr yinyay nyajil | 2du.NOM flat.rock there see.NPST", "r248_2du_landform_there_see_jalngarr", ["yubal", "jalngarr", "yinyay", "nyajil"], EVIDENCE["jalngarr"]),
    row(24, "You two see the smooth rock there.", "Yubal jinali yinyay nyajil.", "yubal jinali yinyay nyajil | 2du.NOM smooth.rock there see.NPST", "r248_2du_landform_there_see_jinali", ["yubal", "jinali", "yinyay", "nyajil"], EVIDENCE["jinali"]),
    row(25, "You two see the valley there.", "Yubal kangku yinyay nyajil.", "yubal kangku yinyay nyajil | 2du.NOM valley there see.NPST", "r248_2du_landform_there_see_kangku", ["yubal", "kangku", "yinyay", "nyajil"], EVIDENCE["kangku"]),
    row(26, "You two see the seashore there.", "Yubal yalmba yinyay nyajil.", "yubal yalmba yinyay nyajil | 2du.NOM seashore there see.NPST", "r248_2du_landform_there_see_yalmba", ["yubal", "yalmba", "yinyay", "nyajil"], EVIDENCE["yalmba"]),
    row(27, "You two see the mud there.", "Yubal kambal-kambal yinyay nyajil.", "yubal kambal-kambal yinyay nyajil | 2du.NOM mud there see.NPST", "r248_2du_landform_there_see_kambal_kambal", ["yubal", "kambal-kambal", "yinyay", "nyajil"], EVIDENCE["kambal-kambal"]),
    row(28, "You two see the high bank there.", "Yubal jalkar yinyay nyajil.", "yubal jalkar yinyay nyajil | 2du.NOM high.bank there see.NPST", "r248_2du_landform_there_see_jalkar", ["yubal", "jalkar", "yinyay", "nyajil"], EVIDENCE["jalkar"]),
    row(29, "You two see the hole there.", "Yubal janku yinyay nyajil.", "yubal janku yinyay nyajil | 2du.NOM hole there see.NPST", "r248_2du_landform_there_see_janku", ["yubal", "janku", "yinyay", "nyajil"], EVIDENCE["janku"]),
    row(30, "You two see the hillside there.", "Yubal jamba yinyay nyajil.", "yubal jamba yinyay nyajil | 2du.NOM hillside there see.NPST", "r248_2du_landform_there_see_jamba", ["yubal", "jamba", "yinyay", "nyajil"], EVIDENCE["jamba"]),
    row(31, "We all, not you, see the swamp here.", "Nganjin mabil yaluy nyajil.", "nganjin mabil yaluy nyajil | 1pl.EXCL.NOM swamp here see.NPST", "r248_1pl_excl_landform_here_see_mabil", ["nganjin", "mabil", "yaluy", "nyajil"], EVIDENCE["mabil"]),
    row(32, "We all, not you, see the coral here.", "Nganjin karrirrangkal yaluy nyajil.", "nganjin karrirrangkal yaluy nyajil | 1pl.EXCL.NOM coral here see.NPST", "r248_1pl_excl_landform_here_see_karrirrangkal", ["nganjin", "karrirrangkal", "yaluy", "nyajil"], EVIDENCE["karrirrangkal"]),
    row(33, "We all, not you, see the flat rock here.", "Nganjin jalngarr yaluy nyajil.", "nganjin jalngarr yaluy nyajil | 1pl.EXCL.NOM flat.rock here see.NPST", "r248_1pl_excl_landform_here_see_jalngarr", ["nganjin", "jalngarr", "yaluy", "nyajil"], EVIDENCE["jalngarr"]),
    row(34, "We all, not you, see the smooth rock here.", "Nganjin jinali yaluy nyajil.", "nganjin jinali yaluy nyajil | 1pl.EXCL.NOM smooth.rock here see.NPST", "r248_1pl_excl_landform_here_see_jinali", ["nganjin", "jinali", "yaluy", "nyajil"], EVIDENCE["jinali"]),
    row(35, "We all, not you, see the valley here.", "Nganjin kangku yaluy nyajil.", "nganjin kangku yaluy nyajil | 1pl.EXCL.NOM valley here see.NPST", "r248_1pl_excl_landform_here_see_kangku", ["nganjin", "kangku", "yaluy", "nyajil"], EVIDENCE["kangku"]),
    row(36, "We all, not you, see the seashore here.", "Nganjin yalmba yaluy nyajil.", "nganjin yalmba yaluy nyajil | 1pl.EXCL.NOM seashore here see.NPST", "r248_1pl_excl_landform_here_see_yalmba", ["nganjin", "yalmba", "yaluy", "nyajil"], EVIDENCE["yalmba"]),
    row(37, "We all, not you, see the mud here.", "Nganjin kambal-kambal yaluy nyajil.", "nganjin kambal-kambal yaluy nyajil | 1pl.EXCL.NOM mud here see.NPST", "r248_1pl_excl_landform_here_see_kambal_kambal", ["nganjin", "kambal-kambal", "yaluy", "nyajil"], EVIDENCE["kambal-kambal"]),
    row(38, "We all, not you, see the high bank here.", "Nganjin jalkar yaluy nyajil.", "nganjin jalkar yaluy nyajil | 1pl.EXCL.NOM high.bank here see.NPST", "r248_1pl_excl_landform_here_see_jalkar", ["nganjin", "jalkar", "yaluy", "nyajil"], EVIDENCE["jalkar"]),
    row(39, "We all, not you, see the hole here.", "Nganjin janku yaluy nyajil.", "nganjin janku yaluy nyajil | 1pl.EXCL.NOM hole here see.NPST", "r248_1pl_excl_landform_here_see_janku", ["nganjin", "janku", "yaluy", "nyajil"], EVIDENCE["janku"]),
    row(40, "We all, not you, see the hillside here.", "Nganjin jamba yaluy nyajil.", "nganjin jamba yaluy nyajil | 1pl.EXCL.NOM hillside here see.NPST", "r248_1pl_excl_landform_here_see_jamba", ["nganjin", "jamba", "yaluy", "nyajil"], EVIDENCE["jamba"]),
    row(41, "You all see the swamp there.", "Yurra mabil yinyay nyajil.", "yurra mabil yinyay nyajil | 2pl.NOM swamp there see.NPST", "r248_2pl_landform_there_see_mabil", ["yurra", "mabil", "yinyay", "nyajil"], EVIDENCE["mabil"]),
    row(42, "You all see the coral there.", "Yurra karrirrangkal yinyay nyajil.", "yurra karrirrangkal yinyay nyajil | 2pl.NOM coral there see.NPST", "r248_2pl_landform_there_see_karrirrangkal", ["yurra", "karrirrangkal", "yinyay", "nyajil"], EVIDENCE["karrirrangkal"]),
    row(43, "You all see the flat rock there.", "Yurra jalngarr yinyay nyajil.", "yurra jalngarr yinyay nyajil | 2pl.NOM flat.rock there see.NPST", "r248_2pl_landform_there_see_jalngarr", ["yurra", "jalngarr", "yinyay", "nyajil"], EVIDENCE["jalngarr"]),
    row(44, "You all see the smooth rock there.", "Yurra jinali yinyay nyajil.", "yurra jinali yinyay nyajil | 2pl.NOM smooth.rock there see.NPST", "r248_2pl_landform_there_see_jinali", ["yurra", "jinali", "yinyay", "nyajil"], EVIDENCE["jinali"]),
    row(45, "You all see the valley there.", "Yurra kangku yinyay nyajil.", "yurra kangku yinyay nyajil | 2pl.NOM valley there see.NPST", "r248_2pl_landform_there_see_kangku", ["yurra", "kangku", "yinyay", "nyajil"], EVIDENCE["kangku"]),
    row(46, "You all see the seashore there.", "Yurra yalmba yinyay nyajil.", "yurra yalmba yinyay nyajil | 2pl.NOM seashore there see.NPST", "r248_2pl_landform_there_see_yalmba", ["yurra", "yalmba", "yinyay", "nyajil"], EVIDENCE["yalmba"]),
    row(47, "You all see the mud there.", "Yurra kambal-kambal yinyay nyajil.", "yurra kambal-kambal yinyay nyajil | 2pl.NOM mud there see.NPST", "r248_2pl_landform_there_see_kambal_kambal", ["yurra", "kambal-kambal", "yinyay", "nyajil"], EVIDENCE["kambal-kambal"]),
    row(48, "You all see the high bank there.", "Yurra jalkar yinyay nyajil.", "yurra jalkar yinyay nyajil | 2pl.NOM high.bank there see.NPST", "r248_2pl_landform_there_see_jalkar", ["yurra", "jalkar", "yinyay", "nyajil"], EVIDENCE["jalkar"]),
    row(49, "You all see the hole there.", "Yurra janku yinyay nyajil.", "yurra janku yinyay nyajil | 2pl.NOM hole there see.NPST", "r248_2pl_landform_there_see_janku", ["yurra", "janku", "yinyay", "nyajil"], EVIDENCE["janku"]),
    row(50, "You all see the hillside there.", "Yurra jamba yinyay nyajil.", "yurra jamba yinyay nyajil | 2pl.NOM hillside there see.NPST", "r248_2pl_landform_there_see_jamba", ["yurra", "jamba", "yinyay", "nyajil"], EVIDENCE["jamba"]),
]


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("r248_"):
            issues.append(f"{label}: Rule 248 frame must start with r248_")
        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 248 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(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 1-10 require Ngali TARGET yinyay nyajil")
        if 11 <= seq <= 20 and (not low.startswith("ngaliny ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 11-20 require Ngaliny TARGET yaluy nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yubal ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 21-30 require Yubal TARGET yinyay nyajil")
        if 31 <= seq <= 40 and (not low.startswith("nganjin ") or not low.endswith(" yaluy nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 31-40 require Nganjin TARGET yaluy nyajil")
        if 41 <= seq <= 50 and (not low.startswith("yurra ") or not low.endswith(" yinyay nyajil.") or len(low.split()) != 4):
            issues.append(f"{label}: rows 41-50 require Yurra TARGET yinyay nyajil")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied landform source-prose/action/use 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 248 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 248 source pass retained the ten Rule 232 landform/geography target nouns; checked dictionary line evidence for mabil, karrirrangkal, jalngarr, jinali, kangku, yalmba, kambal-kambal, jalkar, janku, and jamba; 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 248 landform/geography complementary pronoun expansion after Rule 247: same ten Rule 232 target nouns in five zero-collision opposite-location nominative-pronoun perception bands. No new landform lexemes, place-name, route/travel/crossing, water-use, source-material, ecology, danger, ownership, social/legal, property, body-part, 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 247: mabil 16, karrirrangkal 16, jalngarr 18, jinali 18, kangku 17, yalmba 16, kambal-kambal 21, jalkar 28, janku 50, jamba 37; pronouns ngali 131, ngaliny 65, yubal 79, nganjin 81, yurra 124.",
        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 248 landform/geography complementary pronoun-expansion perception surfaces returned zero accepted, revised, or under-review collisions.",
        command="python3 batch_scripts/bank_290.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 248 target noun per row, exactly one selected bare nominative pronoun per row, no loans, no target suffixes, no rejected landform candidates, no excluded place-name/route/travel/crossing/water-use/source-material/reef/sea/collecting/ecology/danger/ownership/social/property/body-part/source-prose drift, no pronoun case, no perception compound, and five planned frame bands only.",
        command="python3 batch_scripts/bank_290.py",
        artifact_path="batch_scripts/bank_290.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 248 landform/geography complementary pronoun-expansion perception rows as under_review.",
        command="python3 batch_scripts/bank_290.py",
        artifact_path="batch_scripts/bank_290.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: {len(ROWS)} under_review rows")
    return 0


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