#!/usr/bin/env python3
"""Bank batch-234: Rule 192 perception-reference construction consolidation."""

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

TARGET_HEADS = {
    "jangkuy": "chewing sound",
    "ngurma-mulkaynji": "image",
    "kankar": "clap sound",
    "nginja": "grunt sound",
    "numburr": "rattling sound",
    "yanja": "rustling noise",
    "kadil": "sharp sound",
    "kuwimbur": "whistle sound",
    "ngangkurr": "barking sound",
    "ngurma": "reflection",
}
PRONOUN_A = {"ngayu", "yundu", "jana", "ngana"}
DENY_KUKU = {
    "jadal-jadal", "jila-burra", "kubarru", "ngingkirr", "ngaru",
    "milkabu", "milka-bu", "nyumal", "nyuman", "miyil-janay", "warrmba-bungal",
    "yanday-nyajil", "ngangkurri-l", "kuwimbur yirrkay", "yirrkay", "badi",
    "kadan", "kaday", "kunjan", "ngara", "ngaral", "yakal", "dama", "daman",
    "baya", "kubu", "muyang", "muwar", "muryi", "kaya", "bulki", "juku",
    "jikan", "wungar", "miyil", "milka", "mara", "ngandal",
}
DENY_ENGLISH = (
    "loan", "english loan", "place name", "homograph", "eat", "eats", "ate", "eating",
    "food", "mouth", "hand", "body", "smell", "taste", "tasted", "tasting", "sick", "sickness",
    "pig", "dog", "motor", "machine", "vehicle", "wind", "tree", "grass", "axe", "cut",
    "cutting", "wood", "fall", "falling", "danger", "whip", "strike", "striking",
    "song", "sing", "singing", "shout", "shouting", "bird", "bone", "make", "making",
    "made", "instrument", "mirror", "water", "picture", "statue", "movie", "cards",
    "shadow spirit", "spirit", "overhear", "listen", "ngara", "plural", "case",
    "productive reduplication", "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.85")


