#!/usr/bin/env python3
"""Bank batch-238: Rule 196 property-adjective construction consolidation."""

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

TARGET_HEADS = {
    "yalbay": "big",
    "karrkay": "small",
    "kalbay": "long",
    "kulka": "short",
    "kulngkul": "heavy",
    "wuban": "strong",
    "yayji": "thin",
    "wumbul": "hot",
    "kiway-kiway": "cold",
    "marral": "dry",
}
SUBJECT_NOUNS = {
    "bayan": "house",
    "baral": "road",
    "juku": "wood/tree",
    "kulji": "stone",
    "bama": "person",
    "wungar": "sun",
    "kaba": "rain",
    "bubu": "ground",
}
SUPPORT = {"yala", "yinya", "yaluy", "yinyay"}
DENY_KUKU = {
    "dakwuy", "bambay", "dukul-wuri", "kala-burra", "warngku-kaday", "wawu-mini", "wawu-buyun",
    "milka-murruji", "ngulkurr", "juwamalmal", "baja-burray", "jibabadi-y", "murru-kaday",
    "mulkay-warri", "wawu-wulay", "wawu-daray", "walu-jirrbu-jirrbu", "ngalu-ngalu",
    "dukul-kulnkul", "muna", "yanji", "wunbu", "kari", "nuka", "waju", "dinda", "kada",
    "dunga", "warri", "bayka", "nyaji", "nyajil", "nyajin", "binal", "wawu", "milka", "dukul",
}
DENY_ENGLISH = (
    "not", "without", "eat", "food", "cook", "travel", "walk", "country", "own", "belong", "pregnant",
    "child", "sick", "doctor", "medicine", "diagnosis", "moral", "good", "bad", "body", "head",
    "ear", "soul", "cause", "because", "become", "make", "very", "rather", "more", "less",
)


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, 3, js(words), js([]), evidence, "0.86")


