#!/usr/bin/env python3
"""Bank batch-178: Rule 136 construction consolidation after Rule 135."""

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-178"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-178-construction-consolidation-after-rule135-process.md"

TARGET_HEADS = {
    "damu": "chest",
    "jibar": "stomach",
    "jakay": "neck",
    "binda": "shoulder",
    "wakuy": "arm",
    "wulu": "ankle",
    "wurra": "navel",
}
STATE_HEADS = {
    "kaka": "sore",
    "marral": "dry",
    "ngulkurr": "good",
    "bajaybajay": "painful",
    "junkurr": "strength",
    "yalbay": "big",
    "bajaku": "very",
}
DENY = {
    "dakil",
    "waba",
    "jandal",
    "jibarr",
    "malbin",
    "jiba",
    "bangkarr",
    "minya",
    "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", "baykan")


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


ROWS = [
    (1, "Grandfather's chest is painful.",
     "Kami damu bajaybajay.",
     "kami damu bajaybajay | grandfather chest painful",
     "r136_state_grandfather_chest_painful", 3, js(["kami", "damu", "bajaybajay"]), js([]),
     "Rule 136 consolidation: dictionary damu chest and bajaybajay painful in old appositional body-state frame.", "0.86"),
    (2, "Mother's stomach is good.",
     "Ngamu jibar ngulkurr.",
     "ngamu jibar ngulkurr | mother stomach good",
     "r136_state_mother_stomach_good", 3, js(["ngamu", "jibar", "ngulkurr"]), js([]),
     "Jibar stomach only, not locational jibarr; ngulkurr good.", "0.86"),
    (3, "Father's neck is dry.",
     "Nganjan jakay marral.",
     "nganjan jakay marral | father neck dry",
     "r136_state_father_neck_dry", 3, js(["nganjan", "jakay", "marral"]), js([]),
     "Jakay neck/back-of-neck; marral dry.", "0.84"),
    (4, "Grandmother's shoulder is good.",
     "Babi binda ngulkurr.",
     "babi binda ngulkurr | grandmother shoulder good",
     "r136_state_grandmother_shoulder_good", 3, js(["babi", "binda", "ngulkurr"]), js([]),
     "Binda shoulder only; fork senses excluded.", "0.86"),
    (5, "The woman's arm is dry.",
     "Jalbu wakuy marral.",
     "jalbu wakuy marral | woman arm dry",
     "r136_state_woman_arm_dry", 3, js(["jalbu", "wakuy", "marral"]), js([]),
     "Wakuy arm; no wakuy case form.", "0.84"),
    (6, "Grandfather's ankle is sore.",
     "Kami wulu kaka.",
     "kami wulu kaka | grandfather ankle sore",
     "r136_state_grandfather_ankle_sore", 3, js(["kami", "wulu", "kaka"]), js([]),
     "Wulu ankle wording; kaka bodily soreness.", "0.86"),
    (7, "The baby's navel is sore.",
     "Ngawa wurra kaka.",
     "ngawa wurra kaka | baby navel sore",
     "r136_state_baby_navel_sore", 3, js(["ngawa", "wurra", "kaka"]), js([]),
     "Wurra navel wording; no umbilical-cord scene.", "0.86"),
    (8, "The child's chest is very sore.",
     "Karrkay damu kaka bajaku.",
     "karrkay damu kaka bajaku | child chest sore very",
     "r136_state_child_chest_very_sore", 3, js(["karrkay", "damu", "kaka", "bajaku"]), js([]),
     "Damu chest; bajaku retained as very/degree particle after predicate.", "0.84"),
    (9, "The young man's stomach is painful.",
     "Warru jibar bajaybajay.",
     "warru jibar bajaybajay | young.man stomach painful",
     "r136_state_young_man_stomach_painful", 3, js(["warru", "jibar", "bajaybajay"]), js([]),
     "Jibar stomach; bajaybajay painful.", "0.86"),
    (10, "Mother's neck is good.",
     "Ngamu jakay ngulkurr.",
     "ngamu jakay ngulkurr | mother neck good",
     "r136_state_mother_neck_good", 3, js(["ngamu", "jakay", "ngulkurr"]), js([]),
     "Jakay neck; ngulkurr good.", "0.86"),
    (11, "The man's shoulder is strong.",
     "Dingkar binda junkurr.",
     "dingkar binda junkurr | man shoulder strength",
     "r136_state_man_shoulder_strong", 3, js(["dingkar", "binda", "junkurr"]), js([]),
     "Binda shoulder; junkurr strength/strong follows accepted body-state precedent.", "0.84"),
    (12, "The woman's arm is strong.",
     "Jalbu wakuy junkurr.",
     "jalbu wakuy junkurr | woman arm strength",
     "r136_state_woman_arm_strong", 3, js(["jalbu", "wakuy", "junkurr"]), js([]),
     "Wakuy arm; junkurr strength/strong in body-state frame.", "0.84"),
    (13, "Grandmother's ankle is dry.",
     "Babi wulu marral.",
     "babi wulu marral | grandmother ankle dry",
     "r136_state_grandmother_ankle_dry", 3, js(["babi", "wulu", "marral"]), js([]),
     "Wulu ankle; marral dry.", "0.84"),
    (14, "The child's navel is good.",
     "Karrkay wurra ngulkurr.",
     "karrkay wurra ngulkurr | child navel good",
     "r136_state_child_navel_good", 3, js(["karrkay", "wurra", "ngulkurr"]), js([]),
     "Wurra navel selected; ngulkurr good.", "0.86"),
    (15, "Father's chest is big.",
     "Nganjan damu yalbay.",
     "nganjan damu yalbay | father chest big",
     "r136_state_father_chest_big", 3, js(["nganjan", "damu", "yalbay"]), js([]),
     "Patz apposition permits modifier after body part; damu chest, yalbay big.", "0.84"),
    (16, "The old man's shoulder is sore.",
     "Mukul-dingkar binda kaka.",
     "mukul-dingkar binda kaka | old-man shoulder sore",
     "r136_state_old_man_shoulder_sore", 3, js(["mukul", "dingkar", "binda", "kaka"]), js([]),
     "Binda shoulder selected; kaka sore.", "0.86"),
    (17, "The baby's stomach is good.",
     "Ngawa jibar ngulkurr.",
     "ngawa jibar ngulkurr | baby stomach good",
     "r136_state_baby_stomach_good", 3, js(["ngawa", "jibar", "ngulkurr"]), js([]),
     "Jibar stomach only; no locational jibarr.", "0.86"),
    (18, "The young man's neck is dry.",
     "Warru jakay marral.",
     "warru jakay marral | young.man neck dry",
     "r136_state_young_man_neck_dry", 3, js(["warru", "jakay", "marral"]), js([]),
     "Jakay neck; marral dry.", "0.84"),
    (19, "Mother's ankle is good.",
     "Ngamu wulu ngulkurr.",
     "ngamu wulu ngulkurr | mother ankle good",
     "r136_state_mother_ankle_good", 3, js(["ngamu", "wulu", "ngulkurr"]), js([]),
     "Wulu ankle; ngulkurr good.", "0.86"),
    (20, "The woman's navel is dry.",
     "Jalbu wurra marral.",
     "jalbu wurra marral | woman navel dry",
     "r136_state_woman_navel_dry", 3, js(["jalbu", "wurra", "marral"]), js([]),
     "Wurra navel; marral dry; no umbilical-cord expansion.", "0.84"),

    (21, "Grandmother saw father's chest.",
     "Babi-ngka nganjan damu nyajin.",
     "babi-ngka nganjan damu nyaji-ny | grandmother-ERG father chest see-PAST",
     "r136_perception_grandmother_father_chest", 4, js(["babi", "nganjan", "damu", "nyajil"]), js([]),
     "Common-noun A ERG; whole-part object with damu chest.", "0.86"),
    (22, "Mother saw grandmother's stomach.",
     "Ngamu-ngku babi jibar nyajin.",
     "ngamu-ngku babi jibar nyaji-ny | mother-ERG grandmother stomach see-PAST",
     "r136_perception_mother_grandmother_stomach", 4, js(["ngamu", "babi", "jibar", "nyajil"]), js([]),
     "Jibar stomach only; common-noun A ERG.", "0.86"),
    (23, "I saw the young man's neck.",
     "Ngayu warru jakay nyajin.",
     "ngayu warru jakay nyaji-ny | I young.man neck see-PAST",
     "r136_perception_pronoun_young_man_neck", 4, js(["ngayu", "warru", "jakay", "nyajil"]), js([]),
     "Pronoun A bare; jakay neck.", "0.86"),
    (24, "Father saw the woman's shoulder.",
     "Nganjan-angka jalbu binda nyajin.",
     "nganjan-angka jalbu binda nyaji-ny | father-ERG woman shoulder see-PAST",
     "r136_perception_father_woman_shoulder", 4, js(["nganjan", "jalbu", "binda", "nyajil"]), js([]),
     "Binda shoulder only; common-noun A ERG.", "0.86"),
    (25, "They saw grandfather's arm.",
     "Jana kami wakuy nyajin.",
     "jana kami wakuy nyaji-ny | they grandfather arm see-PAST",
     "r136_perception_pronoun_grandfather_arm", 4, js(["jana", "kami", "wakuy", "nyajil"]), js([]),
     "Pronoun A bare; wakuy arm.", "0.86"),
    (26, "The woman saw the baby's ankle.",
     "Jalbu-ngku ngawa wulu nyajin.",
     "jalbu-ngku ngawa wulu nyaji-ny | woman-ERG baby ankle see-PAST",
     "r136_perception_woman_baby_ankle", 4, js(["jalbu", "ngawa", "wulu", "nyajil"]), js([]),
     "Wulu ankle; common-noun A ERG.", "0.86"),
    (27, "The child saw mother's navel.",
     "Karrkay-ngku ngamu wurra nyajin.",
     "karrkay-ngku ngamu wurra nyaji-ny | child-ERG mother navel see-PAST",
     "r136_perception_child_mother_navel", 4, js(["karrkay", "ngamu", "wurra", "nyajil"]), js([]),
     "Wurra navel; common-noun A ERG.", "0.86"),
    (28, "We saw the man's chest.",
     "Ngana dingkar damu nyajin.",
     "ngana dingkar damu nyaji-ny | we man chest see-PAST",
     "r136_perception_pronoun_man_chest", 4, js(["ngana", "dingkar", "damu", "nyajil"]), js([]),
     "Pronoun A bare; damu chest.", "0.86"),
    (29, "The man saw grandmother's shoulder.",
     "Dingkar-angka babi binda nyajin.",
     "dingkar-angka babi binda nyaji-ny | man-ERG grandmother shoulder see-PAST",
     "r136_perception_man_grandmother_shoulder", 4, js(["dingkar", "babi", "binda", "nyajil"]), js([]),
     "Binda shoulder selected; common-noun A ERG.", "0.86"),
    (30, "Grandfather saw mother's ankle.",
     "Kami-ngka ngamu wulu nyajin.",
     "kami-ngka ngamu wulu nyaji-ny | grandfather-ERG mother ankle see-PAST",
     "r136_perception_grandfather_mother_ankle", 4, js(["kami", "ngamu", "wulu", "nyajil"]), js([]),
     "Wulu ankle; common-noun A ERG.", "0.86"),

    (31, "Grandfather washed father's chest.",
     "Kami-ngka nganjan damu julurrin.",
     "kami-ngka nganjan damu julurri-ny | grandfather-ERG father chest wash-PAST",
     "r136_wash_grandfather_father_chest", 4, js(["kami", "nganjan", "damu", "julurril"]), js([]),
     "Transitive julurril; affected whole-part object with damu.", "0.86"),
    (32, "Mother washed grandmother's stomach.",
     "Ngamu-ngku babi jibar julurrin.",
     "ngamu-ngku babi jibar julurri-ny | mother-ERG grandmother stomach wash-PAST",
     "r136_wash_mother_grandmother_stomach", 4, js(["ngamu", "babi", "jibar", "julurril"]), js([]),
     "Jibar stomach; common-noun A ERG.", "0.86"),
    (33, "Father washed the young man's neck.",
     "Nganjan-angka warru jakay julurrin.",
     "nganjan-angka warru jakay julurri-ny | father-ERG young.man neck wash-PAST",
     "r136_wash_father_young_man_neck", 4, js(["nganjan", "warru", "jakay", "julurril"]), js([]),
     "Jakay neck in affected wash object; common-noun A ERG.", "0.86"),
    (34, "The woman washed the child's shoulder.",
     "Jalbu-ngku karrkay binda julurrin.",
     "jalbu-ngku karrkay binda julurri-ny | woman-ERG child shoulder wash-PAST",
     "r136_wash_woman_child_shoulder", 4, js(["jalbu", "karrkay", "binda", "julurril"]), js([]),
     "Binda shoulder selected; common-noun A ERG.", "0.86"),
    (35, "Grandmother washed mother's arm.",
     "Babi-ngka ngamu wakuy julurrin.",
     "babi-ngka ngamu wakuy julurri-ny | grandmother-ERG mother arm wash-PAST",
     "r136_wash_grandmother_mother_arm", 4, js(["babi", "ngamu", "wakuy", "julurril"]), js([]),
     "Wakuy arm; no target case form.", "0.86"),
    (36, "Father washed the woman's ankle.",
     "Nganjan-angka jalbu wulu julurrin.",
     "nganjan-angka jalbu wulu julurri-ny | father-ERG woman ankle wash-PAST",
     "r136_wash_father_woman_ankle", 4, js(["nganjan", "jalbu", "wulu", "julurril"]), js([]),
     "Wulu ankle; common-noun A ERG.", "0.86"),
    (37, "Mother washed the child's navel.",
     "Ngamu-ngku karrkay wurra julurrin.",
     "ngamu-ngku karrkay wurra julurri-ny | mother-ERG child navel wash-PAST",
     "r136_wash_mother_child_navel", 4, js(["ngamu", "karrkay", "wurra", "julurril"]), js([]),
     "Wurra navel; no umbilical-cord scene.", "0.86"),
    (38, "The child washed the baby's arm.",
     "Karrkay-ngku ngawa wakuy julurrin.",
     "karrkay-ngku ngawa wakuy julurri-ny | child-ERG baby arm wash-PAST",
     "r136_wash_child_baby_arm", 4, js(["karrkay", "ngawa", "wakuy", "julurril"]), js([]),
     "Wakuy arm in affected object; child A ERG.", "0.84"),

    (39, "Father washed his chest.",
     "Nganjan damu julurri-jin.",
     "nganjan damu julurri-ji-ny | father chest wash-REF-PAST",
     "r136_reflexive_wash_father_chest", 3, js(["nganjan", "damu", "julurriji"]), js([]),
     "Reflexive wash S unmarked; damu chest.", "0.86"),
    (40, "Grandmother washed her stomach.",
     "Babi jibar julurri-jin.",
     "babi jibar julurri-ji-ny | grandmother stomach wash-REF-PAST",
     "r136_reflexive_wash_grandmother_stomach", 3, js(["babi", "jibar", "julurriji"]), js([]),
     "Reflexive wash; jibar stomach; no possessive suffix.", "0.86"),
    (41, "I washed my neck.",
     "Ngayu jakay julurri-jin.",
     "ngayu jakay julurri-ji-ny | I neck wash-REF-PAST",
     "r136_reflexive_wash_pronoun_neck", 3, js(["ngayu", "jakay", "julurriji"]), js([]),
     "Pronoun S bare; jakay neck.", "0.86"),
    (42, "Mother washed her shoulder.",
     "Ngamu binda julurri-jin.",
     "ngamu binda julurri-ji-ny | mother shoulder wash-REF-PAST",
     "r136_reflexive_wash_mother_shoulder", 3, js(["ngamu", "binda", "julurriji"]), js([]),
     "Reflexive wash S unmarked; binda shoulder.", "0.86"),
    (43, "The young man washed his arm.",
     "Warru wakuy julurri-jin.",
     "warru wakuy julurri-ji-ny | young.man arm wash-REF-PAST",
     "r136_reflexive_wash_young_man_arm", 3, js(["warru", "wakuy", "julurriji"]), js([]),
     "Reflexive wash; wakuy arm; no target case form.", "0.86"),
    (44, "The child washed his navel.",
     "Karrkay wurra julurri-jin.",
     "karrkay wurra julurri-ji-ny | child navel wash-REF-PAST",
     "r136_reflexive_wash_child_navel", 3, js(["karrkay", "wurra", "julurriji"]), js([]),
     "Reflexive wash; wurra navel; no possessive suffix.", "0.84"),

    (45, "The dog bit mother's stomach.",
     "Kaya-ngka ngamu jibar baykan.",
     "kaya-ngka ngamu jibar bayka-ny | dog-ERG mother stomach bite-PAST",
     "r136_affected_dog_bite_mother_stomach", 4, js(["kaya", "ngamu", "jibar", "baykal"]), js([]),
     "Bayka-l bite; jibar stomach; common-noun A ERG.", "0.84"),
    (46, "The crab bit grandfather's ankle.",
     "Ngalngal-angka kami wulu baykan.",
     "ngalngal-angka kami wulu bayka-ny | crab-ERG grandfather ankle bite-PAST",
     "r136_affected_crab_bite_grandfather_ankle", 4, js(["ngalngal", "kami", "wulu", "baykal"]), js([]),
     "Ngalngal crab A; wulu ankle affected object.", "0.84"),
    (47, "The dog bit the child's shoulder.",
     "Kaya-ngka karrkay binda baykan.",
     "kaya-ngka karrkay binda bayka-ny | dog-ERG child shoulder bite-PAST",
     "r136_affected_dog_bite_child_shoulder", 4, js(["kaya", "karrkay", "binda", "baykal"]), js([]),
     "Binda shoulder affected object; common-noun A ERG.", "0.84"),
    (48, "The crab bit the young man's arm.",
     "Ngalngal-angka warru wakuy baykan.",
     "ngalngal-angka warru wakuy bayka-ny | crab-ERG young.man arm bite-PAST",
     "r136_affected_crab_bite_young_man_arm", 4, js(["ngalngal", "warru", "wakuy", "baykal"]), js([]),
     "Wakuy arm; crab A ERG.", "0.84"),
    (49, "The dog bit the woman's neck.",
     "Kaya-ngka jalbu jakay baykan.",
     "kaya-ngka jalbu jakay bayka-ny | dog-ERG woman neck bite-PAST",
     "r136_affected_dog_bite_woman_neck", 4, js(["kaya", "jalbu", "jakay", "baykal"]), js([]),
     "Jakay neck affected object; common-noun A ERG.", "0.84"),
    (50, "The crab bit father's chest.",
     "Ngalngal-angka nganjan damu baykan.",
     "ngalngal-angka nganjan damu bayka-ny | crab-ERG father chest bite-PAST",
     "r136_affected_crab_bite_father_chest", 4, js(["ngalngal", "nganjan", "damu", "baykal"]), js([]),
     "Damu chest affected object; 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()
        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("r136_"):
            issues.append(f"{label}: Rule 136 frame must start with r136_")
        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 136 construction 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 136")
        if "COMIT" in analysis:
            issues.append(f"{label}: no comitative rows in Rule 136")
        if re.search(r"-(?:amu|mu|ndamun|andamun)\b", low):
            issues.append(f"{label}: no alienable possessive suffixes in Rule 136")
        if not any(has_base(low, head) for head in TARGET_HEADS):
            issues.append(f"{label}: Rule 136 row must exercise a Rule 135 target head")
        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 new 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/support head {head} missing from words_used")
                if gloss not in analysis_low:
                    issues.append(f"{label}: state/support head {head} should be glossed as {gloss!r}")
        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 "julurri-jin" in low and has_ergative(first):
            issues.append(f"{label}: reflexive wash S should not 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 136 draft: construction consolidation after Rule 135", "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 136 source pass rechecked Rule 135 target heads in dictionary and lexeme table; Patz appositional "
            "body-part grammar including nyulu dukul and nyulu dukul yalbay; nyaji-l, julurri-l/julurri-ji-y, and "
            "bayka-l grammar anchors; and dictionary/lexeme entries for bajaybajay, junkurr, yalbay, bajaku, kaka, "
            "marral, and ngulkurr before authoring construction-consolidation rows."
        ),
        command="rg/sed dictionary.yaml and grammar.md; sqlite lexeme and corpus-surface readback",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="construction-scope",
        actor="claude-main",
        detail=(
            "Rule 136 admits no new target heads. It consolidates damu, jibar, jakay, binda, wakuy, wulu, and wurra "
            "in state, perception, transitive wash, reflexive wash, and affected-object bite frames."
        ),
        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 waba, no jandal, no dakil, no locational jibarr, no Rule 133/134 animal-body support heads, "
            "no target-head case forms, no instruments, comitatives, possessive suffixes, temporal clitics, or "
            "unsourced analogies; common-noun A ERG, pronoun A bare, and reflexive wash S 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 178 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 178 stores source readback, construction-scope decisions, 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 136 construction-consolidation rows as under_review after static and exact duplicate checks.",
        command="python3 batch_scripts/bank_178.py",
        output_excerpt="banked batch-178: 50 rows under_review; duplicate scan clean",
        artifact_path="batch_scripts/bank_178.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())
