#!/usr/bin/env python3
"""Bank batch-162: locational, comitative, and possessive frame consolidation."""

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-162"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-162-locational-comitative-possessive-frames-process.md"

DENY = {"yah", "ngawuya", "walnga", "maja-nga", "yunga"}
CONSTRUCTION_CLITIC_MARKERS = ("-kuda", "-ngarr", "-ngurr", "-l-urr", "wawu-")
PRONOUN_A = {"ngayu", "ngana", "nganjin", "yundu", "yurra", "jana", "nyulu"}
SOURCED_COMITATIVES = ("nganjan-anji", "jawun-unji", "kadar-anji", "juku-ji", "kambi-ji")


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


ROWS = [
    (1, "The dog went down there.",
     "Kaya badabada dungan.",
     "kaya badabada dunga-ny | dog down.there go-PAST",
     "loc_solid_badabada_motion", 3, js(["kaya", "badabada", "dungay"]), js([]),
     "Patz location section and Text 12 source solid badabada with motion; no hyphenation.", "0.84"),
    (2, "The woman came from down there.",
     "Jalbu badabada-mundu kadan.",
     "jalbu badabada-mundu kada-ny | woman down.there-ABESS come-PAST",
     "loc_source_badabada_mundu", 3, js(["jalbu", "badabada", "kaday"]), js([]),
     "Patz ex.110 sources badabada-mundu as a source expression.", "0.86"),
    (3, "People called out from down there.",
     "Bama yirrkan badabada-mundu.",
     "bama yirrka-ny badabada-mundu | people call.out-PAST down.there-ABESS",
     "loc_source_callout", 3, js(["bama", "yirrkay", "badabada"]), js([]),
     "Patz ex.110 has people shouting from badabada-mundu; tense varied to past.", "0.86"),
    (4, "The child sat down there.",
     "Karrkay badabada bundan.",
     "karrkay badabada bunda-ny | child down.there sit-PAST",
     "loc_solid_badabada_posture", 3, js(["karrkay", "badabada", "bunday"]), js([]),
     "Solid badabada as location; intransitive posture subject unmarked.", "0.82"),
    (5, "The old people stayed up there.",
     "Mukul-bama wangkarwangkar bundandan.",
     "mukul-bama wangkarwangkar bundanda-ny | old.people up.there stay-PAST",
     "loc_solid_wangkarwangkar_posture", 3, js(["mukul-bama", "wangkarwangkar", "bunday"]), js([]),
     "Patz Text 12 sources wangkarwangkar with sitting/staying; solid locational.", "0.84"),
    (6, "The man left the dog up there.",
     "Dingkar-angka kaya bawan wangkarwangkar.",
     "dingkar-angka kaya bawa-ny wangkarwangkar | man-ERG dog leave-PAST up.there",
     "loc_solid_wangkarwangkar_object", 4, js(["dingkar", "kaya", "bawal", "wangkarwangkar"]), js([]),
     "Patz Text 11 has dog left up there; A/object varied and kept overt.", "0.84"),
    (7, "The young man went up there.",
     "Warru wangkarwangkar dungan.",
     "warru wangkarwangkar dunga-ny | young.man up.there go-PAST",
     "loc_solid_wangkarwangkar_motion", 3, js(["warru", "wangkarwangkar", "dungay"]), js([]),
     "Warru plus solid wangkarwangkar motion; no productive reduplication.", "0.82"),
    (8, "The child saw grandmother up there.",
     "Karrkay-ngku babi nyajin wangkarwangkar.",
     "karrkay-ngku babi nyaji-ny wangkarwangkar | child-ERG grandmother see-PAST up.there",
     "loc_perception_wangkarwangkar", 4, js(["karrkay", "babi", "nyajil", "wangkarwangkar"]), js([]),
     "Common-noun perceiver marked ERG; perception location is solid wangkarwangkar.", "0.84"),
    (9, "I found a stone up there.",
     "Ngayu kulji wangkarwangkar warrmba-bungan.",
     "ngayu kulji wangkarwangkar warrmba-bunga-ny | I stone up.there find-CAUS-PAST",
     "loc_find_wangkarwangkar", 4, js(["ngayu", "kulji", "wangkarwangkar", "warrmba-bungal"]), js([]),
     "Pronoun A unmarked; found object overt; solid locational.", "0.82"),
    (10, "We sat there.",
     "Ngana yinyay-mba bundan.",
     "ngana yinyay-mba bunda-ny | we there-LOC sit-PAST",
     "loc_yinyay_mba_posture", 3, js(["ngana", "yinyay", "bunday"]), js([]),
     "Patz ex.142-143 source yinyay-mba; posture S unmarked.", "0.84"),

    (11, "The woman found mussels there.",
     "Jalbu-ngku mukirr yinyay-mba warrmba-bungan.",
     "jalbu-ngku mukirr yinyay-mba warrmba-bunga-ny | woman-ERG mussels there-LOC find-CAUS-PAST",
     "loc_yinyay_mba_find", 4, js(["jalbu", "mukirr", "yinyay", "warrmba-bungal"]), js([]),
     "Yinyay-mba location plus overt found object; mukirr source-backed.", "0.84"),
    (12, "The child put the bag there.",
     "Karrkay-ngku balji yijarrin yinyay-mba.",
     "karrkay-ngku balji yijarri-ny yinyay-mba | child-ERG bag put-PAST there-LOC",
     "loc_yinyay_mba_put", 4, js(["karrkay", "balji", "yijarril", "yinyay"]), js([]),
     "Patz ex.117/143 support put plus yinyay-mba; object remains overt.", "0.84"),
    (13, "Grandmother slept at night.",
     "Babi wujurr-bu warngku wunan.",
     "babi wujurr-bu warngku wuna-ny | grandmother night-LOC sleep lie-PAST",
     "loc_time_sleep", 4, js(["babi", "wujurr", "warngku-wunay"]), js([]),
     "Wujurr-bu is sourced as at/during night; warngku wuna-y sleep compound.", "0.84"),
    (14, "People called out at night.",
     "Bama wujurr-bu yirrkan.",
     "bama wujurr-bu yirrka-ny | people night-LOC call.out-PAST",
     "loc_time_callout", 3, js(["bama", "wujurr", "yirrkay"]), js([]),
     "Wujurr-bu time placement; yirrka-y call out.", "0.84"),
    (15, "The child saw the moon at night.",
     "Karrkay-ngku kija nyajin wujurr-bu.",
     "karrkay-ngku kija nyaji-ny wujurr-bu | child-ERG moon see-PAST night-LOC",
     "loc_time_moon_perception", 4, js(["karrkay", "kija", "nyajil", "wujurr"]), js([]),
     "Kija is moon/month only; common-noun perceiver marked ERG.", "0.86"),
    (16, "The child saw a star at night.",
     "Karrkay-ngku dawar nyajin wujurr-bu.",
     "karrkay-ngku dawar nyaji-ny wujurr-bu | child-ERG star see-PAST night-LOC",
     "loc_time_star_perception", 4, js(["karrkay", "dawar", "nyajil", "wujurr"]), js([]),
     "Dawar is the safe star lexeme; common-noun perceiver marked ERG.", "0.86"),
    (17, "Grandmother came from the house.",
     "Babi bayan-muny kadan.",
     "babi bayan-muny kada-ny | grandmother house-ABL come-PAST",
     "loc_source_bayan_muny", 3, js(["babi", "bayan", "kaday"]), js([]),
     "Patz case table and ex.49/124 source bayan-muny as house/camp ABL; participant varied to avoid an exact duplicate.", "0.84"),
    (18, "The girl went to the house.",
     "Maral bayan-ba dungan.",
     "maral bayan-ba dunga-ny | girl house-LOC go-PAST",
     "loc_goal_bayan_ba", 3, js(["maral", "bayan", "dungay"]), js([]),
     "Patz ex.45 sources dunga-y bayan-ba; participant varied.", "0.84"),
    (19, "Grandfather came from the river.",
     "Kami wawubaja-muny kadan.",
     "kami wawubaja-muny kada-ny | grandfather river-ABL come-PAST",
     "loc_source_wawubaja_muny", 3, js(["kami", "wawubaja", "kaday"]), js([]),
     "Patz case table sources wawubaja-muny; motion S unmarked and participant varied to avoid an exact duplicate.", "0.84"),
    (20, "The child came to the river.",
     "Karrkay wawubaja-nga kadan.",
     "karrkay wawubaja-nga kada-ny | child river-LOC come-PAST",
     "loc_goal_wawubaja_nga", 3, js(["karrkay", "wawubaja", "kaday"]), js([]),
     "Wawubaja-nga is recurrent source-backed river LOC/goal.", "0.82"),
    (21, "The friend came from the mountain.",
     "Jawun manjal-muny kadan.",
     "jawun manjal-muny kada-ny | friend mountain-ABL come-PAST",
     "loc_source_manjal_muny", 3, js(["jawun", "manjal", "kaday"]), js([]),
     "Patz case table sources manjal-muny; motion S unmarked.", "0.84"),
    (22, "The old woman went to camp at night.",
     "Wulbuman wujurr-bu bayan-ba dungan.",
     "wulbuman wujurr-bu bayan-ba dunga-ny | old.woman night-LOC camp-LOC go-PAST",
     "loc_time_goal_motion", 4, js(["wulbuman", "wujurr", "bayan", "dungay"]), js([]),
     "Combines sourced wujurr-bu time with dunga-y bayan-ba goal frame.", "0.82"),

    (23, "I went with father.",
     "Ngayu nganjan-anji dungan.",
     "ngayu nganjan-anji dunga-ny | I father-COMIT:pt go-PAST",
     "comit_nganjan_anji_motion", 3, js(["ngayu", "nganjan", "dungay"]), js([]),
     "Patz comitative table sources nganjan-anji.", "0.86"),
    (24, "Grandfather went with a friend.",
     "Kami jawun-unji dungan.",
     "kami jawun-unji dunga-ny | grandfather friend-COMIT:pt go-PAST",
     "comit_jawun_unji_motion", 3, js(["kami", "jawun", "dungay"]), js([]),
     "Patz comitative table and ex.163 source jawun-unji.", "0.86"),
    (25, "The man came with a wallaby.",
     "Dingkar kadar-anji kadan.",
     "dingkar kadar-anji kada-ny | man wallaby-COMIT:pt come-PAST",
     "comit_kadar_anji_motion", 3, js(["dingkar", "kadar", "kaday"]), js([]),
     "Patz comitative table sources kadar-anji.", "0.84"),
    (26, "The old woman went with a stick.",
     "Wulbuman juku-ji dungan.",
     "wulbuman juku-ji dunga-ny | old.woman stick-COMIT go-PAST",
     "comit_juku_ji_motion", 3, js(["wulbuman", "juku", "dungay"]), js([]),
     "Patz comitative table sources juku-ji.", "0.86"),
    (27, "Mother came with clothes.",
     "Ngamu kambi-ji kadan.",
     "ngamu kambi-ji kada-ny | mother clothes-COMIT come-PAST",
     "comit_kambi_ji_motion", 3, js(["ngamu", "kambi", "kaday"]), js([]),
     "Patz ex.87 and comitative discussion source kambi-ji.", "0.84"),
    (28, "The child went with father at night.",
     "Karrkay wujurr-bu nganjan-anji dungan.",
     "karrkay wujurr-bu nganjan-anji dunga-ny | child night-LOC father-COMIT:pt go-PAST",
     "comit_time_motion", 4, js(["karrkay", "wujurr", "nganjan", "dungay"]), js([]),
     "Combines sourced wujurr-bu and nganjan-anji; S unmarked.", "0.82"),
    (29, "We went with a friend to the river.",
     "Ngana jawun-unji wawubaja-nga dungan.",
     "ngana jawun-unji wawubaja-nga dunga-ny | we friend-COMIT:pt river-LOC go-PAST",
     "comit_goal_motion", 4, js(["ngana", "jawun", "wawubaja", "dungay"]), js([]),
     "Patz ex.163 jawun-unji plus river LOC/goal.", "0.84"),
    (30, "The young man came with a stick from the house.",
     "Warru juku-ji bayan-muny kadan.",
     "warru juku-ji bayan-muny kada-ny | young.man stick-COMIT house-ABL come-PAST",
     "comit_source_motion", 4, js(["warru", "juku", "bayan", "kaday"]), js([]),
     "Sourced juku-ji and bayan-muny source in one clause.", "0.82"),

    (31, "Mother got the dog's food.",
     "Ngamu-ngku mayi kaya-nga manin.",
     "ngamu-ngku mayi kaya-nga mani-ny | mother-ERG food dog-POSS.ABS get-PAST",
     "poss_mayi_kaya_nga_get", 4, js(["ngamu", "mayi", "kaya", "manil"]), js([]),
     "Patz possessive table sources mayi kaya-nga; object NP remains overt.", "0.86"),
    (32, "The child ate the dog's food.",
     "Karrkay-ngku mayi kaya-nga nukan.",
     "karrkay-ngku mayi kaya-nga nuka-ny | child-ERG food dog-POSS.ABS eat-PAST",
     "poss_mayi_kaya_nga_eat", 4, js(["karrkay", "mayi", "kaya", "nukal"]), js([]),
     "Same sourced possessive NP in a transitive eating frame.", "0.86"),
    (33, "The child took mother's clothes.",
     "Karrkay-ngku kambi ngamu-ngu wundin.",
     "karrkay-ngku kambi ngamu-ngu wundi-ny | child-ERG clothes mother-POSS.ABS take-PAST",
     "poss_kambi_ngamu_ngu_take", 4, js(["karrkay", "kambi", "ngamu", "wundil"]), js([]),
     "Patz possessive table sources kambi ngamu-ngu.", "0.86"),
    (34, "Grandmother gave mother's clothes to the child.",
     "Babi-ngka kambi ngamu-ngu dajin karrkay-anda.",
     "babi-ngka kambi ngamu-ngu daji-ny karrkay-anda | grandmother-ERG clothes mother-POSS.ABS give-PAST child-LOC:pt",
     "poss_transfer_recipient", 5, js(["babi", "kambi", "ngamu", "dajil", "karrkay"]), js([]),
     "Sourced possessive object plus Rule 118 LOC:pt recipient.", "0.84"),
    (35, "The girl's mother got fish.",
     "Ngamu-ngku maral-andamun-bu kuyu manin.",
     "ngamu-ngku maral-andamun-bu kuyu mani-ny | mother-ERG girl-POSS-ERG fish get-PAST",
     "poss_stem_subject_get", 4, js(["ngamu", "maral", "kuyu", "manil"]), js([]),
     "Patz ex.46 directly sources girl's-mother ERG plus fish-getting.", "0.88"),
    (36, "The girl's mother cooked food.",
     "Ngamu-ngku maral-andamun-bu mayi wajun.",
     "ngamu-ngku maral-andamun-bu mayi waju-ny | mother-ERG girl-POSS-ERG food cook-PAST",
     "poss_stem_subject_cook", 4, js(["ngamu", "maral", "mayi", "wajul"]), js([]),
     "Same sourced possessive subject frame with food-cooking predicate.", "0.84"),
    (37, "The man's mother's house is big.",
     "Dingkar-amu ngamu-mu bayan yalbay.",
     "dingkar-amu ngamu-mu bayan yalbay | man-POSS.ABS mother-POSS.ABS house big",
     "poss_nested_verbless", 4, js(["dingkar", "ngamu", "bayan", "yalbay"]), js([]),
     "Patz ex.48 directly sources nested possessive NP plus big house.", "0.88"),
    (38, "He came from the girl's elder brother's house.",
     "Nyulu kadan maral-andamun-muny yaba-ndamun-muny bayan-muny.",
     "nyulu kada-ny maral-andamun-muny yaba-ndamun-muny bayan-muny | he come-PAST girl-POSS-ABL elder.brother-POSS-ABL house-ABL",
     "poss_nested_source_motion", 5, js(["nyulu", "maral", "yaba", "bayan", "kaday"]), js([]),
     "Patz ex.49 directly sources possessive-ABL nested source phrase.", "0.88"),
    (39, "Grandfather saw mother's clothes.",
     "Kami-ngka kambi ngamu-ngu nyajin.",
     "kami-ngka kambi ngamu-ngu nyaji-ny | grandfather-ERG clothes mother-POSS.ABS see-PAST",
     "poss_perception_object", 4, js(["kami", "kambi", "ngamu", "nyajil"]), js([]),
     "Common-noun perceiver ERG; sourced kambi ngamu-ngu possessive object.", "0.84"),
    (40, "I took the dog's food to camp.",
     "Ngayu mayi kaya-nga bayan-ka wundin.",
     "ngayu mayi kaya-nga bayan-ka wundi-ny | I food dog-POSS.ABS camp-DAT take-PAST",
     "poss_goal_take", 5, js(["ngayu", "mayi", "kaya", "bayan", "wundil"]), js([]),
     "Pronoun A with sourced possessive object plus DAT goal.", "0.84"),

    (41, "The child heard barking from down there.",
     "Karrkay-ngku ngangkurr badabada-mundu nyajin.",
     "karrkay-ngku ngangkurr badabada-mundu nyaji-ny | child-ERG barking.noise down.there-ABESS hear-PAST",
     "loc_source_perception_sound", 4, js(["karrkay", "ngangkurr", "badabada", "nyajil"]), js([]),
     "Common-noun perceiver ERG; source is sourced badabada-mundu.", "0.86"),
    (42, "I heard a song from up there.",
     "Ngayu wulngku wangkarwangkar-mundu nyajin.",
     "ngayu wulngku wangkarwangkar-mundu nyaji-ny | I song up.there-ABESS hear-PAST",
     "loc_source_perception_pronoun", 4, js(["ngayu", "wulngku", "wangkarwangkar", "nyajil"]), js([]),
     "Pronoun A unmarked; Patz Text 12 sources wangkarwangkar-mundu as from up there.", "0.84"),
    (43, "The child saw the dog under the tree.",
     "Karrkay-ngku kaya nyajin badabada juku-ngu.",
     "karrkay-ngku kaya nyaji-ny badabada juku-ngu | child-ERG dog see-PAST underneath tree-LOC",
     "loc_under_tree_perception", 5, js(["karrkay", "kaya", "nyajil", "badabada", "juku"]), js([]),
     "Patz ex.278 sources badabada juku-ngu; common-noun perceiver ERG.", "0.86"),
    (44, "The dog saw the child down there.",
     "Kaya-ngka karrkay nyajin badabada.",
     "kaya-ngka karrkay nyaji-ny badabada | dog-ERG child see-PAST down.there",
     "loc_badabada_perception", 4, js(["kaya", "karrkay", "nyajil", "badabada"]), js([]),
     "Animal common-noun A marked ERG; solid badabada location.", "0.84"),
    (45, "I saw the children up there.",
     "Ngayu karrkay-karrkay nyajin wangkarwangkar.",
     "ngayu karrkay-karrkay nyaji-ny wangkarwangkar | I children see-PAST up.there",
     "loc_wangkarwangkar_perception_pronoun", 4, js(["ngayu", "karrkay", "nyajil", "wangkarwangkar"]), js([]),
     "Pronoun A unmarked; plural object plus solid location.", "0.82"),
    (46, "The child heard thunder at night.",
     "Karrkay-ngku jarramali wujurr-bu nyajin.",
     "karrkay-ngku jarramali wujurr-bu nyaji-ny | child-ERG thunder night-LOC hear-PAST",
     "loc_time_perception_thunder", 4, js(["karrkay", "jarramali", "wujurr", "nyajil"]), js([]),
     "Jarramali used in thunder sense; common-noun perceiver ERG.", "0.84"),
    (47, "We listened at night.",
     "Ngana wujurr-bu milka-janan.",
     "ngana wujurr-bu milka-jana-ny | we night-LOC listen-PAST",
     "loc_time_listen", 3, js(["ngana", "wujurr", "milka-janay"]), js([]),
     "Concrete listening with sourced wujurr-bu; no ngara-l semantic drift.", "0.84"),
    (48, "The old people rested there.",
     "Mukul-bama yinyay-mba wunanan.",
     "mukul-bama yinyay-mba wunana-ny | old.people there-LOC rest-PAST",
     "loc_yinyay_mba_rest", 3, js(["mukul-bama", "yinyay", "wunay"]), js([]),
     "Yinyay-mba location plus source-controlled rest form.", "0.82"),
    (49, "The baby slept near the fire.",
     "Ngawa baya ngundu-ba warngku wunan.",
     "ngawa baya ngundu-ba warngku wuna-ny | baby fire near-LOC sleep lie-PAST",
     "loc_near_fire_sleep", 4, js(["ngawa", "baya", "ngundu", "warngku-wunay"]), js([]),
     "Baya ngundu-ba near-fire location plus warngku wuna-y.", "0.84"),
    (50, "The friend stayed down there.",
     "Jawun badabada bundanday.",
     "jawun badabada bundanda-y | friend down.there stay-NONPAST",
     "loc_badabada_stay", 3, js(["jawun", "badabada", "bunday"]), js([]),
     "Solid badabada with reduced stay form.", "0.82"),
]


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(("loc_", "comit_", "poss_")):
            issues.append(f"{label}: Rule 120 frame must start loc_, comit_, or poss_")
        low = kuku.lower()
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        for marker in CONSTRUCTION_CLITIC_MARKERS:
            if marker in low:
                issues.append(f"{label}: Rule 120 must not reuse temporal clitic marker {marker}")
        for denied in DENY:
            if denied in low:
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        if "bada-bada" in low or "wangkar-wangkar" in low:
            issues.append(f"{label}: badabada/wangkarwangkar must stay solid")
        if "kija" in low and "moon" not in analysis.lower():
            issues.append(f"{label}: kija is moon/month only; analysis must say moon/month")
        if "star" in english.lower() and "dawar" not in low:
            issues.append(f"{label}: star row must use dawar")
        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 "yajarr" in low and not re.search(r"-(?:anka|nka)\b", low):
            issues.append(f"{label}: laugh target should show DAT:pt -anka/-nka")
        if any(suffix in low for suffix in ("-anji", "-unji", "-ji")) and "COMIT" in analysis:
            if not any(form in low for form in SOURCED_COMITATIVES):
                issues.append(f"{label}: comitative form is not in the sourced Rule 120 set")
        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 120 draft: locational, comitative, and possessive frame consolidation", "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 120 source pass checked Patz grammar and dictionary.yaml for locational, comitative, and possessive "
            "frames: badabada/badabada-mundu, wangkarwangkar/wangkarwangkar-mundu, yinyay-mba, wujurr-bu, "
            "bayan-ba/bayan-muny, wawubaja-nga/wawubaja-muny, manjal-muny, nganjan-anji, jawun-unji, kadar-anji, "
            "juku-ji, kambi-ji, mayi kaya-nga, kambi ngamu-ngu, and possessive stems with maral/yaba/ngamu."
        ),
        command="sed/rg over grammar.md and dictionary.yaml; sqlite duplicate scan",
        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 recorded: case tables for LOC/ABL/ABESS, possessive examples 44-49, comitative table "
            "and examples 50-53/163, location-word section examples 106-112, wujurr-bu time word note, ex.142-143 "
            "yinyay-mba, ex.273-274 LOC intermediary caution retained by excluding yunga-l, and Text 11/12 "
            "wangkarwangkar/badabada examples."
        ),
        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 source-backed heads used in Batch 162: kaya, jalbu, bama, karrkay, mukul-bama, dingkar, warru, "
            "babi, kulji, ngana, mukirr, balji, wujurr, warngku wunay, kija, dawar, maral, bayan, wawubaja, jawun, "
            "manjal, wulbuman, ngayu, nganjan, kami, kadar, juku, ngamu, kambi, mayi, kuyu, yaba, ngangkurr, wulngku, "
            "jarramali, ngawa, baya, ngundu, and target predicates dunga-y, kada-y, yirrka-y, bunda-y, bawa-l, "
            "nyaji-l, warrmba-bunga-l, yijarri-l, wundi-l, mani-l, nuka-l, daji-l, waju-l, milka-janay, and wuna-y."
        ),
        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 vocabulary; no badabada/wangkarwangkar hyphenation; no maja-nga; no yunga-l rows; "
            "no babaji-y rows; kija moon-only; dawar star; sourced comitatives only; common-noun nyaji-l perceivers "
            "take ERG; transitive objects remain overt."
        ),
        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 162 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 162 records source inventory, construction 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 120 construction rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_162.py",
        artifact_path="batch_scripts/bank_162.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 solid locationals/yinyay-mba; 11-22 locative/source/time placement; 23-30 sourced comitatives; "
            "31-40 possessive NPs and possessive stems; 41-50 perception/listening/rest/sleep location 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())
