#!/usr/bin/env python3
"""Bank batch-161: source-backed narrative support lexicon II."""

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-161"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-161-source-backed-narrative-support-lexicon-ii-process.md"

DENY = {"yah", "ngawuya", "walnga", "maja-nga", "yala-coordination"}
CONSTRUCTION_CLITIC_MARKERS = ("-kuda", "-ngarr", "-ngurr", "-l-urr", "wawu-")
PRONOUN_A = {"ngayu", "ngana", "nganjin", "yundu", "yurra", "jana", "nyulu"}


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


ROWS = [
    (1, "The girl saw the dog in the house.",
     "Maral-angka kaya nyajin bayan-ba.",
     "maral-angka kaya nyaji-ny bayan-ba | girl-ERG dog see-PAST house-LOC",
     "lex_people_perception", 4, js(["maral", "kaya", "nyajil", "bayan"]), js([]),
     "Dictionary has maral/kaya/bayan; Patz ex.26 and case tables source maral A and nyaji-l perception.", "0.84"),
    (2, "The young man got fish at the river.",
     "Warru-ngku kuyu manin wawubaja-nga.",
     "warru-ngku kuyu mani-ny wawubaja-nga | young.man-ERG fish get-PAST river-LOC",
     "lex_people_food_place", 4, js(["warru", "kuyu", "manil", "wawubaja"]), js([]),
     "Warru/kuyu/wawubaja dictionary-backed; Patz ex.38/135 source fish-getting by the river.", "0.84"),
    (3, "Mother cooked food at camp.",
     "Ngamu-ngku mayi wajun bayan-ba.",
     "ngamu-ngku mayi waju-ny bayan-ba | mother-ERG food cook-PAST camp-LOC",
     "lex_camp_food", 4, js(["ngamu", "mayi", "wajul", "bayan"]), js([]),
     "Patz ex.97 and narrative food rows source mayi waju-l; camp/house LOC is established.", "0.84"),
    (4, "Father ate meat near the fire.",
     "Nganjan-angka minya nukan baya ngundu-ba.",
     "nganjan-angka minya nuka-ny baya ngundu-ba | father-ERG meat eat-PAST fire near-LOC",
     "lex_kin_food_location", 5, js(["nganjan", "minya", "nukal", "baya", "ngundu"]), js([]),
     "Minya/nuka-l and baya/ngundu are grammar-backed; father as A takes ERG.", "0.82"),
    (5, "The dog ate meat outside.",
     "Kaya-ngka minya nukan ngalkal-ba.",
     "kaya-ngka minya nuka-ny ngalkal-ba | dog-ERG meat eat-PAST outside-LOC",
     "lex_animal_food_location", 4, js(["kaya", "minya", "nukal", "ngalkal"]), js([]),
     "Patz ex.5/28 controls dog eating meat; ngalkal-ba outside is source-backed.", "0.82"),
    (6, "The crocodile came from the river.",
     "Bilngkumu wawubaja-muny kadan.",
     "bilngkumu wawubaja-muny kada-ny | crocodile river-ABL come-PAST",
     "lex_animal_motion_source", 3, js(["bilngkumu", "wawubaja", "kaday"]), js([]),
     "Bilngkumu dictionary/grammar; wawubaja-muny ABL and kada-y are sourced.", "0.84"),
    (7, "The snake is lying under the tree.",
     "Jarba badabada juku-ngu wunanay.",
     "jarba badabada juku-ngu wuna-n-wuna-y | snake underneath tree-LOC lie-n-REDUP-NONPAST",
     "lex_animal_location_posture", 4, js(["jarba", "badabada", "juku", "wunay"]), js([]),
     "Patz ex.278 directly sources jarba badabada juku-ngu wunana-y.", "0.88"),
    (8, "The wallaby came to the road.",
     "Kadar baral-ba kadan.",
     "kadar baral-ba kada-ny | wallaby road-LOC come-PAST",
     "lex_animal_motion_goal", 3, js(["kadar", "baral", "kaday"]), js([]),
     "Kadar and baral are source-backed; LOC goal with kada-y follows Patz route examples.", "0.80"),
    (9, "Grandmother got the bag from the house.",
     "Babi-ngka balji manin bayan-muny.",
     "babi-ngka balji mani-ny bayan-muny | grandmother-ERG bag get-PAST house-ABL",
     "lex_kin_object_source", 4, js(["babi", "balji", "manil", "bayan"]), js([]),
     "Balji/bayan source-backed; mani-l get with overt object and ABL source.", "0.82"),
    (10, "The child put the stone on the road.",
     "Karrkay-ngku kulji yijarrin baral-ba.",
     "karrkay-ngku kulji yijarri-ny baral-ba | child-ERG stone put-PAST road-LOC",
     "lex_child_object_location", 4, js(["karrkay", "kulji", "yijarril", "baral"]), js([]),
     "Patz ex.117/143 source kulji plus yijarri-l; baral-ba road location is established.", "0.84"),

    (11, "The woman put wood near the fire.",
     "Jalbu-ngku juku yijarrin baya ngundu-ba.",
     "jalbu-ngku juku yijarri-ny baya ngundu-ba | woman-ERG wood put-PAST fire near-LOC",
     "lex_camp_object_location", 5, js(["jalbu", "juku", "yijarril", "baya", "ngundu"]), js([]),
     "Juku as wood/tree and baya ngundu-ba near-fire are source-backed; object remains overt.", "0.82"),
    (12, "The old woman closed the house.",
     "Wulbuman-angka bayan nandan.",
     "wulbuman-angka bayan nanda-ny | old.woman-ERG house close-PAST",
     "lex_camp_action", 3, js(["wulbuman", "bayan", "nandal"]), js([]),
     "Dictionary nanda-l close; wulbuman and bayan checked.", "0.82"),
    (13, "The man swept the ground at camp.",
     "Dingkar-angka bubu kidan bayan-ba.",
     "dingkar-angka bubu kida-ny bayan-ba | man-ERG ground sweep-PAST camp-LOC",
     "lex_camp_action", 4, js(["dingkar", "bubu", "kidal", "bayan"]), js([]),
     "Kida-l sweep/remove/wipe and bubu ground are source-backed.", "0.82"),
    (14, "The children called out at night.",
     "Karrkay-karrkay wujurr-bu yirrkan.",
     "karrkay-karrkay wujurr-bu yirrka-ny | children night-LOC call.out-PAST",
     "lex_sound_time", 3, js(["karrkay", "wujurr", "yirrkay"]), js([]),
     "Patz Text 8 and ex.152 source yirrka-y; wujurr-bu is night LOC.", "0.84"),
    (15, "Grandfather spoke to the child.",
     "Kami-ngka karrkay-anda balkan.",
     "kami-ngka karrkay-anda balka-ny | grandfather-ERG child-LOC:pt speak-PAST",
     "lex_speech_addressee", 3, js(["kami", "karrkay", "balkal"]), js([]),
     "Balka-l speech addressee frame from Rule 118; kami/karrkay case forms source-backed.", "0.84"),
    (16, "The child asked elder brother about food.",
     "Karrkay yaba-nda mayi-ka babajin.",
     "karrkay yaba-nda mayi-ka babaji-ny | child elder.brother-LOC:pt food-DAT ask-PAST",
     "lex_ask_topic", 4, js(["karrkay", "yaba", "mayi", "babaji"]), js([]),
     "Patz ex.101 sources yaba-nda with babaji-y; mayi-ka DAT topic follows Rule 118.", "0.88"),
    (17, "The girl asked mother about the bag.",
     "Maral ngamu-ndu balji-ka babajin.",
     "maral ngamu-ndu balji-ka babaji-ny | girl mother-LOC:pt bag-DAT ask-PAST",
     "lex_ask_topic", 4, js(["maral", "ngamu", "balji", "babaji"]), js([]),
     "Babaji-y fixed-intransitive frame: asker ABS/S, person LOC:pt, topic DAT.", "0.88"),
    (18, "They gave clothes to the child.",
     "Jana kambi dajin karrkay-anda.",
     "jana kambi daji-ny karrkay-anda | they clothes give-PAST child-LOC:pt",
     "lex_transfer", 4, js(["jana", "kambi", "dajil", "karrkay"]), js([]),
     "Patz ex.59 source jana kambi daji-ny with LOC:pt recipient.", "0.86"),
    (19, "They brought food from the house.",
     "Jana mayi bayan-muny wundin.",
     "jana mayi bayan-muny wundi-ny | they food house-ABL bring-PAST",
     "lex_take_source", 4, js(["jana", "mayi", "bayan", "wundil"]), js([]),
     "Wundi-l object plus ABL source follows Rule 118 and Patz wundi frames.", "0.84"),
    (20, "They took fish to camp.",
     "Jana kuyu bayan-ka wundin.",
     "jana kuyu bayan-ka wundi-ny | they fish camp-DAT take-PAST",
     "lex_take_goal", 4, js(["jana", "kuyu", "bayan", "wundil"]), js([]),
     "Wundi-l object plus DAT goal; kuyu/bayan source-backed.", "0.84"),

    (21, "The young man made a spear from wood.",
     "Warru-ngku kalka balkan juku-muny.",
     "warru-ngku kalka balka-ny juku-muny | young.man-ERG spear make-PAST wood-ABL",
     "lex_make_material", 4, js(["warru", "kalka", "balkal", "juku"]), js([]),
     "Rule 118 material-source frame with a new source-backed participant.", "0.82"),
    (22, "The old woman made a bag from grass.",
     "Wulbuman-angka balji balkan jikan-muny.",
     "wulbuman-angka balji balka-ny jikan-muny | old.woman-ERG bag make-PAST grass-ABL",
     "lex_make_material", 4, js(["wulbuman", "balji", "balkal", "jikan"]), js([]),
     "Balji/jikan material-source frame; participant varied from Rule 118.", "0.82"),
    (23, "The child heard thunder from the mountain.",
     "Karrkay-ngku jarramali manjal-muny nyajin.",
     "karrkay-ngku jarramali manjal-muny nyaji-ny | child-ERG thunder mountain-ABL hear-PAST",
     "lex_hear_source", 4, js(["karrkay", "jarramali", "manjal", "nyajil"]), js([]),
     "Jarramali/manjal source-backed; common-noun perceiver marked ERG.", "0.82"),
    (24, "I heard the dog from the house.",
     "Ngayu kaya bayan-muny nyajin.",
     "ngayu kaya bayan-muny nyaji-ny | I dog house-ABL hear-PAST",
     "lex_hear_source", 4, js(["ngayu", "kaya", "bayan", "nyajil"]), js([]),
     "Pronoun A remains unmarked; nyaji-l object plus ABL source follows Rule 118.", "0.82"),
    (25, "We heard a song at night.",
     "Ngana wulngku wujurr-bu nyajin.",
     "ngana wulngku wujurr-bu nyaji-ny | we song night-LOC hear-PAST",
     "lex_hear_time", 4, js(["ngana", "wulngku", "wujurr", "nyajil"]), js([]),
     "Wulngku and wujurr-bu are source-backed; pronoun A stays unmarked.", "0.82"),
    (26, "The old people listened near the fire.",
     "Mukul-bama baya ngundu-ba milka-janan.",
     "mukul-bama baya ngundu-ba milka-jana-ny | old.people fire near-LOC listen-PAST",
     "lex_listen_location", 4, js(["mukul-bama", "baya", "ngundu", "milka-janay"]), js([]),
     "Concrete listen only; no ngara-l semantic drift.", "0.84"),
    (27, "Father laughed at the child.",
     "Nganjan karrkay-anka yajarrin.",
     "nganjan karrkay-anka yajarri-ny | father child-DAT:pt laugh-PAST",
     "lex_laugh_target", 3, js(["nganjan", "karrkay", "yajarril"]), js([]),
     "Rule 118 DAT:pt laugh target retained; subject ABS/S.", "0.84"),
    (28, "Grandmother laughed at father.",
     "Babi nganjan-anka yajarrin.",
     "babi nganjan-anka yajarri-ny | grandmother father-DAT:pt laugh-PAST",
     "lex_laugh_target", 3, js(["babi", "nganjan", "yajarril"]), js([]),
     "DAT:pt father target `nganjan-anka` follows Batch 160 correction.", "0.84"),
    (29, "The man found mussels in the river.",
     "Dingkar-angka mukirr wawubaja-nga warrmba-bungan.",
     "dingkar-angka mukirr wawubaja-nga warrmba-bunga-ny | man-ERG mussel river-LOC find-CAUS-PAST",
     "lex_find_object_location", 4, js(["dingkar", "mukirr", "wawubaja", "warrmba-bungal"]), js([]),
     "Patz Text 8 sources mukirr and warrmba-bunga-l find with overt object.", "0.84"),
    (30, "I found a rock there.",
     "Ngayu kulji yinyay-mba warrmba-bungan.",
     "ngayu kulji yinyay-mba warrmba-bunga-ny | I rock there-LOC find-CAUS-PAST",
     "lex_find_object_location", 4, js(["ngayu", "kulji", "yinyay", "warrmba-bungal"]), js([]),
     "Patz ex.142/143 source yinyay-mba and kulji; found object remains overt.", "0.84"),

    (31, "The child got a rock there.",
     "Karrkay-ngku kulji manin yinyay-mba.",
     "karrkay-ngku kulji mani-ny yinyay-mba | child-ERG rock get-PAST there-LOC",
     "lex_get_object_location", 4, js(["karrkay", "kulji", "manil", "yinyay"]), js([]),
     "Mani-l get with overt object; yinyay-mba there-LOC source-backed.", "0.82"),
    (32, "The old woman came from down there.",
     "Wulbuman badabada-mundu kadan.",
     "wulbuman badabada-mundu kada-ny | old.woman down.there-ABESS come-PAST",
     "lex_motion_source_location", 3, js(["wulbuman", "badabada", "kaday"]), js([]),
     "Patz ex.110 sources badabada-mundu as a source expression.", "0.82"),
    (33, "The man went up there.",
     "Dingkar wangkarwangkar dungan.",
     "dingkar wangkarwangkar dunga-ny | man up.there go-PAST",
     "lex_motion_location", 3, js(["dingkar", "wangkarwangkar", "dungay"]), js([]),
     "Patz location section and Text 12 source wangkarwangkar with movement/location.", "0.82"),
    (34, "The children were down there.",
     "Karrkay-karrkay badabada.",
     "karrkay-karrkay badabada | children down.there",
     "lex_verbless_location", 2, js(["karrkay", "badabada"]), js([]),
     "Patz ex.297 has Jana karrkay -- badabada; lexical variation keeps the locational predicate.", "0.84"),
    (35, "There is heavy rain today.",
     "Nyiku kaba yalbay.",
     "nyiku kaba yalbay | today rain big/heavy",
     "lex_weather_time", 3, js(["nyiku", "kaba", "yalbay"]), js([]),
     "Patz Text 14 has Kaba yalbay; nyiku today is source-backed.", "0.84"),
    (36, "A thunderstorm came yesterday.",
     "Yilayku jarramali kadan.",
     "yilayku jarramali kada-ny | yesterday thunderstorm come-PAST",
     "lex_weather_time", 3, js(["yilayku", "jarramali", "kaday"]), js([]),
     "Jarramali kada-y and yilayku are grammar-backed.", "0.84"),
    (37, "Tomorrow wind will come from the mountain.",
     "Wunkuny muyar manjal-muny kaday.",
     "wunkuny muyar manjal-muny kada-y | tomorrow wind mountain-ABL come-NONPAST",
     "lex_weather_future_source", 4, js(["wunkuny", "muyar", "manjal", "kaday"]), js([]),
     "Patz notes future with wunkuny plus nonpast; muyar/manjal source frame follows Rule 118.", "0.82"),
    (38, "The sun is above.",
     "Wungar wangkarwangkar.",
     "wungar wangkarwangkar | sun above",
     "lex_time_verbless", 2, js(["wungar", "wangkarwangkar"]), js([]),
     "Patz time section sources wungar wangkarwangkar as a sun/time phrase.", "0.84"),
    (39, "I saw the moon at night.",
     "Ngayu kija nyajin wujurr-bu.",
     "ngayu kija nyaji-ny wujurr-bu | I moon see-PAST night-LOC",
     "lex_celestial_perception", 4, js(["ngayu", "kija", "nyajil", "wujurr"]), js([]),
     "Kija is moon/month only; gloss keeps moon sense explicit.", "0.86"),
    (40, "I saw a star at night.",
     "Ngayu dawar nyajin wujurr-bu.",
     "ngayu dawar nyaji-ny wujurr-bu | I star see-PAST night-LOC",
     "lex_celestial_perception", 4, js(["ngayu", "dawar", "nyajil", "wujurr"]), js([]),
     "Dawar star is dictionary-backed; wujurr-bu and nyaji-l are source-backed.", "0.84"),

    (41, "The baby slept in the house.",
     "Ngawa bayan-ba warngku wunan.",
     "ngawa bayan-ba warngku wuna-ny | baby house-LOC sleep lie-PAST",
     "lex_sleep_location", 4, js(["ngawa", "bayan", "warngku-wunay"]), js([]),
     "Ngawa, bayan, and warngku wuna-y are source-backed.", "0.84"),
    (42, "At daybreak the baby woke.",
     "Muduwajuny ngawa wandin.",
     "muduwaju-ny ngawa wandi-ny | daybreak-TIME baby wake-PAST",
     "lex_wake_time", 3, js(["muduwaju", "ngawa", "wandil"]), js([]),
     "Wandi-l wake/emerge and muduwajuny daybreak are source-backed.", "0.84"),
    (43, "Mother and father were sleeping.",
     "Ngamu nganjan wunanan.",
     "ngamu nganjan wuna-n-wuna-ny | mother father lie-n-REDUP-PAST",
     "lex_sleep_participants", 3, js(["ngamu", "nganjan", "wunay"]), js([]),
     "Patz Text 8 sources ngamu nganjan wunana-ny.", "0.88"),
    (44, "The friend is staying at camp.",
     "Jawun bayan-ba bundanday.",
     "jawun bayan-ba bunda-n-bunda-y | friend camp-LOC sit-n-REDUP-NONPAST",
     "lex_stay_location", 3, js(["jawun", "bayan", "bunday"]), js([]),
     "Jawun and bundanda-y stay/sit-state are source-backed.", "0.84"),
    (45, "The girl came from the house.",
     "Maral bayan-muny kadan.",
     "maral bayan-muny kada-ny | girl house-ABL come-PAST",
     "lex_motion_source", 3, js(["maral", "bayan", "kaday"]), js([]),
     "Patz ex.49 has came from a house with maral/yaba possessive source; simplified frame keeps sourced heads.", "0.82"),
    (46, "The child went to the house.",
     "Karrkay bayan-ba dungan.",
     "karrkay bayan-ba dunga-ny | child house-LOC go-PAST",
     "lex_motion_goal", 3, js(["karrkay", "bayan", "dungay"]), js([]),
     "Patz ex.45 sources dunga-y bayan-ba; past surface follows corpus convention.", "0.82"),
    (47, "Grandfather went with father.",
     "Kami nganjan-anji dungan.",
     "kami nganjan-anji dunga-ny | grandfather father-COMIT:pt go-PAST",
     "lex_motion_comitative", 3, js(["kami", "nganjan", "dungay"]), js([]),
     "Patz comitative table sources nganjan-anji; dunga-y frame is basic movement.", "0.82"),
    (48, "I go with a friend.",
     "Ngayu jawun-unji dungay.",
     "ngayu jawun-unji dunga-y | I friend-COMIT:pt go-NONPAST",
     "lex_motion_comitative", 3, js(["ngayu", "jawun", "dungay"]), js([]),
     "Patz ex.163 directly sources Ngayu jawun-unji dunga-y.", "0.88"),
    (49, "The child took the dog's food.",
     "Karrkay-ngku mayi kaya-nga wundin.",
     "karrkay-ngku mayi kaya-nga wundi-ny | child-ERG food dog-POSS.ABS take-PAST",
     "lex_possessed_object_take", 4, js(["karrkay", "mayi", "kaya", "wundil"]), js([]),
     "Patz possessive table sources mayi kaya-nga; wundi-l keeps moved object overt.", "0.84"),
    (50, "The dog saw the snake under the tree.",
     "Kaya-ngka jarba nyajin badabada juku-ngu.",
     "kaya-ngka jarba nyaji-ny badabada juku-ngu | dog-ERG snake see-PAST underneath tree-LOC",
     "lex_animal_perception_location", 5, js(["kaya", "jarba", "nyajil", "badabada", "juku"]), js([]),
     "Patz ex.10 has seeing a snake; ex.278 sources badabada juku-ngu.", "0.84"),
]


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 in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row
        label = f"{LABEL}.{seq:02d}"
        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("lex_"):
            issues.append(f"{label}: lexical batch frame must start with lex_")
        if kuku.lower() in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(kuku.lower())
        low = kuku.lower()
        for marker in CONSTRUCTION_CLITIC_MARKERS:
            if marker in low:
                issues.append(f"{label}: lexical support batch must not reuse construction clitic marker {marker}")
        for denied in DENY:
            if denied in low:
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        if "yunga" in low and re.search(r"\bto\b", english.lower()):
            issues.append(f"{label}: yunga-l must not be translated as recipient 'to X' without new source evidence")
        if "babajin" in low and re.match(r"^[A-Za-z-]+-(?:ngka|angka|ngku)\b", kuku):
            issues.append(f"{label}: babaji-y asker drifted into ergative A marking")
        if "nyaji" in analysis:
            first = low.split()[0].strip(".,!?")
            if first not in PRONOUN_A and not re.search(r"-(?:ngka|angka|ngku)\b", first):
                issues.append(f"{label}: common-noun perceiver with nyaji-l should be ERG-marked")
        if "yajarrin" in low and not re.search(r"-(?:anka|nka)\b", low):
            issues.append(f"{label}: laugh target should show DAT:pt -anka/-nka")
        if "kija" in low and "moon" not in analysis.lower():
            issues.append(f"{label}: kija is moon/month only; analysis must say moon/month")
        try:
            json.loads(words_used)
            json.loads(loans_used)
        except json.JSONDecodeError as exc:
            issues.append(f"{label}: JSON field failed: {exc}")
        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 119 draft: source-backed narrative support lexicon II", "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 119 source pass checked dictionary.yaml and Patz grammar for source-backed narrative support "
            "lexicon: people/kin, animals/food, places/route words, sound/perception objects, weather/time words, "
            "and the existing movement, perception, transfer, posture, and weather predicates."
        ),
        command="rg over dictionary.yaml word entries and grammar.md examples/tables",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="lexicon-source-check",
        actor="claude-main",
        detail=(
            "Confirmed dictionary-backed Batch 161 heads: babi, balji, bana, baral, baya, bayan, bilngkumu, bubu, "
            "dawar, dingkar, jalbu, jarramali, jawun, jikan, jina, juku, kaba, kalka, kami, karrkay, kaya, kija, "
            "kuku, kuyu, madja, manjal, maral, mayi, minya, mukirr, muyar, ngalkal, ngamu, nganjan, ngawa, ngundu, "
            "nyiku, warru, wawubaja, wujurr, wulbuman, wulngku, wungar, yaba, yilayku, yilay-yilay, yinyay; plus "
            "verbs babaji, balkal, bunday, dajil, daray, dungay, kaday, kidal, manil, milka-janay, nandal, nukal, "
            "nyajil, wandil, warngku wunay, warrmba-bungal, wukurril, wulay, wunay, wundil, yajarril, yijarril, yirrkay."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="grammar-source-check",
        actor="claude-main",
        detail=(
            "Grammar controls used: noun/case tables for bama, maral, kaya, dingkar, kami, karrkay, bayan, baral, "
            "wawubaja, manjal, jikan, juku, wujurr-bu, nganjan-anji, and jawun-unji; examples 38, 45-49, 57, "
            "59, 63, 106, 109-111, 123, 127, 142-145, 163-165, 271-274, 278, 297, Text 8, Text 12, and Text 14."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="risk-controls",
        actor="claude-main",
        detail=(
            "Controls: no new temporal clitics, no maja-nga scrub spelling, no yunga-l recipient rows, no ergative "
            "babaji-y askers, ERG common-noun perceivers with nyaji-l, DAT:pt laugh targets, explicit objects on "
            "transitives, and kija restricted to moon/month."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="existing-surface-scan",
        actor="claude-main",
        detail="Checked all 50 planned Kuku surfaces against the full non-rejected corpus; no exact duplicates found before insert.",
        command="sqlite exact-surface query over planned Batch 161 rows",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="process",
        event_type="process-capture-expanded",
        actor="claude-main",
        detail=(
            "Batch 161 stores source inventory, inherited risk controls, duplicate scan, static guards, row grouping, "
            "bank/lint/review/close/export/docs outcomes, and live verification as process_events plus the narrative process log."
        ),
        artifact_path=PROCESS_LOG,
        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 119 lexical-support rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_161.py",
        artifact_path="batch_scripts/bank_161.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-15 people/animals/camp/perception/food handling; 16-25 speech/asking/transfer/taking/making/hearing; "
            "26-35 finding/locationals/weather/celestial/time; 36-45 sleep/stay/wake/go/come; 46-50 comitative, "
            "possessed food, and animal perception contrasts."
        ),
        artifact_path=PROCESS_LOG,
        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]
    process_events = db.execute("SELECT COUNT(*) FROM process_events WHERE batch_label=?", (LABEL,)).fetchone()[0]
    print(f"banked {LABEL}: {count} rows under_review, avg_words={avg_words:.2f}, process_events={process_events}")
    return 0


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