#!/usr/bin/env python3
"""Bank batch-166: Rule 124 lexical-head case-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-166"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-166-lexical-head-case-frame-consolidation-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 = ("ngawa-nji", "nganjan-anji", "jawun-unji", "juku-ji", "kambi-ji")
LAUGH_TARGET_SUFFIXES = ("-anka", "-anda", "-nda", "-ndu", "-nka")


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


ROWS = [
    (1, "The dilly bag lay on the road.",
     "Ngunyin baral-ba wunan.",
     "ngunyin baral-ba wuna-ny | dilly.bag road-LOC lie-PAST",
     "r124_rest_dillybag_road", 3, js(["ngunyin", "baral", "wunay"]), js([]),
     "Rule 124 consolidates source-backed ngunyin with retained baral-ba and wuna-y.", "0.84"),
    (2, "The remains dilly bag lay on the ground.",
     "Yinkar bubu-ngu wunan.",
     "yinkar bubu-ngu wuna-ny | remains.dilly.bag ground-LOC lie-PAST",
     "r124_rest_yinkar_ground", 3, js(["yinkar", "bubu", "wunay"]), js([]),
     "Yinkar remains the narrow remains-dilly-bag sense; bubu-ngu and wuna-y are retained controls.", "0.84"),
    (3, "The fish net lay at the sea.",
     "Makarr jalun-bu wunan.",
     "makarr jalun-bu wuna-ny | fish.net sea-LOC lie-PAST",
     "r124_rest_net_sea", 3, js(["makarr", "jalun", "wunay"]), js([]),
     "Makarr is dictionary/Patz-backed; jalun-bu sea LOC and wuna-y are controlled.", "0.84"),
    (4, "The rope lay in the shade.",
     "Karra wabarr-ba wunan.",
     "karra wabarr-ba wuna-ny | rope shade-LOC lie-PAST",
     "r124_rest_rope_shade", 3, js(["karra", "wabarr", "wunay"]), js([]),
     "Karra rope and wabarr-ba shade LOC are source-backed ordinary heads/forms.", "0.84"),
    (5, "The lawyer-cane top lay outside.",
     "Kaku ngalkal-ba wunan.",
     "kaku ngalkal-ba wuna-ny | lawyer.cane.top outside-LOC lie-PAST",
     "r124_rest_cane_top_outside", 3, js(["kaku", "ngalkal", "wunay"]), js([]),
     "Kaku is the noun lawyer-cane top; ngalkal-ba outside LOC is retained.", "0.82"),
    (6, "The bark lay in the bag.",
     "Dumbul balji-nga wunan.",
     "dumbul balji-nga wuna-ny | bark bag-LOC lie-PAST",
     "r124_rest_bark_bag", 3, js(["dumbul", "balji", "wunay"]), js([]),
     "Dumbul bark and balji-nga bag LOC are checked source forms.", "0.84"),
    (7, "The mango lay outside.",
     "Mayngku ngalkal-ba wunan.",
     "mayngku ngalkal-ba wuna-ny | mango outside-LOC lie-PAST",
     "r124_rest_mango_outside", 3, js(["mayngku", "ngalkal", "wunay"]), js(["mayngku"]),
     "Mayngku is an attested loan; ngalkal-ba outside LOC and wuna-y are retained.", "0.84"),
    (8, "News came from the sea.",
     "Kuku jalun-muny kadan.",
     "kuku jalun-muny kada-ny | news sea-ABL come-PAST",
     "r124_source_news_sea", 3, js(["kuku", "jalun", "kaday"]), js([]),
     "Kuku news sense and jalun-muny sea source are source-backed controls.", "0.82"),
    (9, "The old man came from the sea.",
     "Wulman jalun-muny kadan.",
     "wulman jalun-muny kada-ny | old.man sea-ABL come-PAST",
     "r124_source_old_man_sea", 3, js(["wulman", "jalun", "kaday"]), js(["wulman"]),
     "Wulman is grammar-backed as old man/old person; jalun-muny and kada-y are retained.", "0.82"),
    (10, "The pig came from the house.",
     "Bikibiki bayan-muny kadan.",
     "bikibiki bayan-muny kada-ny | pig house-ABL come-PAST",
     "r124_source_pig_house", 3, js(["bikibiki", "bayan", "kaday"]), js(["bikibiki"]),
     "Bikibiki pig and bayan-muny house/camp source are checked.", "0.84"),

    (11, "The man put the dilly bag on the ground.",
     "Dingkar-angka ngunyin bubu-ngu yijarrin.",
     "dingkar-angka ngunyin bubu-ngu yijarri-ny | man-ERG dilly.bag ground-LOC put-PAST",
     "r124_object_dillybag_ground", 4, js(["dingkar", "ngunyin", "bubu", "yijarril"]), js([]),
     "Changed from the handoff's road placement to bubu-ngu to avoid exact duplication while keeping the frame.", "0.84"),
    (12, "Grandmother left the remains dilly bag in the shade.",
     "Babi-ngka yinkar wabarr-ba bawan.",
     "babi-ngka yinkar wabarr-ba bawa-ny | grandmother-ERG remains.dilly.bag shade-LOC leave-PAST",
     "r124_object_yinkar_shade", 4, js(["babi", "yinkar", "wabarr", "bawal"]), js([]),
     "Yinkar is explicitly remains dilly bag; bawa-l leave frame keeps A marking.", "0.84"),
    (13, "The child put the fish net on the road.",
     "Karrkay-ngku makarr baral-ba yijarrin.",
     "karrkay-ngku makarr baral-ba yijarri-ny | child-ERG fish.net road-LOC put-PAST",
     "r124_object_net_road", 4, js(["karrkay", "makarr", "baral", "yijarril"]), js([]),
     "Makarr fish net is moved as an overt object before baral-ba.", "0.84"),
    (14, "The woman left the rope outside.",
     "Jalbu-ngku karra ngalkal-ba bawan.",
     "jalbu-ngku karra ngalkal-ba bawa-ny | woman-ERG rope outside-LOC leave-PAST",
     "r124_object_rope_outside", 4, js(["jalbu", "karra", "ngalkal", "bawal"]), js([]),
     "Karra rope and ngalkal-ba outside are checked; bawa-l keeps the transitive leave frame.", "0.84"),
    (15, "Mother put the lawyer-cane top in the shade.",
     "Ngamu-ngku kaku wabarr-ba yijarrin.",
     "ngamu-ngku kaku wabarr-ba yijarri-ny | mother-ERG lawyer.cane.top shade-LOC put-PAST",
     "r124_object_cane_top_shade", 4, js(["ngamu", "kaku", "wabarr", "yijarril"]), js([]),
     "Kaku noun sense stays explicit; ngamu-ngku A marking follows the placement frame.", "0.82"),
    (16, "The old woman brought bark from the house.",
     "Wulbuman-angka dumbul bayan-muny wundin.",
     "wulbuman-angka dumbul bayan-muny wundi-ny | old.woman-ERG bark house-ABL bring-PAST",
     "r124_object_bark_house_source", 4, js(["wulbuman", "dumbul", "bayan", "wundil"]), js([]),
     "Dumbul bark as object with bayan-muny source and wundi-l transfer.", "0.84"),
    (17, "Grandfather got the mango at camp.",
     "Kami-ngka mayngku bayan-ba manin.",
     "kami-ngka mayngku bayan-ba mani-ny | grandfather-ERG mango camp-LOC get-PAST",
     "r124_object_mango_camp", 4, js(["kami", "mayngku", "bayan", "manil"]), js(["mayngku"]),
     "Mayngku loan is source-backed; bayan-ba camp/house LOC and mani-l get are retained.", "0.84"),
    (18, "The young man brought news from the sea.",
     "Warru-ngku kuku jalun-muny wundin.",
     "warru-ngku kuku jalun-muny wundi-ny | young.man-ERG news sea-ABL bring-PAST",
     "r124_object_news_sea_source", 4, js(["warru", "kuku", "jalun", "wundil"]), js([]),
     "Kuku news object with jalun-muny source uses the existing transfer frame.", "0.82"),
    (19, "We took the fish net to the river.",
     "Ngana makarr wawubaja-nga wundin.",
     "ngana makarr wawubaja-nga wundi-ny | we fish.net river-LOC take-PAST",
     "r124_object_net_river_goal", 4, js(["ngana", "makarr", "wawubaja", "wundil"]), js([]),
     "Pronoun A remains unmarked; wawubaja-nga is a retained river goal/location control.", "0.84"),
    (20, "I took the rope to camp.",
     "Ngayu karra bayan-ka wundin.",
     "ngayu karra bayan-ka wundi-ny | I rope camp-DAT take-PAST",
     "r124_object_rope_camp_goal", 4, js(["ngayu", "karra", "bayan", "wundil"]), js([]),
     "Pronoun A remains bare; karra object and bayan-ka DAT goal are checked.", "0.84"),

    (21, "The woman saw the dilly bag on the road.",
     "Jalbu-ngku ngunyin baral-ba nyajin.",
     "jalbu-ngku ngunyin baral-ba nyaji-ny | woman-ERG dilly.bag road-LOC see-PAST",
     "r124_perception_woman_dillybag_road", 4, js(["jalbu", "ngunyin", "baral", "nyajil"]), js([]),
     "Common-noun perceiver is ERG-marked; ngunyin/baral-ba contrast row 22.", "0.84"),
    (22, "I saw the dilly bag on the road.",
     "Ngayu ngunyin baral-ba nyajin.",
     "ngayu ngunyin baral-ba nyaji-ny | I dilly.bag road-LOC see-PAST",
     "r124_perception_pronoun_dillybag_road", 4, js(["ngayu", "ngunyin", "baral", "nyajil"]), js([]),
     "Pronoun A remains unmarked in the same object/location frame.", "0.84"),
    (23, "The man saw the remains dilly bag on the ground.",
     "Dingkar-angka yinkar bubu-ngu nyajin.",
     "dingkar-angka yinkar bubu-ngu nyaji-ny | man-ERG remains.dilly.bag ground-LOC see-PAST",
     "r124_perception_man_yinkar_ground", 4, js(["dingkar", "yinkar", "bubu", "nyajil"]), js([]),
     "Yinkar fidelity retained; common-noun perceiver marked ERG.", "0.84"),
    (24, "We saw the remains dilly bag on the ground.",
     "Ngana yinkar bubu-ngu nyajin.",
     "ngana yinkar bubu-ngu nyaji-ny | we remains.dilly.bag ground-LOC see-PAST",
     "r124_perception_pronoun_yinkar_ground", 4, js(["ngana", "yinkar", "bubu", "nyajil"]), js([]),
     "Pronoun A contrast for row 23; yinkar remains narrow in English and gloss.", "0.84"),
    (25, "The child saw the fish net outside.",
     "Karrkay-ngku makarr ngalkal-ba nyajin.",
     "karrkay-ngku makarr ngalkal-ba nyaji-ny | child-ERG fish.net outside-LOC see-PAST",
     "r124_perception_child_net_outside", 4, js(["karrkay", "makarr", "ngalkal", "nyajil"]), js([]),
     "Makarr object and ngalkal-ba location are checked; karrkay-ngku A marking retained.", "0.84"),
    (26, "I saw the fish net outside.",
     "Ngayu makarr ngalkal-ba nyajin.",
     "ngayu makarr ngalkal-ba nyaji-ny | I fish.net outside-LOC see-PAST",
     "r124_perception_pronoun_net_outside", 4, js(["ngayu", "makarr", "ngalkal", "nyajil"]), js([]),
     "Pronoun A contrast for row 25.", "0.84"),
    (27, "Grandmother saw the rope in the shade.",
     "Babi-ngka karra wabarr-ba nyajin.",
     "babi-ngka karra wabarr-ba nyaji-ny | grandmother-ERG rope shade-LOC see-PAST",
     "r124_perception_grandmother_rope_shade", 4, js(["babi", "karra", "wabarr", "nyajil"]), js([]),
     "Karra rope object with ERG common-noun perceiver and shade location.", "0.84"),
    (28, "We saw the rope in the shade.",
     "Ngana karra wabarr-ba nyajin.",
     "ngana karra wabarr-ba nyaji-ny | we rope shade-LOC see-PAST",
     "r124_perception_pronoun_rope_shade", 4, js(["ngana", "karra", "wabarr", "nyajil"]), js([]),
     "Pronoun A contrast for row 27.", "0.84"),
    (29, "The young man saw bark in the grass.",
     "Warru-ngku dumbul jikan-ba nyajin.",
     "warru-ngku dumbul jikan-ba nyaji-ny | young.man-ERG bark grass-LOC see-PAST",
     "r124_perception_young_man_bark_grass", 4, js(["warru", "dumbul", "jikan", "nyajil"]), js([]),
     "Dumbul bark and jikan-ba grass LOC are checked; common-noun A marked ERG.", "0.84"),
    (30, "I saw the sun outside.",
     "Ngayu wungar ngalkal-ba nyajin.",
     "ngayu wungar ngalkal-ba nyaji-ny | I sun outside-LOC see-PAST",
     "r124_perception_pronoun_sun_outside", 4, js(["ngayu", "wungar", "ngalkal", "nyajil"]), js([]),
     "Wungar sun is source-backed; pronoun A remains unmarked.", "0.84"),

    (31, "The old man asked grandfather about news.",
     "Wulman kami-nda kuku-ku babajin.",
     "wulman kami-nda kuku-ku babaji-ny | old.man grandfather-LOC:pt news-DAT ask-PAST",
     "r124_ask_old_man_grandfather_news", 4, js(["wulman", "kami", "kuku", "babaji"]), js(["wulman"]),
     "Babaji-y asker stays ABS/S; kuku-ku is grammar-sourced DAT topic.", "0.86"),
    (32, "The young man asked mother about news.",
     "Warru ngamu-ndu kuku-ku babajin.",
     "warru ngamu-ndu kuku-ku babaji-ny | young.man mother-LOC:pt news-DAT ask-PAST",
     "r124_ask_young_man_mother_news", 4, js(["warru", "ngamu", "kuku", "babaji"]), js([]),
     "ABS/S asker, LOC:pt person asked, DAT topic; no ERG drift.", "0.86"),
    (33, "The woman asked father about the mango.",
     "Jalbu nganjan-anda mayngku-ku babajin.",
     "jalbu nganjan-anda mayngku-ku babaji-ny | woman father-LOC:pt mango-DAT ask-PAST",
     "r124_ask_woman_father_mango", 4, js(["jalbu", "nganjan", "mayngku", "babaji"]), js(["mayngku"]),
     "Mayngku-ku is grammar-sourced; asker remains unmarked ABS/S.", "0.86"),
    (34, "The friend asked the girl about the mango.",
     "Jawun maral-anda mayngku-ku babajin.",
     "jawun maral-anda mayngku-ku babaji-ny | friend girl-LOC:pt mango-DAT ask-PAST",
     "r124_ask_friend_girl_mango", 4, js(["jawun", "maral", "mayngku", "babaji"]), js(["mayngku"]),
     "Maral-anda LOC:pt person asked; mayngku-ku DAT topic retained.", "0.86"),
    (35, "The child asked grandmother about food.",
     "Karrkay babi-nda mayi-ka babajin.",
     "karrkay babi-nda mayi-ka babaji-ny | child grandmother-LOC:pt food-DAT ask-PAST",
     "r124_ask_child_grandmother_food", 4, js(["karrkay", "babi", "mayi", "babaji"]), js([]),
     "Mayi-ka DAT topic is retained; child asker has no ERG marking.", "0.86"),

    (36, "The old man laughed at grandfather.",
     "Wulman kami-nda yajarrin.",
     "wulman kami-nda yajarri-ny | old.man grandfather-DAT:pt laugh-PAST",
     "r124_laugh_old_man_grandfather", 3, js(["wulman", "kami", "yajarril"]), js(["wulman"]),
     "Yajarri-l keeps DAT:pt target; laugher is intransitive S.", "0.84"),
    (37, "Mother laughed at the child.",
     "Ngamu karrkay-anka yajarrin.",
     "ngamu karrkay-anka yajarri-ny | mother child-DAT:pt laugh-PAST",
     "r124_laugh_mother_child", 3, js(["ngamu", "karrkay", "yajarril"]), js([]),
     "Karrkay-anka target form retained; no ERG on laugher.", "0.84"),
    (38, "Father laughed at the woman.",
     "Nganjan jalbu-ndu yajarrin.",
     "nganjan jalbu-ndu yajarri-ny | father woman-DAT:pt laugh-PAST",
     "r124_laugh_father_woman", 3, js(["nganjan", "jalbu", "yajarril"]), js([]),
     "Jalbu-ndu target form retained.", "0.84"),
    (39, "The girl laughed at the young man.",
     "Maral warru-ndu yajarrin.",
     "maral warru-ndu yajarri-ny | girl young.man-DAT:pt laugh-PAST",
     "r124_laugh_girl_young_man", 3, js(["maral", "warru", "yajarril"]), js([]),
     "Warru-ndu target form retained.", "0.84"),
    (40, "Grandmother laughed at the girl.",
     "Babi maral-anda yajarrin.",
     "babi maral-anda yajarri-ny | grandmother girl-DAT:pt laugh-PAST",
     "r124_laugh_grandmother_girl", 3, js(["babi", "maral", "yajarril"]), js([]),
     "Maral-anda avoids the duplicate handoff target while preserving the DAT:pt laugh frame.", "0.84"),

    (41, "The old man went to the sea with the baby.",
     "Wulman ngawa-nji jalun-bu dungan.",
     "wulman ngawa-nji jalun-bu dunga-ny | old.man baby-COMIT:pt sea-LOC go-PAST",
     "r124_comit_old_man_baby_sea", 4, js(["wulman", "ngawa", "jalun", "dungay"]), js(["wulman"]),
     "Ngawa-nji is direct-source only; no new wulman case form is coined.", "0.84"),
    (42, "Grandmother went to the shade with a friend.",
     "Babi jawun-unji wabarr-ba dungan.",
     "babi jawun-unji wabarr-ba dunga-ny | grandmother friend-COMIT:pt shade-LOC go-PAST",
     "r124_comit_grandmother_friend_shade", 4, js(["babi", "jawun", "wabarr", "dungay"]), js([]),
     "Jawun-unji is in the closed comitative set; wabarr-ba shade LOC retained.", "0.84"),
    (43, "The young man went to the road with father.",
     "Warru nganjan-anji baral-ba dungan.",
     "warru nganjan-anji baral-ba dunga-ny | young.man father-COMIT:pt road-LOC go-PAST",
     "r124_comit_young_man_father_road", 4, js(["warru", "nganjan", "baral", "dungay"]), js([]),
     "Nganjan-anji is in the closed comitative set; baral-ba road LOC retained.", "0.84"),
    (44, "The woman came from the house with a stick.",
     "Jalbu juku-ji bayan-muny kadan.",
     "jalbu juku-ji bayan-muny kada-ny | woman stick-COMIT house-ABL come-PAST",
     "r124_comit_woman_stick_house_source", 4, js(["jalbu", "juku", "bayan", "kaday"]), js([]),
     "Juku-ji is the sourced neutral comitative; bayan-muny source retained.", "0.84"),
    (45, "Mother went to the sea with clothes.",
     "Ngamu kambi-ji jalun-bu dungan.",
     "ngamu kambi-ji jalun-bu dunga-ny | mother clothes-COMIT sea-LOC go-PAST",
     "r124_comit_mother_clothes_sea", 4, js(["ngamu", "kambi", "jalun", "dungay"]), js([]),
     "Kambi-ji is in the closed comitative set; jalun-bu sea LOC retained.", "0.84"),
    (46, "The child is staying outside with the baby.",
     "Karrkay ngalkal-ba bundanday ngawa-nji.",
     "karrkay ngalkal-ba bunda-n-bunda-y ngawa-nji | child outside-LOC stay-REDUP-NONPAST baby-COMIT:pt",
     "r124_comit_child_baby_outside", 4, js(["karrkay", "ngalkal", "bunday", "ngawa"]), js([]),
     "Ngawa-nji remains direct-source only; row tests subject-place-verb-comitative order.", "0.84"),

    (47, "The man put the dog's food on the ground.",
     "Dingkar-angka mayi kaya-nga bubu-ngu yijarrin.",
     "dingkar-angka mayi kaya-nga bubu-ngu yijarri-ny | man-ERG food dog-POSS.ABS ground-LOC put-PAST",
     "r124_poss_food_man_ground", 5, js(["dingkar", "mayi", "kaya", "bubu", "yijarril"]), js([]),
     "Mayi kaya-nga is reused only as fixed Patz-backed possessive object; POSS analysis explicit.", "0.84"),
    (48, "The old woman brought the dog's food from the house.",
     "Wulbuman-angka mayi kaya-nga bayan-muny wundin.",
     "wulbuman-angka mayi kaya-nga bayan-muny wundi-ny | old.woman-ERG food dog-POSS.ABS house-ABL bring-PAST",
     "r124_poss_food_old_woman_house_source", 5, js(["wulbuman", "mayi", "kaya", "bayan", "wundil"]), js([]),
     "Fixed possessive object plus bayan-muny source; no new possessive template.", "0.84"),
    (49, "The young man left mother's clothes in the shade.",
     "Warru-ngku kambi ngamu-ngu wabarr-ba bawan.",
     "warru-ngku kambi ngamu-ngu wabarr-ba bawa-ny | young.man-ERG clothes mother-POSS.ABS shade-LOC leave-PAST",
     "r124_poss_clothes_young_man_shade", 5, js(["warru", "kambi", "ngamu", "wabarr", "bawal"]), js([]),
     "Kambi ngamu-ngu is the fixed source-backed possessive NP; bawa-l frame retained.", "0.84"),
    (50, "I took mother's clothes to the sea.",
     "Ngayu kambi ngamu-ngu jalun-bu wundin.",
     "ngayu kambi ngamu-ngu jalun-bu wundi-ny | I clothes mother-POSS.ABS sea-LOC take-PAST",
     "r124_poss_clothes_pronoun_sea_goal", 5, js(["ngayu", "kambi", "ngamu", "jalun", "wundil"]), js([]),
     "Pronoun A remains unmarked; fixed possessive object and jalun-bu goal/location retained.", "0.84"),
]


def has_denied_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", low) is not None


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}"
        low = kuku.lower()
        analysis_low = analysis.lower()
        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("r124_"):
            issues.append(f"{label}: Rule 124 batch frame must start with r124_")
        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 consolidation batch must not reuse temporal clitic marker {marker}")
        for denied in DENY:
            if has_denied_token(low, denied):
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        if "baral-muny" in low:
            issues.append(f"{label}: baral-muny is not source-logged")
        if "kadar-anji" in low:
            issues.append(f"{label}: kadar-anji is outside this batch's closed comitative set")
        if "ngunyin-ji" in low:
            issues.append(f"{label}: ngunyin-ji is attested but not admitted by Rule 124's closed-comitative policy")
        if "kija" in low and not re.search(r"\b(moon|month)\b", analysis_low):
            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_low:
            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 any(suffix in low for suffix in LAUGH_TARGET_SUFFIXES):
            issues.append(f"{label}: laugh target should show sourced DAT:pt target suffix")
        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 closed sourced 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 124 draft: lexical-head case-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 124 source pass checked dictionary.yaml, Patz grammar, and existing corpus lexemes before authoring. "
            "The batch consolidates Rule 123 lexical heads across retained case frames without adding morphology."
        ),
        command="rg over dictionary.yaml and grammar.md; sqlite readback from lexemes and planned-surface duplicate scan",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="dictionary-source-check",
        actor="claude-main",
        detail=(
            "Exact dictionary readback confirmed all foregrounded heads: ngunyin, yinkar, makarr, karra, kaku, dumbul, "
            "bikibiki, mayngku, kuku, wungar, wulman, and reused kin/person/place/object heads."
        ),
        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 checked: wulman narrative noun, bikibiki, wungar, bayan-muny, jalun-bu/jalun-muny, "
            "makarr with wundi-l, kuku-ku, mayngku-ku, wawubaja-nga, baral-ba, ngawa-nji, and the closed comitative set."
        ),
        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 ngunyin-ji despite attestation; no new comitatives; no free possessive templates; no "
            "baral-muny; no kadar-anji; no maja-nga or yunga-l; yinkar translated as remains dilly bag; kaku noun "
            "sense only; no ergative babaji-y askers; common-noun nyaji-l perceivers ERG-marked; yajarri-l targets DAT:pt."
        ),
        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. The handoff duplicate "
            "Babi nganjan-anka yajarrin was replaced with Babi maral-anda yajarrin before insert; final scan was clean."
        ),
        command="sqlite exact-surface query over planned Batch 166 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 166 stores source inventory, duplicate adjustments, sense restrictions, guard design, row grouping, "
            "duplicate scan, static checks, bank outcome, and later lint/review/close/export/docs evidence as process_events plus the 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 124 lexical-head case-frame consolidation rows under_review after static guards and duplicate checks.",
        command="python3 batch_scripts/bank_166.py",
        artifact_path="batch_scripts/bank_166.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="row-inventory",
        actor="claude-main",
        detail=(
            "Rows 1-10 place/source/rest; 11-20 object place/source/goal; 21-30 perception contrasts; "
            "31-35 ask-about DAT topics; 36-40 laugh targets; 41-46 closed comitatives; 47-50 fixed 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())
