#!/usr/bin/env python3
"""Bank batch-186: Rule 144 construction consolidation after Rule 143."""

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

TARGET_HEADS = {
    "dikal": "bird",
    "kurranji": "cassowary",
    "diwan": "scrub.turkey",
    "wabul": "Torres.Strait.Island.pigeon",
    "nili-nili": "duck",
    "kalbu": "black.crow",
    "dulku": "white.crane",
    "birrbirr": "parrot.lorikeet",
    "jamarbina": "goose",
}
SUPPORT_FORMS = {"bayan-ba", "madja-nga", "ngalkal-ba"}
SUPPORT_HEADS = {"bayan": "camp", "madja": "scrub", "ngalkal": "outside", "yinyay": "there"}
DENY = {
    "wakuka", "jarruka", "diburr", "ngalku", "bajar", "mili", "babajaka", "baril-baril", "baway", "bilar",
    "kabal", "yalngkal", "yumu", "jimal", "ngunjil", "dumbul", "buyku", "jikan", "baya", "kubu", "nganjirr",
}
PRONOUN_A = {"ngayu", "ngana", "jana"}
COMMON_A = {"ngamu-ngku", "nganjan-angka", "babi-ngka", "karrkay-ngku", "kami-ngka", "jalbu-ngku", "dingkar-angka"}
DENIED_ENGLISH = (
    "egg", "nest", "hunt", "hunted", "hunting", "food", "meat", "eat", "eating", "omen", "messenger",
    "totem", "storm", "warning", "myth", "story", "domestic turkey", "islander",
)


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