ROWS = [
    row(1, "The big house is here.", "Bayan yalbay yaluy.", "bayan yalbay yaluy | house big here", "r196_np_adj_here_bayan_yalbay", ["bayan", "yalbay", "yaluy"], "Rule 195 yalbay retained; local demonstrative yaluy 'here'."),
    row(2, "The small house is here.", "Bayan karrkay yaluy.", "bayan karrkay yaluy | house small here", "r196_np_adj_here_bayan_karrkay", ["bayan", "karrkay", "yaluy"], "Rule 195 karrkay adjective retained; no child-noun reading."),
    row(3, "The long road is here.", "Baral kalbay yaluy.", "baral kalbay yaluy | road long here", "r196_np_adj_here_baral_kalbay", ["baral", "kalbay", "yaluy"], "Rule 195 kalbay retained; no travel event."),
    row(4, "The short wood is here.", "Juku kulka yaluy.", "juku kulka yaluy | wood short here", "r196_np_adj_here_juku_kulka", ["juku", "kulka", "yaluy"], "Rule 195 kulka retained; no cutting/material-use claim."),
    row(5, "The heavy stone is here.", "Kulji kulngkul yaluy.", "kulji kulngkul yaluy | stone heavy here", "r196_np_adj_here_kulji_kulngkul", ["kulji", "kulngkul", "yaluy"], "Rule 195 kulngkul retained as heavy only."),
    row(6, "The strong person is here.", "Bama wuban yaluy.", "bama wuban yaluy | person strong here", "r196_np_adj_here_bama_wuban", ["bama", "wuban", "yaluy"], "Rule 195 wuban retained as strong only."),
    row(7, "The thin person is here.", "Bama yayji yaluy.", "bama yayji yaluy | person thin here", "r196_np_adj_here_bama_yayji", ["bama", "yayji", "yaluy"], "Rule 195 yayji retained as thin only; no diagnosis."),
    row(8, "The hot sun is here.", "Wungar wumbul yaluy.", "wungar wumbul yaluy | sun hot here", "r196_np_adj_here_wungar_wumbul", ["wungar", "wumbul", "yaluy"], "Rule 195 wumbul retained as hot only."),
    row(9, "The cold rain is here.", "Kaba kiway-kiway yaluy.", "kaba kiway-kiway yaluy | rain cold here", "r196_np_adj_here_kaba_kiway_kiway", ["kaba", "kiway-kiway", "yaluy"], "Rule 195 kiway-kiway retained as cold only."),
    row(10, "The dry ground is here.", "Bubu marral yaluy.", "bubu marral yaluy | ground dry here", "r196_np_adj_here_bubu_marral", ["bubu", "marral", "yaluy"], "Rule 195 marral retained as dry only."),
    row(11, "The big tree is there.", "Juku yalbay yinyay.", "juku yalbay yinyay | tree big there", "r196_np_adj_there_juku_yalbay", ["juku", "yalbay", "yinyay"], "Duplicate-avoidance replacement for earlier big-house there row."),
    row(12, "The small house is there.", "Bayan karrkay yinyay.", "bayan karrkay yinyay | house small there", "r196_np_adj_there_bayan_karrkay", ["bayan", "karrkay", "yinyay"], "Rule 195 karrkay adjective retained."),
    row(13, "The long road is there.", "Baral kalbay yinyay.", "baral kalbay yinyay | road long there", "r196_np_adj_there_baral_kalbay", ["baral", "kalbay", "yinyay"], "Rule 195 kalbay retained; no travel event."),
    row(14, "The short wood is there.", "Juku kulka yinyay.", "juku kulka yinyay | wood short there", "r196_np_adj_there_juku_kulka", ["juku", "kulka", "yinyay"], "Rule 195 kulka retained; no material-use claim."),
    row(15, "The heavy stone is there.", "Kulji kulngkul yinyay.", "kulji kulngkul yinyay | stone heavy there", "r196_np_adj_there_kulji_kulngkul", ["kulji", "kulngkul", "yinyay"], "Rule 195 kulngkul retained as heavy only."),
    row(16, "The strong person is there.", "Bama wuban yinyay.", "bama wuban yinyay | person strong there", "r196_np_adj_there_bama_wuban", ["bama", "wuban", "yinyay"], "Rule 195 wuban retained as strong only."),
    row(17, "The thin person is there.", "Bama yayji yinyay.", "bama yayji yinyay | person thin there", "r196_np_adj_there_bama_yayji", ["bama", "yayji", "yinyay"], "Rule 195 yayji retained as thin only."),
    row(18, "The hot sun is there.", "Wungar wumbul yinyay.", "wungar wumbul yinyay | sun hot there", "r196_np_adj_there_wungar_wumbul", ["wungar", "wumbul", "yinyay"], "Rule 195 wumbul retained as hot only."),
    row(19, "The cold rain is there.", "Kaba kiway-kiway yinyay.", "kaba kiway-kiway yinyay | rain cold there", "r196_np_adj_there_kaba_kiway_kiway", ["kaba", "kiway-kiway", "yinyay"], "Rule 195 kiway-kiway retained as cold only."),
    row(20, "The dry ground is there.", "Bubu marral yinyay.", "bubu marral yinyay | ground dry there", "r196_np_adj_there_bubu_marral", ["bubu", "marral", "yinyay"], "Rule 195 marral retained as dry only."),
    row(21, "This house is big.", "Yala bayan yalbay.", "yala bayan yalbay | this house big", "r196_this_np_adj_bayan_yalbay", ["yala", "bayan", "yalbay"], "Dictionary yala demonstrative 'here, this'."),
    row(22, "This house is small.", "Yala bayan karrkay.", "yala bayan karrkay | this house small", "r196_this_np_adj_bayan_karrkay", ["yala", "bayan", "karrkay"], "Yala demonstrative plus NP+ADJ predicate."),
    row(23, "This road is long.", "Yala baral kalbay.", "yala baral kalbay | this road long", "r196_this_np_adj_baral_kalbay", ["yala", "baral", "kalbay"], "Yala demonstrative plus NP+ADJ predicate."),
    row(24, "This wood is short.", "Yala juku kulka.", "yala juku kulka | this wood short", "r196_this_np_adj_juku_kulka", ["yala", "juku", "kulka"], "Yala demonstrative plus NP+ADJ predicate."),
    row(25, "This stone is heavy.", "Yala kulji kulngkul.", "yala kulji kulngkul | this stone heavy", "r196_this_np_adj_kulji_kulngkul", ["yala", "kulji", "kulngkul"], "Yala demonstrative plus NP+ADJ predicate."),
    row(26, "This person is strong.", "Yala bama wuban.", "yala bama wuban | this person strong", "r196_this_np_adj_bama_wuban", ["yala", "bama", "wuban"], "Yala demonstrative plus NP+ADJ predicate."),
    row(27, "This person is thin.", "Yala bama yayji.", "yala bama yayji | this person thin", "r196_this_np_adj_bama_yayji", ["yala", "bama", "yayji"], "Yala demonstrative plus NP+ADJ predicate; no diagnosis."),
    row(28, "This sun is hot.", "Yala wungar wumbul.", "yala wungar wumbul | this sun hot", "r196_this_np_adj_wungar_wumbul", ["yala", "wungar", "wumbul"], "Yala demonstrative plus NP+ADJ predicate."),
    row(29, "This rain is cold.", "Yala kaba kiway-kiway.", "yala kaba kiway-kiway | this rain cold", "r196_this_np_adj_kaba_kiway_kiway", ["yala", "kaba", "kiway-kiway"], "Yala demonstrative plus NP+ADJ predicate."),
    row(30, "This ground is dry.", "Yala bubu marral.", "yala bubu marral | this ground dry", "r196_this_np_adj_bubu_marral", ["yala", "bubu", "marral"], "Yala demonstrative plus NP+ADJ predicate."),
    row(31, "This big house is here.", "Yala bayan yalbay yaluy.", "yala bayan yalbay yaluy | this house big here", "r196_this_np_adj_here_bayan_yalbay", ["yala", "bayan", "yalbay", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(32, "This small house is here.", "Yala bayan karrkay yaluy.", "yala bayan karrkay yaluy | this house small here", "r196_this_np_adj_here_bayan_karrkay", ["yala", "bayan", "karrkay", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(33, "This long road is here.", "Yala baral kalbay yaluy.", "yala baral kalbay yaluy | this road long here", "r196_this_np_adj_here_baral_kalbay", ["yala", "baral", "kalbay", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(34, "This short wood is here.", "Yala juku kulka yaluy.", "yala juku kulka yaluy | this wood short here", "r196_this_np_adj_here_juku_kulka", ["yala", "juku", "kulka", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(35, "This heavy stone is here.", "Yala kulji kulngkul yaluy.", "yala kulji kulngkul yaluy | this stone heavy here", "r196_this_np_adj_here_kulji_kulngkul", ["yala", "kulji", "kulngkul", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(36, "This strong person is here.", "Yala bama wuban yaluy.", "yala bama wuban yaluy | this person strong here", "r196_this_np_adj_here_bama_wuban", ["yala", "bama", "wuban", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(37, "This thin person is here.", "Yala bama yayji yaluy.", "yala bama yayji yaluy | this person thin here", "r196_this_np_adj_here_bama_yayji", ["yala", "bama", "yayji", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(38, "This hot sun is here.", "Yala wungar wumbul yaluy.", "yala wungar wumbul yaluy | this sun hot here", "r196_this_np_adj_here_wungar_wumbul", ["yala", "wungar", "wumbul", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(39, "This cold rain is here.", "Yala kaba kiway-kiway yaluy.", "yala kaba kiway-kiway yaluy | this rain cold here", "r196_this_np_adj_here_kaba_kiway_kiway", ["yala", "kaba", "kiway-kiway", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(40, "This dry ground is here.", "Yala bubu marral yaluy.", "yala bubu marral yaluy | this ground dry here", "r196_this_np_adj_here_bubu_marral", ["yala", "bubu", "marral", "yaluy"], "Yala plus local demonstrative yaluy."),
    row(41, "That big house is there.", "Yinya bayan yalbay yinyay.", "yinya bayan yalbay yinyay | that house big there", "r196_that_np_adj_there_bayan_yalbay", ["yinya", "bayan", "yalbay", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(42, "That small house is there.", "Yinya bayan karrkay yinyay.", "yinya bayan karrkay yinyay | that house small there", "r196_that_np_adj_there_bayan_karrkay", ["yinya", "bayan", "karrkay", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(43, "That long road is there.", "Yinya baral kalbay yinyay.", "yinya baral kalbay yinyay | that road long there", "r196_that_np_adj_there_baral_kalbay", ["yinya", "baral", "kalbay", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(44, "That short wood is there.", "Yinya juku kulka yinyay.", "yinya juku kulka yinyay | that wood short there", "r196_that_np_adj_there_juku_kulka", ["yinya", "juku", "kulka", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(45, "That heavy stone is there.", "Yinya kulji kulngkul yinyay.", "yinya kulji kulngkul yinyay | that stone heavy there", "r196_that_np_adj_there_kulji_kulngkul", ["yinya", "kulji", "kulngkul", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(46, "That strong person is there.", "Yinya bama wuban yinyay.", "yinya bama wuban yinyay | that person strong there", "r196_that_np_adj_there_bama_wuban", ["yinya", "bama", "wuban", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(47, "That thin person is there.", "Yinya bama yayji yinyay.", "yinya bama yayji yinyay | that person thin there", "r196_that_np_adj_there_bama_yayji", ["yinya", "bama", "yayji", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(48, "That hot sun is there.", "Yinya wungar wumbul yinyay.", "yinya wungar wumbul yinyay | that sun hot there", "r196_that_np_adj_there_wungar_wumbul", ["yinya", "wungar", "wumbul", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(49, "That cold rain is there.", "Yinya kaba kiway-kiway yinyay.", "yinya kaba kiway-kiway yinyay | that rain cold there", "r196_that_np_adj_there_kaba_kiway_kiway", ["yinya", "kaba", "kiway-kiway", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
    row(50, "That dry ground is there.", "Yinya bubu marral yinyay.", "yinya bubu marral yinyay | that ground dry there", "r196_that_np_adj_there_bubu_marral", ["yinya", "bubu", "marral", "yinyay"], "Yinya demonstrative plus local demonstrative yinyay."),
]


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


def has_forbidden_target_morphology(low: str, head: str) -> bool:
    suffixes = "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|baja"
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:{suffixes})\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("r196_"):
            issues.append(f"{label}: Rule 196 frame must start with r196_")
        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 196")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied carry-forward 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 195 property 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 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_forbidden_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        subjects = [head for head in SUBJECT_NOUNS if has_token(low, head)]
        if len(subjects) != 1:
            issues.append(f"{label}: expected exactly one subject noun, found {subjects}")
        for support in SUPPORT:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if 1 <= seq <= 10 and (not low.endswith(" yaluy.") or low.startswith(("yala ", "yinya "))):
            issues.append(f"{label}: rows 1-10 require bare NP ADJ yaluy")
        if 11 <= seq <= 20 and (not low.endswith(" yinyay.") or low.startswith(("yala ", "yinya "))):
            issues.append(f"{label}: rows 11-20 require bare NP ADJ yinyay")
        if 21 <= seq <= 30 and (not low.startswith("yala ") or low.endswith((" yaluy.", " yinyay."))):
            issues.append(f"{label}: rows 21-30 require yala NP ADJ")
        if 31 <= seq <= 40 and (not low.startswith("yala ") or not low.endswith(" yaluy.")):
            issues.append(f"{label}: rows 31-40 require yala NP ADJ yaluy")
        if 41 <= seq <= 50 and (not low.startswith("yinya ") or not low.endswith(" yinyay.")):
            issues.append(f"{label}: rows 41-50 require yinya NP ADJ yinyay")
        if "NEG" in analysis or "PRIV" in analysis or "-kari" in low:
            issues.append(f"{label}: Rule 196 must not reopen NEG/PRIV frames")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied event/source/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 196 draft: property-adjective construction consolidation", "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 196 source pass retained exactly the ten Rule 195 property adjectives and rotated only local demonstrative support. Grammar/dictionary readback confirmed NP+ADJ predicates, adjective modifiers before local demonstratives, yala this/here, yinya that/there, yaluy here, and yinyay there.",
                 command="operator handoff readback; Rule 195 process log readback; dictionary structured readback; Patz/local corpus deictic-location readback; sqlite exact duplicate preflight",
                 artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="scope", event_type="construction-scope", actor="claude-main",
                 detail="Rule 196 is a construction-consolidation pass. It uses bare NP ADJ yaluy, bare NP ADJ yinyay, yala NP ADJ, yala NP ADJ yaluy, and yinya NP ADJ yinyay frames. It excludes pre-adjective kari, post-adjective -kari, general privative possession/absence, case, degree, causative, inchoative, plural, target compounds, productive reduplication, and Rule 193-195 trap reopenings.",
                 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 replaced candidate Bayan yalbay yinyay because it duplicated Batch 134 row 32; final plan had 50 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_238.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="static-guard-pass", actor="claude-main",
                 detail="Static guard passed: exactly one retained target adjective and one subject noun per row, no loans, no NEG/PRIV, no denied carry-forward tokens, no target morphology, correct frame distribution, and no denied event/polysemy English fields.",
                 command="python3 batch_scripts/bank_238.py pre-bank scan", artifact_path="batch_scripts/bank_238.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", NOW, NOW)
         for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS],
    )
    db.commit()

    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    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={count}, under_review={under_review}, verified_before_close={verified}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
