#!/usr/bin/env python3
"""Bank batch-208: Rule 166 construction consolidation after Rule 165."""

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

TARGET_HEADS = {
    "babajaka": "bloodwood tree",
    "balmbalka": "balmbalka tree",
    "baway": "black bean tree",
    "bijin": "tea tree",
    "dalban": "oak tree",
    "dingki": "dingki tree",
    "dulmbil": "umbrella palm",
    "duwar": "black palm",
    "kalal": "vine",
    "kumul": "grass",
}
PRONOUN_A = {"ngayu", "yundu", "jana", "ngana"}
DENY_KUKU = {
    "mayi", "nuka", "nukan", "waju", "wajun", "warrmba", "warrmban", "mani", "manin", "daji", "dajin",
    "dinda", "dindan", "duda", "dudan", "wukurri", "wukurrin", "ngara", "ngaran", "kuni", "kunin",
    "yaka", "yakan", "balka", "balkan", "kida", "kidan", "dunga", "dungan", "wandi", "wandin",
    "baya", "kubu", "kurrma", "medicine", "juku", "bark", "spear", "storm", "bayan", "bubu",
    "buyku", "bijan",
}
DENY_ENGLISH = (
    "eat", "eats", "ate", "eaten", "food", "fruit", "nut", "medicine", "heal", "healed", "sore",
    "bark", "burn", "burned", "burnt", "fire", "smoke", "storm", "spear", "woomera", "timber",
    "wood", "stick", "weave", "woven", "tie", "tied", "basket", "place", "camp", "beach", "fruit",
    "gather", "gathered", "harvest", "harvested", "dig", "dug", "cook", "cooked", "roast", "roasted",
    "pod", "pods", "seed", "seeds",
)


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 bloodwood tree is there.", "Babajaka yinyay.", "babajaka yinyay | bloodwood.tree there", "r166_deictic_babajaka_there", ["babajaka", "yinyay"], "Rule 166 retains Rule 165 babajaka as bloodwood tree; bare yinyay deictic consolidation."),
    row(2, "The balmbalka tree is there.", "Balmbalka yinyay.", "balmbalka yinyay | balmbalka.tree there", "r166_deictic_balmbalka_there", ["balmbalka", "yinyay"], "Rule 166 retains Rule 165 balmbalka as tree with English name unknown; bare yinyay deictic consolidation."),
    row(3, "The black bean tree is there.", "Baway yinyay.", "baway yinyay | black.bean.tree there", "r166_deictic_baway_there", ["baway", "yinyay"], "Rule 166 retains Rule 165 baway as black bean tree; no seed/fruit/food-processing claim."),
    row(4, "The tea tree is there.", "Bijin yinyay.", "bijin yinyay | tea.tree there", "r166_deictic_bijin_there", ["bijin", "yinyay"], "Rule 166 retains Rule 165 bijin as tea tree; no medicine/bark-use claim."),
    row(5, "The oak tree is there.", "Dalban yinyay.", "dalban yinyay | oak.tree there", "r166_deictic_dalban_there", ["dalban", "yinyay"], "Rule 166 retains Rule 165 dalban as oak tree; no timber/tool claim."),
    row(6, "The dingki tree is there.", "Dingki yinyay.", "dingki yinyay | dingki.tree there", "r166_deictic_dingki_there", ["dingki", "yinyay"], "Rule 166 retains Rule 165 dingki as dictionary-attested tree; no dinghy/loan drift."),
    row(7, "The umbrella palm is there.", "Dulmbil yinyay.", "dulmbil yinyay | umbrella.palm there", "r166_deictic_dulmbil_there", ["dulmbil", "yinyay"], "Rule 166 retains Rule 165 dulmbil as umbrella palm; no place-name or weaving/material claim."),
    row(8, "The black palm is there.", "Duwar yinyay.", "duwar yinyay | black.palm there", "r166_deictic_duwar_there", ["duwar", "yinyay"], "Rule 166 retains Rule 165 duwar as black palm; spear/material-source sense remains closed."),
    row(9, "The vine is there.", "Kalal yinyay.", "kalal yinyay | vine there", "r166_deictic_kalal_there", ["kalal", "yinyay"], "Rule 166 retains Rule 165 kalal as vine; Patz confirms kalal 'vine'."),
    row(10, "The grass is there.", "Kumul yinyay.", "kumul yinyay | grass there", "r166_deictic_kumul_there", ["kumul", "yinyay"], "Rule 166 retains Rule 165 kumul as grass; no fire/fuel or ground-cover claim."),
    row(11, "I see the bloodwood tree here.", "Ngayu babajaka yaluy nyajil.", "ngayu babajaka yaluy nyaji-l | I bloodwood.tree here see-NONPAST", "r166_present_i_babajaka_here", ["ngayu", "babajaka", "yaluy", "nyaji-l"], "Pronoun A bare; babajaka object bare; yaluy support only."),
    row(12, "I see the balmbalka tree here.", "Ngayu balmbalka yaluy nyajil.", "ngayu balmbalka yaluy nyaji-l | I balmbalka.tree here see-NONPAST", "r166_present_i_balmbalka_here", ["ngayu", "balmbalka", "yaluy", "nyaji-l"], "Pronoun A bare; balmbalka object bare; yaluy support only."),
    row(13, "I see the black bean tree here.", "Ngayu baway yaluy nyajil.", "ngayu baway yaluy nyaji-l | I black.bean.tree here see-NONPAST", "r166_present_i_baway_here", ["ngayu", "baway", "yaluy", "nyaji-l"], "Pronoun A bare; baway object bare; no seeds/food row."),
    row(14, "I see the tea tree here.", "Ngayu bijin yaluy nyajil.", "ngayu bijin yaluy nyaji-l | I tea.tree here see-NONPAST", "r166_present_i_bijin_here", ["ngayu", "bijin", "yaluy", "nyaji-l"], "Pronoun A bare; bijin object bare; no medicine/bark row."),
    row(15, "I see the oak tree here.", "Ngayu dalban yaluy nyajil.", "ngayu dalban yaluy nyaji-l | I oak.tree here see-NONPAST", "r166_present_i_dalban_here", ["ngayu", "dalban", "yaluy", "nyaji-l"], "Pronoun A bare; dalban object bare; no timber/tool row."),
    row(16, "I see the dingki tree here.", "Ngayu dingki yaluy nyajil.", "ngayu dingki yaluy nyaji-l | I dingki.tree here see-NONPAST", "r166_present_i_dingki_here", ["ngayu", "dingki", "yaluy", "nyaji-l"], "Pronoun A bare; dingki object bare; no dinghy/loan drift."),
    row(17, "I see the umbrella palm here.", "Ngayu dulmbil yaluy nyajil.", "ngayu dulmbil yaluy nyaji-l | I umbrella.palm here see-NONPAST", "r166_present_i_dulmbil_here", ["ngayu", "dulmbil", "yaluy", "nyaji-l"], "Pronoun A bare; dulmbil object bare; no place-name/material row."),
    row(18, "I see the black palm here.", "Ngayu duwar yaluy nyajil.", "ngayu duwar yaluy nyaji-l | I black.palm here see-NONPAST", "r166_present_i_duwar_here", ["ngayu", "duwar", "yaluy", "nyaji-l"], "Pronoun A bare; duwar object bare; no spear/material-source row."),
    row(19, "I see the vine here.", "Ngayu kalal yaluy nyajil.", "ngayu kalal yaluy nyaji-l | I vine here see-NONPAST", "r166_present_i_kalal_here", ["ngayu", "kalal", "yaluy", "nyaji-l"], "Pronoun A bare; kalal object bare; no tying/rope claim."),
    row(20, "I see the grass here.", "Ngayu kumul yaluy nyajil.", "ngayu kumul yaluy nyaji-l | I grass here see-NONPAST", "r166_present_i_kumul_here", ["ngayu", "kumul", "yaluy", "nyaji-l"], "Pronoun A bare; kumul object bare; no fire/fuel row."),
    row(21, "You see the bloodwood tree here.", "Yundu babajaka yaluy nyajil.", "yundu babajaka yaluy nyaji-l | you bloodwood.tree here see-NONPAST", "r166_present_you_babajaka_here", ["yundu", "babajaka", "yaluy", "nyaji-l"], "Second-person pronoun A bare; babajaka object bare; yaluy support only."),
    row(22, "You see the balmbalka tree here.", "Yundu balmbalka yaluy nyajil.", "yundu balmbalka yaluy nyaji-l | you balmbalka.tree here see-NONPAST", "r166_present_you_balmbalka_here", ["yundu", "balmbalka", "yaluy", "nyaji-l"], "Second-person pronoun A bare; balmbalka object bare; yaluy support only."),
    row(23, "You see the black bean tree here.", "Yundu baway yaluy nyajil.", "yundu baway yaluy nyaji-l | you black.bean.tree here see-NONPAST", "r166_present_you_baway_here", ["yundu", "baway", "yaluy", "nyaji-l"], "Second-person pronoun A bare; no fruit/seed/food row."),
    row(24, "You see the tea tree here.", "Yundu bijin yaluy nyajil.", "yundu bijin yaluy nyaji-l | you tea.tree here see-NONPAST", "r166_present_you_bijin_here", ["yundu", "bijin", "yaluy", "nyaji-l"], "Second-person pronoun A bare; no medicine/bark row."),
    row(25, "You see the oak tree here.", "Yundu dalban yaluy nyajil.", "yundu dalban yaluy nyaji-l | you oak.tree here see-NONPAST", "r166_present_you_dalban_here", ["yundu", "dalban", "yaluy", "nyaji-l"], "Second-person pronoun A bare; target object bare; no timber row."),
    row(26, "You see the dingki tree here.", "Yundu dingki yaluy nyajil.", "yundu dingki yaluy nyaji-l | you dingki.tree here see-NONPAST", "r166_present_you_dingki_here", ["yundu", "dingki", "yaluy", "nyaji-l"], "Second-person pronoun A bare; target object bare; no dinghy/loan drift."),
    row(27, "You see the umbrella palm here.", "Yundu dulmbil yaluy nyajil.", "yundu dulmbil yaluy nyaji-l | you umbrella.palm here see-NONPAST", "r166_present_you_dulmbil_here", ["yundu", "dulmbil", "yaluy", "nyaji-l"], "Second-person pronoun A bare; no place/material row."),
    row(28, "You see the black palm here.", "Yundu duwar yaluy nyajil.", "yundu duwar yaluy nyaji-l | you black.palm here see-NONPAST", "r166_present_you_duwar_here", ["yundu", "duwar", "yaluy", "nyaji-l"], "Second-person pronoun A bare; no spear/material-source row."),
    row(29, "You see the vine here.", "Yundu kalal yaluy nyajil.", "yundu kalal yaluy nyaji-l | you vine here see-NONPAST", "r166_present_you_kalal_here", ["yundu", "kalal", "yaluy", "nyaji-l"], "Second-person pronoun A bare; no tying/rope claim."),
    row(30, "You see the grass here.", "Yundu kumul yaluy nyajil.", "yundu kumul yaluy nyaji-l | you grass here see-NONPAST", "r166_present_you_kumul_here", ["yundu", "kumul", "yaluy", "nyaji-l"], "Second-person pronoun A bare; target object bare; no fire/fuel row."),
    row(31, "They saw the bloodwood tree here.", "Jana babajaka yaluy nyajin.", "jana babajaka yaluy nyaji-ny | they bloodwood.tree here see-PAST", "r166_past_they_babajaka_here", ["jana", "babajaka", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; past perception; target bare; yaluy support only."),
    row(32, "They saw the balmbalka tree here.", "Jana balmbalka yaluy nyajin.", "jana balmbalka yaluy nyaji-ny | they balmbalka.tree here see-PAST", "r166_past_they_balmbalka_here", ["jana", "balmbalka", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; past perception; target bare."),
    row(33, "They saw the black bean tree here.", "Jana baway yaluy nyajin.", "jana baway yaluy nyaji-ny | they black.bean.tree here see-PAST", "r166_past_they_baway_here", ["jana", "baway", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; no food/seed row."),
    row(34, "They saw the tea tree here.", "Jana bijin yaluy nyajin.", "jana bijin yaluy nyaji-ny | they tea.tree here see-PAST", "r166_past_they_bijin_here", ["jana", "bijin", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; no medicine/bark row."),
    row(35, "They saw the oak tree here.", "Jana dalban yaluy nyajin.", "jana dalban yaluy nyaji-ny | they oak.tree here see-PAST", "r166_past_they_dalban_here", ["jana", "dalban", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; target object bare; no timber row."),
    row(36, "They saw the dingki tree here.", "Jana dingki yaluy nyajin.", "jana dingki yaluy nyaji-ny | they dingki.tree here see-PAST", "r166_past_they_dingki_here", ["jana", "dingki", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; target object bare; no dinghy/loan drift."),
    row(37, "They saw the umbrella palm here.", "Jana dulmbil yaluy nyajin.", "jana dulmbil yaluy nyaji-ny | they umbrella.palm here see-PAST", "r166_past_they_dulmbil_here", ["jana", "dulmbil", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; target object bare; no place/material row."),
    row(38, "They saw the black palm here.", "Jana duwar yaluy nyajin.", "jana duwar yaluy nyaji-ny | they black.palm here see-PAST", "r166_past_they_duwar_here", ["jana", "duwar", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; no spear/material-source row."),
    row(39, "They saw the vine here.", "Jana kalal yaluy nyajin.", "jana kalal yaluy nyaji-ny | they vine here see-PAST", "r166_past_they_kalal_here", ["jana", "kalal", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; no tying/rope claim."),
    row(40, "They saw the grass here.", "Jana kumul yaluy nyajin.", "jana kumul yaluy nyaji-ny | they grass here see-PAST", "r166_past_they_kumul_here", ["jana", "kumul", "yaluy", "nyaji-l"], "Third-person plural pronoun A bare; target object bare; no fire/fuel row."),
    row(41, "We see the bloodwood tree there.", "Ngana babajaka yinyay nyajil.", "ngana babajaka yinyay nyaji-l | we bloodwood.tree there see-NONPAST", "r166_present_we_babajaka_there", ["ngana", "babajaka", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; yinyay support only."),
    row(42, "We see the balmbalka tree there.", "Ngana balmbalka yinyay nyajil.", "ngana balmbalka yinyay nyaji-l | we balmbalka.tree there see-NONPAST", "r166_present_we_balmbalka_there", ["ngana", "balmbalka", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; target object bare; yinyay support only."),
    row(43, "We see the black bean tree there.", "Ngana baway yinyay nyajil.", "ngana baway yinyay nyaji-l | we black.bean.tree there see-NONPAST", "r166_present_we_baway_there", ["ngana", "baway", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; no fruit/seed/food row."),
    row(44, "We see the tea tree there.", "Ngana bijin yinyay nyajil.", "ngana bijin yinyay nyaji-l | we tea.tree there see-NONPAST", "r166_present_we_bijin_there", ["ngana", "bijin", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; no medicine/bark row."),
    row(45, "We see the oak tree there.", "Ngana dalban yinyay nyajil.", "ngana dalban yinyay nyaji-l | we oak.tree there see-NONPAST", "r166_present_we_dalban_there", ["ngana", "dalban", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; target object bare; no timber row."),
    row(46, "We see the dingki tree there.", "Ngana dingki yinyay nyajil.", "ngana dingki yinyay nyaji-l | we dingki.tree there see-NONPAST", "r166_present_we_dingki_there", ["ngana", "dingki", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; target object bare; no dinghy/loan drift."),
    row(47, "We see the umbrella palm there.", "Ngana dulmbil yinyay nyajil.", "ngana dulmbil yinyay nyaji-l | we umbrella.palm there see-NONPAST", "r166_present_we_dulmbil_there", ["ngana", "dulmbil", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; no place/material row."),
    row(48, "We see the black palm there.", "Ngana duwar yinyay nyajil.", "ngana duwar yinyay nyaji-l | we black.palm there see-NONPAST", "r166_present_we_duwar_there", ["ngana", "duwar", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; no spear/material-source row."),
    row(49, "We see the vine there.", "Ngana kalal yinyay nyajil.", "ngana kalal yinyay nyaji-l | we vine there see-NONPAST", "r166_present_we_kalal_there", ["ngana", "kalal", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; no tying/rope claim."),
    row(50, "We see the grass there.", "Ngana kumul yinyay nyajil.", "ngana kumul yinyay nyaji-l | we grass there see-NONPAST", "r166_present_we_kumul_there", ["ngana", "kumul", "yinyay", "nyaji-l"], "Inclusive pronoun A bare; target object bare; no fire/fuel row."),
]


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("r166_"):
            issues.append(f"{label}: Rule 166 frame must start with r166_")
        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 166")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied use/homograph 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 166 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 and selected 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 166 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 166 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 "here" not in english_low or "saw" not in english_low):
                issues.append(f"{label}: rows 31-40 require jana + yaluy/here + 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 use/event/homograph 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 166 draft: construction consolidation after Rule 165", "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 166 source pass retained the Rule 165 flora heads babajaka, balmbalka, baway, bijin, dalban, dingki, dulmbil, duwar, kalal, and kumul. Dictionary exact readback reconfirmed selected senses. Patz readback confirmed kalal 'vine', support forms yaluy/yinyay, nyaji-l/nyaji-ny, and bare pronoun A forms, while flagging unrelated dingki-ji dinghy-comitative as a homograph/loan drift trap.",
                 command="rg dictionary exact readback; rg grammar readback; sqlite lexeme 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 166 is construction consolidation over the Rule 165 flora set. It uses bare target heads only and rotates yinyay/yaluy support across deictic and perception frames. It excludes food-processing, fruit, seeds, medicine, bark use, spear-making, timber/material-source, weaving, tying, cutting, burning, storm-control, place-name, target case, target compounds, possession, and ecological-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_208.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 166 flora target per row; all targets bare; no loans; no use/event/homograph tokens; no target case/compound forms; yinyay deictic rows; yaluy-supported ngayu/yundu/jana perception rows; yinyay-supported ngana perception rows.",
                 command="python3 batch_scripts/bank_208.py static scan", artifact_path="batch_scripts/bank_208.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())