ROWS = [
    (1, "The bird is standing in the scrub.", "Dikal madja-nga janay.",
     "dikal madja-nga jana-y | bird scrub-LOC stand-NONPAST", "r144_stand_bird_scrub", 3, js(["dikal", "madja", "jana-y"]), js([]),
     "Construction consolidation: dikal bird with support-only madja-nga scrub LOC.", "0.84"),
    (2, "The bird is standing outside.", "Dikal ngalkal-ba janay.",
     "dikal ngalkal-ba jana-y | bird outside-LOC stand-NONPAST", "r144_stand_bird_outside", 3, js(["dikal", "ngalkal", "jana-y"]), js([]),
     "Dikal bird with support-only outside LOC.", "0.84"),
    (3, "The cassowary is standing at camp.", "Kurranji bayan-ba janay.",
     "kurranji bayan-ba jana-y | cassowary camp-LOC stand-NONPAST", "r144_stand_cassowary_camp", 3, js(["kurranji", "bayan", "jana-y"]), js([]),
     "Kurranji cassowary with support-only bayan-ba camp LOC.", "0.84"),
    (4, "The cassowary is standing outside.", "Kurranji ngalkal-ba janay.",
     "kurranji ngalkal-ba jana-y | cassowary outside-LOC stand-NONPAST", "r144_stand_cassowary_outside", 3, js(["kurranji", "ngalkal", "jana-y"]), js([]),
     "Kurranji target remains bare; outside is support only.", "0.84"),
    (5, "The scrub turkey is standing at camp.", "Diwan bayan-ba janay.",
     "diwan bayan-ba jana-y | scrub.turkey camp-LOC stand-NONPAST", "r144_stand_scrub_turkey_camp", 3, js(["diwan", "bayan", "jana-y"]), js([]),
     "Diwan scrub turkey only; no egg or domestic-turkey row.", "0.84"),
    (6, "The scrub turkey is standing outside.", "Diwan ngalkal-ba janay.",
     "diwan ngalkal-ba jana-y | scrub.turkey outside-LOC stand-NONPAST", "r144_stand_scrub_turkey_outside", 3, js(["diwan", "ngalkal", "jana-y"]), js([]),
     "Diwan scrub turkey with support-only outside LOC.", "0.84"),
    (7, "The Torres Strait Island pigeon is standing in the scrub.", "Wabul madja-nga janay.",
     "wabul madja-nga jana-y | Torres.Strait.Island.pigeon scrub-LOC stand-NONPAST", "r144_stand_pigeon_scrub", 3, js(["wabul", "madja", "jana-y"]), js([]),
     "Wabul Torres Strait Island pigeon bird sense only.", "0.84"),
    (8, "The Torres Strait Island pigeon is standing outside.", "Wabul ngalkal-ba janay.",
     "wabul ngalkal-ba jana-y | Torres.Strait.Island.pigeon outside-LOC stand-NONPAST", "r144_stand_pigeon_outside", 3, js(["wabul", "ngalkal", "jana-y"]), js([]),
     "Wabul target remains bare; no human/islander reading.", "0.84"),
    (9, "The duck is standing at camp.", "Nili-nili bayan-ba janay.",
     "nili-nili bayan-ba jana-y | duck camp-LOC stand-NONPAST", "r144_stand_duck_camp", 3, js(["nili-nili", "bayan", "jana-y"]), js([]),
     "Nili-nili duck kept as a whole lexical head.", "0.84"),
    (10, "The duck is standing in the scrub.", "Nili-nili madja-nga janay.",
     "nili-nili madja-nga jana-y | duck scrub-LOC stand-NONPAST", "r144_stand_duck_scrub", 3, js(["nili-nili", "madja", "jana-y"]), js([]),
     "Nili-nili whole head with support-only scrub LOC.", "0.84"),
    (11, "The duck is standing outside.", "Nili-nili ngalkal-ba janay.",
     "nili-nili ngalkal-ba jana-y | duck outside-LOC stand-NONPAST", "r144_stand_duck_outside", 3, js(["nili-nili", "ngalkal", "jana-y"]), js([]),
     "Nili-nili whole head with outside support.", "0.84"),
    (12, "The black crow is standing at camp.", "Kalbu bayan-ba janay.",
     "kalbu bayan-ba jana-y | black.crow camp-LOC stand-NONPAST", "r144_stand_black_crow_camp", 3, js(["kalbu", "bayan", "jana-y"]), js([]),
     "Kalbu black crow only; no totem claim.", "0.84"),
    (13, "The black crow is standing in the scrub.", "Kalbu madja-nga janay.",
     "kalbu madja-nga jana-y | black.crow scrub-LOC stand-NONPAST", "r144_stand_black_crow_scrub", 3, js(["kalbu", "madja", "jana-y"]), js([]),
     "Kalbu with support-only scrub LOC.", "0.84"),
    (14, "The white crane is standing at camp.", "Dulku bayan-ba janay.",
     "dulku bayan-ba jana-y | white.crane camp-LOC stand-NONPAST", "r144_stand_white_crane_camp", 3, js(["dulku", "bayan", "jana-y"]), js([]),
     "Dulku white crane with camp support.", "0.84"),
    (15, "The white crane is standing in the scrub.", "Dulku madja-nga janay.",
     "dulku madja-nga jana-y | white.crane scrub-LOC stand-NONPAST", "r144_stand_white_crane_scrub", 3, js(["dulku", "madja", "jana-y"]), js([]),
     "Dulku white crane with scrub support.", "0.84"),
    (16, "The parrot is standing in the scrub.", "Birrbirr madja-nga janay.",
     "birrbirr madja-nga jana-y | parrot.lorikeet scrub-LOC stand-NONPAST", "r144_stand_parrot_scrub", 3, js(["birrbirr", "madja", "jana-y"]), js([]),
     "Birrbirr parrot/lorikeet kept as a whole lexical head.", "0.84"),
    (17, "The parrot is standing outside.", "Birrbirr ngalkal-ba janay.",
     "birrbirr ngalkal-ba jana-y | parrot.lorikeet outside-LOC stand-NONPAST", "r144_stand_parrot_outside", 3, js(["birrbirr", "ngalkal", "jana-y"]), js([]),
     "Birrbirr whole head with outside support.", "0.84"),
    (18, "The goose is standing at camp.", "Jamarbina bayan-ba janay.",
     "jamarbina bayan-ba jana-y | goose camp-LOC stand-NONPAST", "r144_stand_goose_camp", 3, js(["jamarbina", "bayan", "jana-y"]), js([]),
     "Jamarbina goose with camp support.", "0.84"),
    (19, "The goose is standing in the scrub.", "Jamarbina madja-nga janay.",
     "jamarbina madja-nga jana-y | goose scrub-LOC stand-NONPAST", "r144_stand_goose_scrub", 3, js(["jamarbina", "madja", "jana-y"]), js([]),
     "Jamarbina goose with scrub support.", "0.84"),

    (20, "Mother saw the bird in the scrub.", "Ngamu-ngku dikal madja-nga nyajin.",
     "ngamu-ngku dikal madja-nga nyaji-ny | mother-ERG bird scrub-LOC see-PAST", "r144_perception_mother_bird_scrub", 4, js(["ngamu", "dikal", "madja", "nyaji-l"]), js([]),
     "Common-noun A ERG; dikal object bare; madja-nga support location.", "0.84"),
    (21, "Father saw the bird outside.", "Nganjan-angka dikal ngalkal-ba nyajin.",
     "nganjan-angka dikal ngalkal-ba nyaji-ny | father-ERG bird outside-LOC see-PAST", "r144_perception_father_bird_outside", 4, js(["nganjan", "dikal", "ngalkal", "nyaji-l"]), js([]),
     "Common-noun A ERG; outside support only.", "0.84"),
    (22, "Grandmother saw the cassowary at camp.", "Babi-ngka kurranji bayan-ba nyajin.",
     "babi-ngka kurranji bayan-ba nyaji-ny | grandmother-ERG cassowary camp-LOC see-PAST", "r144_perception_grandmother_cassowary_camp", 4, js(["babi", "kurranji", "bayan", "nyaji-l"]), js([]),
     "Common-noun A ERG; kurranji object bare.", "0.84"),
    (23, "The child saw the cassowary outside.", "Karrkay-ngku kurranji ngalkal-ba nyajin.",
     "karrkay-ngku kurranji ngalkal-ba nyaji-ny | child-ERG cassowary outside-LOC see-PAST", "r144_perception_child_cassowary_outside", 4, js(["karrkay", "kurranji", "ngalkal", "nyaji-l"]), js([]),
     "Common-noun A ERG; outside support only.", "0.84"),
    (24, "Grandfather saw the scrub turkey at camp.", "Kami-ngka diwan bayan-ba nyajin.",
     "kami-ngka diwan bayan-ba nyaji-ny | grandfather-ERG scrub.turkey camp-LOC see-PAST", "r144_perception_grandfather_scrub_turkey_camp", 4, js(["kami", "diwan", "bayan", "nyaji-l"]), js([]),
     "Diwan scrub turkey only; no egg row.", "0.84"),
    (25, "The woman saw the scrub turkey outside.", "Jalbu-ngku diwan ngalkal-ba nyajin.",
     "jalbu-ngku diwan ngalkal-ba nyaji-ny | woman-ERG scrub.turkey outside-LOC see-PAST", "r144_perception_woman_scrub_turkey_outside", 4, js(["jalbu", "diwan", "ngalkal", "nyaji-l"]), js([]),
     "Diwan scrub turkey with outside support; no domestic-turkey row.", "0.84"),
    (26, "The man saw the Torres Strait Island pigeon at camp.", "Dingkar-angka wabul bayan-ba nyajin.",
     "dingkar-angka wabul bayan-ba nyaji-ny | man-ERG Torres.Strait.Island.pigeon camp-LOC see-PAST", "r144_perception_man_pigeon_camp", 4, js(["dingkar", "wabul", "bayan", "nyaji-l"]), js([]),
     "Wabul bird sense only; common-noun A ERG.", "0.84"),
    (27, "I saw the Torres Strait Island pigeon there.", "Ngayu wabul yinyay nyajin.",
     "ngayu wabul yinyay nyaji-ny | I Torres.Strait.Island.pigeon there see-PAST", "r144_perception_i_pigeon_there", 4, js(["ngayu", "wabul", "yinyay", "nyaji-l"]), js([]),
     "Pronoun A bare; wabul bird sense only.", "0.84"),
    (28, "We saw the duck at camp.", "Ngana nili-nili bayan-ba nyajin.",
     "ngana nili-nili bayan-ba nyaji-ny | we duck camp-LOC see-PAST", "r144_perception_we_duck_camp", 4, js(["ngana", "nili-nili", "bayan", "nyaji-l"]), js([]),
     "Pronoun A bare; nili-nili whole head.", "0.84"),
    (29, "They saw the duck in the scrub.", "Jana nili-nili madja-nga nyajin.",
     "jana nili-nili madja-nga nyaji-ny | they duck scrub-LOC see-PAST", "r144_perception_they_duck_scrub", 4, js(["jana", "nili-nili", "madja", "nyaji-l"]), js([]),
     "Pronoun A bare; scrub support only.", "0.84"),
    (30, "Mother saw the black crow at camp.", "Ngamu-ngku kalbu bayan-ba nyajin.",
     "ngamu-ngku kalbu bayan-ba nyaji-ny | mother-ERG black.crow camp-LOC see-PAST", "r144_perception_mother_black_crow_camp", 4, js(["ngamu", "kalbu", "bayan", "nyaji-l"]), js([]),
     "Kalbu black crow only; no totem row.", "0.84"),
    (31, "Father saw the black crow in the scrub.", "Nganjan-angka kalbu madja-nga nyajin.",
     "nganjan-angka kalbu madja-nga nyaji-ny | father-ERG black.crow scrub-LOC see-PAST", "r144_perception_father_black_crow_scrub", 4, js(["nganjan", "kalbu", "madja", "nyaji-l"]), js([]),
     "Kalbu with scrub support; common-noun A ERG.", "0.84"),
    (32, "Grandmother saw the white crane in the scrub.", "Babi-ngka dulku madja-nga nyajin.",
     "babi-ngka dulku madja-nga nyaji-ny | grandmother-ERG white.crane scrub-LOC see-PAST", "r144_perception_grandmother_white_crane_scrub", 4, js(["babi", "dulku", "madja", "nyaji-l"]), js([]),
     "Dulku white crane with scrub support.", "0.84"),
    (33, "The child saw the white crane outside.", "Karrkay-ngku dulku ngalkal-ba nyajin.",
     "karrkay-ngku dulku ngalkal-ba nyaji-ny | child-ERG white.crane outside-LOC see-PAST", "r144_perception_child_white_crane_outside", 4, js(["karrkay", "dulku", "ngalkal", "nyaji-l"]), js([]),
     "Dulku white crane with outside support.", "0.84"),
    (34, "Grandfather saw the parrot there.", "Kami-ngka birrbirr yinyay nyajin.",
     "kami-ngka birrbirr yinyay nyaji-ny | grandfather-ERG parrot.lorikeet there see-PAST", "r144_perception_grandfather_parrot_there", 4, js(["kami", "birrbirr", "yinyay", "nyaji-l"]), js([]),
     "Birrbirr whole head; yinyay deictic support.", "0.84"),
    (35, "The woman saw the parrot in the scrub.", "Jalbu-ngku birrbirr madja-nga nyajin.",
     "jalbu-ngku birrbirr madja-nga nyaji-ny | woman-ERG parrot.lorikeet scrub-LOC see-PAST", "r144_perception_woman_parrot_scrub", 4, js(["jalbu", "birrbirr", "madja", "nyaji-l"]), js([]),
     "Birrbirr whole head with scrub support.", "0.84"),
    (36, "The man saw the goose at camp.", "Dingkar-angka jamarbina bayan-ba nyajin.",
     "dingkar-angka jamarbina bayan-ba nyaji-ny | man-ERG goose camp-LOC see-PAST", "r144_perception_man_goose_camp", 4, js(["dingkar", "jamarbina", "bayan", "nyaji-l"]), js([]),
     "Jamarbina goose with camp support.", "0.84"),
    (37, "We saw the goose outside.", "Ngana jamarbina ngalkal-ba nyajin.",
     "ngana jamarbina ngalkal-ba nyaji-ny | we goose outside-LOC see-PAST", "r144_perception_we_goose_outside", 4, js(["ngana", "jamarbina", "ngalkal", "nyaji-l"]), js([]),
     "Pronoun A bare; outside support only.", "0.84"),

    (38, "I see the bird.", "Ngayu dikal nyajil.",
     "ngayu dikal nyaji-l | I bird see-NONPAST", "r144_present_i_bird", 4, js(["ngayu", "dikal", "nyaji-l"]), js([]),
     "Nyaji-l nonpast perception construction; pronoun A bare.", "0.84"),
    (39, "We see the cassowary.", "Ngana kurranji nyajil.",
     "ngana kurranji nyaji-l | we cassowary see-NONPAST", "r144_present_we_cassowary", 4, js(["ngana", "kurranji", "nyaji-l"]), js([]),
     "Nyaji-l nonpast; kurranji object bare.", "0.84"),
    (40, "They see the scrub turkey.", "Jana diwan nyajil.",
     "jana diwan nyaji-l | they scrub.turkey see-NONPAST", "r144_present_they_scrub_turkey", 4, js(["jana", "diwan", "nyaji-l"]), js([]),
     "Diwan scrub turkey only; present perception frame.", "0.84"),
    (41, "I see the Torres Strait Island pigeon.", "Ngayu wabul nyajil.",
     "ngayu wabul nyaji-l | I Torres.Strait.Island.pigeon see-NONPAST", "r144_present_i_pigeon", 4, js(["ngayu", "wabul", "nyaji-l"]), js([]),
     "Wabul bird sense only; no human/islander reading.", "0.84"),
    (42, "We see the duck.", "Ngana nili-nili nyajil.",
     "ngana nili-nili nyaji-l | we duck see-NONPAST", "r144_present_we_duck", 4, js(["ngana", "nili-nili", "nyaji-l"]), js([]),
     "Nili-nili whole duck head in present perception frame.", "0.84"),
    (43, "They see the black crow.", "Jana kalbu nyajil.",
     "jana kalbu nyaji-l | they black.crow see-NONPAST", "r144_present_they_black_crow", 4, js(["jana", "kalbu", "nyaji-l"]), js([]),
     "Kalbu black crow only; no totem row.", "0.84"),
    (44, "I see the white crane.", "Ngayu dulku nyajil.",
     "ngayu dulku nyaji-l | I white.crane see-NONPAST", "r144_present_i_white_crane", 4, js(["ngayu", "dulku", "nyaji-l"]), js([]),
     "Dulku white crane object bare.", "0.84"),
    (45, "We see the parrot.", "Ngana birrbirr nyajil.",
     "ngana birrbirr nyaji-l | we parrot.lorikeet see-NONPAST", "r144_present_we_parrot", 4, js(["ngana", "birrbirr", "nyaji-l"]), js([]),
     "Birrbirr whole parrot/lorikeet head.", "0.84"),
    (46, "They see the goose.", "Jana jamarbina nyajil.",
     "jana jamarbina nyaji-l | they goose see-NONPAST", "r144_present_they_goose", 4, js(["jana", "jamarbina", "nyaji-l"]), js([]),
     "Jamarbina goose object bare.", "0.84"),
    (47, "Mother sees the bird.", "Ngamu-ngku dikal nyajil.",
     "ngamu-ngku dikal nyaji-l | mother-ERG bird see-NONPAST", "r144_present_mother_bird", 4, js(["ngamu", "dikal", "nyaji-l"]), js([]),
     "Common-noun A ERG in present perception frame.", "0.84"),
    (48, "Father sees the cassowary.", "Nganjan-angka kurranji nyajil.",
     "nganjan-angka kurranji nyaji-l | father-ERG cassowary see-NONPAST", "r144_present_father_cassowary", 4, js(["nganjan", "kurranji", "nyaji-l"]), js([]),
     "Common-noun A ERG; kurranji object bare.", "0.84"),
    (49, "Grandmother sees the scrub turkey.", "Babi-ngka diwan nyajil.",
     "babi-ngka diwan nyaji-l | grandmother-ERG scrub.turkey see-NONPAST", "r144_present_grandmother_scrub_turkey", 4, js(["babi", "diwan", "nyaji-l"]), js([]),
     "Common-noun A ERG; diwan scrub turkey only.", "0.84"),
    (50, "The child sees the Torres Strait Island pigeon.", "Karrkay-ngku wabul nyajil.",
     "karrkay-ngku wabul nyaji-l | child-ERG Torres.Strait.Island.pigeon see-NONPAST", "r144_present_child_pigeon", 4, js(["karrkay", "wabul", "nyaji-l"]), js([]),
     "Common-noun A ERG; wabul bird sense only.", "0.84"),
]


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


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


