#!/usr/bin/env python3
"""Bank batch-146: adjective syntax, degree, comparison, and property change."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3
import sys

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-146"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")

DENY = {
    "niji", "kija", "warral", "jindal", "green", "blue", "brown", "purple",
    "ngana-jinji", "warngkul", "jarrakay", "bubu-warral", "balbay-nja",
    "buyun", "jinbi", "jibin", "answer", "hospital", "medicine",
}


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


ROWS = [
    (1, "My house is big.",
     "Ngayku bayan yalbay.",
     "ngayku bayan yalbay | my house big",
     "possessed_np_adjective_predicate", 4, js(["ngayku", "bayan", "yalbay"]), js([]),
     "Patz NP section has ngayku bayan (yalbay), with adjective following the possessed head.", "0.88"),
    (2, "That is the man's new spear.",
     "Yinya kalka jirakal dingkar-amu.",
     "yinya kalka jirakal dingkar-amu | that spear new man-POSS",
     "demonstrative_np_possessive_modifier", 5, js(["yinya", "kalka", "jirakal", "dingkar"]), js([]),
     "Patz NP section gives kalka (jirakal) dingkar-amu with the adjective after the head and possessor after the NP.", "0.87"),
    (3, "I saw the big house.",
     "Ngayu bayan yalbay nyajin.",
     "ngayu bayan yalbay nyaji-ny | I house big see-PAST",
     "object_np_adjective_modifier", 4, js(["ngayu", "bayan", "yalbay", "nyajil"]), js([]),
     "Adjective modifier follows the noun inside the object NP; verb surface follows established nyaji-ny pattern.", "0.84"),
    (4, "The man got the new spear.",
     "Dingkar-angka kalka jirakal manin.",
     "dingkar-angka kalka jirakal mani-ny | man-ERG spear new get-PAST",
     "ergative_object_np_adjective", 4, js(["dingkar", "kalka", "jirakal", "manil"]), js([]),
     "Tests adjective-after-head order inside a simple transitive A O V clause.", "0.84"),
    (5, "Two vicious dogs are there.",
     "Jambul kaya kuli-ji yinyay.",
     "jambul kaya kuli-ji yinyay | two dog rage-COMIT there",
     "quantifier_np_comitative_adjective", 4, js(["jambul", "kaya", "kuli-ji", "yinyay"]), js([]),
     "Patz NP examples include jambul kaya kuli-ji 'two vicious dogs'.", "0.89"),
    (6, "Two red flowers are there.",
     "Jambul nganka ngala-ngala yinyay.",
     "jambul nganka ngala-ngala yinyay | two flower red there",
     "quantifier_np_colour_adjective", 4, js(["jambul", "nganka", "ngala-ngala", "yinyay"]), js([]),
     "Extends the source quantifier-plus-head-plus-adjective pattern using the grammar's red colour term.", "0.84"),
    (7, "Many big trees are standing there.",
     "Wubul juku yalbay yinyay janjanay.",
     "wubul juku yalbay yinyay janjana-y | many tree big there stand-NONPAST",
     "quantifier_plus_adjective_np", 5, js(["wubul", "juku", "yalbay", "yinyay", "janjanay"]), js([]),
     "Patz says quantifier tends to precede the head when an adjective is also present.", "0.82"),
    (8, "The child's big head is sore.",
     "Karrkay dukul yalbay kaka.",
     "karrkay dukul yalbay kaka | child head big sore",
     "inalienable_part_adjective_modifier", 5, js(["karrkay", "dukul", "yalbay", "kaka"]), js([]),
     "Patz inalienable possession places whole and part in apposition; optional modifier refers to the part.", "0.84"),
    (9, "The clean clothes are there.",
     "Kambi dayirr yinyay.",
     "kambi dayirr yinyay | clothes clean there",
     "np_adjective_modifier_deictic", 3, js(["kambi", "dayirr", "yinyay"]), js([]),
     "Short NP-plus-adjective row for adjective-after-head order; dayirr bounded to clean/clear.", "0.84"),
    (10, "The good food is there.",
     "Mayi ngulkurr yinyay.",
     "mayi ngulkurr yinyay | food good there",
     "np_adjective_modifier_deictic", 3, js(["mayi", "ngulkurr", "yinyay"]), js([]),
     "Patz ex.295 controls mayi ngulkurr as good food/food is good.", "0.87"),
    (11, "The shell on the zamia nut is hard.",
     "Dumbul marra-nga dandi.",
     "dumbul marra-nga dandi | shell zamia.nut-LOC hard",
     "inanimate_possessor_locative_adjective", 5, js(["dumbul", "marra", "dandi"]), js([]),
     "Patz ex.526: Dumbul marra-nga dandi; locative accepted, ablative rejected for shell on/of zamia nut.", "0.92"),
    (12, "Fruit from that tree is good to eat.",
     "Mayi yinya-muny juku-muny ngulkurr nuka-ji.",
     "mayi yinya-muny juku-muny ngulkurr nuka-ji-y | fruit that-ABL tree-ABL good eat-ITR-NONPAST",
     "inanimate_possessor_ablative_good_to_eat", 6, js(["mayi", "yinya", "juku", "ngulkurr", "nukaji"]), js([]),
     "Patz ex.525 licenses yinya-muny juku-muny and ngulkurr nuka-ji-y 'good to eat'.", "0.90"),

    (13, "That stone is rather big.",
     "Yinya kulji jarra-yalbay.",
     "yinya kulji jarra-yalbay | that stone rather-big",
     "jarra_degree_adjective", 5, js(["yinya", "kulji", "jarra-", "yalbay"]), js([]),
     "Patz degree section: jarra- precedes adjectives for rather/fairly.", "0.84"),
    (14, "The mango is rather ripe.",
     "Mayngku jarra-kulbul.",
     "mayngku jarra-kulbul | mango rather-ripe",
     "jarra_degree_adjective", 5, js(["mayngku", "jarra-", "kulbul"]), js([]),
     "Patz ex.79 has mayngku-ngu jarra-kulbul-bu 'on a rather ripe mango'.", "0.88"),
    (15, "The sand is rather soft.",
     "Jukar jarra-kima.",
     "jukar jarra-kima | sand rather-soft",
     "jarra_degree_adjective", 5, js(["jukar", "jarra-", "kima"]), js([]),
     "Patz comparison section uses jarra-kima for softer/more soft; this row uses the degree sense.", "0.83"),
    (16, "The child is rather sick.",
     "Karrkay jarra-bambay.",
     "karrkay jarra-bambay | child rather-sick",
     "jarra_degree_adjective", 5, js(["karrkay", "jarra-", "bambay"]), js([]),
     "Patz degree examples list jarra-bambay 'rather sick'.", "0.88"),
    (17, "The pig is rather tame.",
     "Bikibiki jarra-minday.",
     "bikibiki jarra-minday | pig rather-tame",
     "jarra_degree_adjective", 5, js(["bikibiki", "jarra-", "minday"]), js([]),
     "Patz degree examples list jarra-minday 'rather tame'.", "0.88"),
    (18, "The dog is rather wild.",
     "Kaya jarra-kuli-ji.",
     "kaya jarra-kuli-ji | dog rather-rage-COMIT",
     "jarra_degree_comitative_adjective", 5, js(["kaya", "jarra-", "kuli-ji"]), js([]),
     "Patz ex.80 has kaya-ngka jarra-kuli-ji-ngka for a rather wild dog.", "0.88"),
    (19, "The bark is rather hard.",
     "Dumbul jarra-dandi.",
     "dumbul jarra-dandi | bark rather-hard",
     "jarra_degree_adjective", 5, js(["dumbul", "jarra-", "dandi"]), js([]),
     "Jarra- is productive with adjectives; dandi is a verified property adjective.", "0.81"),
    (20, "The road is rather narrow.",
     "Baral jarra-darra.",
     "baral jarra-darra | road rather-narrow",
     "jarra_degree_adjective", 5, js(["baral", "jarra-", "darra"]), js([]),
     "Jarra- is productive with adjectives; darra is source-backed narrow/too-small.", "0.81"),
    (21, "The spear is rather blunt.",
     "Kalka jarra-dudu.",
     "kalka jarra-dudu | spear rather-blunt",
     "jarra_degree_adjective", 5, js(["kalka", "jarra-", "dudu"]), js([]),
     "Patz ex.300 has kalka dudu as blunt spear; jarra- tests degree on the same adjectival predicate.", "0.80"),
    (22, "The flower is rather beautiful.",
     "Nganka jarra-ngulkurrijin.",
     "nganka jarra-ngulkurrijin | flower rather-beautiful",
     "jarra_degree_adjective", 5, js(["nganka", "jarra-", "ngulkurrijin"]), js([]),
     "Jarra- is restricted to adjectives/quantifiers; ngulkurrijin is the verified beautiful adjective.", "0.81"),

    (23, "Sea-turtle meat is softer than beef.",
     "Minya ngawiya jarra-kima minya-ka bulki-ka.",
     "minya ngawiya jarra-kima minya-ka bulki-ka | meat sea.turtle more-soft meat-DAT cattle-DAT",
     "comparative_jarra_dative_standard", 6, js(["minya", "ngawiya", "jarra-", "kima", "bulki"]), js([]),
     "Patz ex.81: sea-turtle meat is softer than beef; corpus spelling normalizes ngawuya to ngawiya.", "0.90"),
    (24, "A sea turtle is much bigger than a freshwater turtle.",
     "Ngawiya ngujay-ngka yalbay-bajaku.",
     "ngawiya ngujay-ngka yalbay-baja-ku | sea.turtle freshwater.turtle-DAT big-DEG-EMPH",
     "comparative_dative_bajaku", 6, js(["ngawiya", "ngujay", "yalbay", "-baja-ku"]), js([]),
     "Patz ex.265 has ngawuya/ngujay-ngka yalbay-bajaku for sea turtle bigger than freshwater turtle.", "0.90"),
    (25, "The man is bigger than the child.",
     "Dingkar jarra-yalbay karrkay-anka.",
     "dingkar jarra-yalbay karrkay-anka | man more-big child-DAT",
     "comparative_jarra_dative_standard", 6, js(["dingkar", "jarra-", "yalbay", "karrkay"]), js([]),
     "Patz comparison rule: jarra-ADJ plus dative standard; karrkay-anka is source-shaped potent dative.", "0.84"),
    (26, "The stick is longer than the spear.",
     "Juku jarra-kalbay kalka-ka.",
     "juku jarra-kalbay kalka-ka | stick more-long spear-DAT",
     "comparative_jarra_dative_standard", 6, js(["juku", "jarra-", "kalbay", "kalka"]), js([]),
     "Jarra-ADJ comparison with neutral dative standard.", "0.82"),
    (27, "The bark is harder than the clothes.",
     "Dumbul jarra-dandi kambi-ka.",
     "dumbul jarra-dandi kambi-ka | bark more-hard clothes-DAT",
     "comparative_jarra_dative_standard", 6, js(["dumbul", "jarra-", "dandi", "kambi"]), js([]),
     "Jarra-ADJ comparison with neutral dative standard.", "0.82"),
    (28, "The sand is softer than the stone.",
     "Jukar jarra-kima kulji-ka.",
     "jukar jarra-kima kulji-ka | sand more-soft stone-DAT",
     "comparative_jarra_dative_standard", 6, js(["jukar", "jarra-", "kima", "kulji"]), js([]),
     "Jarra-ADJ comparison; kulji-ka follows neutral dative allomorphy.", "0.82"),
    (29, "The fire is hotter than the water.",
     "Baya jarra-wumbul bana-ka.",
     "baya jarra-wumbul bana-ka | fire more-hot water-DAT",
     "comparative_jarra_dative_standard", 6, js(["baya", "jarra-", "wumbul", "bana"]), js([]),
     "Jarra-ADJ comparison with a concrete property adjective and dative standard.", "0.82"),
    (30, "The water is cleaner than the grass.",
     "Bana jarra-dayirr jikan-ka.",
     "bana jarra-dayirr jikan-ka | water more-clean grass-DAT",
     "comparative_jarra_dative_standard", 6, js(["bana", "jarra-", "dayirr", "jikan"]), js([]),
     "Dayirr is clean/clear/bright; English is narrowed to clean for the grass comparison.", "0.80"),
    (31, "The food is better than the meat.",
     "Mayi jarra-ngulkurr minya-ka.",
     "mayi jarra-ngulkurr minya-ka | food more-good meat-DAT",
     "comparative_jarra_dative_standard", 6, js(["mayi", "jarra-", "ngulkurr", "minya"]), js([]),
     "Jarra-ADJ comparison with ngulkurr good and dative standard.", "0.82"),
    (32, "The road is narrower than the open ground.",
     "Baral jarra-darra bubu-ku.",
     "baral jarra-darra bubu-ku | road more-narrow ground-DAT",
     "comparative_jarra_dative_standard", 6, js(["baral", "jarra-", "darra", "bubu"]), js([]),
     "Jarra-ADJ comparison; bubu-ku follows source-backed neutral dative for place/ground.", "0.80"),

    (33, "I am very hungry.",
     "Ngayu dakwuy-bajaku.",
     "ngayu dakwuy-baja-ku | I hungry-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["ngayu", "dakwuy", "-baja-ku"]), js([]),
     "Patz ex.82 has hungry-baja-ku; dictionary/corpus spelling is dakwuy.", "0.88"),
    (34, "The baby is very cute.",
     "Ngawa kingkin-bajaku.",
     "ngawa kingkin-baja-ku | baby cute-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["ngawa", "kingkin", "-baja-ku"]), js([]),
     "Patz ex.83 has Ngawa kingkin-baja-ku 'The baby is very cute'.", "0.90"),
    (35, "The food is very good.",
     "Mayi ngulkurr-bajaku.",
     "mayi ngulkurr-baja-ku | food good-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["mayi", "ngulkurr", "-baja-ku"]), js([]),
     "Patz degree rule: adjective plus -baja followed by emphatic -ku.", "0.84"),
    (36, "The bark is very hard.",
     "Dumbul dandi-bajaku.",
     "dumbul dandi-baja-ku | bark hard-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["dumbul", "dandi", "-baja-ku"]), js([]),
     "Patz degree rule applied to verified dandi adjective.", "0.82"),
    (37, "The sand is very soft.",
     "Jukar kima-bajaku.",
     "jukar kima-baja-ku | sand soft-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["jukar", "kima", "-baja-ku"]), js([]),
     "Patz degree rule applied to verified kima adjective.", "0.82"),
    (38, "The water is very cold.",
     "Bana kiway-kiway-bajaku.",
     "bana kiway-kiway-baja-ku | water cold-REDUP-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["bana", "kiway-kiway", "-baja-ku"]), js([]),
     "Kiway-kiway is the verified cold adjective; -baja-ku supplies high degree.", "0.80"),
    (39, "The fire is very hot.",
     "Baya wumbul-bajaku.",
     "baya wumbul-baja-ku | fire hot-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["baya", "wumbul", "-baja-ku"]), js([]),
     "Patz degree rule applied to verified wumbul adjective.", "0.82"),
    (40, "The clothes are very dirty.",
     "Kambi mumbu-bajaku.",
     "kambi mumbu-baja-ku | clothes dirty-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["kambi", "mumbu", "-baja-ku"]), js([]),
     "Patz degree rule applied to verified mumbu adjective.", "0.82"),
    (41, "The flower is very red.",
     "Nganka ngala-ngala-bajaku.",
     "nganka ngala-ngala-baja-ku | flower red-DEG-EMPH",
     "degree_baja_ku_colour", 5, js(["nganka", "ngala-ngala", "-baja-ku"]), js([]),
     "The grammar permits -baja-ku with adjectives; ngala-ngala is one of the three grammar colour terms.", "0.80"),
    (42, "The spear is very blunt.",
     "Kalka dudu-bajaku.",
     "kalka dudu-baja-ku | spear blunt-DEG-EMPH",
     "degree_baja_ku_predicate", 5, js(["kalka", "dudu", "-baja-ku"]), js([]),
     "Patz ex.300 controls dudu as blunt predicate; -baja-ku supplies high degree.", "0.80"),
    (43, "The very strong man lifted the pig.",
     "Dingkar-angka junkurr-ji-baja-ngka-ku bikibiki kulban.",
     "dingkar-angka junkurr-ji-baja-ngka-ku bikibiki kulba-ny | man-ERG strength-COMIT-DEG-ERG-EMPH pig lift-PAST",
     "degree_baja_case_slot_ergative_modifier", 6, js(["dingkar", "junkurr-ji", "-baja-ku", "bikibiki", "kulbal"]), js([]),
     "Patz ex.84: case suffix goes between -baja and -ku when the adjectival stem is case-marked.", "0.92"),
    (44, "I am sorry for the very sick child.",
     "Ngayu jiba-badi karrkay-anka bambay-baja-nka-ku.",
     "ngayu jiba-badi-y karrkay-anka bambay-baja-nka-ku | I liver-cry-NONPAST child-DAT sick-DEG-DAT-EMPH",
     "degree_baja_case_slot_dative_modifier", 6, js(["ngayu", "jiba-badi-y", "karrkay", "bambay", "-baja-ku"]), js([]),
     "Patz ex.85: bambay-baja-nka-ku with dative case between -baja and -ku; surface jiba-badi drops final -iy.", "0.92"),

    (45, "That food became good.",
     "Yinya mayi ngulkurrman.",
     "yinya mayi ngulkurr-ma-ny | that food good-INCHO-PAST",
     "property_inchoative_adjective", 5, js(["yinya", "mayi", "ngulkurr", "ngulkurrmal"]), js([]),
     "Patz ex.314: Yinya mayi ngulkurr-ma-ny.", "0.92"),
    (46, "His spear became blunt.",
     "Nyungu kalka duduman.",
     "nyungu kalka dudu-ma-ny | his spear blunt-INCHO-PAST",
     "property_inchoative_adjective", 5, js(["nyungu", "kalka", "dudu", "dudumal"]), js([]),
     "Patz ex.318: Nyungu kalka dudu-ma-ny.", "0.92"),
    (47, "The man made his spear blunt.",
     "Dingkar-angka nyungu kalka dudu-bungan.",
     "dingkar-angka nyungu kalka dudu-bunga-ny | man-ERG his spear blunt-CAUS-PAST",
     "property_state_causative_adjective", 5, js(["dingkar", "nyungu", "kalka", "dudu", "dudu-bungal"]), js([]),
     "Patz ex.322: Dingar-angka nyungu kalka dudu-bunga-ny.", "0.92"),
    (48, "Soon the flame tree will get flowers.",
     "Juma juku ngakun nganka-ji-maniji.",
     "juma juku ngakun nganka-ji-maniji-y | soon tree flame.tree flower-COMIT-INCHO-NONPAST",
     "comitative_inchoative_property", 6, js(["juma", "juku", "ngakun", "nganka", "nganka-ji-maniji-y"]), js([]),
     "Patz ex.319: Juma juku ngakun nganka-ji-maniji-y 'Soon the flame tree will get flowers'.", "0.92"),
    (49, "The crocodile made that woman his wife.",
     "Bilngkumu-ngku yinya jalbu manyarr-bungan.",
     "bilngkumu-ngku yinya jalbu manyarr-bunga-ny | crocodile-ERG that woman wife-CAUS-PAST",
     "nominal_state_causative", 6, js(["bilngkumu", "yinya", "jalbu", "manyarr", "manyarr-bungal"]), js([]),
     "Patz ex.323: Bilngkumu-ngku yinya jalbu manyarr-bunga-ny.", "0.92"),
    (50, "Mother made the clothes clean.",
     "Ngamu-ngku kambi dayirr-bungan.",
     "ngamu-ngku kambi dayirr-bunga-ny | mother-ERG clothes clean-CAUS-PAST",
     "property_state_causative_adjective", 5, js(["ngamu", "kambi", "dayirr", "dayirr-bungal"]), js([]),
     "Patz ex.499 lists dayirr-bunga-l 'make clean' in the house-cleaning frame.", "0.86"),
]


def degree_token_ok(token: str) -> bool:
    t = token.strip(".,;:!?").lower()
    if "baja" not in t:
        return True
    if t in {"baja", "bajaku"}:
        return False
    return t.endswith("bajaku") or bool(re.search(r"-baja-[a-z]+-ku$", t))


def scan_static() -> list[str]:
    issues: list[str] = []
    if len(ROWS) != 50:
        issues.append(f"expected 50 rows, got {len(ROWS)}")
    seqs = [r[0] for r in ROWS]
    if seqs != list(range(1, 51)):
        issues.append(f"seqs must be 1..50, got {seqs[:5]}...{seqs[-5:]}")
    seen_ku: set[str] = set()
    for row in ROWS:
        seq, english, kuku, analysis, _frame, _tier, words_used, loans_used, _evidence, _confidence = row
        label = f"{LABEL}.{seq:02d}"
        e_start = english.lstrip("'\"")
        e_end = english.rstrip("'\"").rstrip()
        if not e_start[:1].isupper() or e_end[-1:] not in ".!?":
            issues.append(f"{label}: English must be capitalized and final-punctuated")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        k_start = kuku.lstrip("'\"")
        if not k_start[:1].isupper():
            issues.append(f"{label}: Kuku surface must start uppercase or with a quote before uppercase")
        for field_name, payload in (("words_used", words_used), ("loans_used", loans_used)):
            try:
                json.loads(payload)
            except Exception as exc:
                issues.append(f"{label}: {field_name} is not JSON: {exc}")
        low = f" {kuku.lower()} {analysis.lower()} "
        for word in DENY:
            if re.search(rf"(?<![a-z]){re.escape(word)}(?![a-z])", low):
                issues.append(f"{label}: denied phantom/wrong-sense/blocked token {word!r}")
        for token in kuku.split():
            if not degree_token_ok(token):
                issues.append(f"{label}: malformed degree token {token!r}; use ADJ-bajaku or ADJ-baja-CASE-ku")
        if kuku.lower() in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(kuku.lower())
    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 104 draft: adjective syntax, degree, comparison, and property change", "operator-guide-2026-07-07", "under_review", NOW),
        )
        batch_id = cur.lastrowid

    dupes = []
    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 104 source pass checked Patz adjective modifier order, nominal/adjectival predicates, inanimate "
            "possessor locative/ablative examples, jarra- degree and comparison, dative comparison standards, "
            "-baja-ku degree with case-slot behavior, and property inchoative/causative examples 314-324 and 499."
        ),
        command="rg/sed over grammar.md, dictionary.yaml, living books, plus sqlite duplicate scan",
        artifact_path="process_logs/batch-146-adjective-syntax-process.md",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="risk-controls",
        actor="claude-main",
        detail=(
            "Rejected risky forms before banking: bare bajaku as a separate word for Rule 104, bare -baja without -ku, "
            "superlative overreach beyond sourced high-degree/comparison patterns, exact duplicate Maral yinilman, "
            "exact duplicate Dubu-ngku maral yinil-kangan, and older colour or cold/heavy phantoms."
        ),
        artifact_path="process_logs/batch-146-adjective-syntax-process.md",
        created_at=NOW,
    )
    db.executemany(
        """
        INSERT INTO sentences(
            batch_id, seq, english, kuku, analysis, frame, tier, words_used,
            loans_used, evidence, status, confidence, rights_status, author,
            created_at, updated_at
        ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (
                batch_id, seq, english, kuku, analysis, frame, tier, words_used,
                loans_used, evidence, "under_review", confidence,
                "project_approved_synthetic_pending_elder_verification",
                "claude-main", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail="Banked 50 hand-authored Rule 104 adjective-syntax, degree, comparison, and property-change rows under_review after static and duplicate checks.",
        command="python3 batch_scripts/bank_146.py",
        artifact_path="batch_scripts/bank_146.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-12 NP modifier order, inalienable/inanimate possessor, and good-to-eat frame; 13-22 jarra- "
            "rather/fairly degree; 23-32 comparisons with dative standards; 33-44 -baja-ku degree and case-slot "
            "behavior; 45-50 property/nominal inchoative and causative changes."
        ),
        artifact_path="process_logs/batch-146-adjective-syntax-process.md",
        created_at=NOW,
    )
    db.commit()
    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    avg_words = db.execute(
        "SELECT AVG(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1.0) FROM sentences WHERE batch_id=?",
        (batch_id,),
    ).fetchone()[0]
    print(f"banked {LABEL}: {count} rows under_review; avg Kuku words/row={avg_words:.2f}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
