#!/usr/bin/env python3
"""Bank batch-159: narrative sequence support lexicon."""

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

DENY = {"yah", "ngawuya", "walnga", "yala-coordination"}
DENY_STANDALONE = {"ba", "ma"}
CONSTRUCTION_CLITIC_MARKERS = ("-kuda", "-ngarr", "-ngurr", "-l-urr", "wawu-")


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


ROWS = [
    (1, "Tomorrow we go to the river.",
     "Wunkuny ngana wawubaja-nga dungay.",
     "wunkuny ngana wawubaja-nga dunga-y | tomorrow we river-LOC go-NONPAST",
     "lex_movement_go", 3, js(["wunkuny", "ngana", "wawubaja", "dungay"]), js([]),
     "Patz ex.114 has wunkuny with dunga-y; river locative is a recurring sourced frame.", "0.86"),
    (2, "Yesterday the children came back to camp.",
     "Yilayku karrkay-karrkay bayan-ka kadan-baja.",
     "yilayku karrkay-karrkay bayan-ka kada-ny-baja | yesterday children camp-DAT come-PAST-again",
     "lex_movement_return", 4, js(["yilayku", "karrkay", "bayan", "kaday"]), js([]),
     "Patz text sources kada-ny-baja; subject and temporal word vary prior rows.", "0.84"),
    (3, "I went along the road, and father followed me.",
     "Ngayu baral-darr dungan, nganjan-angka nganya wukurrin.",
     "ngayu baral-darr dunga-ny, nganjan-angka nganya wukurri-ny | I road-PERL go-PAST father-ERG me follow-PAST",
     "lex_movement_follow", 5, js(["ngayu", "baral", "dungay", "nganjan", "nganya", "wukurril"]), js([]),
     "Patz ex.111 sources wukurri-ny follow; baral-darr travel frame is already source-backed.", "0.84"),
    (4, "The dog followed the children from behind.",
     "Kaya-ngka karrkay-karrkay wukurrin kudamundu.",
     "kaya-ngka karrkay-karrkay wukurri-ny kudamundu | dog-ERG children follow-PAST behind.LOC",
     "lex_movement_follow", 4, js(["kaya", "karrkay", "wukurril", "kudamundu"]), js([]),
     "Patz ex.111 has wukurri-ny kudamundu; actor/object changed.", "0.82"),
    (5, "The old woman left the dog up there.",
     "Wulbuman-angka kaya bawan wangkarwangkar.",
     "wulbuman-angka kaya bawa-ny wangkarwangkar | old.woman-ERG dog leave-PAST up.there",
     "lex_movement_leave", 4, js(["wulbuman", "kaya", "bawal", "wangkarwangkar"]), js([]),
     "Dictionary bawal 'leave/quit'; Patz text lines source bawa-ny with wangkarwangkar.", "0.82"),
    (6, "I left the camp and went to the river.",
     "Ngayu bayan bawan, wawubaja-nga dungan.",
     "ngayu bayan bawa-ny, wawubaja-nga dunga-ny | I camp leave-PAST river-LOC go-PAST",
     "lex_movement_leave_go", 4, js(["ngayu", "bayan", "bawal", "wawubaja", "dungay"]), js([]),
     "Dictionary bawal example has leave-place plus go-place; places varied from the source.", "0.82"),
    (7, "Rain came, and we turned back.",
     "Kaba kadan, ngana nyunban.",
     "kaba kada-ny, ngana nyunba-ny | rain come-PAST we turn.back-PAST",
     "lex_movement_turn_back", 4, js(["kaba", "kaday", "ngana", "nyunbay"]), js([]),
     "Dictionary nyunbay 'return/go back'; Patz text line has ngana nyunba-y-baja after heavy rain.", "0.84"),
    (8, "The friend came from the sea.",
     "Jawun jalun-muny kadan.",
     "jawun jalun-muny kada-ny | friend sea-ABL come-PAST",
     "lex_movement_come_from", 3, js(["jawun", "jalun", "kaday"]), js([]),
     "Ablative source frame varies Batch 157 wind-from-sea row with a human subject.", "0.80"),
    (9, "Mother brought the child to the house.",
     "Ngamu-ngku karrkay bayan-ka wundin.",
     "ngamu-ngku karrkay bayan-ka wundi-ny | mother-ERG child house-DAT bring-PAST",
     "lex_transfer_bring", 4, js(["ngamu", "karrkay", "bayan", "wundil"]), js([]),
     "Dictionary wundil 'bring/take'; Patz examples use wundi-ny for taking/bringing people and objects.", "0.84"),
    (10, "The two walked for a long time.",
     "Bula ngadi-ngka jilba-dungan.",
     "bula ngadi-ngka jilba-dunga-ny | they.two long.time-DAT walk-go-PAST",
     "lex_movement_walk", 4, js(["bula", "ngadi", "jilba-dunga-y"]), js([]),
     "Patz sources ngadi-ngka 'for a long time' and jilba dunga-y 'go for a walk'.", "0.82"),

    (11, "In the morning grandmother got firewood.",
     "Wunkuwingku babi-ngka baya manin.",
     "wunkuwingku babi-ngka baya mani-ny | morning grandmother-ERG firewood get-PAST",
     "lex_preparation_get", 4, js(["wunkuwingku", "babi", "baya", "manil"]), js([]),
     "Dictionary manil/get and Patz firewood/food-get narrative frames; temporal varies prior cooking rows.", "0.82"),
    (12, "We took water for the road.",
     "Ngana bana wundin baral-ka.",
     "ngana bana wundi-ny baral-ka | we water take-PAST road-DAT",
     "lex_preparation_take", 4, js(["ngana", "bana", "wundil", "baral"]), js([]),
     "Patz examples 397-398 source bana wundi-l for road-water preparation.", "0.86"),
    (13, "Father filled the billycan with water.",
     "Nganjan-angka jungkalu warmban bana-ji.",
     "nganjan-angka jungkalu warmba-ny bana-ji | father-ERG billycan fill-PAST water-COMIT",
     "lex_preparation_fill", 4, js(["nganjan", "jungkalu", "warmbal", "bana"]), js([]),
     "Dictionary/prior source-backed warmbal 'fill'; comitative water frame follows established rows.", "0.78"),
    (14, "The young man put the spear in the house.",
     "Warru-ngku kalka yijarrin bayan-ba.",
     "warru-ngku kalka yijarri-ny bayan-ba | young.man-ERG spear put-PAST house-LOC",
     "lex_preparation_put", 4, js(["warru", "kalka", "yijarril", "bayan"]), js([]),
     "Dictionary yijarril 'put'; Patz ex.200 and text lines source yijarri-ny place frames.", "0.84"),
    (15, "We set up camp near the river.",
     "Ngana bayan ngaran wawubaja ngundu-ba.",
     "ngana bayan ngara-ny wawubaja ngundu-ba | we camp set.up-PAST river close-LOC",
     "lex_camp_set_up", 5, js(["ngana", "bayan", "ngaral", "wawubaja", "ngundu"]), js([]),
     "Patz ex.234 and text lines source bayan ngara-ny 'set up camp'.", "0.84"),
    (16, "The child swept the ground for camp.",
     "Karrkay-ngku bubu kidan bayan-ka.",
     "karrkay-ngku bubu kida-ny bayan-ka | child-ERG ground sweep-PAST camp-DAT",
     "lex_camp_sweep", 4, js(["karrkay", "bubu", "kidal", "bayan"]), js([]),
     "Dictionary kidal 'sweep/remove/wipe'; Patz ex.499 and 5439 source bubu kida-l camp-prep frames.", "0.84"),
    (17, "In the late afternoon mother closed the house.",
     "Yilay-yilay ngamu-ngku bayan nandan.",
     "yilay-yilay ngamu-ngku bayan nanda-ny | late.afternoon mother-ERG house close-PAST",
     "lex_camp_close", 4, js(["yilay-yilay", "ngamu", "bayan", "nandal"]), js([]),
     "Dictionary nandal 'close/shut/bury'; time word avoids exact prior house-close rows.", "0.82"),
    (18, "Grandfather cooked fish at camp.",
     "Kami-ngka kuyu wajun bayan-ba.",
     "kami-ngka kuyu waju-ny bayan-ba | grandfather-ERG fish cook-PAST camp-LOC",
     "lex_food_cook", 4, js(["kami", "kuyu", "wajul", "bayan"]), js([]),
     "Dictionary waju-l cook/burn and Patz food/fire cooking examples.", "0.82"),
    (19, "The children ate food in the shade.",
     "Karrkay-karrkay mayi nukan wabarr-ba.",
     "karrkay-karrkay mayi nuka-ny wabarr-ba | children food eat-PAST shade-LOC",
     "lex_food_eat", 4, js(["karrkay", "mayi", "nukal", "wabarr"]), js([]),
     "Dictionary nukal eat/drink; locative shade frame varies earlier food rows.", "0.82"),
    (20, "Give water to grandmother.",
     "Bana daya babi-nda!",
     "bana daya babi-nda | water give.IMP grandmother-DAT",
     "lex_transfer_give_imperative", 3, js(["bana", "dajil", "babi"]), js([]),
     "Dictionary/Patz command form daya for daji-l give; object varied to water.", "0.82"),

    (21, "Today the children stay at camp.",
     "Nyiku karrkay-karrkay bayan-ba bundanday.",
     "nyiku karrkay-karrkay bayan-ba bundanda-y | today children camp-LOC stay.REDUP-NONPAST",
     "lex_rest_stay", 4, js(["nyiku", "karrkay", "bayan", "bundanday"]), js([]),
     "Patz posture section sources bundanda-y as stay/live/sit-state; dictionary is secondary here.", "0.86"),
    (22, "Father rested near the fire.",
     "Nganjan baya ngundu-ba wunanan.",
     "nganjan baya ngundu-ba wunana-ny | father fire close-LOC lie.REDUP-PAST",
     "lex_rest_rest", 4, js(["nganjan", "baya", "ngundu", "wunay"]), js([]),
     "Patz ex.228 has Nganjan wunana-y 'Father is resting'; row adds sourced location frame.", "0.86"),
    (23, "The old people sat in the shade.",
     "Mukul-bama wabarr-ba bundan.",
     "mukul-bama wabarr-ba bunda-ny | old-people shade-LOC sit-PAST",
     "lex_rest_sit", 4, js(["mukul-bama", "wabarr", "bundanday"]), js([]),
     "Patz posture examples source bunda-ny/bundanda-y; shade locative from prior source-backed rows.", "0.84"),
    (24, "The baby slept without a blanket.",
     "Ngawa kararr-kari warngku wunan.",
     "ngawa kararr-kari warngku wuna-ny | baby blanket-PRIV sleep lie-PAST",
     "lex_sleep_sleep", 4, js(["ngawa", "kararr", "warngku-wunay"]), js([]),
     "Patz ex.563 has baby sleep without blanket; tense varied to past surface.", "0.86"),
    (25, "At daybreak grandmother got up.",
     "Muduwajuny babi wandin.",
     "muduwaju-ny babi wandi-ny | daybreak-TIME grandmother get.up-PAST",
     "lex_wake_get_up", 3, js(["muduwaju", "babi", "wandil"]), js([]),
     "Dictionary wandil 'get up/come out'; Patz table gives wandi-l wake up.", "0.84"),
    (26, "In the morning the child came out of the house.",
     "Wunkuwingku karrkay bayan-muny wandin.",
     "wunkuwingku karrkay bayan-muny wandi-ny | morning child house-ABL come.out-PAST",
     "lex_wake_come_out", 4, js(["wunkuwingku", "karrkay", "bayan", "wandil"]), js([]),
     "Patz text line Wunkuny-ku nyulu wandi-ny, dunga-ny sources wandi-ny as emerge/come out.", "0.84"),
    (27, "The man was tired and slept.",
     "Dingkar baja-burray, warngku wunan.",
     "dingkar baja-burray, warngku wuna-ny | man tired sleep lie-PAST",
     "lex_sleep_tired_sleep", 4, js(["dingkar", "baja-burray", "warngku-wunay"]), js([]),
     "Patz ex.395/396 links tired/exhausted state with warngku-wuna-y sleep.", "0.80"),
    (28, "We lay down in the house.",
     "Ngana bayan-ba wunan.",
     "ngana bayan-ba wuna-ny | we house-LOC lie-PAST",
     "lex_rest_lie", 3, js(["ngana", "bayan", "wunay"]), js([]),
     "Dictionary wunay and Patz posture section source wuna-ny as lie down.", "0.84"),
    (29, "The child slept beside mother.",
     "Karrkay ngamu ngundu-ba warngku wunan.",
     "karrkay ngamu ngundu-ba warngku wuna-ny | child mother close-LOC sleep lie-PAST",
     "lex_sleep_near", 5, js(["karrkay", "ngamu", "ngundu", "warngku-wunay"]), js([]),
     "Warngku wunay dictionary/Patz sleep compound; ngundu-ba close-locative frame is source-backed.", "0.82"),
    (30, "Rain stopped at sunset.",
     "Wungar daray, kaba wulan.",
     "wungar daray, kaba wula-ny | sun fall.NP rain stop/die-PAST",
     "lex_weather_stop", 4, js(["wungar daray", "kaba", "wulay"]), js([]),
     "Dictionary wulay 'die'; prior source-backed extension uses rain stopping after rain sequence.", "0.78"),

    (31, "The child asked grandmother about food.",
     "Karrkay-ngku babi-nda mayi-ka babajin.",
     "karrkay-ngku babi-nda mayi-ka babaji-ny | child-ERG grandmother-DAT food-DAT ask-PAST",
     "lex_speech_ask", 4, js(["karrkay", "babi", "mayi", "babaji"]), js([]),
     "Dictionary babaji ask and Patz ask examples; mayi-ka follows Patz DAT harmony for food.", "0.82"),
    (32, "Grandmother told the child to sit.",
     "Babi-ngka karrkay-anda balkan bunda-nka.",
     "babi-ngka karrkay-anda balka-ny bunda-nka | grandmother-ERG child-DAT tell-PAST sit-PURP",
     "lex_speech_tell", 5, js(["babi", "karrkay", "balkal", "bundanday"]), js([]),
     "Dictionary balkal tell/make; Patz ex.415 sources balka-ny plus purposive complement.", "0.84"),
    (33, "We hear a song from far away.",
     "Ngana wulngku kalakalbay-mundu nyajil.",
     "ngana wulngku kalakalbay-mundu nyaji-l | we song far.away-ABESS hear-NONPAST",
     "lex_perception_hear", 4, js(["ngana", "wulngku", "kalakalbay", "nyajil"]), js([]),
     "Patz ex.109 sources wulngku kalakalbay-mundu nyaji-l; subject varied.", "0.86"),
    (34, "In the morning the children listened.",
     "Wunkuwingku karrkay-karrkay milka-janan.",
     "wunkuwingku karrkay-karrkay milka-jana-ny | morning children ear-stand-PAST",
     "lex_perception_listen", 4, js(["wunkuwingku", "karrkay", "milka-janay"]), js([]),
     "Dictionary milka-janay listen/obey; Patz notes milka-jana-y 'listen'.", "0.84"),
    (35, "Mother sent a message to father.",
     "Ngamu-ngku kuku nganjan-anda yungan.",
     "ngamu-ngku kuku nganjan-anda yunga-ny | mother-ERG word/message father-DAT send-PAST",
     "lex_speech_send", 4, js(["ngamu", "kuku", "nganjan", "yungal"]), js([]),
     "Dictionary yungal send and Patz ex.274 Maja-ngka kuku yunga-ny buliman-anda.", "0.84"),
    (36, "The young man laughed at the children.",
     "Warru-ngku kaykay-anka yajarrin.",
     "warru-ngku kaykay-anka yajarri-ny | young.man-ERG children-DAT laugh-PAST",
     "lex_response_laugh", 3, js(["warru", "kaykay", "yajarril"]), js([]),
     "Dictionary yajarril laugh; Patz text line has yajarri-ny kaykay-anka.", "0.82"),
    (37, "Grandfather called out at night.",
     "Wujurr-bu kami yirrkan.",
     "wujurr-bu kami yirrka-ny | night-LOC grandfather call.out-PAST",
     "lex_speech_call_out", 3, js(["wujurr", "kami", "yirrka-y"]), js([]),
     "Patz examples source yirrka-ny 'called/sang out'; night locative from prior source-backed rows.", "0.82"),
    (38, "The old people sang, and the children listened.",
     "Mukul-bama wulngku badin, karrkay-karrkay milka-janan.",
     "mukul-bama wulngku badi-ny, karrkay-karrkay milka-jana-ny | old-people song sing-PAST children listen-PAST",
     "lex_perception_listen_to_song", 5, js(["mukul-bama", "wulngku", "badi", "karrkay", "milka-janay"]), js([]),
     "Badi/wulngku from Rule 115 source pass; milka-janay listen from dictionary and Patz note.", "0.82"),
    (39, "I saw the track on the road.",
     "Ngayu baral-ba jina nyajin.",
     "ngayu baral-ba jina nyaji-ny | I road-LOC track see-PAST",
     "lex_perception_see", 4, js(["ngayu", "baral", "jina", "nyajil"]), js([]),
     "Dictionary nyajil see/hear; track/road frame is source-backed in movement batches.", "0.82"),
    (40, "Father found many tracks.",
     "Nganjan-angka jina wubul warrmba-bungan.",
     "nganjan-angka jina wubul warrmba-bunga-ny | father-ERG track many find-CAUS-PAST",
     "lex_search_find", 4, js(["nganjan", "jina", "wubul", "warrmba-bungal"]), js([]),
     "Dictionary warrmba-bungal find; Patz ex.142 has jarruka wubul warrmba-bunga-ny.", "0.84"),

    (41, "Rain fell at daybreak.",
     "Muduwajuny kaba daran.",
     "muduwaju-ny kaba dara-ny | daybreak-TIME rain fall-PAST",
     "lex_weather_rain_fall", 3, js(["muduwaju", "kaba", "daray"]), js([]),
     "Dictionary daray fall; temporal word varies Batch 157 rain rows.", "0.82"),
    (42, "In the late afternoon, wind came from the sea.",
     "Yilay-yilay muyar jalun-muny kadan.",
     "yilay-yilay muyar jalun-muny kada-ny | late.afternoon wind sea-ABL come-PAST",
     "lex_weather_wind", 4, js(["yilay-yilay", "muyar", "jalun", "kaday"]), js([]),
     "Reuses sourced muyar/jalun-muny/kada-ny frame with a new temporal anchor.", "0.82"),
    (43, "A thunderstorm came from the sea.",
     "Jarramali jalun-muny kadan.",
     "jarramali jalun-muny kada-ny | thunderstorm sea-ABL come-PAST",
     "lex_weather_storm", 3, js(["jarramali", "jalun", "kaday"]), js([]),
     "Rule 115 source pass verified jarramali thunderstorm and weather-event kada-y.", "0.82"),
    (44, "In the morning, rain stopped.",
     "Wunkuwingku kaba wulan.",
     "wunkuwingku kaba wula-ny | morning rain stop/die-PAST",
     "lex_weather_stop", 3, js(["wunkuwingku", "kaba", "wulay"]), js([]),
     "Dictionary wulay die/stop; temporal anchor avoids copying Batch 158 rain-stop sequence.", "0.78"),
    (45, "From the rain, the water rose.",
     "Kaba-muny bana yalbayman.",
     "kaba-muny bana yalbay-ma-ny | rain-ABL water big-INCHO-PAST",
     "lex_weather_water_rise", 3, js(["kaba", "bana", "yalbaymal"]), js([]),
     "Rule 115/116 source-backed bana yalbay-ma-ny for water rising; ablative marks rain as source/cause.", "0.80"),
    (46, "Today the ground was dry.",
     "Nyiku bubu marral.",
     "nyiku bubu marral | today ground dry",
     "lex_weather_dry_ground", 2, js(["nyiku", "bubu", "marral"]), js([]),
     "Marral dry and bubu ground from prior source-backed weather/property batches.", "0.78"),
    (47, "At daybreak the sun came out over the scrub.",
     "Muduwajuny wungar maja-nga wandin.",
     "muduwaju-ny wungar maja-nga wandi-ny | daybreak-TIME sun scrub-LOC emerge-PAST",
     "lex_weather_sunrise", 4, js(["muduwaju", "wungar", "maja", "wandil"]), js([]),
     "Patz text sources wandi-ny emerge; Batch 157 sunrise row varied with scrub locative.", "0.80"),
    (48, "At sunset, people came to camp.",
     "Wungar daray, bama bayan-ka kadan.",
     "wungar daray, bama bayan-ka kada-ny | sun fall.NP people camp-DAT come-PAST",
     "lex_time_sunset_arrival", 4, js(["wungar daray", "bama", "bayan", "kaday"]), js([]),
     "Wungar daray sunset and bayan-ka kadan camp-arrival are source-backed; subject varied.", "0.82"),
    (49, "In the middle of the night, wind came.",
     "Kunarangkal muyar kadan.",
     "kunarangkal muyar kada-ny | middle.of.night wind come-PAST",
     "lex_weather_night_wind", 3, js(["kunarangkal", "muyar", "kaday"]), js([]),
     "Kunarangkal temporal and muyar/kaday weather-event frame from Rule 115 source inventory.", "0.82"),
    (50, "Tomorrow perhaps a thunderstorm will come.",
     "Wunkuny nguba jarramali kaday.",
     "wunkuny nguba jarramali kada-y | tomorrow perhaps thunderstorm come-NONPAST",
     "lex_weather_possible_storm", 4, js(["wunkuny", "nguba", "jarramali", "kaday"]), js([]),
     "Uses source-backed nguba perhaps and jarramali/kada-y weather event; avoids copied rain-perhaps row.", "0.82"),
]


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}"
        if not english.lstrip("'\"")[:1].isupper() or english.rstrip("'\"").rstrip()[-1:] not in ".!?":
            issues.append(f"{label}: English must be capitalized and final-punctuated")
        if not kuku.lstrip("'\"")[:1].isupper():
            issues.append(f"{label}: Kuku surface must start uppercase")
        if " | " not in analysis:
            issues.append(f"{label}: analysis missing gloss separator")
        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()} {evidence.lower()} "
        for word in DENY:
            if re.search(rf"(?<![a-z]){re.escape(word)}(?![a-z])", low):
                issues.append(f"{label}: denied blocked/special token {word!r}")
        for marker in CONSTRUCTION_CLITIC_MARKERS:
            if marker in kuku.lower() or marker in analysis.lower():
                issues.append(f"{label}: Rule 117 lexical batch must not re-drill construction clitic marker {marker!r}")
        for tok in re.sub(r"[.!?,]", " ", kuku.lower()).split():
            stripped = tok.strip("'\"")
            if stripped in DENY_STANDALONE:
                issues.append(f"{label}: denied standalone special interjection {stripped!r}")
            if stripped.endswith("-da"):
                issues.append(f"{label}: Rule 117 lexical batch must not use TEMP1 -da")
        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())
    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 117 draft: narrative sequence support lexicon", "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 117 source pass read dictionary.yaml plus Patz grammar.md for narrative-support lexicon: "
            "movement/return/following/leaving, taking/bringing/getting/giving/cooking/eating/closing, "
            "sitting/staying/lying/sleeping/waking/stopping, speech/perception/listening/sending/laughing/finding, "
            "and weather/time event predicates."
        ),
        command="rg/sed over dictionary.yaml and grammar.md for Rule 117 lexical targets",
        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 heads: balkal, bawal, daray, kidal, nandal, ngaral, nyajil, wandil, "
            "warngku wunay, wukurril, wulay, wunay, wundil, yajarril, yijarril, yungal, warrmba-bungal, "
            "milka-janay, nyunbay, nukal, manil, kaday, dungay, waju-l, babaji."
        ),
        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 used as controlling evidence for posture/rest and narrative frames: wukurri-ny kudamundu, "
            "wundi-ny take/bring, wandi-l wake/come out, wuna-y/wunana-y lie/sleep/rest, bundanda-y stay, "
            "bayan ngara-ny set up camp, yijarri-ny put, bubu kida-l sweep, wulngku nyaji-l hear, "
            "milka-jana-y listen, yunga-ny send, yajarri-ny laugh."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="existing-surface-scan",
        actor="claude-main",
        detail=(
            "Scanned Batches 157-158 and corpus surfaces for target words. Avoided exact recent rows such as "
            "Wunkuwingku kaba daran; Muyar jalun-muny kadan; Muduwajuny wungar wandin; and Rule 116 clitic-heavy "
            "sequence rows."
        ),
        command="sqlite scan over accepted/revised rows for Rule 117 target vocabulary",
        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 invented discourse particles; no Rule 116 construction clitic drill; no unsourced abstract "
            "narrative vocabulary; grammar overrides thin dictionary entries for posture/rest; exact duplicate check "
            "against full corpus before insert."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="process",
        event_type="process-capture-expanded",
        actor="claude-main",
        detail=(
            "Because the operator requested maximal process storage, Batch 159 records source inventory, grammar-vs-dictionary "
            "source decisions, excluded constructions, row grouping, static guards, bank result, lint/review/close/export/docs "
            "results, and live verification as queryable process_events plus this 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 117 lexical rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_159.py",
        artifact_path="batch_scripts/bank_159.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 movement/return/following; 11-20 preparation/transfer/camp work; 21-30 rest/sleep/wake; "
            "31-40 speech/perception/response; 41-50 weather/time event predicates."
        ),
        artifact_path="batch_scripts/bank_159.py",
        created_at=NOW,
    )
    db.commit()
    avg_words = sum(len(row[2].replace("!", " !").replace("?", " ?").split()) for row in ROWS) / len(ROWS)
    print(f"banked {LABEL}: 50 rows under_review; avg Kuku words/row={avg_words:.2f}")
    return 0


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