def has_target_case(low: str, head: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:ngka|ngku|angka|nga|ngu|ba|muny|bu|ji|jirr|ku|mundu|amu|mu)\b", 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()
        english_low = english.lower()
        analysis_low = analysis.lower()
        words = json.loads(words_used)
        loans = json.loads(loans_used)
        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("r144_"):
            issues.append(f"{label}: Rule 144 frame must start with r144_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if loans:
            issues.append(f"{label}: no loan rows in this batch")
        for denied in DENY:
            if has_token(low, denied):
                issues.append(f"{label}: denied or out-of-scope token {denied!r}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected exactly one Rule 144 target head, found {targets}")
        for head in targets:
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if TARGET_HEADS[head] not in analysis:
                issues.append(f"{label}: target head {head} must be glossed as {TARGET_HEADS[head]}")
            if has_target_case(low, head):
                issues.append(f"{label}: target head {head} has forbidden case/compound morphology")
        for form in re.findall(r"\b[a-z]+-(?:nga|ba|muny|ngu)\b", low):
            if form not in SUPPORT_FORMS:
                issues.append(f"{label}: unapproved support local form {form}")
        for form in SUPPORT_FORMS:
            head = form.rsplit("-", 1)[0]
            if has_token(low, form) and head not in words:
                issues.append(f"{label}: support head {head} missing from words_used")
        if has_token(low, "yinyay") and "yinyay" not in words:
            issues.append(f"{label}: yinyay missing from words_used")
        if has_token(low, "diwan"):
            if "scrub turkey" not in english_low or "egg" in english_low or "domestic" in english_low:
                issues.append(f"{label}: diwan must be scrub turkey only, no eggs/domestic turkey")
        if has_token(low, "wabul") and ("pigeon" not in english_low or "islander" in english_low):
            issues.append(f"{label}: wabul must be pigeon only")
        if "-bu" in low or "INST" in analysis:
            issues.append(f"{label}: no instrument morphology in Rule 144")
        if any(bad in english_low for bad in DENIED_ENGLISH):
            issues.append(f"{label}: denied bird-use/omen/totem/food field in English")
        if "nyajin" in low or "nyajil" in low:
            first = first_word(low)
            if first not in PRONOUN_A and first not in COMMON_A:
                issues.append(f"{label}: perception A should be an approved bare pronoun or ERG-marked common noun")
        if "nyajil" in low and "see-NONPAST" not in analysis:
            issues.append(f"{label}: nyajil must be glossed as see-NONPAST")
        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 144 draft: construction consolidation after Rule 143", "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 144 source pass admitted no new target heads. It retained the Rule 143 bird names and source-checked "
            "standing/perception support: bayan-ba, madja-nga, ngalkal-ba, yinyay, jana-y, nyaji-ny, and nyaji-l."
        ),
        command="dictionary.yaml structured readback; grammar.md bird/perception examples; corpus readback for nyajil/janay; sqlite duplicate preflight",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="construction-scope",
        actor="claude-main",
        detail=(
            "Rule 144 consolidates the bird-name field in standing and perception frames only. It excludes eggs, nests, "
            "hunting, food, meat, eating, omens, messenger claims, totem claims, storm warnings, mythic claims, story "
            "claims, target case forms, instruments, wakuka, and jarruka."
        ),
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="duplicate-preflight",
        actor="claude-main",
        detail="Exact duplicate preflight found 50 planned Kuku surfaces, 50 unique within-batch surfaces, and no exact accepted/revised cross-corpus duplicates.",
        command="sqlite exact lower(trim(kuku)) duplicate scan before banking",
        artifact_path="batch_scripts/bank_186.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="Static guards passed: one retained bird target per row, no loans, no target case forms, no instruments, no egg/food/hunting/omen/totem/story field, common-noun A ERG in perception rows, and pronoun A bare.",
        command="python3 batch_scripts/bank_186.py static scan",
        artifact_path="batch_scripts/bank_186.py",
        created_at=NOW,
    )

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, author, created_at, updated_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (batch_id, seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence,
             "under_review", confidence, "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 under_review Rule 144 bird-name construction-consolidation rows.",
        command="python3 batch_scripts/bank_186.py",
        artifact_path="batch_scripts/bank_186.py",
        created_at=NOW,
    )
    db.commit()

    verified = db.execute("SELECT COUNT(*) FROM sentences WHERE status IN ('accepted','revised')").fetchone()[0]
    under_review = db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]
    print(f"banked {LABEL}: rows=50, under_review={under_review}, verified_before_close={verified}")
    return 0


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