#!/usr/bin/env python3
"""Bank batch-163: source-backed place, object, and participant 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-163"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-163-place-object-participant-lexicon-process.md"

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


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


ROWS = [
    (1, "People stayed at the sea.",
     "Bama jalun-bu bundandan.",
     "bama jalun-bu bundanda-ny | people sea-LOC stay-PAST",
     "lex_place_sea_stay", 3, js(["bama", "jalun", "bunday"]), js([]),
     "Dictionary bama/jalun; Patz direction and text examples source jalun-bu sea-LOC; bundanda-y stay from posture/rest rules.", "0.84"),
    (2, "The children called out at the sea.",
     "Karrkay-karrkay jalun-bu yirrkan.",
     "karrkay-karrkay jalun-bu yirrka-ny | children sea-LOC call.out-PAST",
     "lex_place_sea_callout", 3, js(["karrkay", "jalun", "yirrkay"]), js([]),
     "Karrkay and yirrka-y are source-backed; jalun-bu is sea-LOC.", "0.82"),
    (3, "The woman came from the sea.",
     "Jalbu jalun-muny kadan.",
     "jalbu jalun-muny kada-ny | woman sea-ABL come-PAST",
     "lex_place_sea_source", 3, js(["jalbu", "jalun", "kaday"]), js([]),
     "Patz ex.299/Rule 127 source jalun-muny as sea-ABL; jalbu and kada-y checked.", "0.84"),
    (4, "The young man sat on the road.",
     "Warru baral-ba bundan.",
     "warru baral-ba bunda-ny | young.man road-LOC sit-PAST",
     "lex_place_road_posture", 3, js(["warru", "baral", "bunday"]), js([]),
     "Baral road/path and baral-ba road-LOC are Patz-backed; posture subject unmarked.", "0.82"),
    (5, "Mother came to the road.",
     "Ngamu baral-ba kadan.",
     "ngamu baral-ba kada-ny | mother road-LOC come-PAST",
     "lex_place_road_goal", 3, js(["ngamu", "baral", "kaday"]), js([]),
     "Ngamu dictionary entry; baral-ba and kada-y motion frame source-backed.", "0.82"),
    (6, "The dog lay outside.",
     "Kaya ngalkal-ba wunan.",
     "kaya ngalkal-ba wuna-ny | dog outside-LOC lie-PAST",
     "lex_place_outside_posture", 3, js(["kaya", "ngalkal", "wunay"]), js([]),
     "Ngalkal-ba outside-LOC is source-backed; kaya and wuna-y checked.", "0.82"),
    (7, "The baby slept in the shade.",
     "Ngawa wabarr-ba warngku wunan.",
     "ngawa wabarr-ba warngku wuna-ny | baby shade-LOC sleep lie-PAST",
     "lex_place_shade_sleep", 4, js(["ngawa", "wabarr", "warngku-wunay"]), js([]),
     "Ngawa baby and wabarr shade checked; warngku wuna-y sleep compound from Patz/posture rules.", "0.84"),
    (8, "Grandmother sat on the ground.",
     "Babi bubu-ngu bundan.",
     "babi bubu-ngu bunda-ny | grandmother ground-LOC sit-PAST",
     "lex_place_ground_posture", 3, js(["babi", "bubu", "bunday"]), js([]),
     "Bubu-ngu neutral LOC is source-backed; babi/bunda-y checked.", "0.82"),
    (9, "The snake is lying in the grass.",
     "Jarba jikan-ba wunanay.",
     "jarba jikan-ba wuna-n-wuna-y | snake grass-LOC lie-REDUP-NONPAST",
     "lex_place_grass_posture", 3, js(["jarba", "jikan", "wunay"]), js([]),
     "Jarba and jikan checked; Patz source shape supports snake lying in/under vegetation with wuna-n-wuna-y.", "0.86"),
    (10, "The wallaby came to the grass.",
     "Kadar jikan-ba kadan.",
     "kadar jikan-ba kada-ny | wallaby grass-LOC come-PAST",
     "lex_place_grass_goal", 3, js(["kadar", "jikan", "kaday"]), js([]),
     "Kadar and jikan source-backed; LOC goal with kada-y follows recent motion case frames.", "0.80"),

    (11, "The man put the spear on the road.",
     "Dingkar-angka kalka baral-ba yijarrin.",
     "dingkar-angka kalka baral-ba yijarri-ny | man-ERG spear road-LOC put-PAST",
     "lex_object_place_spear", 4, js(["dingkar", "kalka", "baral", "yijarril"]), js([]),
     "Kalka spear, baral-ba road-LOC, and yijarri-l put are source-backed; common-noun A takes ERG.", "0.84"),
    (12, "The woman left the bag outside.",
     "Jalbu-ngku balji ngalkal-ba bawan.",
     "jalbu-ngku balji ngalkal-ba bawa-ny | woman-ERG bag outside-LOC leave-PAST",
     "lex_object_place_bag", 4, js(["jalbu", "balji", "ngalkal", "bawal"]), js([]),
     "Balji bag and ngalkal-ba outside are source-backed; bawa-l keeps the left object overt.", "0.84"),
    (13, "The child put the billycan in the shade.",
     "Karrkay-ngku jungkalu wabarr-ba yijarrin.",
     "karrkay-ngku jungkalu wabarr-ba yijarri-ny | child-ERG billycan shade-LOC put-PAST",
     "lex_object_place_billycan", 4, js(["karrkay", "jungkalu", "wabarr", "yijarril"]), js([]),
     "Jungkalu and wabarr-ba are verified; transitive put frame retains the object.", "0.82"),
    (14, "Grandmother put food in the bag.",
     "Babi-ngka mayi balji-nga yijarrin.",
     "babi-ngka mayi balji-nga yijarri-ny | grandmother-ERG food bag-LOC put-PAST",
     "lex_object_place_food", 4, js(["babi", "mayi", "balji", "yijarril"]), js([]),
     "Mayi and balji-nga are source-backed; Babi-ngka marks common-noun A.", "0.84"),
    (15, "The man got fish at the sea.",
     "Dingkar-angka kuyu jalun-bu manin.",
     "dingkar-angka kuyu jalun-bu mani-ny | man-ERG fish sea-LOC get-PAST",
     "lex_object_place_fish", 4, js(["dingkar", "kuyu", "jalun", "manil"]), js([]),
     "Kuyu/jalun source-backed; mani-l get with overt object and location.", "0.84"),
    (16, "The woman got mussels at the river.",
     "Jalbu-ngku mukirr wawubaja-nga manin.",
     "jalbu-ngku mukirr wawubaja-nga mani-ny | woman-ERG mussels river-LOC get-PAST",
     "lex_object_place_mussels", 4, js(["jalbu", "mukirr", "wawubaja", "manil"]), js([]),
     "Mukirr and wawubaja-nga are source-backed; common-noun A takes ERG.", "0.84"),
    (17, "The dog ate meat in the grass.",
     "Kaya-ngka minya jikan-ba nukan.",
     "kaya-ngka minya jikan-ba nuka-ny | dog-ERG meat grass-LOC eat-PAST",
     "lex_object_place_meat", 4, js(["kaya", "minya", "jikan", "nukal"]), js([]),
     "Kaya/minya/jikan checked; animal A takes ERG with nuka-l.", "0.82"),
    (18, "Grandmother cooked food at camp.",
     "Babi-ngka mayi bayan-ba wajun.",
     "babi-ngka mayi bayan-ba waju-ny | grandmother-ERG food camp-LOC cook-PAST",
     "lex_object_place_cook", 4, js(["babi", "mayi", "bayan", "wajul"]), js([]),
     "Mayi waju-l cooking rows and bayan-ba camp LOC are established; A marked ERG.", "0.82"),
    (19, "The young man brought firewood from the scrub.",
     "Warru-ngku baya madja-muny wundin.",
     "warru-ngku baya madja-muny wundi-ny | young.man-ERG firewood scrub-ABL bring-PAST",
     "lex_object_source_firewood", 4, js(["warru", "baya", "madja", "wundil"]), js([]),
     "Baya includes firewood; madja-muny scrub-ABL and wundi-l bring/take are source-backed.", "0.84"),
    (20, "Mother left clothes in the shade.",
     "Ngamu-ngku kambi wabarr-ba bawan.",
     "ngamu-ngku kambi wabarr-ba bawa-ny | mother-ERG clothes shade-LOC leave-PAST",
     "lex_object_place_clothes", 4, js(["ngamu", "kambi", "wabarr", "bawal"]), js([]),
     "Kambi clothing sense and wabarr-ba are source-backed; bawa-l keeps object overt.", "0.82"),

    (21, "The woman saw the crocodile in the river.",
     "Jalbu-ngku bilngkumu wawubaja-nga nyajin.",
     "jalbu-ngku bilngkumu wawubaja-nga nyaji-ny | woman-ERG crocodile river-LOC see-PAST",
     "lex_participant_perception_crocodile", 4, js(["jalbu", "bilngkumu", "wawubaja", "nyajil"]), js([]),
     "Bilngkumu and wawubaja-nga checked; common-noun perceiver marked ERG.", "0.84"),
    (22, "The young man saw the snake in the grass.",
     "Warru-ngku jarba jikan-ba nyajin.",
     "warru-ngku jarba jikan-ba nyaji-ny | young.man-ERG snake grass-LOC see-PAST",
     "lex_participant_perception_snake", 4, js(["warru", "jarba", "jikan", "nyajil"]), js([]),
     "Jarba/jikan source-backed; common-noun perceiver marked ERG.", "0.84"),
    (23, "Grandmother saw the dog outside.",
     "Babi-ngka kaya ngalkal-ba nyajin.",
     "babi-ngka kaya ngalkal-ba nyaji-ny | grandmother-ERG dog outside-LOC see-PAST",
     "lex_participant_perception_dog", 4, js(["babi", "kaya", "ngalkal", "nyajil"]), js([]),
     "Kaya/ngalkal-ba checked; Babi-ngka marks common-noun perceiver.", "0.84"),
    (24, "I heard barking from the house.",
     "Ngayu ngangkurr bayan-muny nyajin.",
     "ngayu ngangkurr bayan-muny nyaji-ny | I barking.noise house-ABL hear-PAST",
     "lex_sound_source_barking", 4, js(["ngayu", "ngangkurr", "bayan", "nyajil"]), js([]),
     "Ngangkurr barking sound and bayan-muny source are verified; pronoun A unmarked.", "0.84"),
    (25, "We heard a song from the sea.",
     "Ngana wulngku jalun-muny nyajin.",
     "ngana wulngku jalun-muny nyaji-ny | we song sea-ABL hear-PAST",
     "lex_sound_source_song", 4, js(["ngana", "wulngku", "jalun", "nyajil"]), js([]),
     "Wulngku song and jalun-muny source are checked; pronoun A unmarked.", "0.84"),
    (26, "Grandfather gave food to the child.",
     "Kami-ngka mayi dajin karrkay-anda.",
     "kami-ngka mayi daji-ny karrkay-anda | grandfather-ERG food give-PAST child-LOC:pt",
     "lex_transfer_food", 4, js(["kami", "mayi", "dajil", "karrkay"]), js([]),
     "Daji-l plus LOC:pt recipient frame is established; common-noun A marked ERG.", "0.84"),
    (27, "The woman gave water to grandmother.",
     "Jalbu-ngku bana dajin babi-nda.",
     "jalbu-ngku bana daji-ny babi-nda | woman-ERG water give-PAST grandmother-LOC:pt",
     "lex_transfer_water", 4, js(["jalbu", "bana", "dajil", "babi"]), js([]),
     "Bana/daji-l and LOC:pt recipient are source-backed; A marked ERG.", "0.84"),
    (28, "I brought the bag from the house.",
     "Ngayu balji bayan-muny wundin.",
     "ngayu balji bayan-muny wundi-ny | I bag house-ABL bring-PAST",
     "lex_transfer_source_bag", 4, js(["ngayu", "balji", "bayan", "wundil"]), js([]),
     "Pronoun A unmarked; wundi-l object plus ABL source follows Rule 118.", "0.82"),
    (29, "Mother took clothes to camp.",
     "Ngamu-ngku kambi bayan-ka wundin.",
     "ngamu-ngku kambi bayan-ka wundi-ny | mother-ERG clothes camp-DAT take-PAST",
     "lex_transfer_goal_clothes", 4, js(["ngamu", "kambi", "bayan", "wundil"]), js([]),
     "Kambi clothing sense; bayan-ka DAT goal and wundi-l are source-backed.", "0.82"),
    (30, "The child took the stone from the river.",
     "Karrkay-ngku kulji wawubaja-muny wundin.",
     "karrkay-ngku kulji wawubaja-muny wundi-ny | child-ERG stone river-ABL take-PAST",
     "lex_transfer_source_stone", 4, js(["karrkay", "kulji", "wawubaja", "wundil"]), js([]),
     "Kulji and wawubaja-muny are checked; transitive object remains overt.", "0.82"),

    (31, "The girl asked father about food.",
     "Maral nganjan-anda mayi-ka babajin.",
     "maral nganjan-anda mayi-ka babaji-ny | girl father-LOC:pt food-DAT ask-PAST",
     "lex_ask_food", 4, js(["maral", "nganjan", "mayi", "babaji"]), js([]),
     "Babaji-y fixed-intransitive ask frame; no ergative asker; mayi-ka sourced DAT topic.", "0.86"),
    (32, "The young man asked mother about the spear.",
     "Warru ngamu-ndu kalka-ka babajin.",
     "warru ngamu-ndu kalka-ka babaji-ny | young.man mother-LOC:pt spear-DAT ask-PAST",
     "lex_ask_spear", 4, js(["warru", "ngamu", "kalka", "babaji"]), js([]),
     "Ask-about frame keeps ABS/S asker, LOC:pt person, DAT topic; kalka-ka is sourced.", "0.86"),
    (33, "The child asked grandfather about water.",
     "Karrkay kami-nda bana-ka babajin.",
     "karrkay kami-nda bana-ka babaji-ny | child grandfather-LOC:pt water-DAT ask-PAST",
     "lex_ask_water", 4, js(["karrkay", "kami", "bana", "babaji"]), js([]),
     "Babaji-y fixed-intransitive; bana-ka topic follows Rule 118.", "0.86"),
    (34, "The friend laughed at the child.",
     "Jawun karrkay-anka yajarrin.",
     "jawun karrkay-anka yajarri-ny | friend child-DAT:pt laugh-PAST",
     "lex_laugh_child", 3, js(["jawun", "karrkay", "yajarril"]), js([]),
     "Yajarri-l laugh target takes DAT:pt; subject is intransitive S.", "0.84"),
    (35, "The old people laughed at father.",
     "Mukul-bama nganjan-anka yajarrin.",
     "mukul-bama nganjan-anka yajarri-ny | old.people father-DAT:pt laugh-PAST",
     "lex_laugh_father", 3, js(["mukul-bama", "nganjan", "yajarril"]), js([]),
     "Nganjan-anka laugh target is sourced from Rule 118/120 corrections.", "0.84"),

    (36, "Tomorrow wind will come from the sea.",
     "Wunkuny muyar jalun-muny kaday.",
     "wunkuny muyar jalun-muny kada-y | tomorrow wind sea-ABL come-NONPAST",
     "lex_weather_sea_source", 4, js(["wunkuny", "muyar", "jalun", "kaday"]), js([]),
     "Wunkuny future with nonpast kada-y; muyar and jalun-muny are source-backed.", "0.82"),
    (37, "A thunderstorm came from the mountain.",
     "Jarramali manjal-muny kadan.",
     "jarramali manjal-muny kada-ny | thunderstorm mountain-ABL come-PAST",
     "lex_weather_mountain_source", 3, js(["jarramali", "manjal", "kaday"]), js([]),
     "Jarramali thunderstorm sense; manjal-muny ABL source from case table/rules.", "0.84"),
    (38, "Rain fell outside.",
     "Kaba ngalkal-ba daran.",
     "kaba ngalkal-ba dara-ny | rain outside-LOC fall-PAST",
     "lex_weather_outside", 3, js(["kaba", "ngalkal", "daray"]), js([]),
     "Kaba dara-y rain-fall frame; ngalkal-ba outside location checked.", "0.82"),
    (39, "A star was there at night.",
     "Dawar wujurr-bu yinyay.",
     "dawar wujurr-bu yinyay | star night-LOC there",
     "lex_celestial_star", 3, js(["dawar", "wujurr", "yinyay"]), js([]),
     "Dawar is the safe star lexeme; wujurr-bu and yinyay are source-backed.", "0.84"),
    (40, "The moon was there at night.",
     "Kija wujurr-bu yinyay.",
     "kija wujurr-bu yinyay | moon night-LOC there",
     "lex_celestial_moon", 3, js(["kija", "wujurr", "yinyay"]), js([]),
     "Kija restricted to moon/month; wujurr-bu time location checked.", "0.86"),

    (41, "The woman is staying in the house with the baby.",
     "Jalbu bayan-ba bundanday ngawa-nji.",
     "jalbu bayan-ba bunda-n-bunda-y ngawa-nji | woman house-LOC stay-REDUP-NONPAST baby-COMIT:pt",
     "lex_comit_baby_stay", 4, js(["jalbu", "bayan", "bunday", "ngawa"]), js([]),
     "Patz ex.549 directly sources house-LOC stay with ngawa-nji 'with baby'.", "0.88"),
    (42, "Grandmother went to the sea with a friend.",
     "Babi jawun-unji jalun-bu dungan.",
     "babi jawun-unji jalun-bu dunga-ny | grandmother friend-COMIT:pt sea-LOC go-PAST",
     "lex_comit_friend_sea", 4, js(["babi", "jawun", "jalun", "dungay"]), js([]),
     "Jawun-unji is sourced comitative; jalun-bu and dunga-y checked.", "0.84"),
    (43, "The child went to the road with father.",
     "Karrkay nganjan-anji baral-ba dungan.",
     "karrkay nganjan-anji baral-ba dunga-ny | child father-COMIT:pt road-LOC go-PAST",
     "lex_comit_father_road", 4, js(["karrkay", "nganjan", "baral", "dungay"]), js([]),
     "Nganjan-anji is sourced comitative; baral-ba road location checked.", "0.84"),
    (44, "The old woman came from the scrub with a stick.",
     "Wulbuman juku-ji madja-muny kadan.",
     "wulbuman juku-ji madja-muny kada-ny | old.woman stick-COMIT scrub-ABL come-PAST",
     "lex_comit_stick_scrub", 4, js(["wulbuman", "juku", "madja", "kaday"]), js([]),
     "Juku-ji sourced comitative; madja-muny scrub-ABL and kada-y checked.", "0.84"),
    (45, "Mother came outside with clothes.",
     "Ngamu kambi-ji ngalkal-ba kadan.",
     "ngamu kambi-ji ngalkal-ba kada-ny | mother clothes-COMIT outside-LOC come-PAST",
     "lex_comit_clothes_outside", 4, js(["ngamu", "kambi", "ngalkal", "kaday"]), js([]),
     "Kambi-ji is sourced comitative; ngalkal-ba outside and kada-y checked.", "0.84"),

    (46, "Grandmother left the dog's food at camp.",
     "Babi-ngka mayi kaya-nga bayan-ba bawan.",
     "babi-ngka mayi kaya-nga bayan-ba bawa-ny | grandmother-ERG food dog-POSS.ABS camp-LOC leave-PAST",
     "lex_poss_food_leave", 5, js(["babi", "mayi", "kaya", "bayan", "bawal"]), js([]),
     "Patz table sources mayi kaya-nga; bawa-l object plus bayan-ba location is established.", "0.84"),
    (47, "Grandmother left mother's clothes in the shade.",
     "Babi-ngka kambi ngamu-ngu wabarr-ba bawan.",
     "babi-ngka kambi ngamu-ngu wabarr-ba bawa-ny | grandmother-ERG clothes mother-POSS.ABS shade-LOC leave-PAST",
     "lex_poss_clothes_leave", 5, js(["babi", "kambi", "ngamu", "wabarr", "bawal"]), js([]),
     "Patz table sources kambi ngamu-ngu; location and bawa-l frame checked.", "0.84"),
    (48, "I saw the dog's food outside.",
     "Ngayu mayi kaya-nga ngalkal-ba nyajin.",
     "ngayu mayi kaya-nga ngalkal-ba nyaji-ny | I food dog-POSS.ABS outside-LOC see-PAST",
     "lex_poss_food_perception", 5, js(["ngayu", "mayi", "kaya", "ngalkal", "nyajil"]), js([]),
     "Pronoun A unmarked; possessive object is Patz-backed; ngalkal-ba checked.", "0.84"),
    (49, "The child put mother's clothes in the bag.",
     "Karrkay-ngku kambi ngamu-ngu balji-nga yijarrin.",
     "karrkay-ngku kambi ngamu-ngu balji-nga yijarri-ny | child-ERG clothes mother-POSS.ABS bag-LOC put-PAST",
     "lex_poss_clothes_put", 5, js(["karrkay", "kambi", "ngamu", "balji", "yijarril"]), js([]),
     "Patz-backed possessive object plus balji-nga and yijarri-l put frame.", "0.84"),
    (50, "We took the dog's food to camp.",
     "Ngana mayi kaya-nga bayan-ka wundin.",
     "ngana mayi kaya-nga bayan-ka wundi-ny | we food dog-POSS.ABS camp-DAT take-PAST",
     "lex_poss_food_goal", 5, js(["ngana", "mayi", "kaya", "bayan", "wundil"]), js([]),
     "Pronoun A unmarked; mayi kaya-nga and bayan-ka goal are sourced; object remains overt.", "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_")
        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}: 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 "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 "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 "COMIT" in analysis and not any(form in low for form in SOURCED_COMITATIVES):
            issues.append(f"{label}: comitative form is not in the sourced Rule 121 set")
        if any(form in low for form in ("kaya-nga", "ngamu-ngu")) and "POSS" not in analysis:
            issues.append(f"{label}: possessive form needs explicit POSS analysis")
        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 121 draft: source-backed place, object, and participant lexicon", "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 121 source pass checked dictionary.yaml and Patz grammar for place, object, and participant heads "
            "that can vary Rule 120 location/source/comitative/possessive frames without inventing new predicates or "
            "unsourced allomorphs."
        ),
        command="rg over grammar.md/dictionary.yaml; YAML entry readback for target heads",
        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: bama, jalbu, dingkar, karrkay, maral, ngawa, babi, kami, ngamu, "
            "nganjan, warru, wulbuman, jawun, kaya, jarba, kadar, bilngkumu, jalun, baral, ngalkal, wabarr, bubu, "
            "jikan, bayan, wawubaja, madja, manjal, kalka, balji, jungkalu, mayi, bana, kuyu, mukirr, minya, baya, "
            "kambi, kulji, ngangkurr, wulngku, jarramali, kaba, muyar, dawar, kija, yinyay, and the reused predicates."
        ),
        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: jalun-bu/jalun-muny, baral-ba, ngalkal-ba, wabarr-ba, jikan-ba, bubu-ngu, "
            "wawubaja-nga/wawubaja-muny, bayan-ba/bayan-muny, manjal-muny, madja-muny, ngawa-nji from ex.549, "
            "Patz possessives mayi kaya-nga and kambi ngamu-ngu, ask-about LOC:pt + DAT topic, DAT:pt laugh "
            "targets, and ERG common-noun nyaji-l perceivers."
        ),
        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 temporal clitics; no free possessive template; no unsourced comitatives; no maja-nga; "
            "no yunga-l; no ergative babaji-y askers; kija moon-only; dawar for star; common-noun nyaji-l perceivers "
            "ERG-marked; transitive objects explicit."
        ),
        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 163 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 163 stores source inventory, lexical scope, risk controls, duplicate scan, static guards, row grouping, "
            "bank/lint/review/close/export/docs outcomes, and live verification as 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 121 lexical-support rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_163.py",
        artifact_path="batch_scripts/bank_163.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 places in motion/posture; 11-20 ordinary objects in place/source/handling frames; 21-30 "
            "participants/animals in perception, transfer, and source/goal frames; 31-35 ask/laugh frames; 36-40 "
            "weather/celestial heads; 41-45 sourced comitative/place combinations; 46-50 Patz-backed possessive objects."
        ),
        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())
