#!/usr/bin/env python3
"""Bank batch-220: Rule 178 construction consolidation after Rule 177."""

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

TARGET_HEADS = {
    "bimbarrbay": "scrub wallaby",
    "durrkin": "white-tailed rat",
    "jibul": "large bat",
    "kirbaji": "dugong",
    "jumar": "little wallaby",
    "murrumu": "dingo",
    "nyarrkali": "black kangaroo",
    "wambiji": "platypus",
    "waykal": "native cat",
    "yalmburrajaka": "whale",
}
PRONOUN_A = {"ngayu", "yundu", "jana", "ngana"}
DENY_KUKU = {
    "biki", "bikibiki", "buji", "danda", "kaya", "kaya-kaya", "yarraman", "bala", "jakal",
    "jarrabina", "kambi", "kukur", "kulngu", "mali", "mayarriji", "murral", "murramu",
    "ngamu-murramu", "ngangkin", "ngumbi", "yawa", "darrba",
}
DENY_ENGLISH = (
    "loan", "pig", "horse", "dog", "cat loan", "pussy", "clothing", "iron", "mother", "food", "edible",
    "eat", "eats", "ate", "eaten", "hunt", "hunting", "catch", "catches", "caught", "trap", "trapped",
    "kill", "killed", "bite", "bit", "spear", "speared", "totem", "moiety", "story", "story-site",
    "myth", "mythological", "place", "place-name", "cave", "kangkaji", "kaway", "kurrbi", "skin",
    "body-part", "tail", "sinew", "gristle", "blood vessel", "habitat", "tree-climbing", "climb",
    "river", "sea", "ocean", "water", "country", "ceremony", "introduced", "post-contact", "use", "uses",
    "made", "material", "source", "bone", "meat", "plural", "many",
)


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 scrub wallaby is there.", "Bimbarrbay yinyay.", "bimbarrbay yinyay | scrub.wallaby there", "r178_deictic_bimbarrbay_there", ["bimbarrbay", "yinyay"], "Rule 178 consolidates bimbarrbay as scrub wallaby with yinyay support."),
    row(2, "The white-tailed rat is there.", "Durrkin yinyay.", "durrkin yinyay | white-tailed.rat there", "r178_deictic_durrkin_there", ["durrkin", "yinyay"], "Rule 178 consolidates durrkin as white-tailed rat; tail/body-part rows remain closed."),
    row(3, "The large bat is there.", "Jibul yinyay.", "jibul yinyay | large.bat there", "r178_deictic_jibul_there", ["jibul", "yinyay"], "Rule 178 consolidates jibul as large bat; cave/place/story-site prose remains closed."),
    row(4, "The dugong is there.", "Kirbaji yinyay.", "kirbaji yinyay | dugong there", "r178_deictic_kirbaji_there", ["kirbaji", "yinyay"], "Rule 178 consolidates kirbaji as dugong; marine-event prose not imported."),
    row(5, "The little wallaby is there.", "Jumar yinyay.", "jumar yinyay | little.wallaby there", "r178_deictic_jumar_there", ["jumar", "yinyay"], "Rule 178 consolidates jumar as little wallaby; no size predicate row."),
    row(6, "The dingo is there.", "Murrumu yinyay.", "murrumu yinyay | dingo there", "r178_deictic_murrumu_there", ["murrumu", "yinyay"], "Rule 178 consolidates murrumu as dingo; dog/loan drift remains closed."),
    row(7, "The black kangaroo is there.", "Nyarrkali yinyay.", "nyarrkali yinyay | black.kangaroo there", "r178_deictic_nyarrkali_there", ["nyarrkali", "yinyay"], "Rule 178 consolidates nyarrkali as black kangaroo; no colour predicate row."),
    row(8, "The platypus is there.", "Wambiji yinyay.", "wambiji yinyay | platypus there", "r178_deictic_wambiji_there", ["wambiji", "yinyay"], "Rule 178 consolidates wambiji as platypus; no water/habitat row."),
    row(9, "The native cat is there.", "Waykal yinyay.", "waykal yinyay | native.cat there", "r178_deictic_waykal_there", ["waykal", "yinyay"], "Rule 178 consolidates waykal as native cat; not cat-loan buji."),
    row(10, "The whale is there.", "Yalmburrajaka yinyay.", "yalmburrajaka yinyay | whale there", "r178_deictic_yalmburrajaka_there", ["yalmburrajaka", "yinyay"], "Rule 178 consolidates yalmburrajaka as whale; no sea/ocean event row."),
    row(11, "I see the scrub wallaby here.", "Ngayu bimbarrbay yaluy nyajil.", "ngayu bimbarrbay yaluy nyaji-l | I scrub.wallaby here see-NONPAST", "r178_present_i_bimbarrbay_here", ["ngayu", "bimbarrbay", "yaluy", "nyaji-l"], "First-person present perception with yaluy support."),
    row(12, "I see the white-tailed rat here.", "Ngayu durrkin yaluy nyajil.", "ngayu durrkin yaluy nyaji-l | I white-tailed.rat here see-NONPAST", "r178_present_i_durrkin_here", ["ngayu", "durrkin", "yaluy", "nyaji-l"], "First-person present perception; target object bare."),
    row(13, "I see the large bat here.", "Ngayu jibul yaluy nyajil.", "ngayu jibul yaluy nyaji-l | I large.bat here see-NONPAST", "r178_present_i_jibul_here", ["ngayu", "jibul", "yaluy", "nyaji-l"], "First-person present perception; cave/place/story sense closed."),
    row(14, "I see the dugong here.", "Ngayu kirbaji yaluy nyajil.", "ngayu kirbaji yaluy nyaji-l | I dugong here see-NONPAST", "r178_present_i_kirbaji_here", ["ngayu", "kirbaji", "yaluy", "nyaji-l"], "First-person present perception with yaluy support."),
    row(15, "I see the little wallaby here.", "Ngayu jumar yaluy nyajil.", "ngayu jumar yaluy nyaji-l | I little.wallaby here see-NONPAST", "r178_present_i_jumar_here", ["ngayu", "jumar", "yaluy", "nyaji-l"], "First-person present perception; target object bare."),
    row(16, "I see the dingo here.", "Ngayu murrumu yaluy nyajil.", "ngayu murrumu yaluy nyaji-l | I dingo here see-NONPAST", "r178_present_i_murrumu_here", ["ngayu", "murrumu", "yaluy", "nyaji-l"], "First-person present perception; dog/loan drift closed."),
    row(17, "I see the black kangaroo here.", "Ngayu nyarrkali yaluy nyajil.", "ngayu nyarrkali yaluy nyaji-l | I black.kangaroo here see-NONPAST", "r178_present_i_nyarrkali_here", ["ngayu", "nyarrkali", "yaluy", "nyaji-l"], "First-person present perception with yaluy support."),
    row(18, "I see the platypus here.", "Ngayu wambiji yaluy nyajil.", "ngayu wambiji yaluy nyaji-l | I platypus here see-NONPAST", "r178_present_i_wambiji_here", ["ngayu", "wambiji", "yaluy", "nyaji-l"], "First-person present perception; habitat prose not imported."),
    row(19, "I see the native cat here.", "Ngayu waykal yaluy nyajil.", "ngayu waykal yaluy nyaji-l | I native.cat here see-NONPAST", "r178_present_i_waykal_here", ["ngayu", "waykal", "yaluy", "nyaji-l"], "First-person present perception; native cat as dictionary head only."),
    row(20, "I see the whale here.", "Ngayu yalmburrajaka yaluy nyajil.", "ngayu yalmburrajaka yaluy nyaji-l | I whale here see-NONPAST", "r178_present_i_yalmburrajaka_here", ["ngayu", "yalmburrajaka", "yaluy", "nyaji-l"], "First-person present perception; marine-event prose not imported."),
    row(21, "You see the scrub wallaby here.", "Yundu bimbarrbay yaluy nyajil.", "yundu bimbarrbay yaluy nyaji-l | you scrub.wallaby here see-NONPAST", "r178_present_you_bimbarrbay_here", ["yundu", "bimbarrbay", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(22, "You see the white-tailed rat here.", "Yundu durrkin yaluy nyajil.", "yundu durrkin yaluy nyaji-l | you white-tailed.rat here see-NONPAST", "r178_present_you_durrkin_here", ["yundu", "durrkin", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(23, "You see the large bat here.", "Yundu jibul yaluy nyajil.", "yundu jibul yaluy nyaji-l | you large.bat here see-NONPAST", "r178_present_you_jibul_here", ["yundu", "jibul", "yaluy", "nyaji-l"], "Second-person present perception; place/story sense closed."),
    row(24, "You see the dugong here.", "Yundu kirbaji yaluy nyajil.", "yundu kirbaji yaluy nyaji-l | you dugong here see-NONPAST", "r178_present_you_kirbaji_here", ["yundu", "kirbaji", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(25, "You see the little wallaby here.", "Yundu jumar yaluy nyajil.", "yundu jumar yaluy nyaji-l | you little.wallaby here see-NONPAST", "r178_present_you_jumar_here", ["yundu", "jumar", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(26, "You see the dingo here.", "Yundu murrumu yaluy nyajil.", "yundu murrumu yaluy nyaji-l | you dingo here see-NONPAST", "r178_present_you_murrumu_here", ["yundu", "murrumu", "yaluy", "nyaji-l"], "Second-person present perception; dog/loan drift closed."),
    row(27, "You see the black kangaroo here.", "Yundu nyarrkali yaluy nyajil.", "yundu nyarrkali yaluy nyaji-l | you black.kangaroo here see-NONPAST", "r178_present_you_nyarrkali_here", ["yundu", "nyarrkali", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(28, "You see the platypus here.", "Yundu wambiji yaluy nyajil.", "yundu wambiji yaluy nyaji-l | you platypus here see-NONPAST", "r178_present_you_wambiji_here", ["yundu", "wambiji", "yaluy", "nyaji-l"], "Second-person present perception with yaluy support."),
    row(29, "You see the native cat here.", "Yundu waykal yaluy nyajil.", "yundu waykal yaluy nyaji-l | you native.cat here see-NONPAST", "r178_present_you_waykal_here", ["yundu", "waykal", "yaluy", "nyaji-l"], "Second-person present perception; native cat as dictionary head only."),
    row(30, "You see the whale here.", "Yundu yalmburrajaka yaluy nyajil.", "yundu yalmburrajaka yaluy nyaji-l | you whale here see-NONPAST", "r178_present_you_yalmburrajaka_here", ["yundu", "yalmburrajaka", "yaluy", "nyaji-l"], "Second-person present perception; sea/ocean event prose closed."),
    row(31, "They saw the scrub wallaby here.", "Jana bimbarrbay yaluy nyajin.", "jana bimbarrbay yaluy nyaji-ny | they scrub.wallaby here see-PAST", "r178_past_they_bimbarrbay_here", ["jana", "bimbarrbay", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(32, "They saw the white-tailed rat here.", "Jana durrkin yaluy nyajin.", "jana durrkin yaluy nyaji-ny | they white-tailed.rat here see-PAST", "r178_past_they_durrkin_here", ["jana", "durrkin", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(33, "They saw the large bat here.", "Jana jibul yaluy nyajin.", "jana jibul yaluy nyaji-ny | they large.bat here see-PAST", "r178_past_they_jibul_here", ["jana", "jibul", "yaluy", "nyaji-l"], "Third-person plural past perception; cave/place/story sense closed."),
    row(34, "They saw the dugong here.", "Jana kirbaji yaluy nyajin.", "jana kirbaji yaluy nyaji-ny | they dugong here see-PAST", "r178_past_they_kirbaji_here", ["jana", "kirbaji", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(35, "They saw the little wallaby here.", "Jana jumar yaluy nyajin.", "jana jumar yaluy nyaji-ny | they little.wallaby here see-PAST", "r178_past_they_jumar_here", ["jana", "jumar", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(36, "They saw the dingo here.", "Jana murrumu yaluy nyajin.", "jana murrumu yaluy nyaji-ny | they dingo here see-PAST", "r178_past_they_murrumu_here", ["jana", "murrumu", "yaluy", "nyaji-l"], "Third-person plural past perception; dog/loan drift closed."),
    row(37, "They saw the black kangaroo here.", "Jana nyarrkali yaluy nyajin.", "jana nyarrkali yaluy nyaji-ny | they black.kangaroo here see-PAST", "r178_past_they_nyarrkali_here", ["jana", "nyarrkali", "yaluy", "nyaji-l"], "Third-person plural past perception with yaluy support."),
    row(38, "They saw the platypus here.", "Jana wambiji yaluy nyajin.", "jana wambiji yaluy nyaji-ny | they platypus here see-PAST", "r178_past_they_wambiji_here", ["jana", "wambiji", "yaluy", "nyaji-l"], "Third-person plural past perception; habitat prose not imported."),
    row(39, "They saw the native cat here.", "Jana waykal yaluy nyajin.", "jana waykal yaluy nyaji-ny | they native.cat here see-PAST", "r178_past_they_waykal_here", ["jana", "waykal", "yaluy", "nyaji-l"], "Third-person plural past perception; native cat as dictionary head only."),
    row(40, "They saw the whale here.", "Jana yalmburrajaka yaluy nyajin.", "jana yalmburrajaka yaluy nyaji-ny | they whale here see-PAST", "r178_past_they_yalmburrajaka_here", ["jana", "yalmburrajaka", "yaluy", "nyaji-l"], "Third-person plural past perception; marine-event prose closed."),
    row(41, "We see the scrub wallaby there.", "Ngana bimbarrbay yinyay nyajil.", "ngana bimbarrbay yinyay nyaji-l | we scrub.wallaby there see-NONPAST", "r178_present_we_bimbarrbay_there", ["ngana", "bimbarrbay", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(42, "We see the white-tailed rat there.", "Ngana durrkin yinyay nyajil.", "ngana durrkin yinyay nyaji-l | we white-tailed.rat there see-NONPAST", "r178_present_we_durrkin_there", ["ngana", "durrkin", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(43, "We see the large bat there.", "Ngana jibul yinyay nyajil.", "ngana jibul yinyay nyaji-l | we large.bat there see-NONPAST", "r178_present_we_jibul_there", ["ngana", "jibul", "yinyay", "nyaji-l"], "Inclusive present perception; place/story sense closed."),
    row(44, "We see the dugong there.", "Ngana kirbaji yinyay nyajil.", "ngana kirbaji yinyay nyaji-l | we dugong there see-NONPAST", "r178_present_we_kirbaji_there", ["ngana", "kirbaji", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(45, "We see the little wallaby there.", "Ngana jumar yinyay nyajil.", "ngana jumar yinyay nyaji-l | we little.wallaby there see-NONPAST", "r178_present_we_jumar_there", ["ngana", "jumar", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(46, "We see the dingo there.", "Ngana murrumu yinyay nyajil.", "ngana murrumu yinyay nyaji-l | we dingo there see-NONPAST", "r178_present_we_murrumu_there", ["ngana", "murrumu", "yinyay", "nyaji-l"], "Inclusive present perception; dog/loan drift closed."),
    row(47, "We see the black kangaroo there.", "Ngana nyarrkali yinyay nyajil.", "ngana nyarrkali yinyay nyaji-l | we black.kangaroo there see-NONPAST", "r178_present_we_nyarrkali_there", ["ngana", "nyarrkali", "yinyay", "nyaji-l"], "Inclusive present perception with yinyay support."),
    row(48, "We see the platypus there.", "Ngana wambiji yinyay nyajil.", "ngana wambiji yinyay nyaji-l | we platypus there see-NONPAST", "r178_present_we_wambiji_there", ["ngana", "wambiji", "yinyay", "nyaji-l"], "Inclusive present perception; habitat prose not imported."),
    row(49, "We see the native cat there.", "Ngana waykal yinyay nyajil.", "ngana waykal yinyay nyaji-l | we native.cat there see-NONPAST", "r178_present_we_waykal_there", ["ngana", "waykal", "yinyay", "nyaji-l"], "Inclusive present perception; native cat as dictionary head only."),
    row(50, "We see the whale there.", "Ngana yalmburrajaka yinyay nyajil.", "ngana yalmburrajaka yinyay nyaji-l | we whale there see-NONPAST", "r178_present_we_yalmburrajaka_there", ["ngana", "yalmburrajaka", "yinyay", "nyaji-l"], "Inclusive present perception; sea/ocean event prose closed."),
]


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("r178_"):
            issues.append(f"{label}: Rule 178 frame must start with r178_")
        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 178")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied mammal/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 Rule 177 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 178 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 178 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 mammal event/use/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 178 draft: construction consolidation after Rule 177", "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 178 source pass retained exactly the ten Rule 177 native mammal-reference heads: bimbarrbay, durrkin, jibul, kirbaji, jumar, murrumu, nyarrkali, wambiji, waykal, and yalmburrajaka. Dictionary exact readback reconfirmed selected senses. Patz readback reconfirmed support forms yaluy/yinyay, nyaji-l/nyaji-ny, and bare pronoun A forms. Corpus readback found the heads present through Batch 219 and zero Batch 220 rows before banking.",
                 command="rg dictionary readback; rg 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 178 is a construction consolidation pass for the Rule 177 native mammal-reference field. It admits no new mammal heads and excludes dog/pig/horse/cat-loan drift, introduced-animal rows, place/story/cave senses, iron/clothing/source-material extensions, food, hunting, catching, biting, killing, habitat/ecology, body-part, target case, target compounds, possession, plural derivation, 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_220.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guards passed: exactly one retained Rule 177 mammal target per row; all targets bare; no loans; no old mammal/use/event/polysemy tokens; no target case/compound forms; pronoun A bare; yinyay/yaluy support only.",
                 command="python3 batch_scripts/bank_220.py static scan", artifact_path="batch_scripts/bank_220.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())
