#!/usr/bin/env python3
"""Bank batch-175: Rule 133 animal/body-flesh lexical expansion."""

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-175"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-175-animal-body-flesh-lexical-expansion-process.md"

TARGET_HEADS = {
    "bajibay": "bone",
    "bangkarr": "body",
    "biji": "tail",
    "jiba": "liver",
    "malbin": "upper leg",
}
SUPPORT_HEADS = {
    "minya": "meat",
    "kuyu": "fish",
}
DENY = {
    "dakil",
    "yah",
    "ngawuya",
    "walnga",
    "maja-nga",
    "yunga",
    "bada-bada",
    "wangkar-wangkar",
    "biyul",
    "warrur",
}
FORBIDDEN_MARKERS = ("-ngarr", "-ngurr", "-kuda", "-l-urr", "wawu-")
PRONOUN_A = {"ngayu", "ngana", "nganjin", "yundu", "yurra", "jana", "nyulu"}
TRANSITIVE_PAST = ("nyajin", "julurrin", "yijarrin", "baykan", "nukan", "wajun", "manin", "dajin", "bawan")
SAFE_DAT_TOPICS = {"jiba-ka", "bajibay-ngka", "minya-ka", "kuyu-ku", "bajibay-kari-ka"}


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


ROWS = [
    (1, "The old woman's upper leg is sore.",
     "Mukul-jalbu malbin kaka.",
     "mukul-jalbu malbin kaka | old-woman upper.leg sore",
     "r133_state_old_woman_upper_leg_sore", 2, js(["mukul", "jalbu", "malbin", "kaka"]), js([]),
     "malbin dictionary line 17557: upper leg, leg between knee and thigh; Patz Text 76 line 95 lists malbin in the body-part inventory. Kaka retained as sore/pain only.", "0.84"),
    (2, "The young man's upper leg is dry.",
     "Warru malbin marral.",
     "warru malbin marral | young.man upper.leg dry",
     "r133_state_young_man_upper_leg_dry", 2, js(["warru", "malbin", "marral"]), js([]),
     "malbin upper-leg sense selected; state-predicate frame retained from Rule 131/132 body-part rows.", "0.84"),
    (3, "The child's body is good.",
     "Karrkay bangkarr ngulkurr.",
     "karrkay bangkarr ngulkurr | child body good",
     "r133_state_child_body_good", 2, js(["karrkay", "bangkarr", "ngulkurr"]), js([]),
     "bangkarr dictionary line 1533: person's body/flesh; prior corpus body-state rows support bare body apposition.", "0.86"),
    (4, "Grandfather's body is tired.",
     "Kami bangkarr bajaburray.",
     "kami bangkarr bajaburray | grandfather body tired",
     "r133_state_grandfather_body_tired", 2, js(["kami", "bangkarr", "bajaburray"]), js([]),
     "bangkarr body plus sourced tired predicate from previous body-state rows; no possessive suffix.", "0.86"),
    (5, "The fish tail is dry.",
     "Kuyu biji marral.",
     "kuyu biji marral | fish tail dry",
     "r133_state_fish_tail_dry", 2, js(["kuyu", "biji", "marral"]), js([]),
     "biji dictionary line 2313: tail; Patz ex.520 has kuyu biji as whole-part NP.", "0.86"),
    (6, "The dog's tail is long.",
     "Kaya biji kalbay.",
     "kaya biji kalbay | dog tail long",
     "r133_state_dog_tail_long", 2, js(["kaya", "biji", "kalbay"]), js([]),
     "biji tail is an animal body part; dog whole-part apposition follows the sourced fish-tail model without possessive suffix.", "0.84"),
    (7, "The bone is good.",
     "Bajibay ngulkurr.",
     "bajibay ngulkurr | bone good",
     "r133_state_bone_good", 2, js(["bajibay", "ngulkurr"]), js([]),
     "bajibay dictionary line 512: bone; simple adjective predicate keeps the selected noun sense.", "0.86"),
    (8, "The bone is dry.",
     "Bajibay marral.",
     "bajibay marral | bone dry",
     "r133_state_bone_dry", 2, js(["bajibay", "marral"]), js([]),
     "bajibay bone plus retained dry predicate; no metaphorical story-bone use.", "0.86"),
    (9, "Liver is very good meat.",
     "Jiba minya ngulkurr bajaku.",
     "jiba minya ngulkurr bajaku | liver meat good very",
     "r133_state_liver_good_meat", 2, js(["jiba", "minya", "ngulkurr", "bajaku"]), js([]),
     "jiba dictionary line 8611: liver/insides; Patz Text 76 line 81 asks about liver as food, and batch-009 has jiba minya ngulkurr.", "0.86"),
    (10, "The meat is without bones.",
     "Minya bajibay-kari.",
     "minya bajibay-kari | meat bone-PRIV",
     "r133_privative_meat_without_bones", 3, js(["minya", "bajibay", "kari"]), js([]),
     "minya meat and bajibay bone; Patz line 1946 and prior rows support bajibay-kari 'without bones'.", "0.86"),

    (11, "Mother saw the upper leg.",
     "Ngamu-ngku malbin nyajin.",
     "ngamu-ngku malbin nyaji-ny | mother-ERG upper.leg see-PAST",
     "r133_perception_mother_upper_leg", 4, js(["ngamu", "malbin", "nyajil"]), js([]),
     "Common-noun perceiver ERG; malbin retained as upper leg only.", "0.84"),
    (12, "I saw the old man's upper leg.",
     "Ngayu mukul-dingkar malbin nyajin.",
     "ngayu mukul-dingkar malbin nyaji-ny | I old-man upper.leg see-PAST",
     "r133_perception_pronoun_old_man_upper_leg", 4, js(["ngayu", "mukul", "dingkar", "malbin", "nyajil"]), js([]),
     "Pronoun A bare; whole-part O apposition mirrors Rule 131/132 body perception rows.", "0.84"),
    (13, "Father saw the fish tail.",
     "Nganjan-angka kuyu biji nyajin.",
     "nganjan-angka kuyu biji nyaji-ny | father-ERG fish tail see-PAST",
     "r133_perception_father_fish_tail", 4, js(["nganjan", "kuyu", "biji", "nyajil"]), js([]),
     "Patz ex.520 supports kuyu biji; common-noun perceiver ERG.", "0.86"),
    (14, "The woman saw the dog's tail.",
     "Jalbu-ngku kaya biji nyajin.",
     "jalbu-ngku kaya biji nyaji-ny | woman-ERG dog tail see-PAST",
     "r133_perception_woman_dog_tail", 4, js(["jalbu", "kaya", "biji", "nyajil"]), js([]),
     "Animal tail whole-part apposition; common-noun perceiver ERG.", "0.84"),
    (15, "The child saw the bone.",
     "Karrkay-ngku bajibay nyajin.",
     "karrkay-ngku bajibay nyaji-ny | child-ERG bone see-PAST",
     "r133_perception_child_bone", 4, js(["karrkay", "bajibay", "nyajil"]), js([]),
     "bajibay bone as ordinary perceived object; common-noun A ERG.", "0.86"),
    (16, "I saw the bone on the ground.",
     "Ngayu bajibay bubu-ngu nyajin.",
     "ngayu bajibay bubu-ngu nyaji-ny | I bone ground-LOC see-PAST",
     "r133_perception_pronoun_bone_ground", 4, js(["ngayu", "bajibay", "bubu", "nyajil"]), js([]),
     "Pronoun A bare; bubu-ngu retained as ground LOC; bajibay literal bone only.", "0.86"),
    (17, "Grandmother saw the liver.",
     "Babi-ngka jiba nyajin.",
     "babi-ngka jiba nyaji-ny | grandmother-ERG liver see-PAST",
     "r133_perception_grandmother_liver", 4, js(["babi", "jiba", "nyajil"]), js([]),
     "jiba liver as perceived object; emotional compound senses excluded.", "0.86"),
    (18, "We saw the liver in the bag.",
     "Ngana jiba balji-nga nyajin.",
     "ngana jiba balji-nga nyaji-ny | we liver bag-LOC see-PAST",
     "r133_perception_pronoun_liver_bag", 4, js(["ngana", "jiba", "balji", "nyajil"]), js([]),
     "Pronoun A bare; jiba literal liver; balji-nga retained bag LOC.", "0.84"),
    (19, "The man saw the body.",
     "Dingkar-angka bangkarr nyajin.",
     "dingkar-angka bangkarr nyaji-ny | man-ERG body see-PAST",
     "r133_perception_man_body", 4, js(["dingkar", "bangkarr", "nyajil"]), js([]),
     "bangkarr body/flesh literal; common-noun perceiver ERG.", "0.86"),
    (20, "They saw meat in the shade.",
     "Jana minya wabarr-ba nyajin.",
     "jana minya wabarr-ba nyaji-ny | they meat shade-LOC see-PAST",
     "r133_perception_pronoun_meat_shade", 4, js(["jana", "minya", "wabarr", "nyajil"]), js([]),
     "Pronoun A bare; minya meat/flesh-food support noun and retained wabarr-ba location.", "0.86"),

    (21, "The dog ate a bone.",
     "Kaya-ngka bajibay nukan.",
     "kaya-ngka bajibay nuka-ny | dog-ERG bone eat-PAST",
     "r133_consumption_dog_bone", 4, js(["kaya", "bajibay", "nukal"]), js([]),
     "Directly modelled on Patz line 1319 Kayangka bajibay nukany; modern surface past kept as nukan.", "0.88"),
    (22, "The child ate liver.",
     "Karrkay-ngku jiba nukan.",
     "karrkay-ngku jiba nuka-ny | child-ERG liver eat-PAST",
     "r133_consumption_child_liver", 4, js(["karrkay", "jiba", "nukal"]), js([]),
     "jiba treated as literal liver food, supported by Patz line 6942 liver-want question.", "0.84"),
    (23, "Grandfather ate meat.",
     "Kami-ngka minya nukan.",
     "kami-ngka minya nuka-ny | grandfather-ERG meat eat-PAST",
     "r133_consumption_grandfather_meat", 4, js(["kami", "minya", "nukal"]), js([]),
     "minya meat support noun; common-noun eater ERG.", "0.86"),
    (24, "The old woman cooked liver.",
     "Mukul-jalbu-ngku jiba wajun.",
     "mukul-jalbu-ngku jiba waju-ny | old-woman-ERG liver cook-PAST",
     "r133_cooking_old_woman_liver", 4, js(["mukul", "jalbu", "jiba", "wajul"]), js([]),
     "jiba literal liver object in old waju-l cooking frame; common-noun A ERG.", "0.84"),
    (25, "Mother cooked the liver.",
     "Ngamu-ngku jiba wajun.",
     "ngamu-ngku jiba waju-ny | mother-ERG liver cook-PAST",
     "r133_cooking_mother_liver", 4, js(["ngamu", "jiba", "wajul"]), js([]),
     "Second liver-cooking row changes actor only; keeps literal liver and old transitive cooking frame.", "0.84"),
    (26, "The man put the bone in the bag.",
     "Dingkar-angka bajibay balji-nga yijarrin.",
     "dingkar-angka bajibay balji-nga yijarri-ny | man-ERG bone bag-LOC put-PAST",
     "r133_placement_bone_bag", 4, js(["dingkar", "bajibay", "balji", "yijarril"]), js([]),
     "bajibay ordinary object; balji-nga/yijarri-l frame retained from Rule 125-128 placement rows.", "0.86"),
    (27, "The woman put liver on the ground.",
     "Jalbu-ngku jiba bubu-ngu yijarrin.",
     "jalbu-ngku jiba bubu-ngu yijarri-ny | woman-ERG liver ground-LOC put-PAST",
     "r133_placement_liver_ground", 4, js(["jalbu", "jiba", "bubu", "yijarril"]), js([]),
     "jiba literal liver; bubu-ngu ground LOC; common-noun A ERG.", "0.84"),
    (28, "We put the fish tail in the bag.",
     "Ngana kuyu biji balji-nga yijarrin.",
     "ngana kuyu biji balji-nga yijarri-ny | we fish tail bag-LOC put-PAST",
     "r133_placement_pronoun_fish_tail_bag", 4, js(["ngana", "kuyu", "biji", "balji", "yijarril"]), js([]),
     "Pronoun A bare; kuyu biji whole-part object; no possessive suffix.", "0.84"),
    (29, "Father left the meat in the shade.",
     "Nganjan-angka minya wabarr-ba bawan.",
     "nganjan-angka minya wabarr-ba bawa-ny | father-ERG meat shade-LOC leave-PAST",
     "r133_leave_father_meat_shade", 4, js(["nganjan", "minya", "wabarr", "bawal"]), js([]),
     "minya meat support noun in retained leave-location frame; common-noun A ERG.", "0.86"),
    (30, "Grandmother took the bone from the ground.",
     "Babi-ngka bajibay bubu-muny manin.",
     "babi-ngka bajibay bubu-muny mani-ny | grandmother-ERG bone ground-ABL get-PAST",
     "r133_get_grandmother_bone_ground", 4, js(["babi", "bajibay", "bubu", "manil"]), js([]),
     "bajibay bone object; bubu-muny source; common-noun A ERG.", "0.86"),

    (31, "The child wants liver.",
     "Karrkay jiba-ka wawu.",
     "karrkay jiba-ka wawu | child liver-DAT want",
     "r133_want_child_liver", 3, js(["karrkay", "jiba", "wawu"]), js([]),
     "Directly modelled on Patz Text 76 line 81 Yundu jiba-ka wawu?", "0.88"),
    (32, "The old man wants liver.",
     "Mukul-dingkar jiba-ka wawu.",
     "mukul-dingkar jiba-ka wawu | old-man liver-DAT want",
     "r133_want_old_man_liver", 3, js(["mukul", "dingkar", "jiba", "wawu"]), js([]),
     "jiba-ka dative retained from source liver-want question; subject changed only.", "0.86"),
    (33, "The child wants a bone.",
     "Karrkay bajibay-ngka wawu.",
     "karrkay bajibay-ngka wawu | child bone-DAT want",
     "r133_want_child_bone", 3, js(["karrkay", "bajibay", "wawu"]), js([]),
     "bajibay-ngka dative supported by Patz bone examples and previous dog-wants-bone rows; order follows Patz jiba-ka wawu question.", "0.86"),
    (34, "We want meat without bones.",
     "Ngana wawu minya-ka bajibay-kari-ka.",
     "ngana wawu minya-ka bajibay-kari-ka | we want meat-DAT bone-PRIV-DAT",
     "r133_want_we_boneless_meat", 3, js(["ngana", "minya", "bajibay", "kari", "wawu"]), js([]),
     "minya-ka and bajibay-kari-ka directly parallel prior boneless-meat rows; pronoun subject changed to avoid exact duplicate.", "0.86"),
    (35, "Father wants fish.",
     "Nganjan kuyu-ku wawu.",
     "nganjan kuyu-ku wawu | father fish-DAT want",
     "r133_want_father_fish", 3, js(["nganjan", "kuyu", "wawu"]), js([]),
     "kuyu-ku dative topic in retained wawu frame; kuyu dictionary line 16908 fish.", "0.86"),
    (36, "Mother asked grandmother about liver.",
     "Ngamu babi-nda jiba-ka babajin.",
     "ngamu babi-nda jiba-ka babaji-ny | mother grandmother-LOC:pt liver-DAT ask.about-PAST",
     "r133_ask_mother_grandmother_liver", 4, js(["ngamu", "babi", "jiba", "babajiy"]), js([]),
     "babaji-y ask-about frame: S asker unmarked, person LOC:pt, topic DAT; jiba-ka topic sourced.", "0.86"),
    (37, "The child asked father about a bone.",
     "Karrkay nganjan-anda bajibay-ngka babajin.",
     "karrkay nganjan-anda bajibay-ngka babaji-ny | child father-LOC:pt bone-DAT ask.about-PAST",
     "r133_ask_child_father_bone", 4, js(["karrkay", "nganjan", "bajibay", "babajiy"]), js([]),
     "ask-about frame retained; bajibay-ngka dative topic; no ERG on asker.", "0.86"),
    (38, "The young man asked mother about fish.",
     "Warru ngamu-ndu kuyu-ku babajin.",
     "warru ngamu-ndu kuyu-ku babaji-ny | young.man mother-LOC:pt fish-DAT ask.about-PAST",
     "r133_ask_young_man_mother_fish", 4, js(["warru", "ngamu", "kuyu", "babajiy"]), js([]),
     "Support kuyu-ku topic in established ask-about frame; no tail-dative extrapolation.", "0.86"),
    (39, "Grandmother gave liver to the child.",
     "Babi-ngka jiba dajin karrkay-anda.",
     "babi-ngka jiba daji-ny karrkay-anda | grandmother-ERG liver give-PAST child-LOC:pt",
     "r133_give_grandmother_liver_child", 4, js(["babi", "jiba", "daya", "karrkay"]), js([]),
     "daji-ny transfer frame with human recipient LOC:pt; jiba literal liver object.", "0.84"),
    (40, "Father gave the bone to grandmother.",
     "Nganjan-angka bajibay dajin babi-nda.",
     "nganjan-angka bajibay daji-ny babi-nda | father-ERG bone give-PAST grandmother-LOC:pt",
     "r133_give_father_bone_grandmother", 4, js(["nganjan", "bajibay", "daya", "babi"]), js([]),
     "bajibay bone object in old transfer frame; human recipient LOC:pt.", "0.86"),

    (41, "The old man washed his upper leg.",
     "Mukul-dingkar malbin julurri-jin.",
     "mukul-dingkar malbin julurri-ji-ny | old-man upper.leg wash-REF-PAST",
     "r133_reflexive_wash_old_man_upper_leg", 3, js(["mukul", "dingkar", "malbin", "julurriji"]), js([]),
     "malbin upper leg in reflexive wash frame; S unmarked, no possessive suffix.", "0.84"),
    (42, "The girl washed her upper leg.",
     "Maral malbin julurri-jin.",
     "maral malbin julurri-ji-ny | girl upper.leg wash-REF-PAST",
     "r133_reflexive_wash_girl_upper_leg", 3, js(["maral", "malbin", "julurriji"]), js([]),
     "Second malbin reflexive row changes possessor only; no new case form.", "0.84"),
    (43, "The dog bit the fish tail.",
     "Kaya-ngka kuyu biji baykan.",
     "kaya-ngka kuyu biji bayka-ny | dog-ERG fish tail bite-PAST",
     "r133_affected_dog_bite_fish_tail", 4, js(["kaya", "kuyu", "biji", "baykal"]), js([]),
     "Whole-part affected O parallels Patz ex.520 kuyu biji and Rule 132 affected-body rows.", "0.84"),
    (44, "The crab bit the dog's tail.",
     "Ngalngal-angka kaya biji baykan.",
     "ngalngal-angka kaya biji bayka-ny | crab-ERG dog tail bite-PAST",
     "r133_affected_crab_bite_dog_tail", 4, js(["ngalngal", "kaya", "biji", "baykal"]), js([]),
     "Animal tail whole-part O; crab bite frame retained from Rule 132.", "0.84"),
    (45, "Mother washed the child's body.",
     "Ngamu-ngku karrkay bangkarr julurrin.",
     "ngamu-ngku karrkay bangkarr julurri-ny | mother-ERG child body wash-PAST",
     "r133_wash_mother_child_body", 4, js(["ngamu", "karrkay", "bangkarr", "julurril"]), js([]),
     "bangkarr body in whole-part O; transitive wash frame retained.", "0.86"),
    (46, "We washed the body.",
     "Ngana bangkarr julurrin.",
     "ngana bangkarr julurri-ny | we body wash-PAST",
     "r133_wash_pronoun_body", 4, js(["ngana", "bangkarr", "julurril"]), js([]),
     "Pronoun A bare; bangkarr body literal; no instrument imported.", "0.86"),
    (47, "The bone lay in the bag.",
     "Bajibay balji-nga wunan.",
     "bajibay balji-nga wuna-ny | bone bag-LOC lie-PAST",
     "r133_rest_bone_bag", 3, js(["bajibay", "balji", "wunay"]), js([]),
     "bajibay bone in retained rest-location frame; no metaphorical use.", "0.86"),
    (48, "The liver lay on the ground.",
     "Jiba bubu-ngu wunan.",
     "jiba bubu-ngu wuna-ny | liver ground-LOC lie-PAST",
     "r133_rest_liver_ground", 3, js(["jiba", "bubu", "wunay"]), js([]),
     "jiba literal liver in retained rest-location frame.", "0.84"),
    (49, "The fish tail lay in the shade.",
     "Kuyu biji wabarr-ba wunan.",
     "kuyu biji wabarr-ba wuna-ny | fish tail shade-LOC lie-PAST",
     "r133_rest_fish_tail_shade", 3, js(["kuyu", "biji", "wabarr", "wunay"]), js([]),
     "kuyu biji whole-part subject; rest-location frame retained.", "0.84"),
    (50, "The woman saw grandmother's upper leg.",
     "Jalbu-ngku babi malbin nyajin.",
     "jalbu-ngku babi malbin nyaji-ny | woman-ERG grandmother upper.leg see-PAST",
     "r133_perception_woman_grandmother_upper_leg", 4, js(["jalbu", "babi", "malbin", "nyajil"]), js([]),
     "Final malbin row keeps upper-leg sense and whole-part perception frame; common-noun A ERG.", "0.84"),
]


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


