#!/usr/bin/env python3
"""Bank batch-183: Rule 141 named-tree lexical expansion after Rule 140."""

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-183"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-183-named-tree-lexical-expansion-after-rule140-process.md"

TARGET_HEADS = {
    "babajaka": "bloodwood.tree",
    "bajar": "Moreton.Bay.ash.tree",
    "baril-baril": "Moreton.Bay.fig.tree",
    "baway": "black.bean.tree",
    "bilar": "candlenut.tree",
    "kabal": "Leichhardt.tree",
    "mili": "stinging.tree",
    "yalngkal": "white.wattle",
    "yumu": "bottlebrush.tree",
}
STATE_HEADS = {"ngulkurr": "good", "yalbay": "big"}
SUPPORT_FORMS = {"madja-nga", "ngalkal-ba"}
SUPPORT_HEADS = {"madja": "scrub", "ngalkal": "outside", "yinyay": "there"}
DENY = {
    "ngalku",
    "jimal",
    "ngunjil",
    "dumbul",
    "buyku",
    "jikan",
    "baya",
    "kubu",
    "nganjirr",
    "badur",
    "karra",
    "makarr",
    "ngunyin",
    "wangal",
    "warrbi",
    "kunjuri",
    "kalka",
    "dakil",
    "waba",
    "jandal",
    "jibarr",
    "malbin",
    "jiba",
    "bangkarr",
    "minya",
}
PRONOUN_A = {"ngayu", "ngana", "nganjin", "yundu", "yurra", "jana", "nyulu"}
COMMON_A = {"ngamu-ngku", "nganjan-angka", "babi-ngka", "karrkay-ngku", "kami-ngka", "jalbu-ngku", "dingkar-angka"}


def js(values: list[str]) -> str:
    return json.dumps(values, ensure_ascii=False)


