#!/usr/bin/env python3
"""Bank batch-230: Rule 188 clothing/adornment construction consolidation after Rule 187."""

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-230"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-230-clothing-adornment-reference-construction-consolidation-after-rule187-process.md"

TARGET_HEADS = {
    "dawarr": "straw hat",
    "kayamuku": "felt hat",
    "malnkurr": "headband",
    "mirrimbal": "cockatoo crest",
    "wayarri": "hair belt",
    "dambal": "shoes",
    "jilngarr": "seashell headband",
    "julkun": "bark belt",
    "bilangkurr": "blanket",
    "wandu": "hat",
}
PRONOUN_A = {"ngayu", "yundu", "jana", "ngana"}
DENY_KUKU = {
    "jurruja", "yangkija", "baki", "biyin", "gilaja", "binjin", "julnji", "nyumbirr",
    "dawarr-bungal", "wandu-bungal", "kayamuku-bungal", "mirrimbal-bungal", "julkun-bungal",
    "binda-dijarr", "duli", "jamar", "kabay", "karrangkal", "kuri", "ngamar", "nganyay", "ngidin",
    "warrmba", "warrmba-bungal", "ngunnga", "nyamun", "nurru", "kurrkal",
}
DENY_ENGLISH = (
    "loan", "english", "trousers", "handkerchief", "wear", "wears", "wore", "worn", "wearing",
    "make", "makes", "made", "making", "sew", "sews", "sewing", "tie", "ties", "tying", "cover",
    "covers", "covering", "sleep", "sleeps", "sleeping", "warm", "warming", "own", "owns", "ownership",
    "possess", "possession", "dog", "back of a dog", "body", "body-part", "bird", "feather", "feathers",
    "beeswax", "wax", "ceremony", "ceremonial", "corroboree", "mourning", "death", "dead", "tree",
    "source", "hair source", "shellfish", "beach", "collect", "collecting", "material", "walk", "walking",
    "foot", "feet", "plural", "many", "productive reduplication", "reduplicate", "event",
)


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, 4, js(words), js([]), evidence, "0.84")


