#!/usr/bin/env python3
"""Bank batch-179: Rule 137 handled-object lexical expansion."""

from __future__ import annotations

import datetime as dt
import json
import re
import sqlite3

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

TARGET_HEADS = {
    "badur": "hook.line",
    "karra": "rope",
    "makarr": "fish.net",
    "ngunyin": "dilly.bag",
    "wangal": "boomerang",
    "warrbi": "axe",
    "kunjuri": "shield",
    "kalka": "spear",
}
STATE_HEADS = {
    "ngulkurr": "good",
    "marral": "dry",
    "yalbay": "big",
}
SUPPORT_HEADS = {
    "balji": "bag",
    "bubu": "ground",
    "wabarr": "shade",
    "bayan": "camp",
}
APPROVED_LOCAL_FORMS = {"balji-nga", "bubu-ngu", "wabarr-ba", "bayan-ba", "bubu-muny"}
DENY = {
    "dakil",
    "waba",
    "jandal",
    "jibarr",
    "malbin",
    "jiba",
    "bangkarr",
    "minya",
    "jungkalu",
    "juku",
    "kulji",
    "kambi",
    "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", "yijarrin", "bawan", "manin")


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


ROWS = [
    (1, "The hook and line is good.",
     "Badur ngulkurr.",
     "badur ngulkurr | hook.line good",
     "r137_state_hook_line_good", 2, js(["badur", "ngulkurr"]), js([]),
     "Dictionary badur line 256 gives hook and line/fishing line; ngulkurr is an already verified good predicate.", "0.86"),
    (2, "The rope is dry.",
     "Karra marral.",
     "karra marral | rope dry",
     "r137_state_rope_dry", 2, js(["karra", "marral"]), js([]),
     "Dictionary karra line 13534 gives rope/cord/string; marral dry is already verified.", "0.86"),
    (3, "The fish net is good.",
     "Makarr ngulkurr.",
     "makarr ngulkurr | fish.net good",
     "r137_state_fish_net_good", 2, js(["makarr", "ngulkurr"]), js([]),
     "Dictionary makarr line 17173 gives fish net; ngulkurr good is already verified.", "0.86"),
    (4, "The dilly bag is dry.",
     "Ngunyin marral.",
     "ngunyin marral | dilly.bag dry",
     "r137_state_dilly_bag_dry", 2, js(["ngunyin", "marral"]), js([]),
     "Dictionary ngunyin line 23741 includes dilly bag; this row selects the dilly-bag sense only.", "0.84"),
    (5, "The boomerang is big.",
     "Wangal yalbay.",
     "wangal yalbay | boomerang big",
     "r137_state_boomerang_big", 2, js(["wangal", "yalbay"]), js([]),
     "Dictionary wangal line 25943 gives boomerang; yalbay big is already verified.", "0.86"),
    (6, "The axe is good.",
     "Warrbi ngulkurr.",
     "warrbi ngulkurr | axe good",
     "r137_state_axe_good", 2, js(["warrbi", "ngulkurr"]), js([]),
     "Dictionary warrbi line 26610 gives axe; this row selects axe only.", "0.84"),
    (7, "The shield is dry.",
     "Kunjuri marral.",
     "kunjuri marral | shield dry",
     "r137_state_shield_dry", 2, js(["kunjuri", "marral"]), js([]),
     "Dictionary kunjuri line 15915 gives shield; marral dry is already verified.", "0.86"),
    (8, "The spear is big.",
     "Kalka yalbay.",
     "kalka yalbay | spear big",
     "r137_state_spear_big", 2, js(["kalka", "yalbay"]), js([]),
     "Dictionary kalka line 12579 gives fishing spear and generic spear; yalbay big is already verified.", "0.86"),

    (9, "The hook and line lay in the shade.",
     "Badur wabarr-ba wunan.",
     "badur wabarr-ba wuna-ny | hook.line shade-LOC lie-PAST",
     "r137_rest_hook_line_shade", 3, js(["badur", "wabarr", "wunay"]), js([]),
     "Patz optional locative NP rule plus wuna-y lie; wabarr-ba shade LOC is already source-checked.", "0.84"),
    (10, "The rope lay at camp.",
     "Karra bayan-ba wunan.",
     "karra bayan-ba wuna-ny | rope camp-LOC lie-PAST",
     "r137_rest_rope_camp", 3, js(["karra", "bayan", "wunay"]), js([]),
     "Patz wuna-y lie and optional locative NP rule; bayan-ba camp/house LOC is already source-checked.", "0.84"),
    (11, "The fish net lay on the ground.",
     "Makarr bubu-ngu wunan.",
     "makarr bubu-ngu wuna-ny | fish.net ground-LOC lie-PAST",
     "r137_rest_fish_net_ground", 3, js(["makarr", "bubu", "wunay"]), js([]),
     "Patz optional locative NP rule supports bubu-ngu ground LOC in rest clauses.", "0.84"),
    (12, "The dilly bag lay on the ground.",
     "Ngunyin bubu-ngu wunan.",
     "ngunyin bubu-ngu wuna-ny | dilly.bag ground-LOC lie-PAST",
     "r137_rest_dilly_bag_ground", 3, js(["ngunyin", "bubu", "wunay"]), js([]),
     "Ngunyin selected as dilly bag only; wuna-y and bubu-ngu are controlled support.", "0.84"),
    (13, "The boomerang lay in the bag.",
     "Wangal balji-nga wunan.",
     "wangal balji-nga wuna-ny | boomerang bag-LOC lie-PAST",
     "r137_rest_boomerang_bag", 3, js(["wangal", "balji", "wunay"]), js([]),
     "Patz ex.579 sources balji-nga bag LOC; wangal is boomerang noun only.", "0.84"),
    (14, "The axe lay in the shade.",
     "Warrbi wabarr-ba wunan.",
     "warrbi wabarr-ba wuna-ny | axe shade-LOC lie-PAST",
     "r137_rest_axe_shade", 3, js(["warrbi", "wabarr", "wunay"]), js([]),
     "Warrbi selected as axe only; wabarr-ba shade LOC and wuna-y are controlled support.", "0.84"),
    (15, "The shield lay in the bag.",
     "Kunjuri balji-nga wunan.",
     "kunjuri balji-nga wuna-ny | shield bag-LOC lie-PAST",
     "r137_rest_shield_bag", 3, js(["kunjuri", "balji", "wunay"]), js([]),
     "Patz ex.579 sources balji-nga bag LOC; kunjuri shield remains bare.", "0.84"),
    (16, "The spear lay in the shade.",
     "Kalka wabarr-ba wunan.",
     "kalka wabarr-ba wuna-ny | spear shade-LOC lie-PAST",
     "r137_rest_spear_shade", 3, js(["kalka", "wabarr", "wunay"]), js([]),
     "Kalka spear in a rest-location frame; no comitative or instrument morphology.", "0.84"),

    (17, "Mother saw the hook and line in the bag.",
     "Ngamu-ngku badur balji-nga nyajin.",
     "ngamu-ngku badur balji-nga nyaji-ny | mother-ERG hook.line bag-LOC see-PAST",
     "r137_perception_mother_hook_line_bag", 4, js(["ngamu", "badur", "balji", "nyajil"]), js([]),
     "Common-noun A ERG; Patz nyaji-ny see/hear plus balji-nga bag LOC.", "0.84"),
    (18, "Father saw the rope on the ground.",
     "Nganjan-angka karra bubu-ngu nyajin.",
     "nganjan-angka karra bubu-ngu nyaji-ny | father-ERG rope ground-LOC see-PAST",
     "r137_perception_father_rope_ground", 4, js(["nganjan", "karra", "bubu", "nyajil"]), js([]),
     "Common-noun A ERG; karra rope object plus bubu-ngu ground LOC.", "0.84"),
    (19, "Grandmother saw the fish net in the shade.",
     "Babi-ngka makarr wabarr-ba nyajin.",
     "babi-ngka makarr wabarr-ba nyaji-ny | grandmother-ERG fish.net shade-LOC see-PAST",
     "r137_perception_grandmother_fish_net_shade", 4, js(["babi", "makarr", "wabarr", "nyajil"]), js([]),
     "Common-noun A ERG; makarr fish net and wabarr-ba shade LOC.", "0.84"),
    (20, "The child saw the dilly bag on the ground.",
     "Karrkay-ngku ngunyin bubu-ngu nyajin.",
     "karrkay-ngku ngunyin bubu-ngu nyaji-ny | child-ERG dilly.bag ground-LOC see-PAST",
     "r137_perception_child_dilly_bag_ground", 4, js(["karrkay", "ngunyin", "bubu", "nyajil"]), js([]),
     "Common-noun A ERG; ngunyin selected as dilly bag only.", "0.84"),
    (21, "I saw the boomerang in the shade.",
     "Ngayu wangal wabarr-ba nyajin.",
     "ngayu wangal wabarr-ba nyaji-ny | I boomerang shade-LOC see-PAST",
     "r137_perception_pronoun_boomerang_shade", 4, js(["ngayu", "wangal", "wabarr", "nyajil"]), js([]),
     "Pronoun A bare; wangal boomerang noun only.", "0.84"),
    (22, "Grandfather saw the axe on the ground.",
     "Kami-ngka warrbi bubu-ngu nyajin.",
     "kami-ngka warrbi bubu-ngu nyaji-ny | grandfather-ERG axe ground-LOC see-PAST",
     "r137_perception_grandfather_axe_ground", 4, js(["kami", "warrbi", "bubu", "nyajil"]), js([]),
     "Common-noun A ERG; warrbi selected as axe only.", "0.84"),
    (23, "The woman saw the shield in the shade.",
     "Jalbu-ngku kunjuri wabarr-ba nyajin.",
     "jalbu-ngku kunjuri wabarr-ba nyaji-ny | woman-ERG shield shade-LOC see-PAST",
     "r137_perception_woman_shield_shade", 4, js(["jalbu", "kunjuri", "wabarr", "nyajil"]), js([]),
     "Common-noun A ERG; kunjuri shield object remains bare.", "0.84"),
    (24, "The man saw the spear on the ground.",
     "Dingkar-angka kalka bubu-ngu nyajin.",
     "dingkar-angka kalka bubu-ngu nyaji-ny | man-ERG spear ground-LOC see-PAST",
     "r137_perception_man_spear_ground", 4, js(["dingkar", "kalka", "bubu", "nyajil"]), js([]),
     "Common-noun A ERG; kalka spear object plus bubu-ngu ground LOC.", "0.84"),
    (25, "They saw the hook and line in the shade.",
     "Jana badur wabarr-ba nyajin.",
     "jana badur wabarr-ba nyaji-ny | they hook.line shade-LOC see-PAST",
     "r137_perception_they_hook_line_shade", 4, js(["jana", "badur", "wabarr", "nyajil"]), js([]),
     "Pronoun A bare; badur hook-and-line sense selected.", "0.84"),
    (26, "We saw the fish net on the ground.",
     "Ngana makarr bubu-ngu nyajin.",
     "ngana makarr bubu-ngu nyaji-ny | we fish.net ground-LOC see-PAST",
     "r137_perception_we_fish_net_ground", 4, js(["ngana", "makarr", "bubu", "nyajil"]), js([]),
     "Pronoun A bare; makarr fish net plus bubu-ngu ground LOC.", "0.84"),

    (27, "Mother put the hook and line in the bag.",
     "Ngamu-ngku badur balji-nga yijarrin.",
     "ngamu-ngku badur balji-nga yijarri-ny | mother-ERG hook.line bag-LOC put-PAST",
     "r137_place_mother_hook_line_bag", 4, js(["ngamu", "badur", "balji", "yijarril"]), js([]),
     "Yijarri-l put/place plus Patz ex.579 balji-nga bag LOC; common-noun A ERG.", "0.84"),
    (28, "Father put the rope on the ground.",
     "Nganjan-angka karra bubu-ngu yijarrin.",
     "nganjan-angka karra bubu-ngu yijarri-ny | father-ERG rope ground-LOC put-PAST",
     "r137_place_father_rope_ground", 4, js(["nganjan", "karra", "bubu", "yijarril"]), js([]),
     "Yijarri-l put/place with karra rope object and bubu-ngu ground LOC.", "0.84"),
    (29, "Grandmother put the fish net in the shade.",
     "Babi-ngka makarr wabarr-ba yijarrin.",
     "babi-ngka makarr wabarr-ba yijarri-ny | grandmother-ERG fish.net shade-LOC put-PAST",
     "r137_place_grandmother_fish_net_shade", 4, js(["babi", "makarr", "wabarr", "yijarril"]), js([]),
     "Yijarri-l put/place with makarr fish net object and shade LOC.", "0.84"),
    (30, "The child put the dilly bag on the ground.",
     "Karrkay-ngku ngunyin bubu-ngu yijarrin.",
     "karrkay-ngku ngunyin bubu-ngu yijarri-ny | child-ERG dilly.bag ground-LOC put-PAST",
     "r137_place_child_dilly_bag_ground", 4, js(["karrkay", "ngunyin", "bubu", "yijarril"]), js([]),
     "Ngunyin selected as dilly bag only; yijarri-l and bubu-ngu are controlled support.", "0.84"),
    (31, "I put the boomerang in the shade.",
     "Ngayu wangal wabarr-ba yijarrin.",
     "ngayu wangal wabarr-ba yijarri-ny | I boomerang shade-LOC put-PAST",
     "r137_place_pronoun_boomerang_shade", 4, js(["ngayu", "wangal", "wabarr", "yijarril"]), js([]),
     "Pronoun A bare; wangal boomerang noun only.", "0.84"),
    (32, "Grandfather put the axe on the ground.",
     "Kami-ngka warrbi bubu-ngu yijarrin.",
     "kami-ngka warrbi bubu-ngu yijarri-ny | grandfather-ERG axe ground-LOC put-PAST",
     "r137_place_grandfather_axe_ground", 4, js(["kami", "warrbi", "bubu", "yijarril"]), js([]),
     "Common-noun A ERG; warrbi axe object with no instrument morphology.", "0.84"),
    (33, "The woman put the shield in the shade.",
     "Jalbu-ngku kunjuri wabarr-ba yijarrin.",
     "jalbu-ngku kunjuri wabarr-ba yijarri-ny | woman-ERG shield shade-LOC put-PAST",
     "r137_place_woman_shield_shade", 4, js(["jalbu", "kunjuri", "wabarr", "yijarril"]), js([]),
     "Common-noun A ERG; kunjuri shield object remains bare.", "0.84"),
    (34, "The man put the spear on the ground.",
     "Dingkar-angka kalka bubu-ngu yijarrin.",
     "dingkar-angka kalka bubu-ngu yijarri-ny | man-ERG spear ground-LOC put-PAST",
     "r137_place_man_spear_ground", 4, js(["dingkar", "kalka", "bubu", "yijarril"]), js([]),
     "Common-noun A ERG; kalka spear object plus bubu-ngu ground LOC.", "0.84"),
    (35, "They put the hook and line in the shade.",
     "Jana badur wabarr-ba yijarrin.",
     "jana badur wabarr-ba yijarri-ny | they hook.line shade-LOC put-PAST",
     "r137_place_they_hook_line_shade", 4, js(["jana", "badur", "wabarr", "yijarril"]), js([]),
     "Pronoun A bare; badur hook-and-line sense selected.", "0.84"),
    (36, "We put the fish net on the ground.",
     "Ngana makarr bubu-ngu yijarrin.",
     "ngana makarr bubu-ngu yijarri-ny | we fish.net ground-LOC put-PAST",
     "r137_place_we_fish_net_ground", 4, js(["ngana", "makarr", "bubu", "yijarril"]), js([]),
     "Pronoun A bare; makarr fish net plus bubu-ngu ground LOC.", "0.84"),

    (37, "Mother left the hook and line in the shade.",
     "Ngamu-ngku badur wabarr-ba bawan.",
     "ngamu-ngku badur wabarr-ba bawa-ny | mother-ERG hook.line shade-LOC leave-PAST",
     "r137_leave_mother_hook_line_shade", 4, js(["ngamu", "badur", "wabarr", "bawal"]), js([]),
     "Bawa-l leave with object plus location; common-noun A ERG.", "0.84"),
    (38, "Father left the rope on the ground.",
     "Nganjan-angka karra bubu-ngu bawan.",
     "nganjan-angka karra bubu-ngu bawa-ny | father-ERG rope ground-LOC leave-PAST",
     "r137_leave_father_rope_ground", 4, js(["nganjan", "karra", "bubu", "bawal"]), js([]),
     "Bawa-l leave with karra rope and bubu-ngu ground LOC.", "0.84"),
    (39, "Grandmother left the fish net in the bag.",
     "Babi-ngka makarr balji-nga bawan.",
     "babi-ngka makarr balji-nga bawa-ny | grandmother-ERG fish.net bag-LOC leave-PAST",
     "r137_leave_grandmother_fish_net_bag", 4, js(["babi", "makarr", "balji", "bawal"]), js([]),
     "Bawa-l leave with makarr fish net and Patz-sourced balji-nga bag LOC.", "0.84"),
    (40, "The child left the dilly bag in the shade.",
     "Karrkay-ngku ngunyin wabarr-ba bawan.",
     "karrkay-ngku ngunyin wabarr-ba bawa-ny | child-ERG dilly.bag shade-LOC leave-PAST",
     "r137_leave_child_dilly_bag_shade", 4, js(["karrkay", "ngunyin", "wabarr", "bawal"]), js([]),
     "Ngunyin selected as dilly bag only; common-noun A ERG.", "0.84"),
    (41, "I left the boomerang on the ground.",
     "Ngayu wangal bubu-ngu bawan.",
     "ngayu wangal bubu-ngu bawa-ny | I boomerang ground-LOC leave-PAST",
     "r137_leave_pronoun_boomerang_ground", 4, js(["ngayu", "wangal", "bubu", "bawal"]), js([]),
     "Pronoun A bare; wangal boomerang noun only.", "0.84"),
    (42, "Grandfather left the axe in the shade.",
     "Kami-ngka warrbi wabarr-ba bawan.",
     "kami-ngka warrbi wabarr-ba bawa-ny | grandfather-ERG axe shade-LOC leave-PAST",
     "r137_leave_grandfather_axe_shade", 4, js(["kami", "warrbi", "wabarr", "bawal"]), js([]),
     "Common-noun A ERG; warrbi axe object with no tool-as-instrument reading.", "0.84"),
    (43, "The woman left the shield on the ground.",
     "Jalbu-ngku kunjuri bubu-ngu bawan.",
     "jalbu-ngku kunjuri bubu-ngu bawa-ny | woman-ERG shield ground-LOC leave-PAST",
     "r137_leave_woman_shield_ground", 4, js(["jalbu", "kunjuri", "bubu", "bawal"]), js([]),
     "Common-noun A ERG; kunjuri shield remains a bare object.", "0.84"),
    (44, "The man left the spear in the shade.",
     "Dingkar-angka kalka wabarr-ba bawan.",
     "dingkar-angka kalka wabarr-ba bawa-ny | man-ERG spear shade-LOC leave-PAST",
     "r137_leave_man_spear_shade", 4, js(["dingkar", "kalka", "wabarr", "bawal"]), js([]),
     "Common-noun A ERG; kalka spear object with no comitative/instrument morphology.", "0.84"),

    (45, "Mother took the hook and line from the ground.",
     "Ngamu-ngku badur bubu-muny manin.",
     "ngamu-ngku badur bubu-muny mani-ny | mother-ERG hook.line ground-ABL get-PAST",
     "r137_get_mother_hook_line_ground", 4, js(["ngamu", "badur", "bubu", "manil"]), js([]),
     "Mani-l get/take plus bubu-muny ground ABL; common-noun A ERG.", "0.84"),
    (46, "Father took the rope from the ground.",
     "Nganjan-angka karra bubu-muny manin.",
     "nganjan-angka karra bubu-muny mani-ny | father-ERG rope ground-ABL get-PAST",
     "r137_get_father_rope_ground", 4, js(["nganjan", "karra", "bubu", "manil"]), js([]),
     "Mani-l get/take with karra rope and bubu-muny source.", "0.84"),
    (47, "Grandmother took the fish net from the ground.",
     "Babi-ngka makarr bubu-muny manin.",
     "babi-ngka makarr bubu-muny mani-ny | grandmother-ERG fish.net ground-ABL get-PAST",
     "r137_get_grandmother_fish_net_ground", 4, js(["babi", "makarr", "bubu", "manil"]), js([]),
     "Mani-l get/take with makarr fish net and bubu-muny source.", "0.84"),
    (48, "The child took the dilly bag from the ground.",
     "Karrkay-ngku ngunyin bubu-muny manin.",
     "karrkay-ngku ngunyin bubu-muny mani-ny | child-ERG dilly.bag ground-ABL get-PAST",
     "r137_get_child_dilly_bag_ground", 4, js(["karrkay", "ngunyin", "bubu", "manil"]), js([]),
     "Ngunyin selected as dilly bag only; bubu-muny source is controlled support.", "0.84"),
    (49, "The woman took the axe from the ground.",
     "Jalbu-ngku warrbi bubu-muny manin.",
     "jalbu-ngku warrbi bubu-muny mani-ny | woman-ERG axe ground-ABL get-PAST",
     "r137_get_woman_axe_ground", 4, js(["jalbu", "warrbi", "bubu", "manil"]), js([]),
     "Warrbi selected as axe only; common-noun A ERG.", "0.84"),
    (50, "The man took the shield from the ground.",
     "Dingkar-angka kunjuri bubu-muny manin.",
     "dingkar-angka kunjuri bubu-muny mani-ny | man-ERG shield ground-ABL get-PAST",
     "r137_get_man_shield_ground", 4, js(["dingkar", "kunjuri", "bubu", "manil"]), js([]),
     "Kunjuri shield object with mani-l get/take and bubu-muny source.", "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("r137_"):
            issues.append(f"{label}: Rule 137 frame must start with r137_")
        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 137 handled-object batch 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 137")
        if "COMIT" in analysis:
            issues.append(f"{label}: no comitative rows in Rule 137")
        if re.search(r"-(?:amu|mu|ndamun|andamun)\b", low):
            issues.append(f"{label}: no alienable possessive suffixes in Rule 137")
        for form in re.findall(r"\b[a-z]+-(?:nga|ngu|ba|muny)\b", low):
            if form not in APPROVED_LOCAL_FORMS:
                issues.append(f"{label}: unapproved local-case support form {form!r}")
        if not any(has_base(low, head) for head in TARGET_HEADS):
            issues.append(f"{label}: Rule 137 row must exercise a selected handled-object target")
        for head, gloss in TARGET_HEADS.items():
            if has_base(low, head):
                if head not in words:
                    issues.append(f"{label}: target head {head} missing from words_used")
                if gloss not in analysis_low:
                    issues.append(f"{label}: {head} must be glossed with selected sense {gloss!r}")
            if f"{head}-" in low:
                issues.append(f"{label}: no target-head case form or compound for {head}")
        for head, gloss in STATE_HEADS.items():
            if has_base(low, head):
                if head not in words:
                    issues.append(f"{label}: state head {head} missing from words_used")
                if gloss not in analysis_low:
                    issues.append(f"{label}: state head {head} should be glossed as {gloss!r}")
        for head in SUPPORT_HEADS:
            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, "warrbi") and ("house" in english_low or "house" in analysis_low):
            issues.append(f"{label}: warrbi must stay axe only, not grass-house")
        if has_base(low, "ngunyin") and any(bad in english_low or bad in analysis_low for bad in ("bark", "spider", "placenta")):
            issues.append(f"{label}: ngunyin must stay dilly-bag only")
        if has_base(low, "wangal") and ("dance" in english_low or "dance" in analysis_low):
            issues.append(f"{label}: wangal must stay boomerang noun only")
        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 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 137 draft: handled-object lexical expansion after Rule 136", "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 137 source pass checked dictionary entries for badur hook/line, karra rope, makarr fish net, "
            "ngunyin dilly bag, wangal boomerang, warrbi axe, kunjuri shield, and kalka spear; Patz grammar "
            "anchors for wuna-y lie/rest, optional locative NPs, balji-nga bag LOC, nyaji-l see/perceive, "
            "yijarri-l put/place, bawa-l leave, mani-l get/take, bubu-ngu ground LOC, bubu-muny ground ABL, "
            "wabarr-ba shade LOC, and bayan-ba camp/house LOC."
        ),
        command="rg/sed dictionary.yaml and grammar.md; sqlite lexeme and exact-surface readback",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="lexical-expansion-scope",
        actor="claude-main",
        detail=(
            "Rule 137 admits handled-object targets only: badur, karra, makarr, ngunyin, wangal, warrbi, "
            "kunjuri, and kalka. Balji is support only, so balji-nga can serve as a sourced container LOC."
        ),
        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 body-part traps waba/jandal/dakil/jibarr; no Rule 133/134 animal-body heads; no jungkalu, "
            "juku, kulji, or kambi drift; no target-head case forms; no instruments, comitatives, possessives, "
            "temporal clitics, or unsourced analogies; common-noun A ERG and pronoun A bare."
        ),
        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 after replacing a duplicate rope-in-bag rest row.",
        command="sqlite exact-surface query over planned Batch 179 rows",
        output_excerpt="initial duplicate: Karra balji-nga wunan = batch-149.26; revised Karra bayan-ba wunan; final duplicate query emitted no 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 179 stores source readback, scope decisions, static guards, the duplicate-scan repair, 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 137 handled-object lexical-expansion rows as under_review after static and exact duplicate checks.",
        command="python3 batch_scripts/bank_179.py",
        output_excerpt="banked batch-179: 50 rows under_review; duplicate scan clean",
        artifact_path="batch_scripts/bank_179.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())