ROWS = [
    (1, "The bloodwood tree is big.", "Babajaka yalbay.",
     "babajaka yalbay | bloodwood.tree big", "r141_state_bloodwood_big", 2, js(["babajaka", "yalbay"]), js([]),
     "Dictionary: babajaka species of bloodwood tree; state predicate yalbay big already verified.", "0.86"),
    (2, "The Moreton Bay ash tree is big.", "Bajar yalbay.",
     "bajar yalbay | Moreton.Bay.ash.tree big", "r141_state_moreton_bay_ash_big", 2, js(["bajar", "yalbay"]), js([]),
     "Dictionary: bajar is Moreton Bay ash / white gum tree, not residue ash; yalbay big.", "0.86"),
    (3, "The Moreton Bay fig tree is big.", "Baril-baril yalbay.",
     "baril-baril yalbay | Moreton.Bay.fig.tree big", "r141_state_moreton_bay_fig_big", 2, js(["baril-baril", "yalbay"]), js([]),
     "Dictionary: baril-baril Moreton Bay fig tree; reduplicated lexical head retained whole.", "0.86"),
    (4, "The black bean tree is good.", "Baway ngulkurr.",
     "baway ngulkurr | black.bean.tree good", "r141_state_black_bean_good", 2, js(["baway", "ngulkurr"]), js([]),
     "Dictionary: baway black bean tree; ngulkurr good.", "0.86"),
    (5, "The candlenut tree is good.", "Bilar ngulkurr.",
     "bilar ngulkurr | candlenut.tree good", "r141_state_candlenut_good", 2, js(["bilar", "ngulkurr"]), js([]),
     "Dictionary: bilar candle nut / candlenut tree; no nut-use row opened.", "0.86"),
    (6, "The Leichhardt tree is big.", "Kabal yalbay.",
     "kabal yalbay | Leichhardt.tree big", "r141_state_leichhardt_big", 2, js(["kabal", "yalbay"]), js([]),
     "Dictionary and Patz Text 51 support kabal Leichhardt tree/fruit; tree sense selected.", "0.86"),
    (7, "The stinging tree is big.", "Mili yalbay.",
     "mili yalbay | stinging.tree big", "r141_state_stinging_tree_big", 2, js(["mili", "yalbay"]), js([]),
     "Dictionary: mili stinging tree; jellyfish extension and burn frames excluded.", "0.84"),
    (8, "The white wattle is good.", "Yalngkal ngulkurr.",
     "yalngkal ngulkurr | white.wattle good", "r141_state_white_wattle_good", 2, js(["yalngkal", "ngulkurr"]), js([]),
     "Dictionary: yalngkal white wattle; ngulkurr good.", "0.86"),
    (9, "The bottlebrush tree is good.", "Yumu ngulkurr.",
     "yumu ngulkurr | bottlebrush.tree good", "r141_state_bottlebrush_good", 2, js(["yumu", "ngulkurr"]), js([]),
     "Dictionary: yumu bottlebrush tree; ngulkurr good.", "0.86"),
    (10, "The bloodwood tree is good.", "Babajaka ngulkurr.",
     "babajaka ngulkurr | bloodwood.tree good", "r141_state_bloodwood_good", 2, js(["babajaka", "ngulkurr"]), js([]),
     "Babajaka tree head in a second safe state frame.", "0.86"),
    (11, "The Leichhardt tree is good.", "Kabal ngulkurr.",
     "kabal ngulkurr | Leichhardt.tree good", "r141_state_leichhardt_good", 2, js(["kabal", "ngulkurr"]), js([]),
     "Kabal tree sense with ngulkurr good; no fruit or bark-use construction.", "0.86"),
    (12, "The black bean tree is big.", "Baway yalbay.",
     "baway yalbay | black.bean.tree big", "r141_state_black_bean_big", 2, js(["baway", "yalbay"]), js([]),
     "Baway black bean tree with yalbay big.", "0.86"),

    (13, "The bloodwood tree is standing there.", "Babajaka yinyay janjanay.",
     "babajaka yinyay janja-na-y | bloodwood.tree there stand-REDUP-NONPAST", "r141_stand_bloodwood_there", 3, js(["babajaka", "yinyay", "janjana-y"]), js([]),
     "Patz tree-standing frame with yinyay there and janjana-y standing; target remains bare.", "0.84"),
    (14, "The Moreton Bay ash tree is standing there.", "Bajar yinyay janjanay.",
     "bajar yinyay janja-na-y | Moreton.Bay.ash.tree there stand-REDUP-NONPAST", "r141_stand_moreton_bay_ash_there", 3, js(["bajar", "yinyay", "janjana-y"]), js([]),
     "Bajar selected as tree name only in generic standing frame.", "0.84"),
    (15, "The Moreton Bay fig tree is standing there.", "Baril-baril yinyay janjanay.",
     "baril-baril yinyay janja-na-y | Moreton.Bay.fig.tree there stand-REDUP-NONPAST", "r141_stand_moreton_bay_fig_there", 3, js(["baril-baril", "yinyay", "janjana-y"]), js([]),
     "Baril-baril tree head remains bare in standing frame.", "0.84"),
    (16, "The black bean tree is standing there.", "Baway yinyay janjanay.",
     "baway yinyay janja-na-y | black.bean.tree there stand-REDUP-NONPAST", "r141_stand_black_bean_there", 3, js(["baway", "yinyay", "janjana-y"]), js([]),
     "Baway tree head in Patz-modeled standing frame.", "0.84"),
    (17, "The candlenut tree is standing there.", "Bilar yinyay janjanay.",
     "bilar yinyay janja-na-y | candlenut.tree there stand-REDUP-NONPAST", "r141_stand_candlenut_there", 3, js(["bilar", "yinyay", "janjana-y"]), js([]),
     "Bilar selected as tree, not nut-use row.", "0.84"),
    (18, "The Leichhardt tree is standing there.", "Kabal yinyay janjanay.",
     "kabal yinyay janja-na-y | Leichhardt.tree there stand-REDUP-NONPAST", "r141_stand_leichhardt_there", 3, js(["kabal", "yinyay", "janjana-y"]), js([]),
     "Kabal tree in standing frame; no fruit-taking construction.", "0.84"),
    (19, "The stinging tree is standing there.", "Mili yinyay janjanay.",
     "mili yinyay janja-na-y | stinging.tree there stand-REDUP-NONPAST", "r141_stand_stinging_tree_there", 3, js(["mili", "yinyay", "janjana-y"]), js([]),
     "Mili selected as stinging tree; no burn or instrument morphology.", "0.82"),
    (20, "The white wattle is standing in the scrub.", "Yalngkal madja-nga janjanay.",
     "yalngkal madja-nga janja-na-y | white.wattle scrub-LOC stand-REDUP-NONPAST", "r141_stand_white_wattle_scrub", 3, js(["yalngkal", "madja", "janjana-y"]), js([]),
     "Yalngkal tree head plus verified madja-nga scrub LOC.", "0.84"),
    (21, "The bottlebrush tree is standing there.", "Yumu yinyay janjanay.",
     "yumu yinyay janja-na-y | bottlebrush.tree there stand-REDUP-NONPAST", "r141_stand_bottlebrush_there", 3, js(["yumu", "yinyay", "janjana-y"]), js([]),
     "Yumu bottlebrush tree in standing frame.", "0.84"),
    (22, "The bloodwood tree is standing in the scrub.", "Babajaka madja-nga janjanay.",
     "babajaka madja-nga janja-na-y | bloodwood.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_bloodwood_scrub", 3, js(["babajaka", "madja", "janjana-y"]), js([]),
     "Babajaka tree head plus support-only scrub LOC.", "0.84"),
    (23, "The black bean tree is standing in the scrub.", "Baway madja-nga janjanay.",
     "baway madja-nga janja-na-y | black.bean.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_black_bean_scrub", 3, js(["baway", "madja", "janjana-y"]), js([]),
     "Baway tree head plus madja-nga support location.", "0.84"),
    (24, "The candlenut tree is standing in the scrub.", "Bilar madja-nga janjanay.",
     "bilar madja-nga janja-na-y | candlenut.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_candlenut_scrub", 3, js(["bilar", "madja", "janjana-y"]), js([]),
     "Bilar tree head; no nut-use row.", "0.84"),
    (25, "The Leichhardt tree is standing in the scrub.", "Kabal madja-nga janjanay.",
     "kabal madja-nga janja-na-y | Leichhardt.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_leichhardt_scrub", 3, js(["kabal", "madja", "janjana-y"]), js([]),
     "Kabal tree head in scrub-location standing frame.", "0.84"),
    (26, "The stinging tree is standing in the scrub.", "Mili madja-nga janjanay.",
     "mili madja-nga janja-na-y | stinging.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_stinging_tree_scrub", 3, js(["mili", "madja", "janjana-y"]), js([]),
     "Mili stinging tree only; no jellyfish or burn semantics.", "0.82"),
    (27, "The Moreton Bay fig tree is standing in the scrub.", "Baril-baril madja-nga janjanay.",
     "baril-baril madja-nga janja-na-y | Moreton.Bay.fig.tree scrub-LOC stand-REDUP-NONPAST", "r141_stand_moreton_bay_fig_scrub", 3, js(["baril-baril", "madja", "janjana-y"]), js([]),
     "Baril-baril tree head with support-only scrub LOC.", "0.84"),
    (28, "The Moreton Bay ash tree is standing outside.", "Bajar ngalkal-ba janjanay.",
     "bajar ngalkal-ba janja-na-y | Moreton.Bay.ash.tree outside-LOC stand-REDUP-NONPAST", "r141_stand_moreton_bay_ash_outside", 3, js(["bajar", "ngalkal", "janjana-y"]), js([]),
     "Bajar tree name with support-only ngalkal-ba outside LOC.", "0.84"),
    (29, "The white wattle is standing outside.", "Yalngkal ngalkal-ba janjanay.",
     "yalngkal ngalkal-ba janja-na-y | white.wattle outside-LOC stand-REDUP-NONPAST", "r141_stand_white_wattle_outside", 3, js(["yalngkal", "ngalkal", "janjana-y"]), js([]),
     "Yalngkal white wattle with ngalkal-ba support LOC.", "0.84"),
    (30, "The bottlebrush tree is standing outside.", "Yumu ngalkal-ba janjanay.",
     "yumu ngalkal-ba janja-na-y | bottlebrush.tree outside-LOC stand-REDUP-NONPAST", "r141_stand_bottlebrush_outside", 3, js(["yumu", "ngalkal", "janjana-y"]), js([]),
     "Yumu bottlebrush tree with ngalkal-ba support LOC.", "0.84"),

    (31, "Mother saw the bloodwood tree.", "Ngamu-ngku babajaka nyajin.",
     "ngamu-ngku babajaka nyaji-ny | mother-ERG bloodwood.tree see-PAST", "r141_perception_mother_bloodwood", 4, js(["ngamu", "babajaka", "nyajil"]), js([]),
     "Common-noun A ERG; babajaka bare object.", "0.84"),
    (32, "Father saw the Moreton Bay ash tree.", "Nganjan-angka bajar nyajin.",
     "nganjan-angka bajar nyaji-ny | father-ERG Moreton.Bay.ash.tree see-PAST", "r141_perception_father_moreton_bay_ash", 4, js(["nganjan", "bajar", "nyajil"]), js([]),
     "Bajar tree sense only; no residue ash.", "0.84"),
    (33, "Grandmother saw the Moreton Bay fig tree.", "Babi-ngka baril-baril nyajin.",
     "babi-ngka baril-baril nyaji-ny | grandmother-ERG Moreton.Bay.fig.tree see-PAST", "r141_perception_grandmother_moreton_bay_fig", 4, js(["babi", "baril-baril", "nyajil"]), js([]),
     "Common-noun A ERG; baril-baril bare object.", "0.84"),
    (34, "The child saw the black bean tree.", "Karrkay-ngku baway nyajin.",
     "karrkay-ngku baway nyaji-ny | child-ERG black.bean.tree see-PAST", "r141_perception_child_black_bean", 4, js(["karrkay", "baway", "nyajil"]), js([]),
     "Common-noun A ERG; baway bare object.", "0.84"),
    (35, "Grandfather saw the candlenut tree.", "Kami-ngka bilar nyajin.",
     "kami-ngka bilar nyaji-ny | grandfather-ERG candlenut.tree see-PAST", "r141_perception_grandfather_candlenut", 4, js(["kami", "bilar", "nyajil"]), js([]),
     "Bilar tree object; no nut-use row.", "0.84"),
    (36, "The woman saw the Leichhardt tree.", "Jalbu-ngku kabal nyajin.",
     "jalbu-ngku kabal nyaji-ny | woman-ERG Leichhardt.tree see-PAST", "r141_perception_woman_leichhardt", 4, js(["jalbu", "kabal", "nyajil"]), js([]),
     "Kabal tree object; no fruit-harvest row.", "0.84"),
    (37, "The man saw the stinging tree.", "Dingkar-angka mili nyajin.",
     "dingkar-angka mili nyaji-ny | man-ERG stinging.tree see-PAST", "r141_perception_man_stinging_tree", 4, js(["dingkar", "mili", "nyajil"]), js([]),
     "Mili stinging tree object; no jellyfish or burn semantics.", "0.82"),
    (38, "I saw the white wattle.", "Ngayu yalngkal nyajin.",
     "ngayu yalngkal nyaji-ny | I white.wattle see-PAST", "r141_perception_i_white_wattle", 4, js(["ngayu", "yalngkal", "nyajil"]), js([]),
     "Pronoun A bare; yalngkal selected as white wattle.", "0.84"),
    (39, "We saw the bottlebrush tree.", "Ngana yumu nyajin.",
     "ngana yumu nyaji-ny | we bottlebrush.tree see-PAST", "r141_perception_we_bottlebrush", 4, js(["ngana", "yumu", "nyajil"]), js([]),
     "Pronoun A bare; yumu bottlebrush tree.", "0.84"),
    (40, "They saw the bloodwood tree.", "Jana babajaka nyajin.",
     "jana babajaka nyaji-ny | they bloodwood.tree see-PAST", "r141_perception_they_bloodwood", 4, js(["jana", "babajaka", "nyajil"]), js([]),
     "Pronoun A bare; babajaka bare object.", "0.84"),
    (41, "Mother saw the Moreton Bay fig tree in the scrub.", "Ngamu-ngku baril-baril madja-nga nyajin.",
     "ngamu-ngku baril-baril madja-nga nyaji-ny | mother-ERG Moreton.Bay.fig.tree scrub-LOC see-PAST", "r141_perception_mother_moreton_bay_fig_scrub", 4, js(["ngamu", "baril-baril", "madja", "nyajil"]), js([]),
     "Common-noun A ERG; support-only scrub LOC.", "0.84"),
    (42, "Father saw the black bean tree in the scrub.", "Nganjan-angka baway madja-nga nyajin.",
     "nganjan-angka baway madja-nga nyaji-ny | father-ERG black.bean.tree scrub-LOC see-PAST", "r141_perception_father_black_bean_scrub", 4, js(["nganjan", "baway", "madja", "nyajil"]), js([]),
     "Baway object plus madja-nga support location.", "0.84"),
    (43, "Grandmother saw the candlenut tree in the scrub.", "Babi-ngka bilar madja-nga nyajin.",
     "babi-ngka bilar madja-nga nyaji-ny | grandmother-ERG candlenut.tree scrub-LOC see-PAST", "r141_perception_grandmother_candlenut_scrub", 4, js(["babi", "bilar", "madja", "nyajil"]), js([]),
     "Bilar tree object; no edible-nut scene.", "0.84"),
    (44, "The child saw the Leichhardt tree in the scrub.", "Karrkay-ngku kabal madja-nga nyajin.",
     "karrkay-ngku kabal madja-nga nyaji-ny | child-ERG Leichhardt.tree scrub-LOC see-PAST", "r141_perception_child_leichhardt_scrub", 4, js(["karrkay", "kabal", "madja", "nyajil"]), js([]),
     "Kabal tree object; no fruit row.", "0.84"),
    (45, "Grandfather saw the stinging tree in the scrub.", "Kami-ngka mili madja-nga nyajin.",
     "kami-ngka mili madja-nga nyaji-ny | grandfather-ERG stinging.tree scrub-LOC see-PAST", "r141_perception_grandfather_stinging_tree_scrub", 4, js(["kami", "mili", "madja", "nyajil"]), js([]),
     "Mili stinging tree in perception frame only.", "0.82"),
    (46, "The woman saw the white wattle outside.", "Jalbu-ngku yalngkal ngalkal-ba nyajin.",
     "jalbu-ngku yalngkal ngalkal-ba nyaji-ny | woman-ERG white.wattle outside-LOC see-PAST", "r141_perception_woman_white_wattle_outside", 4, js(["jalbu", "yalngkal", "ngalkal", "nyajil"]), js([]),
     "Yalngkal object plus support-only outside LOC.", "0.84"),
    (47, "The man saw the bottlebrush tree outside.", "Dingkar-angka yumu ngalkal-ba nyajin.",
     "dingkar-angka yumu ngalkal-ba nyaji-ny | man-ERG bottlebrush.tree outside-LOC see-PAST", "r141_perception_man_bottlebrush_outside", 4, js(["dingkar", "yumu", "ngalkal", "nyajil"]), js([]),
     "Yumu object plus ngalkal-ba support location.", "0.84"),
    (48, "I saw the Moreton Bay ash tree outside.", "Ngayu bajar ngalkal-ba nyajin.",
     "ngayu bajar ngalkal-ba nyaji-ny | I Moreton.Bay.ash.tree outside-LOC see-PAST", "r141_perception_i_moreton_bay_ash_outside", 4, js(["ngayu", "bajar", "ngalkal", "nyajil"]), js([]),
     "Pronoun A bare; bajar tree sense only.", "0.84"),
    (49, "We saw the bloodwood tree there.", "Ngana babajaka yinyay nyajin.",
     "ngana babajaka yinyay nyaji-ny | we bloodwood.tree there see-PAST", "r141_perception_we_bloodwood_there", 4, js(["ngana", "babajaka", "yinyay", "nyajil"]), js([]),
     "Pronoun A bare; deictic yinyay there.", "0.84"),
    (50, "They saw the Moreton Bay fig tree there.", "Jana baril-baril yinyay nyajin.",
     "jana baril-baril yinyay nyaji-ny | they Moreton.Bay.fig.tree there see-PAST", "r141_perception_they_moreton_bay_fig_there", 4, js(["jana", "baril-baril", "yinyay", "nyajil"]), js([]),
     "Pronoun A bare; baril-baril object plus yinyay there.", "0.84"),
]


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


