#!/usr/bin/env python3
"""Bank batch-187: Rule 145 water-fauna lexical expansion after Rule 144."""

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

TARGET_HEADS = {
    "kuyu": "fish",
    "ngawiya": "sea.turtle",
    "ngujay": "freshwater.turtle",
    "bilngkumu": "saltwater.crocodile",
    "kubarr": "freshwater.eel",
    "yawu": "stingray",
    "yaburr": "shark",
    "kiju": "mud.crab",
    "ngalngal": "sand.crab",
}
STATE_HEADS = {"ngulkurr": "good", "yalbay": "big"}
SUPPORT_FORMS = {"bana-nga"}
SUPPORT_HEADS = {"bana": "water"}
DENY = {
    "ngiwa", "diwan", "wabul", "wakuka", "jarruka", "dikal", "kurranji", "nili-nili", "kalbu", "dulku",
    "birrbirr", "jamarbina", "bajar", "mili", "ngalku", "babajaka", "baril-baril", "baway", "bilar", "kabal",
    "yalngkal", "yumu", "jimal", "ngunjil", "dumbul", "buyku", "jikan", "baya", "kubu", "nganjirr",
}
PRONOUN_A = {"ngayu", "ngana", "jana"}
COMMON_A = {"ngamu-ngku", "nganjan-angka", "babi-ngka", "karrkay-ngku", "kami-ngka", "jalbu-ngku", "dingkar-angka"}
DENIED_ENGLISH = (
    "catch", "caught", "catching", "spear", "speared", "spearing", "hook", "hooked", "net", "netting",
    "cook", "cooked", "cooking", "roast", "roasted", "stew", "stewed", "eat", "eating", "meat", "food",
    "taboo", "storm", "rain", "fear", "afraid", "bite", "biting", "ill", "sick", "sickness", "story",
    "myth", "totem", "dream",
)


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