ROWS = [
    row(1, "The chewing sound is there.", "Jangkuy yinyay.", "jangkuy yinyay | chewing.sound there", "r192_deictic_jangkuy_there", ["jangkuy", "yinyay"], "Rule 191 source pass: jangkuy 'sound of chewing'; Rule 192 rotates deictic support to yinyay."),
    row(2, "The image is there.", "Ngurma-mulkaynji yinyay.", "ngurma-mulkaynji yinyay | image there", "r192_deictic_ngurma_mulkaynji_there", ["ngurma-mulkaynji", "yinyay"], "Rule 191 source pass: ngurma-mulkaynji 'image'; visual noun only."),
    row(3, "The clap sound is there.", "Kankar yinyay.", "kankar yinyay | clap.sound there", "r192_deictic_kankar_there", ["kankar", "yinyay"], "Rule 191 source pass: kankar 'sound of a clap'; no clapping action."),
    row(4, "The grunt sound is there.", "Nginja yinyay.", "nginja yinyay | grunt.sound there", "r192_deictic_nginja_there", ["nginja", "yinyay"], "Rule 191 source pass: nginja 'grunt'; sound noun only."),
    row(5, "The rattling sound is there.", "Numburr yinyay.", "numburr yinyay | rattling.sound there", "r192_deictic_numburr_there", ["numburr", "yinyay"], "Rule 191 source pass: numburr selected as rattling sound only."),
    row(6, "The rustling noise is there.", "Yanja yinyay.", "yanja yinyay | rustling.noise there", "r192_deictic_yanja_there", ["yanja", "yinyay"], "Rule 191 source pass: yanja 'rustling noise'; no source-motion row."),
    row(7, "The sharp sound is there.", "Kadil yinyay.", "kadil yinyay | sharp.sound there", "r192_deictic_kadil_there", ["kadil", "yinyay"], "Rule 191 source pass: kadil selected as sharp sound only."),
    row(8, "The whistle sound is there.", "Kuwimbur yinyay.", "kuwimbur yinyay | whistle.sound there", "r192_deictic_kuwimbur_there", ["kuwimbur", "yinyay"], "Rule 191 source pass: kuwimbur 'whistle'; sound noun only."),
    row(9, "The barking sound is there.", "Ngangkurr yinyay.", "ngangkurr yinyay | barking.sound there", "r192_deictic_ngangkurr_there", ["ngangkurr", "yinyay"], "Rule 191 source pass: ngangkurr selected as barking sound only."),
    row(10, "The reflection is there.", "Ngurma yinyay.", "ngurma yinyay | reflection there", "r192_deictic_ngurma_there", ["ngurma", "yinyay"], "Rule 191 source pass: ngurma selected as reflection only."),
    row(11, "I hear the chewing sound here.", "Ngayu jangkuy yaluy nyajil.", "ngayu jangkuy yaluy nyaji-l | I chewing.sound here perceive-NONPAST", "r192_present_i_jangkuy_here", ["ngayu", "jangkuy", "yaluy", "nyaji-l"], "Rule 192 consolidates first-person present perception with yaluy support."),
    row(12, "I see the image here.", "Ngayu ngurma-mulkaynji yaluy nyajil.", "ngayu ngurma-mulkaynji yaluy nyaji-l | I image here perceive-NONPAST", "r192_present_i_ngurma_mulkaynji_here", ["ngayu", "ngurma-mulkaynji", "yaluy", "nyaji-l"], "Visual target with nyaji-l as see/perceive; image sense only."),
    row(13, "I hear the clap sound here.", "Ngayu kankar yaluy nyajil.", "ngayu kankar yaluy nyaji-l | I clap.sound here perceive-NONPAST", "r192_present_i_kankar_here", ["ngayu", "kankar", "yaluy", "nyaji-l"], "Auditory target with nyaji-l as hear/perceive; no clapping event."),
    row(14, "I hear the grunt sound here.", "Ngayu nginja yaluy nyajil.", "ngayu nginja yaluy nyaji-l | I grunt.sound here perceive-NONPAST", "r192_present_i_nginja_here", ["ngayu", "nginja", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no animal/source row."),
    row(15, "I hear the rattling sound here.", "Ngayu numburr yaluy nyajil.", "ngayu numburr yaluy nyaji-l | I rattling.sound here perceive-NONPAST", "r192_present_i_numburr_here", ["ngayu", "numburr", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no motor/machine/vehicle row."),
    row(16, "I hear the rustling noise here.", "Ngayu yanja yaluy nyajil.", "ngayu yanja yaluy nyaji-l | I rustling.noise here perceive-NONPAST", "r192_present_i_yanja_here", ["ngayu", "yanja", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no wind/tree/grass row."),
    row(17, "I hear the sharp sound here.", "Ngayu kadil yaluy nyajil.", "ngayu kadil yaluy nyaji-l | I sharp.sound here perceive-NONPAST", "r192_present_i_kadil_here", ["ngayu", "kadil", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no axe/cutting/tree-fall row."),
    row(18, "I hear the whistle sound here.", "Ngayu kuwimbur yaluy nyajil.", "ngayu kuwimbur yaluy nyaji-l | I whistle.sound here perceive-NONPAST", "r192_present_i_kuwimbur_here", ["ngayu", "kuwimbur", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no whistling/song/instrument row."),
    row(19, "I hear the barking sound here.", "Ngayu ngangkurr yaluy nyajil.", "ngayu ngangkurr yaluy nyaji-l | I barking.sound here perceive-NONPAST", "r192_present_i_ngangkurr_here", ["ngayu", "ngangkurr", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no dog/source-event row."),
    row(20, "I see the reflection here.", "Ngayu ngurma yaluy nyajil.", "ngayu ngurma yaluy nyaji-l | I reflection here perceive-NONPAST", "r192_present_i_ngurma_here", ["ngayu", "ngurma", "yaluy", "nyaji-l"], "Visual target with nyaji-l as see/perceive; reflection sense only."),
    row(21, "You hear the chewing sound here.", "Yundu jangkuy yaluy nyajil.", "yundu jangkuy yaluy nyaji-l | you chewing.sound here perceive-NONPAST", "r192_present_you_jangkuy_here", ["yundu", "jangkuy", "yaluy", "nyaji-l"], "Rule 192 consolidates second-person present perception with yaluy support."),
    row(22, "You see the image here.", "Yundu ngurma-mulkaynji yaluy nyajil.", "yundu ngurma-mulkaynji yaluy nyaji-l | you image here perceive-NONPAST", "r192_present_you_ngurma_mulkaynji_here", ["yundu", "ngurma-mulkaynji", "yaluy", "nyaji-l"], "Visual target with nyaji-l; image sense only."),
    row(23, "You hear the clap sound here.", "Yundu kankar yaluy nyajil.", "yundu kankar yaluy nyaji-l | you clap.sound here perceive-NONPAST", "r192_present_you_kankar_here", ["yundu", "kankar", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no clapping event."),
    row(24, "You hear the grunt sound here.", "Yundu nginja yaluy nyajil.", "yundu nginja yaluy nyaji-l | you grunt.sound here perceive-NONPAST", "r192_present_you_nginja_here", ["yundu", "nginja", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no animal/source row."),
    row(25, "You hear the rattling sound here.", "Yundu numburr yaluy nyajil.", "yundu numburr yaluy nyaji-l | you rattling.sound here perceive-NONPAST", "r192_present_you_numburr_here", ["yundu", "numburr", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no motor/machine/vehicle row."),
    row(26, "You hear the rustling noise here.", "Yundu yanja yaluy nyajil.", "yundu yanja yaluy nyaji-l | you rustling.noise here perceive-NONPAST", "r192_present_you_yanja_here", ["yundu", "yanja", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no source-motion row."),
    row(27, "You hear the sharp sound here.", "Yundu kadil yaluy nyajil.", "yundu kadil yaluy nyaji-l | you sharp.sound here perceive-NONPAST", "r192_present_you_kadil_here", ["yundu", "kadil", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no axe/cutting/tree-fall row."),
    row(28, "You hear the whistle sound here.", "Yundu kuwimbur yaluy nyajil.", "yundu kuwimbur yaluy nyaji-l | you whistle.sound here perceive-NONPAST", "r192_present_you_kuwimbur_here", ["yundu", "kuwimbur", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no whistling event."),
    row(29, "You hear the barking sound here.", "Yundu ngangkurr yaluy nyajil.", "yundu ngangkurr yaluy nyaji-l | you barking.sound here perceive-NONPAST", "r192_present_you_ngangkurr_here", ["yundu", "ngangkurr", "yaluy", "nyaji-l"], "Auditory target with nyaji-l; no dog/source row."),
    row(30, "You see the reflection here.", "Yundu ngurma yaluy nyajil.", "yundu ngurma yaluy nyaji-l | you reflection here perceive-NONPAST", "r192_present_you_ngurma_here", ["yundu", "ngurma", "yaluy", "nyaji-l"], "Visual target with nyaji-l; reflection sense only."),
    row(31, "They heard the chewing sound here.", "Jana jangkuy yaluy nyajin.", "jana jangkuy yaluy nyaji-ny | they chewing.sound here perceive-PAST", "r192_past_they_jangkuy_here", ["jana", "jangkuy", "yaluy", "nyaji-l"], "Rule 192 consolidates third-person plural past perception with yaluy support."),
    row(32, "They saw the image here.", "Jana ngurma-mulkaynji yaluy nyajin.", "jana ngurma-mulkaynji yaluy nyaji-ny | they image here perceive-PAST", "r192_past_they_ngurma_mulkaynji_here", ["jana", "ngurma-mulkaynji", "yaluy", "nyaji-l"], "Visual target with nyaji-ny past; image sense only."),
    row(33, "They heard the clap sound here.", "Jana kankar yaluy nyajin.", "jana kankar yaluy nyaji-ny | they clap.sound here perceive-PAST", "r192_past_they_kankar_here", ["jana", "kankar", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no clapping event."),
    row(34, "They heard the grunt sound here.", "Jana nginja yaluy nyajin.", "jana nginja yaluy nyaji-ny | they grunt.sound here perceive-PAST", "r192_past_they_nginja_here", ["jana", "nginja", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no animal/source row."),
    row(35, "They heard the rattling sound here.", "Jana numburr yaluy nyajin.", "jana numburr yaluy nyaji-ny | they rattling.sound here perceive-PAST", "r192_past_they_numburr_here", ["jana", "numburr", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no motor/machine row."),
    row(36, "They heard the rustling noise here.", "Jana yanja yaluy nyajin.", "jana yanja yaluy nyaji-ny | they rustling.noise here perceive-PAST", "r192_past_they_yanja_here", ["jana", "yanja", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no source-motion row."),
    row(37, "They heard the sharp sound here.", "Jana kadil yaluy nyajin.", "jana kadil yaluy nyaji-ny | they sharp.sound here perceive-PAST", "r192_past_they_kadil_here", ["jana", "kadil", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no axe/cutting/tree-fall row."),
    row(38, "They heard the whistle sound here.", "Jana kuwimbur yaluy nyajin.", "jana kuwimbur yaluy nyaji-ny | they whistle.sound here perceive-PAST", "r192_past_they_kuwimbur_here", ["jana", "kuwimbur", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no whistling/song row."),
    row(39, "They heard the barking sound here.", "Jana ngangkurr yaluy nyajin.", "jana ngangkurr yaluy nyaji-ny | they barking.sound here perceive-PAST", "r192_past_they_ngangkurr_here", ["jana", "ngangkurr", "yaluy", "nyaji-l"], "Auditory target with nyaji-ny past; no dog/source row."),
    row(40, "They saw the reflection here.", "Jana ngurma yaluy nyajin.", "jana ngurma yaluy nyaji-ny | they reflection here perceive-PAST", "r192_past_they_ngurma_here", ["jana", "ngurma", "yaluy", "nyaji-l"], "Visual target with nyaji-ny past; reflection sense only."),
    row(41, "We hear the chewing sound there.", "Ngana jangkuy yinyay nyajil.", "ngana jangkuy yinyay nyaji-l | we chewing.sound there perceive-NONPAST", "r192_present_we_jangkuy_there", ["ngana", "jangkuy", "yinyay", "nyaji-l"], "Rule 192 consolidates inclusive present perception with yinyay support."),
    row(42, "We see the image there.", "Ngana ngurma-mulkaynji yinyay nyajil.", "ngana ngurma-mulkaynji yinyay nyaji-l | we image there perceive-NONPAST", "r192_present_we_ngurma_mulkaynji_there", ["ngana", "ngurma-mulkaynji", "yinyay", "nyaji-l"], "Visual target with nyaji-l; image sense only."),
    row(43, "We hear the clap sound there.", "Ngana kankar yinyay nyajil.", "ngana kankar yinyay nyaji-l | we clap.sound there perceive-NONPAST", "r192_present_we_kankar_there", ["ngana", "kankar", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no clapping event."),
    row(44, "We hear the grunt sound there.", "Ngana nginja yinyay nyajil.", "ngana nginja yinyay nyaji-l | we grunt.sound there perceive-NONPAST", "r192_present_we_nginja_there", ["ngana", "nginja", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no animal/source row."),
    row(45, "We hear the rattling sound there.", "Ngana numburr yinyay nyajil.", "ngana numburr yinyay nyaji-l | we rattling.sound there perceive-NONPAST", "r192_present_we_numburr_there", ["ngana", "numburr", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no motor/machine row."),
    row(46, "We hear the rustling noise there.", "Ngana yanja yinyay nyajil.", "ngana yanja yinyay nyaji-l | we rustling.noise there perceive-NONPAST", "r192_present_we_yanja_there", ["ngana", "yanja", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no source-motion row."),
    row(47, "We hear the sharp sound there.", "Ngana kadil yinyay nyajil.", "ngana kadil yinyay nyaji-l | we sharp.sound there perceive-NONPAST", "r192_present_we_kadil_there", ["ngana", "kadil", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no source event row."),
    row(48, "We hear the whistle sound there.", "Ngana kuwimbur yinyay nyajil.", "ngana kuwimbur yinyay nyaji-l | we whistle.sound there perceive-NONPAST", "r192_present_we_kuwimbur_there", ["ngana", "kuwimbur", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no whistling/song row."),
    row(49, "We hear the barking sound there.", "Ngana ngangkurr yinyay nyajil.", "ngana ngangkurr yinyay nyaji-l | we barking.sound there perceive-NONPAST", "r192_present_we_ngangkurr_there", ["ngana", "ngangkurr", "yinyay", "nyaji-l"], "Auditory target with nyaji-l; no dog/source row."),
    row(50, "We see the reflection there.", "Ngana ngurma yinyay nyajil.", "ngana ngurma yinyay nyaji-l | we reflection there perceive-NONPAST", "r192_present_we_ngurma_there", ["ngana", "ngurma", "yinyay", "nyaji-l"], "Visual target with nyaji-l; reflection sense only."),
]


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("r192_"):
            issues.append(f"{label}: Rule 192 frame must start with r192_")
        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 192")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied perception-event/rejected-candidate 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 191 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 192 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 192 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 or "nyajil" not in low):
                issues.append(f"{label}: rows 11-20 require ngayu + yaluy/here + nyajil")
            if 21 <= seq <= 30 and (first != "yundu" or not has_token(low, "yaluy") or "here" not in english_low or "nyajil" not in low):
                issues.append(f"{label}: rows 21-30 require yundu + yaluy/here + nyajil")
            if 31 <= seq <= 40 and (first != "jana" or not has_token(low, "yaluy") or "here" not in english_low or "nyajin" not in low or not ("heard" in english_low or "saw" in english_low)):
                issues.append(f"{label}: rows 31-40 require jana + yaluy/here + nyajin + English past")
            if 41 <= seq <= 50 and (first != "ngana" or not has_token(low, "yinyay") or "there" not in english_low or "nyajil" not in low):
                issues.append(f"{label}: rows 41-50 require ngana + yinyay/there + nyajil")
            if "nyajin" in low and "perceive-PAST" not in analysis:
                issues.append(f"{label}: nyajin must be glossed as perceive-PAST")
            if "nyajil" in low and "perceive-NONPAST" not in analysis:
                issues.append(f"{label}: nyajil must be glossed as perceive-NONPAST")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied source-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 192 draft: perception-reference construction consolidation", "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 192 source pass retained exactly the ten Rule 191 perception-reference heads: jangkuy chewing sound, ngurma-mulkaynji image, kankar clap sound, nginja grunt sound, numburr rattling sound, yanja rustling noise, kadil sharp sound, kuwimbur whistle sound, ngangkurr barking sound, and ngurma reflection. Rejected candidates and all source-event traps remain closed. Grammar readback confirmed nyaji-l as broad see/hear/perceive with bare pronoun A, bare absolutive target objects, nyajil nonpast, nyajin past, and yaluy/yinyay support.",
                 command="dictionary YAML readback; grammar/cheatsheet grep for nyaji-l see/hear/perceive; sqlite corpus-frequency check",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="scope", event_type="construction-scope", actor="claude-main",
                 detail="Rule 192 is a construction-consolidation pass, not lexical expansion. It rotates the Rule 191 heads through yinyay bare deictic identification, yaluy-supported present perception with ngayu and yundu, yaluy-supported past perception with jana, and yinyay-supported present perception with ngana. It excludes new target heads, sound-making events, smell/taste verbs, source-event prose, rejected candidates, perception compounds, target case, target compounds, plural, and productive reduplication.",
                 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_234.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guard passed: exactly one retained target per row, no loans, no rejected candidates, no denied source-event Kuku tokens, no target case/compound morphology, correct Rule 192 frame distribution, correct tense glosses, and no denied source-event/polysemy English fields.",
                 command="python3 batch_scripts/bank_234.py pre-bank scan", artifact_path="batch_scripts/bank_234.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", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used,
            loans_used, evidence, confidence in ROWS
        ],
    )
    db.commit()

    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    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={count}, under_review={under_review}, verified_before_close={verified}")
    return 0


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