#!/usr/bin/env python3
"""Bank batch-221: Rule 179 fish and ray reference lexical expansion."""

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

TARGET_HEADS = {
    "badibu": "spotted eagle ray",
    "bilaji": "flat-tailed ray",
    "bunjulu": "giant herring",
    "jilngu": "tarpon",
    "karrulji": "black spotted dart",
    "kilkirr": "tiger shark",
    "midir": "knight fish",
    "ngarmu": "shovelnose ray",
    "yalnganji": "manta ray",
    "yubuji": "sawfish",
}
PRONOUN_A = {"ngayu", "yundu", "jana", "ngana"}
DENY_KUKU = {
    "baban", "balarri", "barrbal", "bayil", "bayilbayil", "bilmbin", "bububay", "dalkal",
    "dalku", "dalmbal", "danbal", "dandarrbina", "dawal", "dijarr", "dukul-barangkal",
    "jalil-jalil", "jalkay", "janbal", "jarbir", "jijirr", "jindi", "judi", "jukar-jukarbina",
    "julaji", "julba", "julmba", "kalngkan", "kalu", "kanyaji", "kijukum", "kubarr",
    "kubija", "kubu-kubu", "kudi", "kundur-kundur", "kundurr-kundurr", "kurriyala", "kurruba",
    "kurrubal", "kuyu", "mangkurr-mangkurr", "mawul", "milkaji", "mujarrka", "murrabal",
    "murraja", "ngalkun", "ngiwa", "walarrji", "walbul-walbul", "walkan", "wara",
    "wuyumbu", "yaba", "yaburr", "yawu", "yidi", "yidilnyul",
    "badur", "makarr", "yinba", "yirrmba", "kanya", "banda",
}
DENY_ENGLISH = (
    "catch", "catches", "caught", "catching", "net", "hook", "line", "spear", "spears", "speared",
    "spearing", "eat", "eats", "ate", "eaten", "edible", "food", "meat", "cook", "cooked", "cooking",
    "taboo", "old people", "allowed to eat", "storm", "thunder", "rainstorm", "story", "story-site",
    "myth", "mythological", "place", "place-name", "river", "sea", "ocean", "water", "freshwater",
    "saltwater", "mangrove", "habitat", "reef", "scale", "fin", "tail", "body-part", "whiskers",
    "with whiskers", "source", "material", "use", "uses", "made", "poison", "danger", "bite", "bit",
    "biting", "kill", "killed", "hunting", "hunt",
)


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 spotted eagle ray is here.", "Badibu yaluy.", "badibu yaluy | spotted.eagle.ray here", "r179_deictic_badibu_here", ["badibu", "yaluy"], "Rule 179 admits badibu as spotted eagle ray; ordinary reference only."),
    row(2, "The flat-tailed ray is here.", "Bilaji yaluy.", "bilaji yaluy | flat-tailed.ray here", "r179_deictic_bilaji_here", ["bilaji", "yaluy"], "Rule 179 admits bilaji as flat-tailed ray; ordinary reference only."),
    row(3, "The giant herring is here.", "Bunjulu yaluy.", "bunjulu yaluy | giant.herring here", "r179_deictic_bunjulu_here", ["bunjulu", "yaluy"], "Rule 179 admits bunjulu as giant herring; empty duplicate entry not used."),
    row(4, "The tarpon is here.", "Jilngu yaluy.", "jilngu yaluy | tarpon here", "r179_deictic_jilngu_here", ["jilngu", "yaluy"], "Rule 179 admits jilngu as tarpon; boney-salmon alternate prose not opened."),
    row(5, "The black spotted dart is here.", "Karrulji yaluy.", "karrulji yaluy | black.spotted.dart here", "r179_deictic_karrulji_here", ["karrulji", "yaluy"], "Rule 179 admits karrulji as black spotted dart."),
    row(6, "The tiger shark is here.", "Kilkirr yaluy.", "kilkirr yaluy | tiger.shark here", "r179_deictic_kilkirr_here", ["kilkirr", "yaluy"], "Rule 179 admits kilkirr as tiger shark; danger/event prose not imported."),
    row(7, "The knight fish is here.", "Midir yaluy.", "midir yaluy | knight.fish here", "r179_deictic_midir_here", ["midir", "yaluy"], "Rule 179 admits midir as knight fish; port-and-light alternate label not opened."),
    row(8, "The shovelnose ray is here.", "Ngarmu yaluy.", "ngarmu yaluy | shovelnose.ray here", "r179_deictic_ngarmu_here", ["ngarmu", "yaluy"], "Rule 179 admits ngarmu as shovelnose ray."),
    row(9, "The manta ray is here.", "Yalnganji yaluy.", "yalnganji yaluy | manta.ray here", "r179_deictic_yalnganji_here", ["yalnganji", "yaluy"], "Rule 179 admits yalnganji as Yalanji manta ray; Nyungkul yidi stays closed."),
    row(10, "The sawfish is here.", "Yubuji yaluy.", "yubuji yaluy | sawfish here", "r179_deictic_yubuji_here", ["yubuji", "yaluy"], "Rule 179 admits yubuji as sawfish; ordinary reference only."),
    row(11, "I see the spotted eagle ray.", "Ngayu badibu nyajil.", "ngayu badibu nyaji-l | I spotted.eagle.ray see-NONPAST", "r179_present_i_badibu", ["ngayu", "badibu", "nyaji-l"], "First-person present perception; target object bare."),
    row(12, "I see the flat-tailed ray.", "Ngayu bilaji nyajil.", "ngayu bilaji nyaji-l | I flat-tailed.ray see-NONPAST", "r179_present_i_bilaji", ["ngayu", "bilaji", "nyaji-l"], "First-person present perception; target object bare."),
    row(13, "I see the giant herring.", "Ngayu bunjulu nyajil.", "ngayu bunjulu nyaji-l | I giant.herring see-NONPAST", "r179_present_i_bunjulu", ["ngayu", "bunjulu", "nyaji-l"], "First-person present perception; target object bare."),
    row(14, "I see the tarpon.", "Ngayu jilngu nyajil.", "ngayu jilngu nyaji-l | I tarpon see-NONPAST", "r179_present_i_jilngu", ["ngayu", "jilngu", "nyaji-l"], "First-person present perception; selected tarpon sense only."),
    row(15, "I see the black spotted dart.", "Ngayu karrulji nyajil.", "ngayu karrulji nyaji-l | I black.spotted.dart see-NONPAST", "r179_present_i_karrulji", ["ngayu", "karrulji", "nyaji-l"], "First-person present perception; target object bare."),
    row(16, "I see the tiger shark.", "Ngayu kilkirr nyajil.", "ngayu kilkirr nyaji-l | I tiger.shark see-NONPAST", "r179_present_i_kilkirr", ["ngayu", "kilkirr", "nyaji-l"], "First-person present perception; danger/event prose not imported."),
    row(17, "I see the knight fish.", "Ngayu midir nyajil.", "ngayu midir nyaji-l | I knight.fish see-NONPAST", "r179_present_i_midir", ["ngayu", "midir", "nyaji-l"], "First-person present perception; target object bare."),
    row(18, "I see the shovelnose ray.", "Ngayu ngarmu nyajil.", "ngayu ngarmu nyaji-l | I shovelnose.ray see-NONPAST", "r179_present_i_ngarmu", ["ngayu", "ngarmu", "nyaji-l"], "First-person present perception; target object bare."),
    row(19, "I see the manta ray.", "Ngayu yalnganji nyajil.", "ngayu yalnganji nyaji-l | I manta.ray see-NONPAST", "r179_present_i_yalnganji", ["ngayu", "yalnganji", "nyaji-l"], "First-person present perception; Yalanji target selected."),
    row(20, "I see the sawfish.", "Ngayu yubuji nyajil.", "ngayu yubuji nyaji-l | I sawfish see-NONPAST", "r179_present_i_yubuji", ["ngayu", "yubuji", "nyaji-l"], "First-person present perception; target object bare."),
    row(21, "You see the spotted eagle ray there.", "Yundu badibu yinyay nyajil.", "yundu badibu yinyay nyaji-l | you spotted.eagle.ray there see-NONPAST", "r179_present_you_badibu_there", ["yundu", "badibu", "yinyay", "nyaji-l"], "Second-person present perception with yinyay support."),
    row(22, "You see the flat-tailed ray there.", "Yundu bilaji yinyay nyajil.", "yundu bilaji yinyay nyaji-l | you flat-tailed.ray there see-NONPAST", "r179_present_you_bilaji_there", ["yundu", "bilaji", "yinyay", "nyaji-l"], "Second-person present perception with yinyay support."),
    row(23, "You see the giant herring there.", "Yundu bunjulu yinyay nyajil.", "yundu bunjulu yinyay nyaji-l | you giant.herring there see-NONPAST", "r179_present_you_bunjulu_there", ["yundu", "bunjulu", "yinyay", "nyaji-l"], "Second-person present perception; target object bare."),
    row(24, "You see the tarpon there.", "Yundu jilngu yinyay nyajil.", "yundu jilngu yinyay nyaji-l | you tarpon there see-NONPAST", "r179_present_you_jilngu_there", ["yundu", "jilngu", "yinyay", "nyaji-l"], "Second-person present perception; selected tarpon sense only."),
    row(25, "You see the black spotted dart there.", "Yundu karrulji yinyay nyajil.", "yundu karrulji yinyay nyaji-l | you black.spotted.dart there see-NONPAST", "r179_present_you_karrulji_there", ["yundu", "karrulji", "yinyay", "nyaji-l"], "Second-person present perception with yinyay support."),
    row(26, "You see the tiger shark there.", "Yundu kilkirr yinyay nyajil.", "yundu kilkirr yinyay nyaji-l | you tiger.shark there see-NONPAST", "r179_present_you_kilkirr_there", ["yundu", "kilkirr", "yinyay", "nyaji-l"], "Second-person present perception; danger/event prose not imported."),
    row(27, "You see the knight fish there.", "Yundu midir yinyay nyajil.", "yundu midir yinyay nyaji-l | you knight.fish there see-NONPAST", "r179_present_you_midir_there", ["yundu", "midir", "yinyay", "nyaji-l"], "Second-person present perception with yinyay support."),
    row(28, "You see the shovelnose ray there.", "Yundu ngarmu yinyay nyajil.", "yundu ngarmu yinyay nyaji-l | you shovelnose.ray there see-NONPAST", "r179_present_you_ngarmu_there", ["yundu", "ngarmu", "yinyay", "nyaji-l"], "Second-person present perception with yinyay support."),
    row(29, "You see the manta ray there.", "Yundu yalnganji yinyay nyajil.", "yundu yalnganji yinyay nyaji-l | you manta.ray there see-NONPAST", "r179_present_you_yalnganji_there", ["yundu", "yalnganji", "yinyay", "nyaji-l"], "Second-person present perception; Yalanji target selected."),
    row(30, "You see the sawfish there.", "Yundu yubuji yinyay nyajil.", "yundu yubuji yinyay nyaji-l | you sawfish there see-NONPAST", "r179_present_you_yubuji_there", ["yundu", "yubuji", "yinyay", "nyaji-l"], "Second-person present perception; target object bare."),
    row(31, "They saw the spotted eagle ray there.", "Jana badibu yinyay nyajin.", "jana badibu yinyay nyaji-ny | they spotted.eagle.ray there see-PAST", "r179_past_they_badibu_there", ["jana", "badibu", "yinyay", "nyaji-l"], "Third-person plural past perception with yinyay support."),
    row(32, "They saw the flat-tailed ray there.", "Jana bilaji yinyay nyajin.", "jana bilaji yinyay nyaji-ny | they flat-tailed.ray there see-PAST", "r179_past_they_bilaji_there", ["jana", "bilaji", "yinyay", "nyaji-l"], "Third-person plural past perception with yinyay support."),
    row(33, "They saw the giant herring there.", "Jana bunjulu yinyay nyajin.", "jana bunjulu yinyay nyaji-ny | they giant.herring there see-PAST", "r179_past_they_bunjulu_there", ["jana", "bunjulu", "yinyay", "nyaji-l"], "Third-person plural past perception; target object bare."),
    row(34, "They saw the tarpon there.", "Jana jilngu yinyay nyajin.", "jana jilngu yinyay nyaji-ny | they tarpon there see-PAST", "r179_past_they_jilngu_there", ["jana", "jilngu", "yinyay", "nyaji-l"], "Third-person plural past perception; selected tarpon sense only."),
    row(35, "They saw the black spotted dart there.", "Jana karrulji yinyay nyajin.", "jana karrulji yinyay nyaji-ny | they black.spotted.dart there see-PAST", "r179_past_they_karrulji_there", ["jana", "karrulji", "yinyay", "nyaji-l"], "Third-person plural past perception with yinyay support."),
    row(36, "They saw the tiger shark there.", "Jana kilkirr yinyay nyajin.", "jana kilkirr yinyay nyaji-ny | they tiger.shark there see-PAST", "r179_past_they_kilkirr_there", ["jana", "kilkirr", "yinyay", "nyaji-l"], "Third-person plural past perception; danger/event prose not imported."),
    row(37, "They saw the knight fish there.", "Jana midir yinyay nyajin.", "jana midir yinyay nyaji-ny | they knight.fish there see-PAST", "r179_past_they_midir_there", ["jana", "midir", "yinyay", "nyaji-l"], "Third-person plural past perception with yinyay support."),
    row(38, "They saw the shovelnose ray there.", "Jana ngarmu yinyay nyajin.", "jana ngarmu yinyay nyaji-ny | they shovelnose.ray there see-PAST", "r179_past_they_ngarmu_there", ["jana", "ngarmu", "yinyay", "nyaji-l"], "Third-person plural past perception with yinyay support."),
    row(39, "They saw the manta ray there.", "Jana yalnganji yinyay nyajin.", "jana yalnganji yinyay nyaji-ny | they manta.ray there see-PAST", "r179_past_they_yalnganji_there", ["jana", "yalnganji", "yinyay", "nyaji-l"], "Third-person plural past perception; Yalanji target selected."),
    row(40, "They saw the sawfish there.", "Jana yubuji yinyay nyajin.", "jana yubuji yinyay nyaji-ny | they sawfish there see-PAST", "r179_past_they_yubuji_there", ["jana", "yubuji", "yinyay", "nyaji-l"], "Third-person plural past perception; target object bare."),
    row(41, "We see the spotted eagle ray here.", "Ngana badibu yaluy nyajil.", "ngana badibu yaluy nyaji-l | we spotted.eagle.ray here see-NONPAST", "r179_present_we_badibu_here", ["ngana", "badibu", "yaluy", "nyaji-l"], "Inclusive present perception with yaluy support."),
    row(42, "We see the flat-tailed ray here.", "Ngana bilaji yaluy nyajil.", "ngana bilaji yaluy nyaji-l | we flat-tailed.ray here see-NONPAST", "r179_present_we_bilaji_here", ["ngana", "bilaji", "yaluy", "nyaji-l"], "Inclusive present perception with yaluy support."),
    row(43, "We see the giant herring here.", "Ngana bunjulu yaluy nyajil.", "ngana bunjulu yaluy nyaji-l | we giant.herring here see-NONPAST", "r179_present_we_bunjulu_here", ["ngana", "bunjulu", "yaluy", "nyaji-l"], "Inclusive present perception; target object bare."),
    row(44, "We see the tarpon here.", "Ngana jilngu yaluy nyajil.", "ngana jilngu yaluy nyaji-l | we tarpon here see-NONPAST", "r179_present_we_jilngu_here", ["ngana", "jilngu", "yaluy", "nyaji-l"], "Inclusive present perception; selected tarpon sense only."),
    row(45, "We see the black spotted dart here.", "Ngana karrulji yaluy nyajil.", "ngana karrulji yaluy nyaji-l | we black.spotted.dart here see-NONPAST", "r179_present_we_karrulji_here", ["ngana", "karrulji", "yaluy", "nyaji-l"], "Inclusive present perception with yaluy support."),
    row(46, "We see the tiger shark here.", "Ngana kilkirr yaluy nyajil.", "ngana kilkirr yaluy nyaji-l | we tiger.shark here see-NONPAST", "r179_present_we_kilkirr_here", ["ngana", "kilkirr", "yaluy", "nyaji-l"], "Inclusive present perception; danger/event prose not imported."),
    row(47, "We see the knight fish here.", "Ngana midir yaluy nyajil.", "ngana midir yaluy nyaji-l | we knight.fish here see-NONPAST", "r179_present_we_midir_here", ["ngana", "midir", "yaluy", "nyaji-l"], "Inclusive present perception with yaluy support."),
    row(48, "We see the shovelnose ray here.", "Ngana ngarmu yaluy nyajil.", "ngana ngarmu yaluy nyaji-l | we shovelnose.ray here see-NONPAST", "r179_present_we_ngarmu_here", ["ngana", "ngarmu", "yaluy", "nyaji-l"], "Inclusive present perception with yaluy support."),
    row(49, "We see the manta ray here.", "Ngana yalnganji yaluy nyajil.", "ngana yalnganji yaluy nyaji-l | we manta.ray here see-NONPAST", "r179_present_we_yalnganji_here", ["ngana", "yalnganji", "yaluy", "nyaji-l"], "Inclusive present perception; Yalanji target selected."),
    row(50, "We see the sawfish here.", "Ngana yubuji yaluy nyajil.", "ngana yubuji yaluy nyaji-l | we sawfish here see-NONPAST", "r179_present_we_yubuji_here", ["ngana", "yubuji", "yaluy", "nyaji-l"], "Inclusive present perception; target object bare."),
]


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("r179_"):
            issues.append(f"{label}: Rule 179 frame must start with r179_")
        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 179")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied fish/use/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 179 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, "yaluy") or "yaluy" not in words or "here" not in english_low:
                issues.append(f"{label}: Rule 179 deictic rows require yaluy/here")
        else:
            if ("nyajin" not in low and "nyajil" not in low) or "nyaji-l" not in words:
                issues.append(f"{label}: Rule 179 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":
                issues.append(f"{label}: rows 11-20 require ngayu")
            if 21 <= seq <= 30 and (first != "yundu" or not has_token(low, "yinyay") or "there" not in english_low):
                issues.append(f"{label}: rows 21-30 require yundu + yinyay/there")
            if 31 <= seq <= 40 and (first != "jana" or not has_token(low, "yinyay") or "saw" not in english_low):
                issues.append(f"{label}: rows 31-40 require jana + yinyay + past saw")
            if 41 <= seq <= 50 and (first != "ngana" or not has_token(low, "yaluy") or "here" not in english_low):
                issues.append(f"{label}: rows 41-50 require ngana + yaluy/here")
            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 fish 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 179 draft: fish and ray reference lexical expansion after Rule 178", "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 179 source pass selected ten zero-hit fish/ray/shark reference heads with exact dictionary readback: badibu spotted eagle ray, bilaji flat-tailed ray, bunjulu giant herring, jilngu tarpon, karrulji black spotted dart, kilkirr tiger shark, midir knight fish, ngarmu shovelnose ray, yalnganji manta ray, and yubuji sawfish. Dictionary readback found duplicate or alternate entries for several heads, but all selected senses are explicit. Patz readback reconfirmed support forms yaluy/yinyay, nyaji-l/nyaji-ny, and bare pronoun A forms. Boundary-aware corpus readback found zero accepted/revised/under_review rows for selected targets.",
                 command="structured dictionary YAML readback; rg grammar readback; sqlite boundary-aware corpus 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 179 is a lexical fish/ray reference pass for ten bare target heads. It excludes nonselected fish heads, catching, netting, hook/line, spearing, eating, cooking, food, taboo, storm/thunder, story, place, water/river/sea/habitat, fish body-part terms, target case, target compounds, possession, 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_221.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 Rule 179 target per row; all targets bare; no loans; no nonselected fish/use/event/polysemy tokens; no target case/compound forms; pronoun A bare; yinyay/yaluy support only.",
                 command="python3 batch_scripts/bank_221.py static scan", artifact_path="batch_scripts/bank_221.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())