ROWS = [
    row(1, "The straw hat is there.", "Dawarr yinyay.", "dawarr yinyay | straw.hat there", "r188_deictic_dawarr_there", ["dawarr", "yinyay"], "Rule 188 retains dawarr as straw hat and rotates to yinyay deictic support."),
    row(2, "The felt hat is there.", "Kayamuku yinyay.", "kayamuku yinyay | felt.hat there", "r188_deictic_kayamuku_there", ["kayamuku", "yinyay"], "Rule 188 retains kayamuku as felt hat; dog/back literal source closed."),
    row(3, "The headband is there.", "Malnkurr yinyay.", "malnkurr yinyay | headband there", "r188_deictic_malnkurr_there", ["malnkurr", "yinyay"], "Rule 188 retains malnkurr as headband; no wearing/body-part/making row."),
    row(4, "The cockatoo crest is there.", "Mirrimbal yinyay.", "mirrimbal yinyay | cockatoo.crest there", "r188_deictic_mirrimbal_there", ["mirrimbal", "yinyay"], "Rule 188 retains mirrimbal as cockatoo crest; feather/wax/ceremony rows closed."),
    row(5, "The hair belt is there.", "Wayarri yinyay.", "wayarri yinyay | hair.belt there", "r188_deictic_wayarri_there", ["wayarri", "yinyay"], "Rule 188 retains wayarri as hair belt; no body-part hair or wearing row."),
    row(6, "The shoes are there.", "Dambal yinyay.", "dambal yinyay | shoes there", "r188_deictic_dambal_there", ["dambal", "yinyay"], "Rule 188 retains dambal as shoes; no walking or foot/body-part row."),
    row(7, "The seashell headband is there.", "Jilngarr yinyay.", "jilngarr yinyay | seashell.headband there", "r188_deictic_jilngarr_there", ["jilngarr", "yinyay"], "Rule 188 retains jilngarr as seashell headband; shellfish/beach/collecting rows closed."),
    row(8, "The bark belt is there.", "Julkun yinyay.", "julkun yinyay | bark.belt there", "r188_deictic_julkun_there", ["julkun", "yinyay"], "Rule 188 retains julkun as bark belt; mourning/death/tree/material-source rows closed."),
    row(9, "The blanket is there.", "Bilangkurr yinyay.", "bilangkurr yinyay | blanket there", "r188_deictic_bilangkurr_there", ["bilangkurr", "yinyay"], "Rule 188 retains bilangkurr as blanket; no sleeping, covering, warming, or ownership row."),
    row(10, "The hat is there.", "Wandu yinyay.", "wandu yinyay | hat there", "r188_deictic_wandu_there", ["wandu", "yinyay"], "Rule 188 retains wandu as hat; no wearing/making/body-part row."),
    row(11, "I see the straw hat here.", "Ngayu dawarr yaluy nyajil.", "ngayu dawarr yaluy nyaji-l | I straw.hat here see-NONPAST", "r188_present_i_dawarr_here", ["ngayu", "dawarr", "yaluy", "nyaji-l"], "First-person present perception with yaluy support; target clothing head bare."),
    row(12, "I see the felt hat here.", "Ngayu kayamuku yaluy nyajil.", "ngayu kayamuku yaluy nyaji-l | I felt.hat here see-NONPAST", "r188_present_i_kayamuku_here", ["ngayu", "kayamuku", "yaluy", "nyaji-l"], "First-person present perception; dog/back literal source closed."),
    row(13, "I see the headband here.", "Ngayu malnkurr yaluy nyajil.", "ngayu malnkurr yaluy nyaji-l | I headband here see-NONPAST", "r188_present_i_malnkurr_here", ["ngayu", "malnkurr", "yaluy", "nyaji-l"], "First-person present perception; no wearing/body-part row."),
    row(14, "I see the cockatoo crest here.", "Ngayu mirrimbal yaluy nyajil.", "ngayu mirrimbal yaluy nyaji-l | I cockatoo.crest here see-NONPAST", "r188_present_i_mirrimbal_here", ["ngayu", "mirrimbal", "yaluy", "nyaji-l"], "First-person present perception; feather/wax/ceremony rows closed."),
    row(15, "I see the hair belt here.", "Ngayu wayarri yaluy nyajil.", "ngayu wayarri yaluy nyaji-l | I hair.belt here see-NONPAST", "r188_present_i_wayarri_here", ["ngayu", "wayarri", "yaluy", "nyaji-l"], "First-person present perception; no body-part hair row."),
    row(16, "I see the shoes here.", "Ngayu dambal yaluy nyajil.", "ngayu dambal yaluy nyaji-l | I shoes here see-NONPAST", "r188_present_i_dambal_here", ["ngayu", "dambal", "yaluy", "nyaji-l"], "First-person present perception; no walking/foot row."),
    row(17, "I see the seashell headband here.", "Ngayu jilngarr yaluy nyajil.", "ngayu jilngarr yaluy nyaji-l | I seashell.headband here see-NONPAST", "r188_present_i_jilngarr_here", ["ngayu", "jilngarr", "yaluy", "nyaji-l"], "First-person present perception; no shellfish/beach/collecting row."),
    row(18, "I see the bark belt here.", "Ngayu julkun yaluy nyajil.", "ngayu julkun yaluy nyaji-l | I bark.belt here see-NONPAST", "r188_present_i_julkun_here", ["ngayu", "julkun", "yaluy", "nyaji-l"], "First-person present perception; no mourning/death/tree/source row."),
    row(19, "I see the blanket here.", "Ngayu bilangkurr yaluy nyajil.", "ngayu bilangkurr yaluy nyaji-l | I blanket here see-NONPAST", "r188_present_i_bilangkurr_here", ["ngayu", "bilangkurr", "yaluy", "nyaji-l"], "First-person present perception; no sleeping/covering row."),
    row(20, "I see the hat here.", "Ngayu wandu yaluy nyajil.", "ngayu wandu yaluy nyaji-l | I hat here see-NONPAST", "r188_present_i_wandu_here", ["ngayu", "wandu", "yaluy", "nyaji-l"], "First-person present perception; no wearing/making row."),
    row(21, "You see the straw hat here.", "Yundu dawarr yaluy nyajil.", "yundu dawarr yaluy nyaji-l | you straw.hat here see-NONPAST", "r188_present_you_dawarr_here", ["yundu", "dawarr", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(22, "You see the felt hat here.", "Yundu kayamuku yaluy nyajil.", "yundu kayamuku yaluy nyaji-l | you felt.hat here see-NONPAST", "r188_present_you_kayamuku_here", ["yundu", "kayamuku", "yaluy", "nyaji-l"], "Second-person present perception; dog/back literal source closed."),
    row(23, "You see the headband here.", "Yundu malnkurr yaluy nyajil.", "yundu malnkurr yaluy nyaji-l | you headband here see-NONPAST", "r188_present_you_malnkurr_here", ["yundu", "malnkurr", "yaluy", "nyaji-l"], "Second-person present perception; no wearing/body-part row."),
    row(24, "You see the cockatoo crest here.", "Yundu mirrimbal yaluy nyajil.", "yundu mirrimbal yaluy nyaji-l | you cockatoo.crest here see-NONPAST", "r188_present_you_mirrimbal_here", ["yundu", "mirrimbal", "yaluy", "nyaji-l"], "Second-person present perception; feather/wax/ceremony rows closed."),
    row(25, "You see the hair belt here.", "Yundu wayarri yaluy nyajil.", "yundu wayarri yaluy nyaji-l | you hair.belt here see-NONPAST", "r188_present_you_wayarri_here", ["yundu", "wayarri", "yaluy", "nyaji-l"], "Second-person present perception; no body-part hair row."),
    row(26, "You see the shoes here.", "Yundu dambal yaluy nyajil.", "yundu dambal yaluy nyaji-l | you shoes here see-NONPAST", "r188_present_you_dambal_here", ["yundu", "dambal", "yaluy", "nyaji-l"], "Second-person present perception; no walking/foot row."),
    row(27, "You see the seashell headband here.", "Yundu jilngarr yaluy nyajil.", "yundu jilngarr yaluy nyaji-l | you seashell.headband here see-NONPAST", "r188_present_you_jilngarr_here", ["yundu", "jilngarr", "yaluy", "nyaji-l"], "Second-person present perception; no shellfish/beach/collecting row."),
    row(28, "You see the bark belt here.", "Yundu julkun yaluy nyajil.", "yundu julkun yaluy nyaji-l | you bark.belt here see-NONPAST", "r188_present_you_julkun_here", ["yundu", "julkun", "yaluy", "nyaji-l"], "Second-person present perception; no mourning/death/tree/source row."),
    row(29, "You see the blanket here.", "Yundu bilangkurr yaluy nyajil.", "yundu bilangkurr yaluy nyaji-l | you blanket here see-NONPAST", "r188_present_you_bilangkurr_here", ["yundu", "bilangkurr", "yaluy", "nyaji-l"], "Second-person present perception; no sleeping/covering row."),
    row(30, "You see the hat here.", "Yundu wandu yaluy nyajil.", "yundu wandu yaluy nyaji-l | you hat here see-NONPAST", "r188_present_you_wandu_here", ["yundu", "wandu", "yaluy", "nyaji-l"], "Second-person present perception; no wearing/making row."),
    row(31, "They saw the straw hat here.", "Jana dawarr yaluy nyajin.", "jana dawarr yaluy nyaji-ny | they straw.hat here see-PAST", "r188_past_they_dawarr_here", ["jana", "dawarr", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(32, "They saw the felt hat here.", "Jana kayamuku yaluy nyajin.", "jana kayamuku yaluy nyaji-ny | they felt.hat here see-PAST", "r188_past_they_kayamuku_here", ["jana", "kayamuku", "yaluy", "nyaji-l"], "Third-person plural past perception; dog/back literal source closed."),
    row(33, "They saw the headband here.", "Jana malnkurr yaluy nyajin.", "jana malnkurr yaluy nyaji-ny | they headband here see-PAST", "r188_past_they_malnkurr_here", ["jana", "malnkurr", "yaluy", "nyaji-l"], "Third-person plural past perception; no wearing/body-part row."),
    row(34, "They saw the cockatoo crest here.", "Jana mirrimbal yaluy nyajin.", "jana mirrimbal yaluy nyaji-ny | they cockatoo.crest here see-PAST", "r188_past_they_mirrimbal_here", ["jana", "mirrimbal", "yaluy", "nyaji-l"], "Third-person plural past perception; feather/wax/ceremony rows closed."),
    row(35, "They saw the hair belt here.", "Jana wayarri yaluy nyajin.", "jana wayarri yaluy nyaji-ny | they hair.belt here see-PAST", "r188_past_they_wayarri_here", ["jana", "wayarri", "yaluy", "nyaji-l"], "Third-person plural past perception; no body-part hair row."),
    row(36, "They saw the shoes here.", "Jana dambal yaluy nyajin.", "jana dambal yaluy nyaji-ny | they shoes here see-PAST", "r188_past_they_dambal_here", ["jana", "dambal", "yaluy", "nyaji-l"], "Third-person plural past perception; no walking/foot row."),
    row(37, "They saw the seashell headband here.", "Jana jilngarr yaluy nyajin.", "jana jilngarr yaluy nyaji-ny | they seashell.headband here see-PAST", "r188_past_they_jilngarr_here", ["jana", "jilngarr", "yaluy", "nyaji-l"], "Third-person plural past perception; no shellfish/beach/collecting row."),
    row(38, "They saw the bark belt here.", "Jana julkun yaluy nyajin.", "jana julkun yaluy nyaji-ny | they bark.belt here see-PAST", "r188_past_they_julkun_here", ["jana", "julkun", "yaluy", "nyaji-l"], "Third-person plural past perception; no mourning/death/tree/source row."),
    row(39, "They saw the blanket here.", "Jana bilangkurr yaluy nyajin.", "jana bilangkurr yaluy nyaji-ny | they blanket here see-PAST", "r188_past_they_bilangkurr_here", ["jana", "bilangkurr", "yaluy", "nyaji-l"], "Third-person plural past perception; no sleeping/covering row."),
    row(40, "They saw the hat here.", "Jana wandu yaluy nyajin.", "jana wandu yaluy nyaji-ny | they hat here see-PAST", "r188_past_they_wandu_here", ["jana", "wandu", "yaluy", "nyaji-l"], "Third-person plural past perception; no wearing/making row."),
    row(41, "We see the straw hat there.", "Ngana dawarr yinyay nyajil.", "ngana dawarr yinyay nyaji-l | we straw.hat there see-NONPAST", "r188_present_we_dawarr_there", ["ngana", "dawarr", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(42, "We see the felt hat there.", "Ngana kayamuku yinyay nyajil.", "ngana kayamuku yinyay nyaji-l | we felt.hat there see-NONPAST", "r188_present_we_kayamuku_there", ["ngana", "kayamuku", "yinyay", "nyaji-l"], "Inclusive present perception; dog/back literal source closed."),
    row(43, "We see the headband there.", "Ngana malnkurr yinyay nyajil.", "ngana malnkurr yinyay nyaji-l | we headband there see-NONPAST", "r188_present_we_malnkurr_there", ["ngana", "malnkurr", "yinyay", "nyaji-l"], "Inclusive present perception; no wearing/body-part row."),
    row(44, "We see the cockatoo crest there.", "Ngana mirrimbal yinyay nyajil.", "ngana mirrimbal yinyay nyaji-l | we cockatoo.crest there see-NONPAST", "r188_present_we_mirrimbal_there", ["ngana", "mirrimbal", "yinyay", "nyaji-l"], "Inclusive present perception; feather/wax/ceremony rows closed."),
    row(45, "We see the hair belt there.", "Ngana wayarri yinyay nyajil.", "ngana wayarri yinyay nyaji-l | we hair.belt there see-NONPAST", "r188_present_we_wayarri_there", ["ngana", "wayarri", "yinyay", "nyaji-l"], "Inclusive present perception; no body-part hair row."),
    row(46, "We see the shoes there.", "Ngana dambal yinyay nyajil.", "ngana dambal yinyay nyaji-l | we shoes there see-NONPAST", "r188_present_we_dambal_there", ["ngana", "dambal", "yinyay", "nyaji-l"], "Inclusive present perception; no walking/foot row."),
    row(47, "We see the seashell headband there.", "Ngana jilngarr yinyay nyajil.", "ngana jilngarr yinyay nyaji-l | we seashell.headband there see-NONPAST", "r188_present_we_jilngarr_there", ["ngana", "jilngarr", "yinyay", "nyaji-l"], "Inclusive present perception; no shellfish/beach/collecting row."),
    row(48, "We see the bark belt there.", "Ngana julkun yinyay nyajil.", "ngana julkun yinyay nyaji-l | we bark.belt there see-NONPAST", "r188_present_we_julkun_there", ["ngana", "julkun", "yinyay", "nyaji-l"], "Inclusive present perception; no mourning/death/tree/source row."),
    row(49, "We see the blanket there.", "Ngana bilangkurr yinyay nyajil.", "ngana bilangkurr yinyay nyaji-l | we blanket there see-NONPAST", "r188_present_we_bilangkurr_there", ["ngana", "bilangkurr", "yinyay", "nyaji-l"], "Inclusive present perception; no sleeping/covering row."),
    row(50, "We see the hat there.", "Ngana wandu yinyay nyajil.", "ngana wandu yinyay nyaji-l | we hat there see-NONPAST", "r188_present_we_wandu_there", ["ngana", "wandu", "yinyay", "nyaji-l"], "Inclusive present perception; no wearing/making row."),
]


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:
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?: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)\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)}\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("r188_"):
            issues.append(f"{label}: Rule 188 frame must start with r188_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: no loans are admitted in Rule 188")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied clothing/loan/old-field 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 retained Rule 187 target head, found {targets}")
        for head in targets:
            selected = TARGET_HEADS[head]
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if selected.replace(" ", ".") not in analysis:
                issues.append(f"{label}: target head {head} must be glossed as {selected}")
            if selected not in english_low:
                issues.append(f"{label}: {head} must be translated with selected sense {selected!r}")
            if has_target_case(low, head):
                issues.append(f"{label}: target head {head} has forbidden case/compound morphology")
        if seq <= 10:
            if "nyajil" in low or "nyajin" in low or "nyaji-l" in words:
                issues.append(f"{label}: deictic rows must not use nyaji-l")
            if not has_token(low, "yinyay") or "yinyay" not in words or "there" not in english_low:
                issues.append(f"{label}: Rule 188 deictic rows require yinyay/there")
        else:
            if ("nyajin" not in low and "nyajil" not in low) or "nyaji-l" not in words:
                issues.append(f"{label}: Rule 188 perception rows must use nyaji-l")
            first = first_word(low)
            if first not in PRONOUN_A:
                issues.append(f"{label}: perception A should be an approved bare pronoun")
            if 11 <= seq <= 20 and (first != "ngayu" or not has_token(low, "yaluy") or "here" not in english_low):
                issues.append(f"{label}: rows 11-20 require ngayu + yaluy/here")
            if 21 <= seq <= 30 and (first != "yundu" or not has_token(low, "yaluy") or "here" not in english_low):
                issues.append(f"{label}: rows 21-30 require yundu + yaluy/here")
            if 31 <= seq <= 40 and (first != "jana" or not has_token(low, "yaluy") or "saw" not in english_low):
                issues.append(f"{label}: rows 31-40 require jana + yaluy + past saw")
            if 41 <= seq <= 50 and (first != "ngana" or not has_token(low, "yinyay") or "there" not in english_low):
                issues.append(f"{label}: rows 41-50 require ngana + yinyay/there")
            if "nyajin" in low and "see-PAST" not in analysis:
                issues.append(f"{label}: nyajin must be glossed as see-PAST")
            if "nyajil" in low and "see-NONPAST" not in analysis:
                issues.append(f"{label}: nyajil must be glossed as see-NONPAST")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied clothing use/material/event/polysemy 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 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 188 draft: construction consolidation after Rule 187", "operator-guide-2026-07-08", "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 188 source pass retained exactly the ten Rule 187 clothing/adornment reference heads: dawarr, kayamuku, malnkurr, mirrimbal, wayarri, dambal, jilngarr, julkun, bilangkurr, and wandu. Fresh dictionary readback reconfirmed selected senses and traps: kayamuku literal dog-back source closed; mirrimbal feather/wax/ceremony prose closed; jilngarr seashell material/collecting closed; julkun bark/hair/mourning prose closed; dambal walking/foot body-part import closed. Rejected loan candidates jurruja and yangkija stay closed. Patz readback reconfirmed yaluy/yinyay, nyaji-l/nyaji-ny, absolutive bare objects, and bare pronoun A forms.",
                 command="dictionary YAML readback; Patz grammar readback; sqlite corpus readback",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="scope", event_type="construction-scope", actor="claude-main",
                 detail="Rule 188 is a clothing/adornment construction consolidation pass for the Rule 187 field. It admits no new target heads and excludes loans, rejected clothing candidates, wearing, making, sewing, tying, covering, sleeping, warming, ownership, possession, dog/back compound, body-part, bird, feather, wax, ceremony, corroboree, mourning, death, tree/bark-source, hair-source, shellfish, beach, collecting, material-source, walking, foot/body-part, target case, target compounds, plural derivation, productive reduplication, and event claims.",
                 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/under_review cross-corpus duplicates.",
                 command="sqlite exact lower(trim(kuku)) duplicate scan before banking", artifact_path="batch_scripts/bank_230.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guards passed: all 50 rows have exactly one retained Rule 187 target, no loans, no denied Kuku tokens, no denied English import fields, no target case/compound morphology, required deictic/perception support, bare pronoun A where applicable, and correct nyajil/nyajin glosses.",
                 command="python3 batch_scripts/bank_230.py internal scan_static()", artifact_path="batch_scripts/bank_230.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, *row_data[:9], "under_review", row_data[9], "claude-main", NOW, NOW) for row_data in ROWS],
    )
    db.commit()

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


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