def first_word(low: str) -> str:
    return low.split()[0].strip(".,!?;:\"")


def has_target_case(low: str, head: str) -> bool:
    if head == "baril-baril":
        return re.search(r"(?<![a-z-])baril-baril-(?:ngka|ngku|angka|nga|ngu|ba|muny|bu|ji|jirr|ku|mundu)\b", low) is not None
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:ngka|ngku|angka|nga|ngu|ba|muny|bu|ji|jirr|ku|mundu)\b", 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 in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        english_low = english.lower()
        analysis_low = analysis.lower()
        words = json.loads(words_used)
        loans = json.loads(loans_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("r141_"):
            issues.append(f"{label}: Rule 141 frame must start with r141_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if loans:
            issues.append(f"{label}: no loan rows in this batch")
        for denied in DENY:
            if has_token(low, denied):
                issues.append(f"{label}: denied or out-of-scope 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 141 target head, found {targets}")
        for head in targets:
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if TARGET_HEADS[head] not in analysis:
                issues.append(f"{label}: target head {head} must be glossed as {TARGET_HEADS[head]}")
            if has_target_case(low, head):
                issues.append(f"{label}: target head {head} has forbidden case/compound morphology")
        for head, gloss in STATE_HEADS.items():
            if has_token(low, head) and (head not in words or gloss not in analysis_low):
                issues.append(f"{label}: state head {head} must be logged and glossed as {gloss}")
        for form in re.findall(r"\b[a-z]+-(?:nga|ba|muny|ngu)\b", low):
            if form not in SUPPORT_FORMS:
                issues.append(f"{label}: unapproved support local form {form}")
        for head in SUPPORT_HEADS:
            if has_token(low, head) and head not in words:
                issues.append(f"{label}: support/deictic head {head} missing from words_used")
        if has_token(low, "bajar"):
            if "moreton bay ash tree" not in english_low or "residue" in english_low or "cooking ash" in english_low:
                issues.append(f"{label}: bajar must be English-labelled as Moreton Bay ash tree only")
            if "Moreton.Bay.ash.tree" not in analysis:
                issues.append(f"{label}: bajar analysis must mark tree sense")
        if has_token(low, "mili"):
            if "stinging tree" not in english_low or "jellyfish" in english_low or "burn" in english_low:
                issues.append(f"{label}: mili must be stinging-tree only")
        if "-bu" in low or "-INST" in analysis or "INST" in analysis:
            issues.append(f"{label}: no instrument morphology in Rule 141")
        use_scene_english = english_low.replace("candlenut tree", "").replace("candlenut", "")
        if any(bad in use_scene_english for bad in ("fruit", "nut", "bark", "medicine", "spear", "canoe", "basket", "edible")):
            issues.append(f"{label}: no use/material/edible rows in Rule 141")
        if "nyajin" in low:
            first = first_word(low)
            if first not in PRONOUN_A and first not in COMMON_A:
                issues.append(f"{label}: common-noun A in perception row should be an approved ERG-marked form")
        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 sentences WHERE batch_id=?", (batch_id,))
        db.execute("UPDATE batches SET notes=?, created_at=? WHERE id=?", ("rebanked under_review", NOW, batch_id))
    else:
        cur = db.execute(
            "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
            (LABEL, "Rule 141 draft: named-tree lexical expansion", "operator-guide-2026-07-07", "under_review", NOW),
        )
        batch_id = cur.lastrowid

    dupes: list[str] = []
    for seq, _english, kuku, *_rest in ROWS:
        hit = db.execute(
            """
            SELECT b.label, s.seq
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(trim(s.kuku))=lower(trim(?))
              AND s.status!='rejected'
              AND s.batch_id!=?
            """,
            (kuku, batch_id),
        ).fetchone()
        if hit:
            dupes.append(f"{LABEL}.{seq:02d} duplicates {hit[0]}.{hit[1]:02d}: {kuku}")
    if dupes:
        db.rollback()
        print("cross-corpus duplicate check failed:")
        for dupe in dupes:
            print(f"  - {dupe}")
        return 1

    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-pass",
        actor="claude-main",
        detail=(
            "Rule 141 source pass opened named trees only: babajaka bloodwood, bajar Moreton Bay ash/white gum tree, "
            "baril-baril Moreton Bay fig, baway black bean tree, bilar candlenut tree, kabal Leichhardt tree, "
            "mili stinging tree, yalngkal white wattle, and yumu bottlebrush tree. Patz tree-standing and tree-object "
            "perception frames supply the grammar model; existing lexeme DB confirms support forms and predicates."
        ),
        command="dictionary.yaml structured readback; grammar.md tree-standing/perception examples; sqlite lexeme readback",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="lexical-scope",
        actor="claude-main",
        detail=(
            "Rule 141 admits tree-name rows only. Bajar is allowed only as Moreton Bay ash / white gum tree, not residue ash. "
            "Mili is allowed only as stinging tree, not jellyfish and not burn/instrument frames. No fruit, nut, medicine, "
            "spear, canoe, basket, bark-use, target-case, instrument, comitative, possessive, or compound rows admitted."
        ),
        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 found 50 planned Kuku surfaces, 50 unique within-batch surfaces, and no exact accepted/revised cross-corpus duplicates.",
        command="sqlite exact lower(trim(kuku)) duplicate scan before banking",
        artifact_path="batch_scripts/bank_183.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="Static guards passed: selected tree sense for every target, no loans, no target case forms, no instruments, no use/material rows, common-noun A ERG in perception rows, and pronoun A bare.",
        command="python3 batch_scripts/bank_183.py static scan",
        artifact_path="batch_scripts/bank_183.py",
        created_at=NOW,
    )

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, author, created_at, updated_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (batch_id, seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence,
             "under_review", confidence, "claude-main", NOW, NOW)
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 under_review Rule 141 named-tree rows.",
        command="python3 batch_scripts/bank_183.py",
        artifact_path="batch_scripts/bank_183.py",
        created_at=NOW,
    )
    db.commit()

    verified = db.execute("SELECT COUNT(*) FROM sentences WHERE status IN ('accepted','revised')").fetchone()[0]
    under_review = db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]
    print(f"banked {LABEL}: rows=50, under_review={under_review}, verified_before_close={verified}")
    return 0


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