ROWS = [
    (1, "The fish is good.", "Kuyu ngulkurr.",
     "kuyu ngulkurr | fish good", "r145_state_fish_good", 2, js(["kuyu", "ngulkurr"]), js([]),
     "Dictionary/Patz: kuyu fish; ngulkurr good.", "0.86"),
    (2, "The sea turtle is big.", "Ngawiya yalbay.",
     "ngawiya yalbay | sea.turtle big", "r145_state_sea_turtle_big", 2, js(["ngawiya", "yalbay"]), js([]),
     "Dictionary: ngawiya sea turtle; yalbay big. Patz examples also show ngawuya.", "0.84"),
    (3, "The freshwater turtle is big.", "Ngujay yalbay.",
     "ngujay yalbay | freshwater.turtle big", "r145_state_freshwater_turtle_big", 2, js(["ngujay", "yalbay"]), js([]),
     "Dictionary/Patz: ngujay freshwater turtle; yalbay big.", "0.84"),
    (4, "The saltwater crocodile is big.", "Bilngkumu yalbay.",
     "bilngkumu yalbay | saltwater.crocodile big", "r145_state_crocodile_big", 2, js(["bilngkumu", "yalbay"]), js([]),
     "Dictionary: bilngkumu salt water crocodile; no story/fear row.", "0.84"),
    (5, "The freshwater eel is big.", "Kubarr yalbay.",
     "kubarr yalbay | freshwater.eel big", "r145_state_eel_big", 2, js(["kubarr", "yalbay"]), js([]),
     "Dictionary/Patz: kubarr big black freshwater eel; no cooking/eating row.", "0.84"),
    (6, "The stingray is big.", "Yawu yalbay.",
     "yawu yalbay | stingray big", "r145_state_stingray_big", 2, js(["yawu", "yalbay"]), js([]),
     "Dictionary/Patz: yawu stingray; no spearing/storm belief row.", "0.84"),
    (7, "The shark is big.", "Yaburr yalbay.",
     "yaburr yalbay | shark big", "r145_state_shark_big", 2, js(["yaburr", "yalbay"]), js([]),
     "Dictionary/Patz: yaburr shark; no biting row.", "0.84"),
    (8, "The mud crab is good.", "Kiju ngulkurr.",
     "kiju ngulkurr | mud.crab good", "r145_state_mud_crab_good", 2, js(["kiju", "ngulkurr"]), js([]),
     "Dictionary/Patz: kiju mud crab; no fear/nip row.", "0.84"),
    (9, "The sand crab is good.", "Ngalngal ngulkurr.",
     "ngalngal ngulkurr | sand.crab good", "r145_state_sand_crab_good", 2, js(["ngalngal", "ngulkurr"]), js([]),
     "Dictionary/Patz: ngalngal sand crab; reduplicated lexical head kept whole.", "0.84"),
    (10, "The fish is big.", "Kuyu yalbay.",
     "kuyu yalbay | fish big", "r145_state_fish_big", 2, js(["kuyu", "yalbay"]), js([]),
     "Kuyu fish in second safe state frame.", "0.86"),
    (11, "The sea turtle is good.", "Ngawiya ngulkurr.",
     "ngawiya ngulkurr | sea.turtle good", "r145_state_sea_turtle_good", 2, js(["ngawiya", "ngulkurr"]), js([]),
     "Ngawiya sea turtle; no meat/hunting row.", "0.84"),
    (12, "The freshwater turtle is good.", "Ngujay ngulkurr.",
     "ngujay ngulkurr | freshwater.turtle good", "r145_state_freshwater_turtle_good", 2, js(["ngujay", "ngulkurr"]), js([]),
     "Ngujay freshwater turtle; no comparison or meat row.", "0.84"),
    (13, "The mud crab is big.", "Kiju yalbay.",
     "kiju yalbay | mud.crab big", "r145_state_mud_crab_big", 2, js(["kiju", "yalbay"]), js([]),
     "Kiju mud crab with yalbay big.", "0.84"),
    (14, "The sand crab is big.", "Ngalngal yalbay.",
     "ngalngal yalbay | sand.crab big", "r145_state_sand_crab_big", 2, js(["ngalngal", "yalbay"]), js([]),
     "Ngalngal sand crab head kept whole.", "0.84"),
    (15, "The stingray is good.", "Yawu ngulkurr.",
     "yawu ngulkurr | stingray good", "r145_state_stingray_good", 2, js(["yawu", "ngulkurr"]), js([]),
     "Yawu stingray only; no storm or taboo row.", "0.84"),

    (16, "The fish is in the water.", "Kuyu bana-nga.",
     "kuyu bana-nga | fish water-LOC", "r145_location_fish_water", 3, js(["kuyu", "bana"]), js([]),
     "Patz supports bana-nga water LOC and nominal location clauses.", "0.84"),
    (17, "The sea turtle is in the water.", "Ngawiya bana-nga.",
     "ngawiya bana-nga | sea.turtle water-LOC", "r145_location_sea_turtle_water", 3, js(["ngawiya", "bana"]), js([]),
     "Ngawiya with support-only bana-nga water LOC.", "0.84"),
    (18, "The freshwater turtle is in the water.", "Ngujay bana-nga.",
     "ngujay bana-nga | freshwater.turtle water-LOC", "r145_location_freshwater_turtle_water", 3, js(["ngujay", "bana"]), js([]),
     "Ngujay with support-only water LOC.", "0.84"),
    (19, "The saltwater crocodile is in the water.", "Bilngkumu bana-nga.",
     "bilngkumu bana-nga | saltwater.crocodile water-LOC", "r145_location_crocodile_water", 3, js(["bilngkumu", "bana"]), js([]),
     "Bilngkumu with water LOC; no story/fear/biting row.", "0.84"),
    (20, "The freshwater eel is in the water.", "Kubarr bana-nga.",
     "kubarr bana-nga | freshwater.eel water-LOC", "r145_location_eel_water", 3, js(["kubarr", "bana"]), js([]),
     "Kubarr with support-only water LOC.", "0.84"),
    (21, "The stingray is in the water.", "Yawu bana-nga.",
     "yawu bana-nga | stingray water-LOC", "r145_location_stingray_water", 3, js(["yawu", "bana"]), js([]),
     "Yawu with water LOC; no spearing/storm row.", "0.84"),
    (22, "The shark is in the water.", "Yaburr bana-nga.",
     "yaburr bana-nga | shark water-LOC", "r145_location_shark_water", 3, js(["yaburr", "bana"]), js([]),
     "Yaburr with support-only water LOC; no biting row.", "0.84"),
    (23, "The mud crab is in the water.", "Kiju bana-nga.",
     "kiju bana-nga | mud.crab water-LOC", "r145_location_mud_crab_water", 3, js(["kiju", "bana"]), js([]),
     "Kiju with support-only water LOC.", "0.84"),
    (24, "The sand crab is in the water.", "Ngalngal bana-nga.",
     "ngalngal bana-nga | sand.crab water-LOC", "r145_location_sand_crab_water", 3, js(["ngalngal", "bana"]), js([]),
     "Ngalngal with water LOC; head kept whole.", "0.84"),

    (25, "Mother saw the fish.", "Ngamu-ngku kuyu nyajin.",
     "ngamu-ngku kuyu nyaji-ny | mother-ERG fish see-PAST", "r145_perception_mother_fish", 4, js(["ngamu", "kuyu", "nyaji-l"]), js([]),
     "Common-noun A ERG; kuyu fish object.", "0.84"),
    (26, "Father saw the sea turtle.", "Nganjan-angka ngawiya nyajin.",
     "nganjan-angka ngawiya nyaji-ny | father-ERG sea.turtle see-PAST", "r145_perception_father_sea_turtle", 4, js(["nganjan", "ngawiya", "nyaji-l"]), js([]),
     "Common-noun A ERG; ngawiya sea turtle object.", "0.84"),
    (27, "Grandmother saw the freshwater turtle.", "Babi-ngka ngujay nyajin.",
     "babi-ngka ngujay nyaji-ny | grandmother-ERG freshwater.turtle see-PAST", "r145_perception_grandmother_freshwater_turtle", 4, js(["babi", "ngujay", "nyaji-l"]), js([]),
     "Common-noun A ERG; ngujay object.", "0.84"),
    (28, "The child saw the saltwater crocodile.", "Karrkay-ngku bilngkumu nyajin.",
     "karrkay-ngku bilngkumu nyaji-ny | child-ERG saltwater.crocodile see-PAST", "r145_perception_child_crocodile", 4, js(["karrkay", "bilngkumu", "nyaji-l"]), js([]),
     "Common-noun A ERG; bilngkumu object; no fear/story row.", "0.84"),
    (29, "Grandfather saw the freshwater eel.", "Kami-ngka kubarr nyajin.",
     "kami-ngka kubarr nyaji-ny | grandfather-ERG freshwater.eel see-PAST", "r145_perception_grandfather_eel", 4, js(["kami", "kubarr", "nyaji-l"]), js([]),
     "Common-noun A ERG; kubarr eel object.", "0.84"),
    (30, "The woman saw the stingray.", "Jalbu-ngku yawu nyajin.",
     "jalbu-ngku yawu nyaji-ny | woman-ERG stingray see-PAST", "r145_perception_woman_stingray", 4, js(["jalbu", "yawu", "nyaji-l"]), js([]),
     "Common-noun A ERG; yawu stingray object.", "0.84"),
    (31, "The man saw the shark.", "Dingkar-angka yaburr nyajin.",
     "dingkar-angka yaburr nyaji-ny | man-ERG shark see-PAST", "r145_perception_man_shark", 4, js(["dingkar", "yaburr", "nyaji-l"]), js([]),
     "Common-noun A ERG; yaburr shark object.", "0.84"),
    (32, "I saw the mud crab.", "Ngayu kiju nyajin.",
     "ngayu kiju nyaji-ny | I mud.crab see-PAST", "r145_perception_i_mud_crab", 4, js(["ngayu", "kiju", "nyaji-l"]), js([]),
     "Pronoun A bare; kiju mud crab object.", "0.84"),
    (33, "We saw the sand crab.", "Ngana ngalngal nyajin.",
     "ngana ngalngal nyaji-ny | we sand.crab see-PAST", "r145_perception_we_sand_crab", 4, js(["ngana", "ngalngal", "nyaji-l"]), js([]),
     "Pronoun A bare; ngalngal whole head.", "0.84"),
    (34, "They saw the fish.", "Jana kuyu nyajin.",
     "jana kuyu nyaji-ny | they fish see-PAST", "r145_perception_they_fish", 4, js(["jana", "kuyu", "nyaji-l"]), js([]),
     "Pronoun A bare; kuyu fish object.", "0.84"),
    (35, "Mother saw the fish in the water.", "Ngamu-ngku kuyu bana-nga nyajin.",
     "ngamu-ngku kuyu bana-nga nyaji-ny | mother-ERG fish water-LOC see-PAST", "r145_perception_mother_fish_water", 4, js(["ngamu", "kuyu", "bana", "nyaji-l"]), js([]),
     "Common-noun A ERG; bana-nga support location.", "0.84"),
    (36, "Father saw the sea turtle in the water.", "Nganjan-angka ngawiya bana-nga nyajin.",
     "nganjan-angka ngawiya bana-nga nyaji-ny | father-ERG sea.turtle water-LOC see-PAST", "r145_perception_father_sea_turtle_water", 4, js(["nganjan", "ngawiya", "bana", "nyaji-l"]), js([]),
     "Ngawiya object with support-only water LOC.", "0.84"),
    (37, "Grandmother saw the freshwater turtle in the water.", "Babi-ngka ngujay bana-nga nyajin.",
     "babi-ngka ngujay bana-nga nyaji-ny | grandmother-ERG freshwater.turtle water-LOC see-PAST", "r145_perception_grandmother_freshwater_turtle_water", 4, js(["babi", "ngujay", "bana", "nyaji-l"]), js([]),
     "Ngujay object with water LOC.", "0.84"),
    (38, "The child saw the saltwater crocodile in the water.", "Karrkay-ngku bilngkumu bana-nga nyajin.",
     "karrkay-ngku bilngkumu bana-nga nyaji-ny | child-ERG saltwater.crocodile water-LOC see-PAST", "r145_perception_child_crocodile_water", 4, js(["karrkay", "bilngkumu", "bana", "nyaji-l"]), js([]),
     "Bilngkumu object with water LOC; no fear/biting/story row.", "0.84"),
    (39, "Grandfather saw the freshwater eel in the water.", "Kami-ngka kubarr bana-nga nyajin.",
     "kami-ngka kubarr bana-nga nyaji-ny | grandfather-ERG freshwater.eel water-LOC see-PAST", "r145_perception_grandfather_eel_water", 4, js(["kami", "kubarr", "bana", "nyaji-l"]), js([]),
     "Kubarr eel object with water LOC.", "0.84"),
    (40, "The woman saw the stingray in the water.", "Jalbu-ngku yawu bana-nga nyajin.",
     "jalbu-ngku yawu bana-nga nyaji-ny | woman-ERG stingray water-LOC see-PAST", "r145_perception_woman_stingray_water", 4, js(["jalbu", "yawu", "bana", "nyaji-l"]), js([]),
     "Yawu object with water LOC; no storm/spearing row.", "0.84"),
    (41, "The man saw the shark in the water.", "Dingkar-angka yaburr bana-nga nyajin.",
     "dingkar-angka yaburr bana-nga nyaji-ny | man-ERG shark water-LOC see-PAST", "r145_perception_man_shark_water", 4, js(["dingkar", "yaburr", "bana", "nyaji-l"]), js([]),
     "Yaburr shark object with water LOC.", "0.84"),
    (42, "I saw the mud crab in the water.", "Ngayu kiju bana-nga nyajin.",
     "ngayu kiju bana-nga nyaji-ny | I mud.crab water-LOC see-PAST", "r145_perception_i_mud_crab_water", 4, js(["ngayu", "kiju", "bana", "nyaji-l"]), js([]),
     "Pronoun A bare; kiju object with water LOC.", "0.84"),
    (43, "We saw the sand crab in the water.", "Ngana ngalngal bana-nga nyajin.",
     "ngana ngalngal bana-nga nyaji-ny | we sand.crab water-LOC see-PAST", "r145_perception_we_sand_crab_water", 4, js(["ngana", "ngalngal", "bana", "nyaji-l"]), js([]),
     "Pronoun A bare; ngalngal whole head with water LOC.", "0.84"),
    (44, "I see the fish.", "Ngayu kuyu nyajil.",
     "ngayu kuyu nyaji-l | I fish see-NONPAST", "r145_present_i_fish", 4, js(["ngayu", "kuyu", "nyaji-l"]), js([]),
     "Nyaji-l nonpast perception construction; pronoun A bare.", "0.84"),
    (45, "We see the sea turtle.", "Ngana ngawiya nyajil.",
     "ngana ngawiya nyaji-l | we sea.turtle see-NONPAST", "r145_present_we_sea_turtle", 4, js(["ngana", "ngawiya", "nyaji-l"]), js([]),
     "Ngawiya object in present perception frame.", "0.84"),
    (46, "They see the freshwater turtle.", "Jana ngujay nyajil.",
     "jana ngujay nyaji-l | they freshwater.turtle see-NONPAST", "r145_present_they_freshwater_turtle", 4, js(["jana", "ngujay", "nyaji-l"]), js([]),
     "Ngujay object in present perception frame.", "0.84"),
    (47, "I see the saltwater crocodile.", "Ngayu bilngkumu nyajil.",
     "ngayu bilngkumu nyaji-l | I saltwater.crocodile see-NONPAST", "r145_present_i_crocodile", 4, js(["ngayu", "bilngkumu", "nyaji-l"]), js([]),
     "Bilngkumu object; no fear/story row.", "0.84"),
    (48, "We see the freshwater eel.", "Ngana kubarr nyajil.",
     "ngana kubarr nyaji-l | we freshwater.eel see-NONPAST", "r145_present_we_eel", 4, js(["ngana", "kubarr", "nyaji-l"]), js([]),
     "Kubarr eel object in present perception frame.", "0.84"),
    (49, "They see the stingray.", "Jana yawu nyajil.",
     "jana yawu nyaji-l | they stingray see-NONPAST", "r145_present_they_stingray", 4, js(["jana", "yawu", "nyaji-l"]), js([]),
     "Yawu stingray object; no storm/spearing row.", "0.84"),
    (50, "I see the shark.", "Ngayu yaburr nyajil.",
     "ngayu yaburr nyaji-l | I shark see-NONPAST", "r145_present_i_shark", 4, js(["ngayu", "yaburr", "nyaji-l"]), js([]),
     "Yaburr shark object; no biting 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:
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:ngka|ngku|angka|nga|ngu|ba|muny|bu|ji|jirr|ku|mundu|amu|mu)\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("r145_"):
            issues.append(f"{label}: Rule 145 frame must start with r145_")
        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 145 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 form in SUPPORT_FORMS:
            head = form.rsplit("-", 1)[0]
            if has_token(low, form) and head not in words:
                issues.append(f"{label}: support head {head} missing from words_used")
        if has_token(low, "bilngkumu") and ("crocodile" not in english_low or "saltwater" not in english_low):
            issues.append(f"{label}: bilngkumu must be saltwater crocodile")
        if has_token(low, "ngawiya") and "sea turtle" not in english_low:
            issues.append(f"{label}: ngawiya must be sea turtle")
        if has_token(low, "ngujay") and "freshwater turtle" not in english_low:
            issues.append(f"{label}: ngujay must be freshwater turtle")
        if has_token(low, "kubarr") and ("eel" not in english_low or "freshwater" not in english_low):
            issues.append(f"{label}: kubarr must be freshwater eel")
        if has_token(low, "kiju") and "mud crab" not in english_low:
            issues.append(f"{label}: kiju must be mud crab")
        if has_token(low, "ngalngal") and "sand crab" not in english_low:
            issues.append(f"{label}: ngalngal must be sand crab")
        if "-bu" in low or "INST" in analysis:
            issues.append(f"{label}: no instrument morphology in Rule 145")
        if any(bad in english_low for bad in DENIED_ENGLISH):
            issues.append(f"{label}: denied fishing/food/storm/fear/story field in English")
        if "nyajin" in low or "nyajil" in low:
            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 "nyajil" in low and "see-NONPAST" not in analysis:
            issues.append(f"{label}: nyajil must be glossed as see-NONPAST")
        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 145 draft: water-fauna lexical expansion after Rule 144", "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 145 source pass opened water/coastal fauna names only: kuyu fish, ngawiya sea turtle, ngujay "
            "freshwater turtle, bilngkumu saltwater crocodile, kubarr freshwater eel, yawu stingray, yaburr shark, "
            "kiju mud crab, and ngalngal sand crab. Grammar/source readback supports bana-nga water LOC, state "
            "predicates, and perception with nyaji-l."
        ),
        command="dictionary.yaml structured readback; grammar.md fish/turtle/crocodile/eel/stingray/shark/crab examples; sqlite lexeme and duplicate 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 145 admits water-fauna name rows only. It excludes catching, spearing, hooking, netting, cooking, "
            "roasting, stewing, eating, meat, food, taboo, storm, rain, fear, biting, illness, story, myth, totem, "
            "target case forms, instruments, and unstable ngiwa polysemy."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="duplicate-preflight",
        actor="claude-main",
        detail="Exact duplicate preflight found 50 planned Kuku surfaces, 50 unique within-batch surfaces, and no exact accepted/revised cross-corpus duplicates.",
        command="sqlite exact lower(trim(kuku)) duplicate scan before banking",
        artifact_path="batch_scripts/bank_187.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 water-fauna sense for every target, no loans, no target case forms, no instruments, no fishing/food/storm/fear/story field, common-noun A ERG in perception rows, and pronoun A bare.",
        command="python3 batch_scripts/bank_187.py static scan",
        artifact_path="batch_scripts/bank_187.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 145 water-fauna lexical rows.",
        command="python3 batch_scripts/bank_187.py",
        artifact_path="batch_scripts/bank_187.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())