def first_word(low: str) -> str:
    return low.split()[0].strip(".,!?;:\"")


def has_ergative(word: str) -> bool:
    return bool(re.search(r"-(?:ngka|angka|ngku)\b", word))


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()
        english_low = english.lower()
        analysis_low = analysis.lower()
        try:
            words = json.loads(words_used)
            loans = json.loads(loans_used)
        except json.JSONDecodeError as exc:
            issues.append(f"{label}: JSON field failed: {exc}")
            words = []
            loans = []
        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("r133_"):
            issues.append(f"{label}: Rule 133 frame must start with r133_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if loans:
            issues.append(f"{label}: Rule 133 should not introduce loans")
        for denied in DENY:
            if has_base(low, denied):
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        for marker in FORBIDDEN_MARKERS:
            if marker in low:
                issues.append(f"{label}: forbidden construction marker {marker}")
        if "INST" in analysis or "-bu" in analysis_low or re.search(r"\b[a-z]+-bu\b", low):
            issues.append(f"{label}: no instrument rows in Rule 133")
        if "COMIT" in analysis:
            issues.append(f"{label}: no comitative rows in Rule 133")
        if re.search(r"-(?:amu|mu|ndamun|andamun)\b", low):
            issues.append(f"{label}: no alienable possessive suffixes in Rule 133")
        for head, gloss in TARGET_HEADS.items():
            if has_base(low, head) and head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if has_base(low, head):
                if head == "bangkarr":
                    if not any(sense in analysis_low for sense in ("body", "flesh")):
                        issues.append(f"{label}: bangkarr must be glossed body/flesh")
                elif head == "malbin":
                    if "upper.leg" not in analysis_low and "upper leg" not in analysis_low:
                        issues.append(f"{label}: malbin must be glossed upper leg")
                elif gloss not in analysis_low:
                    issues.append(f"{label}: {head} must be glossed with selected sense {gloss!r}")
        for head, gloss in SUPPORT_HEADS.items():
            if has_base(low, head) and head not in words:
                issues.append(f"{label}: support head {head} missing from words_used")
            if has_base(low, head) and gloss not in analysis_low:
                issues.append(f"{label}: support head {head} must be glossed {gloss!r}")
        if has_base(low, "dakil") or "front leg" in english_low or "hind leg" in english_low:
            issues.append(f"{label}: dakil/front-leg/hind-leg disambiguation is excluded from Rule 133")
        if has_base(low, "jiba") and "liver-cry" in analysis_low:
            issues.append(f"{label}: jiba emotional compound excluded; use literal liver only")
        if has_base(low, "bangkarr") and "steak" in analysis_low:
            issues.append(f"{label}: bangkarr steak/flesh-food drift excluded; use body/flesh only")
        if has_base(low, "minya") and "animal" in analysis_low:
            issues.append(f"{label}: minya support rows use meat/flesh-food, not generic animal")
        for dtok in re.findall(r"\b[a-z]+(?:-[a-z]+)+\b", low):
            if dtok.endswith(("-ka", "-ku", "-ngka")) and dtok not in SAFE_DAT_TOPICS and "DAT" in analysis:
                if not dtok.endswith(("-nka", "-anda", "-nda", "-ndu")):
                    issues.append(f"{label}: unapproved dative topic {dtok}")
        if "bajibay-kari" in low and "PRIV" not in analysis:
            issues.append(f"{label}: bajibay-kari must be analysed as PRIV")
        first = first_word(low)
        if any(verb in low for verb in TRANSITIVE_PAST):
            if first not in PRONOUN_A and not has_ergative(first):
                issues.append(f"{label}: common-noun A in transitive past frame should be ERG-marked")
        if "nyajin" in low and first not in PRONOUN_A and not has_ergative(first):
            issues.append(f"{label}: common-noun perceiver with nyaji-l should be ERG-marked")
        if "babajin" in low and has_ergative(first):
            issues.append(f"{label}: babaji-y ask-about frame uses unmarked S asker, not ERG")
        if "julurri-jin" in low and has_ergative(first):
            issues.append(f"{label}: reflexive wash S should not be ERG-marked")
        if "malbin-" in low:
            issues.append(f"{label}: no new malbin case forms in Rule 133")
        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 133 draft: animal/body-flesh lexical expansion after Rule 132", "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 133 source pass checked exact dictionary entries for bajibay, bangkarr, biji, jiba, kuyu, "
            "malbin, minya, and the excluded dakil entry; Patz grammar lines for bone, fish tail, liver-want, "
            "malbin/body-part listing, and minya classifier use; lexeme-table readback; and prior corpus surfaces."
        ),
        command="sed dictionary/grammar snippets; sqlite lexeme and surface readback",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="selected-lexical-set",
        actor="claude-main",
        detail=(
            "Selected target heads: bajibay bone, bangkarr body/flesh, biji tail, jiba liver, malbin upper leg. "
            "Support heads: minya meat/flesh-food and kuyu fish. Dakil was excluded because the live lexeme row "
            "stores branch while Patz also has front-leg/hind-leg narrative senses requiring a separate pass."
        ),
        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 instruments, comitatives, alienable possessive suffixes, temporal clitics, dakil rows, "
            "new malbin case forms, bangkarr steak drift, jiba emotional compounds, or minya generic-animal rows. "
            "Common-noun transitive A must be ERG; pronoun A bare; babaji-y askers unmarked."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="existing-surface-scan",
        actor="claude-main",
        detail="Final planned-surface scan found 50 planned rows, 50 unique within-batch surfaces, and zero exact cross-corpus duplicates.",
        command="sqlite exact-surface query over planned Batch 175 rows",
        output_excerpt="planned 50 unique 50; no duplicate lines emitted",
        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 175 stores dictionary/grammar source readback, selected/excluded senses, static guards, duplicate "
            "scan, bank outcome, and later lint/review/close/export/docs evidence 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-written Rule 133 animal/body-flesh lexical-expansion rows as under_review after static and exact duplicate checks.",
        command="python3 batch_scripts/bank_175.py",
        output_excerpt="banked batch-175: 50 rows under_review; duplicate scan clean",
        artifact_path="batch_scripts/bank_175.py",
        created_at=NOW,
    )
    db.commit()
    count = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=?", (batch_id,)).fetchone()[0]
    under_review = db.execute("SELECT COUNT(*) FROM sentences WHERE batch_id=? AND status='under_review'", (batch_id,)).fetchone()[0]
    verified = db.execute("SELECT COUNT(*) FROM sentences WHERE status IN ('accepted','revised')").fetchone()[0]
    print(f"banked {LABEL}: rows={count}, under_review={under_review}, verified_before_close={verified}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
