#!/usr/bin/env python3
"""Bank batch-184: Rule 142 construction consolidation after Rule 141."""

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-184"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-184-construction-consolidation-after-rule141-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", "marral": "dry"}
SUPPORT_FORMS = {"bayan-ba", "madja-nga", "ngalkal-ba"}
SUPPORT_HEADS = {"bayan": "camp", "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 Moreton Bay ash tree is good.", "Bajar ngulkurr.",
     "bajar ngulkurr | Moreton.Bay.ash.tree good", "r142_state_moreton_bay_ash_good", 2, js(["bajar", "ngulkurr"]), js([]),
     "Rule 142 consolidation: bajar selected as Moreton Bay ash / white gum tree, with ngulkurr good.", "0.86"),
    (2, "The Moreton Bay fig tree is good.", "Baril-baril ngulkurr.",
     "baril-baril ngulkurr | Moreton.Bay.fig.tree good", "r142_state_moreton_bay_fig_good", 2, js(["baril-baril", "ngulkurr"]), js([]),
     "Baril-baril selected as one Moreton Bay fig tree head with ngulkurr good.", "0.86"),
    (3, "The candlenut tree is big.", "Bilar yalbay.",
     "bilar yalbay | candlenut.tree big", "r142_state_candlenut_big", 2, js(["bilar", "yalbay"]), js([]),
     "Bilar tree sense only; no nut-use row; yalbay big.", "0.86"),
    (4, "The stinging tree is good.", "Mili ngulkurr.",
     "mili ngulkurr | stinging.tree good", "r142_state_stinging_tree_good", 2, js(["mili", "ngulkurr"]), js([]),
     "Mili selected as stinging tree only; no jellyfish or burn frame.", "0.84"),
    (5, "The white wattle is big.", "Yalngkal yalbay.",
     "yalngkal yalbay | white.wattle big", "r142_state_white_wattle_big", 2, js(["yalngkal", "yalbay"]), js([]),
     "Yalngkal white wattle with yalbay big.", "0.86"),
    (6, "The bottlebrush tree is big.", "Yumu yalbay.",
     "yumu yalbay | bottlebrush.tree big", "r142_state_bottlebrush_big", 2, js(["yumu", "yalbay"]), js([]),
     "Yumu bottlebrush tree with yalbay big.", "0.86"),
    (7, "The bloodwood tree is dry.", "Babajaka marral.",
     "babajaka marral | bloodwood.tree dry", "r142_state_bloodwood_dry", 2, js(["babajaka", "marral"]), js([]),
     "Babajaka tree head in a dry-state row; no wood/material inference.", "0.84"),
    (8, "The Moreton Bay ash tree is dry.", "Bajar marral.",
     "bajar marral | Moreton.Bay.ash.tree dry", "r142_state_moreton_bay_ash_dry", 2, js(["bajar", "marral"]), js([]),
     "Bajar tree sense only with marral dry; no ash residue.", "0.84"),
    (9, "The Moreton Bay fig tree is dry.", "Baril-baril marral.",
     "baril-baril marral | Moreton.Bay.fig.tree dry", "r142_state_moreton_bay_fig_dry", 2, js(["baril-baril", "marral"]), js([]),
     "Baril-baril tree head with marral dry.", "0.84"),
    (10, "The black bean tree is dry.", "Baway marral.",
     "baway marral | black.bean.tree dry", "r142_state_black_bean_dry", 2, js(["baway", "marral"]), js([]),
     "Baway tree sense only; no edible-fruit scene.", "0.84"),
    (11, "The candlenut tree is dry.", "Bilar marral.",
     "bilar marral | candlenut.tree dry", "r142_state_candlenut_dry", 2, js(["bilar", "marral"]), js([]),
     "Bilar tree sense only; no nut-use row.", "0.84"),
    (12, "The Leichhardt tree is dry.", "Kabal marral.",
     "kabal marral | Leichhardt.tree dry", "r142_state_leichhardt_dry", 2, js(["kabal", "marral"]), js([]),
     "Kabal tree sense only; no fruit or bark-medicine row.", "0.84"),
    (13, "The stinging tree is dry.", "Mili marral.",
     "mili marral | stinging.tree dry", "r142_state_stinging_tree_dry", 2, js(["mili", "marral"]), js([]),
     "Mili stinging tree with marral dry; no burn/jellyfish reading.", "0.82"),
    (14, "The white wattle is dry.", "Yalngkal marral.",
     "yalngkal marral | white.wattle dry", "r142_state_white_wattle_dry", 2, js(["yalngkal", "marral"]), js([]),
     "Yalngkal tree name only with marral dry.", "0.84"),
    (15, "The bottlebrush tree is dry.", "Yumu marral.",
     "yumu marral | bottlebrush.tree dry", "r142_state_bottlebrush_dry", 2, js(["yumu", "marral"]), js([]),
     "Yumu tree name only with marral dry.", "0.84"),

    (16, "The bloodwood tree is standing at camp.", "Babajaka bayan-ba janjanay.",
     "babajaka bayan-ba janja-na-y | bloodwood.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_bloodwood_camp", 3, js(["babajaka", "bayan", "janjana-y"]), js([]),
     "Tree-standing frame with bayan-ba camp support; target remains bare.", "0.84"),
    (17, "The Moreton Bay ash tree is standing at camp.", "Bajar bayan-ba janjanay.",
     "bajar bayan-ba janja-na-y | Moreton.Bay.ash.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_moreton_bay_ash_camp", 3, js(["bajar", "bayan", "janjana-y"]), js([]),
     "Bajar tree sense with bayan-ba support; no ash material row.", "0.84"),
    (18, "The Moreton Bay fig tree is standing at camp.", "Baril-baril bayan-ba janjanay.",
     "baril-baril bayan-ba janja-na-y | Moreton.Bay.fig.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_moreton_bay_fig_camp", 3, js(["baril-baril", "bayan", "janjana-y"]), js([]),
     "Baril-baril remains a whole bare tree target.", "0.84"),
    (19, "The black bean tree is standing at camp.", "Baway bayan-ba janjanay.",
     "baway bayan-ba janja-na-y | black.bean.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_black_bean_camp", 3, js(["baway", "bayan", "janjana-y"]), js([]),
     "Baway tree standing at camp; no fruit scene.", "0.84"),
    (20, "The candlenut tree is standing at camp.", "Bilar bayan-ba janjanay.",
     "bilar bayan-ba janja-na-y | candlenut.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_candlenut_camp", 3, js(["bilar", "bayan", "janjana-y"]), js([]),
     "Bilar tree standing at camp; no nut-use row.", "0.84"),
    (21, "The Leichhardt tree is standing at camp.", "Kabal bayan-ba janjanay.",
     "kabal bayan-ba janja-na-y | Leichhardt.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_leichhardt_camp", 3, js(["kabal", "bayan", "janjana-y"]), js([]),
     "Kabal tree standing at camp; no fruit row.", "0.84"),
    (22, "The stinging tree is standing at camp.", "Mili bayan-ba janjanay.",
     "mili bayan-ba janja-na-y | stinging.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_stinging_tree_camp", 3, js(["mili", "bayan", "janjana-y"]), js([]),
     "Mili stinging tree standing at camp; no instrument form.", "0.82"),
    (23, "The white wattle is standing at camp.", "Yalngkal bayan-ba janjanay.",
     "yalngkal bayan-ba janja-na-y | white.wattle camp-LOC stand-REDUP-NONPAST", "r142_stand_white_wattle_camp", 3, js(["yalngkal", "bayan", "janjana-y"]), js([]),
     "Yalngkal tree target with bayan-ba support.", "0.84"),
    (24, "The bottlebrush tree is standing at camp.", "Yumu bayan-ba janjanay.",
     "yumu bayan-ba janja-na-y | bottlebrush.tree camp-LOC stand-REDUP-NONPAST", "r142_stand_bottlebrush_camp", 3, js(["yumu", "bayan", "janjana-y"]), js([]),
     "Yumu tree target with bayan-ba support.", "0.84"),
    (25, "The Moreton Bay ash tree is standing in the scrub.", "Bajar madja-nga janjanay.",
     "bajar madja-nga janja-na-y | Moreton.Bay.ash.tree scrub-LOC stand-REDUP-NONPAST", "r142_stand_moreton_bay_ash_scrub", 3, js(["bajar", "madja", "janjana-y"]), js([]),
     "Bajar tree sense with madja-nga support; no residue ash.", "0.84"),
    (26, "The bottlebrush tree is standing in the scrub.", "Yumu madja-nga janjanay.",
     "yumu madja-nga janja-na-y | bottlebrush.tree scrub-LOC stand-REDUP-NONPAST", "r142_stand_bottlebrush_scrub", 3, js(["yumu", "madja", "janjana-y"]), js([]),
     "Yumu tree target in scrub support frame.", "0.84"),
    (27, "The bloodwood tree is standing outside.", "Babajaka ngalkal-ba janjanay.",
     "babajaka ngalkal-ba janja-na-y | bloodwood.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_bloodwood_outside", 3, js(["babajaka", "ngalkal", "janjana-y"]), js([]),
     "Babajaka tree target with support-only outside LOC.", "0.84"),
    (28, "The Moreton Bay fig tree is standing outside.", "Baril-baril ngalkal-ba janjanay.",
     "baril-baril ngalkal-ba janja-na-y | Moreton.Bay.fig.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_moreton_bay_fig_outside", 3, js(["baril-baril", "ngalkal", "janjana-y"]), js([]),
     "Baril-baril target remains bare with ngalkal-ba support.", "0.84"),
    (29, "The black bean tree is standing outside.", "Baway ngalkal-ba janjanay.",
     "baway ngalkal-ba janja-na-y | black.bean.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_black_bean_outside", 3, js(["baway", "ngalkal", "janjana-y"]), js([]),
     "Baway tree target outside; no edible-fruit scene.", "0.84"),
    (30, "The candlenut tree is standing outside.", "Bilar ngalkal-ba janjanay.",
     "bilar ngalkal-ba janja-na-y | candlenut.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_candlenut_outside", 3, js(["bilar", "ngalkal", "janjana-y"]), js([]),
     "Bilar tree target outside; no nut-use row.", "0.84"),
    (31, "The Leichhardt tree is standing outside.", "Kabal ngalkal-ba janjanay.",
     "kabal ngalkal-ba janja-na-y | Leichhardt.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_leichhardt_outside", 3, js(["kabal", "ngalkal", "janjana-y"]), js([]),
     "Kabal tree target outside; no fruit row.", "0.84"),
    (32, "The stinging tree is standing outside.", "Mili ngalkal-ba janjanay.",
     "mili ngalkal-ba janja-na-y | stinging.tree outside-LOC stand-REDUP-NONPAST", "r142_stand_stinging_tree_outside", 3, js(["mili", "ngalkal", "janjana-y"]), js([]),
     "Mili stinging tree outside; no burn or instrument reading.", "0.82"),
    (33, "I saw the bottlebrush tree there.", "Ngayu yumu yinyay nyajin.",
     "ngayu yumu yinyay nyaji-ny | I bottlebrush.tree there see-PAST", "r142_perception_i_bottlebrush_there", 4, js(["ngayu", "yumu", "yinyay", "nyajil"]), js([]),
     "Replacement for a duplicate planned white-wattle row; pronoun A bare.", "0.84"),

    (34, "Mother saw the black bean tree.", "Ngamu-ngku baway nyajin.",
     "ngamu-ngku baway nyaji-ny | mother-ERG black.bean.tree see-PAST", "r142_perception_mother_black_bean", 4, js(["ngamu", "baway", "nyajil"]), js([]),
     "Common-noun A ERG; baway tree object bare.", "0.84"),
    (35, "Father saw the candlenut tree.", "Nganjan-angka bilar nyajin.",
     "nganjan-angka bilar nyaji-ny | father-ERG candlenut.tree see-PAST", "r142_perception_father_candlenut", 4, js(["nganjan", "bilar", "nyajil"]), js([]),
     "Common-noun A ERG; bilar tree object, no nut row.", "0.84"),
    (36, "Grandmother saw the Leichhardt tree.", "Babi-ngka kabal nyajin.",
     "babi-ngka kabal nyaji-ny | grandmother-ERG Leichhardt.tree see-PAST", "r142_perception_grandmother_leichhardt", 4, js(["babi", "kabal", "nyajil"]), js([]),
     "Common-noun A ERG; kabal tree object.", "0.84"),
    (37, "The child saw the stinging tree.", "Karrkay-ngku mili nyajin.",
     "karrkay-ngku mili nyaji-ny | child-ERG stinging.tree see-PAST", "r142_perception_child_stinging_tree", 4, js(["karrkay", "mili", "nyajil"]), js([]),
     "Common-noun A ERG; mili stinging tree only.", "0.82"),
    (38, "Grandfather saw the white wattle.", "Kami-ngka yalngkal nyajin.",
     "kami-ngka yalngkal nyaji-ny | grandfather-ERG white.wattle see-PAST", "r142_perception_grandfather_white_wattle", 4, js(["kami", "yalngkal", "nyajil"]), js([]),
     "Common-noun A ERG; yalngkal tree object.", "0.84"),
    (39, "The woman saw the bottlebrush tree.", "Jalbu-ngku yumu nyajin.",
     "jalbu-ngku yumu nyaji-ny | woman-ERG bottlebrush.tree see-PAST", "r142_perception_woman_bottlebrush", 4, js(["jalbu", "yumu", "nyajil"]), js([]),
     "Common-noun A ERG; yumu tree object.", "0.84"),
    (40, "The man saw the bloodwood tree.", "Dingkar-angka babajaka nyajin.",
     "dingkar-angka babajaka nyaji-ny | man-ERG bloodwood.tree see-PAST", "r142_perception_man_bloodwood", 4, js(["dingkar", "babajaka", "nyajil"]), js([]),
     "Common-noun A ERG; babajaka tree object.", "0.84"),
    (41, "I saw the Moreton Bay fig tree.", "Ngayu baril-baril nyajin.",
     "ngayu baril-baril nyaji-ny | I Moreton.Bay.fig.tree see-PAST", "r142_perception_i_moreton_bay_fig", 4, js(["ngayu", "baril-baril", "nyajil"]), js([]),
     "Pronoun A bare; baril-baril whole lexical head.", "0.84"),
    (42, "We saw the black bean tree.", "Ngana baway nyajin.",
     "ngana baway nyaji-ny | we black.bean.tree see-PAST", "r142_perception_we_black_bean", 4, js(["ngana", "baway", "nyajil"]), js([]),
     "Pronoun A bare; baway tree object.", "0.84"),
    (43, "They saw the bottlebrush tree.", "Jana yumu nyajin.",
     "jana yumu nyaji-ny | they bottlebrush.tree see-PAST", "r142_perception_they_bottlebrush", 4, js(["jana", "yumu", "nyajil"]), js([]),
     "Pronoun A bare; yumu tree object.", "0.84"),
    (44, "Mother saw the Moreton Bay ash tree in the scrub.", "Ngamu-ngku bajar madja-nga nyajin.",
     "ngamu-ngku bajar madja-nga nyaji-ny | mother-ERG Moreton.Bay.ash.tree scrub-LOC see-PAST", "r142_perception_mother_moreton_bay_ash_scrub", 4, js(["ngamu", "bajar", "madja", "nyajil"]), js([]),
     "Bajar tree sense only with madja-nga support.", "0.84"),
    (45, "Father saw the bottlebrush tree in the scrub.", "Nganjan-angka yumu madja-nga nyajin.",
     "nganjan-angka yumu madja-nga nyaji-ny | father-ERG bottlebrush.tree scrub-LOC see-PAST", "r142_perception_father_bottlebrush_scrub", 4, js(["nganjan", "yumu", "madja", "nyajil"]), js([]),
     "Common-noun A ERG; yumu object plus scrub LOC.", "0.84"),
    (46, "Grandmother saw the bloodwood tree outside.", "Babi-ngka babajaka ngalkal-ba nyajin.",
     "babi-ngka babajaka ngalkal-ba nyaji-ny | grandmother-ERG bloodwood.tree outside-LOC see-PAST", "r142_perception_grandmother_bloodwood_outside", 4, js(["babi", "babajaka", "ngalkal", "nyajil"]), js([]),
     "Babajaka object plus ngalkal-ba support.", "0.84"),
    (47, "The child saw the Moreton Bay fig tree outside.", "Karrkay-ngku baril-baril ngalkal-ba nyajin.",
     "karrkay-ngku baril-baril ngalkal-ba nyaji-ny | child-ERG Moreton.Bay.fig.tree outside-LOC see-PAST", "r142_perception_child_moreton_bay_fig_outside", 4, js(["karrkay", "baril-baril", "ngalkal", "nyajil"]), js([]),
     "Baril-baril whole lexical head plus outside support.", "0.84"),
    (48, "Grandfather saw the black bean tree outside.", "Kami-ngka baway ngalkal-ba nyajin.",
     "kami-ngka baway ngalkal-ba nyaji-ny | grandfather-ERG black.bean.tree outside-LOC see-PAST", "r142_perception_grandfather_black_bean_outside", 4, js(["kami", "baway", "ngalkal", "nyajil"]), js([]),
     "Baway object plus ngalkal-ba support; no edible-fruit row.", "0.84"),
    (49, "The woman saw the candlenut tree outside.", "Jalbu-ngku bilar ngalkal-ba nyajin.",
     "jalbu-ngku bilar ngalkal-ba nyaji-ny | woman-ERG candlenut.tree outside-LOC see-PAST", "r142_perception_woman_candlenut_outside", 4, js(["jalbu", "bilar", "ngalkal", "nyajil"]), js([]),
     "Bilar tree object plus outside support; no nut-use row.", "0.84"),
    (50, "The man saw the Leichhardt tree outside.", "Dingkar-angka kabal ngalkal-ba nyajin.",
     "dingkar-angka kabal ngalkal-ba nyaji-ny | man-ERG Leichhardt.tree outside-LOC see-PAST", "r142_perception_man_leichhardt_outside", 4, js(["dingkar", "kabal", "ngalkal", "nyajil"]), js([]),
     "Kabal tree object plus outside support; no fruit/bark-use row.", "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("r142_"):
            issues.append(f"{label}: Rule 142 frame must start with r142_")
        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 any(bad in english_low for bad in ("residue", "cooking ash", "hot ash")):
                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 any(bad in english_low for bad in ("jellyfish", "burn")):
                issues.append(f"{label}: mili must be stinging-tree only")
        if "-bu" in low or "INST" in analysis:
            issues.append(f"{label}: no instrument morphology in Rule 142")
        use_scene_english = english_low.replace("candlenut tree", "").replace("candlenut", "").replace("moreton bay ash tree", "")
        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 142")
        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 142 draft: construction consolidation after Rule 141", "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 142 source pass retained only Rule 141 named-tree targets: babajaka, bajar, baril-baril, baway, "
            "bilar, kabal, mili, yalngkal, and yumu. Source readback confirmed Patz tree-standing and tree-perception "
            "models plus support forms bayan-ba, madja-nga, ngalkal-ba, yinyay, and state predicates ngulkurr, yalbay, marral."
        ),
        command="sqlite lexeme readback; rg grammar.md for tree-standing, tree-perception, support forms, and predicates",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="construction-scope",
        actor="claude-main",
        detail=(
            "Rule 142 admits no new target heads. It consolidates named-tree targets in state, standing, and perception "
            "frames only. Bajar remains Moreton Bay ash / white gum tree only; mili remains stinging tree only; no use/material rows."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="duplicate-preflight",
        actor="claude-main",
        detail=(
            "Initial duplicate scan found planned `Yalngkal yinyay janjanay.` duplicated batch-133. It was replaced with "
            "`Ngayu yumu yinyay nyajin.` Final exact duplicate preflight found 50 planned 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_184.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_184.py static scan",
        artifact_path="batch_scripts/bank_184.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 142 named-tree construction-consolidation rows.",
        command="python3 batch_scripts/bank_184.py",
        artifact_path="batch_scripts/bank_184.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())
