#!/usr/bin/env python3
"""Bank batch-195: Rule 153 weather/sky lexical expansion after Rule 152."""

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

TARGET_HEADS = {
    "kaba": "rain",
    "muyar": "wind",
    "ngurrban": "cloud",
    "wungar": "sun",
    "kija": "moon",
    "yiril": "rainbow",
    "jarramali": "thunderstorm",
    "jabulbina": "thunder",
    "kurrubijal": "morning.star",
    "wurunurru": "whirlwind",
}
TARGET_ENGLISH = {
    "kaba": "rain",
    "muyar": "wind",
    "ngurrban": "cloud",
    "wungar": "sun",
    "kija": "moon",
    "yiril": "rainbow",
    "jarramali": "thunderstorm",
    "jabulbina": "thunder",
    "kurrubijal": "morning star",
    "wurunurru": "whirlwind",
}
PRONOUN_A = {"ngayu", "ngana", "jana"}
COMMON_A = {"ngamu-ngku", "nganjan-angka", "babi-ngka", "karrkay-ngku", "kami-ngka", "jalbu-ngku", "dingkar-angka"}
FORBIDDEN_HEADS = {"wuburr", "balay", "dubar", "kaba-kada", "jarunga-kija", "juru-kija", "jurungu-kija"}
DENY_KUKU = {
    "dama", "daman", "damanka", "nyurrba", "nyurrban", "yaka", "yakan", "kuni", "kunin", "baka", "bakan",
    "wundi", "wundin", "bangka", "bangkan", "balka", "balkan", "kaja", "kajan", "yurril", "yurrin",
    "kida", "kidan", "jurrnga", "jurrngan", "yilba", "yilban", "duda", "dudan", "nyanda", "nyandan",
    "mani", "manin", "yijarri", "yijarrin", "daka", "dakan", "waju", "wajun", "wunba", "mayi",
    "dunga", "dungan", "dungay", "kadan", "kaday", "kada", "janji", "janjil", "mariji", "marijin",
    "dara", "daran", "daray", "bunjurril", "wandil", "wandin", "ngaki",
}
DENY_ENGLISH = (
    "fall", "fell", "falling", "come", "came", "coming", "blow", "blows", "blew", "storm came",
    "sunrise", "sunset", "season", "month", "roaring meg", "place name", "rainbird", "rain forest",
    "stingray", "praying mantis", "angry", "hot-tempered", "drive away", "omen",
)


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, "I see the rain there.", "Ngayu kaba yinyay nyajil.", "ngayu kaba yinyay nyaji-l | I rain there see-NONPAST", "r153_present_i_rain_there", ["ngayu", "kaba", "yinyay", "nyaji-l"], "Dictionary kaba rain; Patz has kaba rain as ABS(S); perception frame retained."),
    row(2, "We hear the wind there.", "Ngana muyar yinyay nyajil.", "ngana muyar yinyay nyaji-l | we wind there hear-NONPAST", "r153_present_we_wind_there", ["ngana", "muyar", "yinyay", "nyaji-l"], "Dictionary muyar wind; Patz has muyar wind; nyaji-l covers hear."),
    row(3, "They see the cloud there.", "Jana ngurrban yinyay nyajil.", "jana ngurrban yinyay nyaji-l | they cloud there see-NONPAST", "r153_present_they_cloud_there", ["jana", "ngurrban", "yinyay", "nyaji-l"], "Dictionary ngurrban cloud; prior audited lexeme and perception rows."),
    row(4, "Mother sees the sun there.", "Ngamu-ngku wungar yinyay nyajil.", "ngamu-ngku wungar yinyay nyaji-l | mother-ERG sun there see-NONPAST", "r153_present_mother_sun_there", ["ngamu", "wungar", "yinyay", "nyaji-l"], "Dictionary wungar sun; Patz lists sun/time forms."),
    row(5, "Father sees the moon there.", "Nganjan-angka kija yinyay nyajil.", "nganjan-angka kija yinyay nyaji-l | father-ERG moon there see-NONPAST", "r153_present_father_moon_there", ["nganjan", "kija", "yinyay", "nyaji-l"], "Dictionary/Patz kija moon/month; selected moon only."),
    row(6, "The child sees the rainbow there.", "Karrkay-ngku yiril yinyay nyajil.", "karrkay-ngku yiril yinyay nyaji-l | child-ERG rainbow there see-NONPAST", "r153_present_child_rainbow_there", ["karrkay", "yiril", "yinyay", "nyaji-l"], "Dictionary yiril rainbow; prior audited weather lexeme."),
    row(7, "Grandmother hears the thunderstorm there.", "Babi-ngka jarramali yinyay nyajil.", "babi-ngka jarramali yinyay nyaji-l | grandmother-ERG thunderstorm there hear-NONPAST", "r153_present_grandmother_thunderstorm_there", ["babi", "jarramali", "yinyay", "nyaji-l"], "Dictionary/Patz jarramali thunderstorm; selected weather sense only."),
    row(8, "Grandfather hears the thunder there.", "Kami-ngka jabulbina yinyay nyajil.", "kami-ngka jabulbina yinyay nyaji-l | grandfather-ERG thunder there hear-NONPAST", "r153_present_grandfather_thunder_there", ["kami", "jabulbina", "yinyay", "nyaji-l"], "Dictionary jabulbina thunder out west; selected thunder sense."),
    row(9, "The woman sees the morning star there.", "Jalbu-ngku kurrubijal yinyay nyajil.", "jalbu-ngku kurrubijal yinyay nyaji-l | woman-ERG morning.star there see-NONPAST", "r153_present_woman_morning_star_there", ["jalbu", "kurrubijal", "yinyay", "nyaji-l"], "Dictionary kurrubijal morning star; celestial noun."),
    row(10, "The man sees the whirlwind there.", "Dingkar-angka wurunurru yinyay nyajil.", "dingkar-angka wurunurru yinyay nyaji-l | man-ERG whirlwind there see-NONPAST", "r153_present_man_whirlwind_there", ["dingkar", "wurunurru", "yinyay", "nyaji-l"], "Dictionary wurunurru whirlwind; selected weather noun."),
    row(11, "I see the cloud.", "Ngayu ngurrban nyajil.", "ngayu ngurrban nyaji-l | I cloud see-NONPAST", "r153_present_i_cloud", ["ngayu", "ngurrban", "nyaji-l"], "Pronoun A bare; cloud object."),
    row(12, "We see the sun.", "Ngana wungar nyajil.", "ngana wungar nyaji-l | we sun see-NONPAST", "r153_present_we_sun", ["ngana", "wungar", "nyaji-l"], "Pronoun A bare; sun object."),
    row(13, "They see the moon.", "Jana kija nyajil.", "jana kija nyaji-l | they moon see-NONPAST", "r153_present_they_moon", ["jana", "kija", "nyaji-l"], "Pronoun A bare; kija selected as moon."),
    row(14, "Mother sees the rainbow.", "Ngamu-ngku yiril nyajil.", "ngamu-ngku yiril nyaji-l | mother-ERG rainbow see-NONPAST", "r153_present_mother_rainbow", ["ngamu", "yiril", "nyaji-l"], "Common-noun A ERG; rainbow object."),
    row(15, "Father hears the thunderstorm.", "Nganjan-angka jarramali nyajil.", "nganjan-angka jarramali nyaji-l | father-ERG thunderstorm hear-NONPAST", "r153_present_father_thunderstorm", ["nganjan", "jarramali", "nyaji-l"], "Common-noun A ERG; jarramali weather sense."),
    row(16, "The child hears the thunder.", "Karrkay-ngku jabulbina nyajil.", "karrkay-ngku jabulbina nyaji-l | child-ERG thunder hear-NONPAST", "r153_present_child_thunder", ["karrkay", "jabulbina", "nyaji-l"], "Common-noun A ERG; thunder object."),
    row(17, "Grandmother sees the morning star.", "Babi-ngka kurrubijal nyajil.", "babi-ngka kurrubijal nyaji-l | grandmother-ERG morning.star see-NONPAST", "r153_present_grandmother_morning_star", ["babi", "kurrubijal", "nyaji-l"], "Common-noun A ERG; morning star object."),
    row(18, "Grandfather sees the whirlwind.", "Kami-ngka wurunurru nyajil.", "kami-ngka wurunurru nyaji-l | grandfather-ERG whirlwind see-NONPAST", "r153_present_grandfather_whirlwind", ["kami", "wurunurru", "nyaji-l"], "Common-noun A ERG; whirlwind object."),
    row(19, "The woman sees the rain.", "Jalbu-ngku kaba nyajil.", "jalbu-ngku kaba nyaji-l | woman-ERG rain see-NONPAST", "r153_present_woman_rain", ["jalbu", "kaba", "nyaji-l"], "Common-noun A ERG; rain object."),
    row(20, "The man hears the wind.", "Dingkar-angka muyar nyajil.", "dingkar-angka muyar nyaji-l | man-ERG wind hear-NONPAST", "r153_present_man_wind", ["dingkar", "muyar", "nyaji-l"], "Common-noun A ERG; wind heard with nyaji-l."),
    row(21, "Grandmother saw the cloud.", "Babi-ngka ngurrban nyajin.", "babi-ngka ngurrban nyaji-ny | grandmother-ERG cloud see-PAST", "r153_past_grandmother_cloud", ["babi", "ngurrban", "nyaji-l"], "Duplicate-safe common-noun A; cloud object."),
    row(22, "Grandfather saw the sun.", "Kami-ngka wungar nyajin.", "kami-ngka wungar nyaji-ny | grandfather-ERG sun see-PAST", "r153_past_grandfather_sun", ["kami", "wungar", "nyaji-l"], "Duplicate-safe common-noun A; sun object."),
    row(23, "They saw the moon.", "Jana kija nyajin.", "jana kija nyaji-ny | they moon see-PAST", "r153_past_they_moon", ["jana", "kija", "nyaji-l"], "Pronoun A bare; moon object."),
    row(24, "Mother saw the rainbow.", "Ngamu-ngku yiril nyajin.", "ngamu-ngku yiril nyaji-ny | mother-ERG rainbow see-PAST", "r153_past_mother_rainbow", ["ngamu", "yiril", "nyaji-l"], "Common-noun A ERG; rainbow object."),
    row(25, "Father heard the thunderstorm.", "Nganjan-angka jarramali nyajin.", "nganjan-angka jarramali nyaji-ny | father-ERG thunderstorm hear-PAST", "r153_past_father_thunderstorm", ["nganjan", "jarramali", "nyaji-l"], "Common-noun A ERG; jarramali weather sense."),
    row(26, "The child heard the thunder.", "Karrkay-ngku jabulbina nyajin.", "karrkay-ngku jabulbina nyaji-ny | child-ERG thunder hear-PAST", "r153_past_child_thunder", ["karrkay", "jabulbina", "nyaji-l"], "Common-noun A ERG; thunder object."),
    row(27, "Grandmother saw the morning star.", "Babi-ngka kurrubijal nyajin.", "babi-ngka kurrubijal nyaji-ny | grandmother-ERG morning.star see-PAST", "r153_past_grandmother_morning_star", ["babi", "kurrubijal", "nyaji-l"], "Common-noun A ERG; morning star object."),
    row(28, "Grandfather saw the whirlwind.", "Kami-ngka wurunurru nyajin.", "kami-ngka wurunurru nyaji-ny | grandfather-ERG whirlwind see-PAST", "r153_past_grandfather_whirlwind", ["kami", "wurunurru", "nyaji-l"], "Common-noun A ERG; whirlwind object."),
    row(29, "The woman saw the rain.", "Jalbu-ngku kaba nyajin.", "jalbu-ngku kaba nyaji-ny | woman-ERG rain see-PAST", "r153_past_woman_rain", ["jalbu", "kaba", "nyaji-l"], "Common-noun A ERG; rain object."),
    row(30, "The man heard the wind.", "Dingkar-angka muyar nyajin.", "dingkar-angka muyar nyaji-ny | man-ERG wind hear-PAST", "r153_past_man_wind", ["dingkar", "muyar", "nyaji-l"], "Common-noun A ERG; wind heard with nyaji-l."),
    row(31, "I saw the cloud there.", "Ngayu ngurrban yinyay nyajin.", "ngayu ngurrban yinyay nyaji-ny | I cloud there see-PAST", "r153_past_i_cloud_there", ["ngayu", "ngurrban", "yinyay", "nyaji-l"], "Pronoun A bare; yinyay support."),
    row(32, "We saw the sun there.", "Ngana wungar yinyay nyajin.", "ngana wungar yinyay nyaji-ny | we sun there see-PAST", "r153_past_we_sun_there", ["ngana", "wungar", "yinyay", "nyaji-l"], "Pronoun A bare; yinyay support."),
    row(33, "They saw the moon there.", "Jana kija yinyay nyajin.", "jana kija yinyay nyaji-ny | they moon there see-PAST", "r153_past_they_moon_there", ["jana", "kija", "yinyay", "nyaji-l"], "Pronoun A bare; yinyay support."),
    row(34, "Mother saw the rainbow there.", "Ngamu-ngku yiril yinyay nyajin.", "ngamu-ngku yiril yinyay nyaji-ny | mother-ERG rainbow there see-PAST", "r153_past_mother_rainbow_there", ["ngamu", "yiril", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(35, "Father heard the thunderstorm there.", "Nganjan-angka jarramali yinyay nyajin.", "nganjan-angka jarramali yinyay nyaji-ny | father-ERG thunderstorm there hear-PAST", "r153_past_father_thunderstorm_there", ["nganjan", "jarramali", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(36, "The child heard the thunder there.", "Karrkay-ngku jabulbina yinyay nyajin.", "karrkay-ngku jabulbina yinyay nyaji-ny | child-ERG thunder there hear-PAST", "r153_past_child_thunder_there", ["karrkay", "jabulbina", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(37, "Grandmother saw the morning star there.", "Babi-ngka kurrubijal yinyay nyajin.", "babi-ngka kurrubijal yinyay nyaji-ny | grandmother-ERG morning.star there see-PAST", "r153_past_grandmother_morning_star_there", ["babi", "kurrubijal", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(38, "Grandfather saw the whirlwind there.", "Kami-ngka wurunurru yinyay nyajin.", "kami-ngka wurunurru yinyay nyaji-ny | grandfather-ERG whirlwind there see-PAST", "r153_past_grandfather_whirlwind_there", ["kami", "wurunurru", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(39, "The woman saw the rain there.", "Jalbu-ngku kaba yinyay nyajin.", "jalbu-ngku kaba yinyay nyaji-ny | woman-ERG rain there see-PAST", "r153_past_woman_rain_there", ["jalbu", "kaba", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(40, "The man heard the wind there.", "Dingkar-angka muyar yinyay nyajin.", "dingkar-angka muyar yinyay nyaji-ny | man-ERG wind there hear-PAST", "r153_past_man_wind_there", ["dingkar", "muyar", "yinyay", "nyaji-l"], "Common-noun A ERG; yinyay support."),
    row(41, "The woman sees the cloud there.", "Jalbu-ngku ngurrban yinyay nyajil.", "jalbu-ngku ngurrban yinyay nyaji-l | woman-ERG cloud there see-NONPAST", "r153_present_woman_cloud_there", ["jalbu", "ngurrban", "yinyay", "nyaji-l"], "Common-noun A ERG; cloud plus yinyay."),
    row(42, "The man sees the sun there.", "Dingkar-angka wungar yinyay nyajil.", "dingkar-angka wungar yinyay nyaji-l | man-ERG sun there see-NONPAST", "r153_present_man_sun_there", ["dingkar", "wungar", "yinyay", "nyaji-l"], "Common-noun A ERG; sun plus yinyay."),
    row(43, "I see the moon there.", "Ngayu kija yinyay nyajil.", "ngayu kija yinyay nyaji-l | I moon there see-NONPAST", "r153_present_i_moon_there", ["ngayu", "kija", "yinyay", "nyaji-l"], "Pronoun A bare; moon plus yinyay."),
    row(44, "We see the rainbow there.", "Ngana yiril yinyay nyajil.", "ngana yiril yinyay nyaji-l | we rainbow there see-NONPAST", "r153_present_we_rainbow_there", ["ngana", "yiril", "yinyay", "nyaji-l"], "Pronoun A bare; rainbow plus yinyay."),
    row(45, "They hear the thunderstorm there.", "Jana jarramali yinyay nyajil.", "jana jarramali yinyay nyaji-l | they thunderstorm there hear-NONPAST", "r153_present_they_thunderstorm_there", ["jana", "jarramali", "yinyay", "nyaji-l"], "Pronoun A bare; jarramali weather sense."),
    row(46, "Mother hears the thunder there.", "Ngamu-ngku jabulbina yinyay nyajil.", "ngamu-ngku jabulbina yinyay nyaji-l | mother-ERG thunder there hear-NONPAST", "r153_present_mother_thunder_there", ["ngamu", "jabulbina", "yinyay", "nyaji-l"], "Common-noun A ERG; thunder plus yinyay."),
    row(47, "Father sees the morning star there.", "Nganjan-angka kurrubijal yinyay nyajil.", "nganjan-angka kurrubijal yinyay nyaji-l | father-ERG morning.star there see-NONPAST", "r153_present_father_morning_star_there", ["nganjan", "kurrubijal", "yinyay", "nyaji-l"], "Common-noun A ERG; morning star plus yinyay."),
    row(48, "The child sees the whirlwind there.", "Karrkay-ngku wurunurru yinyay nyajil.", "karrkay-ngku wurunurru yinyay nyaji-l | child-ERG whirlwind there see-NONPAST", "r153_present_child_whirlwind_there", ["karrkay", "wurunurru", "yinyay", "nyaji-l"], "Common-noun A ERG; whirlwind plus yinyay."),
    row(49, "Grandmother sees the rain there.", "Babi-ngka kaba yinyay nyajil.", "babi-ngka kaba yinyay nyaji-l | grandmother-ERG rain there see-NONPAST", "r153_present_grandmother_rain_there", ["babi", "kaba", "yinyay", "nyaji-l"], "Common-noun A ERG; rain plus yinyay."),
    row(50, "Grandfather hears the wind there.", "Kami-ngka muyar yinyay nyajil.", "kami-ngka muyar yinyay nyaji-l | grandfather-ERG wind there hear-NONPAST", "r153_present_grandfather_wind_there", ["kami", "muyar", "yinyay", "nyaji-l"], "Common-noun A ERG; wind plus yinyay."),
]


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|muny|darr|burr|bu|ka|nka|nku|ji|jirr|ku|mundu|amu|mu|iji)\b", low) is not None


def english_has_denied(english_low: str, denied: str) -> bool:
    if " " in denied:
        return denied in english_low
    return 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("r153_"):
            issues.append(f"{label}: Rule 153 frame must start with r153_")
        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 new loans are admitted in Rule 153")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied weather-event/action token {denied!r}")
        for denied in FORBIDDEN_HEADS:
            if has_token(low, denied):
                issues.append(f"{label}: forbidden place-name/old-field trap {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 153 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 TARGET_ENGLISH[head] not in english_low:
                issues.append(f"{label}: {head} must be translated with selected sense {TARGET_ENGLISH[head]!r}")
            if has_target_case(low, head):
                issues.append(f"{label}: target head {head} has forbidden case/compound morphology")
        if has_token(low, "kija") and "moon" not in english_low:
            issues.append(f"{label}: kija must be translated as moon only in Rule 153")
        if has_token(low, "jarramali") and "thunderstorm" not in english_low:
            issues.append(f"{label}: jarramali must be translated as thunderstorm in Rule 153")
        if has_token(low, "jabulbina") and "thunder" not in english_low:
            issues.append(f"{label}: jabulbina must be translated as thunder in Rule 153")
        if ("nyajin" not in low and "nyajil" not in low) or "nyaji-l" not in words:
            issues.append(f"{label}: Rule 153 rows must use nyaji-l perception")
        if "yinyay" in low and "yinyay" not in words:
            issues.append(f"{label}: yinyay support missing from words_used")
        first = first_word(low)
        if first not in PRONOUN_A and first not in COMMON_A:
            issues.append(f"{label}: perception A should be an approved bare pronoun or ERG-marked common noun")
        if "nyajin" in low and not ("see-PAST" in analysis or "hear-PAST" in analysis):
            issues.append(f"{label}: nyajin must be glossed as see/hear-PAST")
        if "nyajil" in low and not ("see-NONPAST" in analysis or "hear-NONPAST" in analysis):
            issues.append(f"{label}: nyajil must be glossed as see/hear-NONPAST")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied weather-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 153 draft: weather/sky lexical expansion after Rule 152", "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 153 source pass admitted weather/sky targets kaba rain, muyar wind, ngurrban cloud, wungar sun, "
            "kija moon, yiril rainbow, jarramali thunderstorm, jabulbina thunder, kurrubijal morning star, and "
            "wurunurru whirlwind. Dictionary, Patz grammar, lexeme-table, and corpus-surface readback completed; "
            "dictionary-only sky nouns were retained only where earlier audited lexeme/source logs supported them."
        ),
        command="dictionary.yaml exact readback; grammar.md occurrence readback; sqlite lexeme and surface 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 153 is a lexical perception pass for bare weather/sky heads. It excludes target case forms, weather "
            "event predicates such as rain falling or wind coming, season compounds, kija place-name/month drift, "
            "jarramali animal/temper senses, kaba-kada place-name drift, and all carry-forward geography/implement traps."
        ),
        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_195.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guards passed: one bare weather/sky target per row, selected English senses enforced, no target case or compound morphology, no weather-event predicates, no place-name/animal/polysemy drift, common-noun A ERG, and pronoun A bare.",
                 command="python3 batch_scripts/bank_195.py static scan", artifact_path="batch_scripts/bank_195.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 153 weather/sky lexical-expansion rows.",
                 command="python3 batch_scripts/bank_195.py", artifact_path="batch_scripts/bank_195.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())
