#!/usr/bin/env python3
"""Bank batch-185: Rule 143 bird-name lexical expansion after Rule 142."""

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

TARGET_HEADS = {
    "dikal": "bird",
    "kurranji": "cassowary",
    "diwan": "scrub.turkey",
    "wabul": "Torres.Strait.pigeon",
    "nili-nili": "duck",
    "kalbu": "black.crow",
    "dulku": "white.crane",
    "birrbirr": "parrot.lorikeet",
    "jamarbina": "goose",
}
STATE_HEADS = {"ngulkurr": "good", "yalbay": "big"}
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", "nganjin", "yundu", "yurra", "jana", "nyulu"}
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 good.", "Dikal ngulkurr.",
     "dikal ngulkurr | bird good", "r143_state_bird_good", 2, js(["dikal", "ngulkurr"]), js([]),
     "Dictionary/Patz: dikal generic bird; ngulkurr good.", "0.86"),
    (2, "The cassowary is big.", "Kurranji yalbay.",
     "kurranji yalbay | cassowary big", "r143_state_cassowary_big", 2, js(["kurranji", "yalbay"]), js([]),
     "Dictionary: kurranji cassowary; yalbay big.", "0.86"),
    (3, "The scrub turkey is good.", "Diwan ngulkurr.",
     "diwan ngulkurr | scrub.turkey good", "r143_state_scrub_turkey_good", 2, js(["diwan", "ngulkurr"]), js([]),
     "Dictionary: diwan scrub turkey; no egg or domestic-turkey row.", "0.84"),
    (4, "The Torres Strait pigeon is good.", "Wabul ngulkurr.",
     "wabul ngulkurr | Torres.Strait.pigeon good", "r143_state_pigeon_good", 2, js(["wabul", "ngulkurr"]), js([]),
     "Dictionary: wabul Torres Strait Island pigeon; bird sense selected.", "0.84"),
    (5, "The duck is good.", "Nili-nili ngulkurr.",
     "nili-nili ngulkurr | duck good", "r143_state_duck_good", 2, js(["nili-nili", "ngulkurr"]), js([]),
     "Dictionary: nili-nili duck; reduplicated lexical head kept whole.", "0.86"),
    (6, "The black crow is good.", "Kalbu ngulkurr.",
     "kalbu ngulkurr | black.crow good", "r143_state_black_crow_good", 2, js(["kalbu", "ngulkurr"]), js([]),
     "Dictionary: kalbu black crow / species of crow; no totem claim.", "0.84"),
    (7, "The white crane is big.", "Dulku yalbay.",
     "dulku yalbay | white.crane big", "r143_state_white_crane_big", 2, js(["dulku", "yalbay"]), js([]),
     "Dictionary: dulku white crane; yalbay big.", "0.86"),
    (8, "The parrot is good.", "Birrbirr ngulkurr.",
     "birrbirr ngulkurr | parrot.lorikeet good", "r143_state_parrot_good", 2, js(["birrbirr", "ngulkurr"]), js([]),
     "Dictionary: birrbirr parrot/parakeet/lorikeet; lexical head kept whole.", "0.84"),
    (9, "The goose is big.", "Jamarbina yalbay.",
     "jamarbina yalbay | goose big", "r143_state_goose_big", 2, js(["jamarbina", "yalbay"]), js([]),
     "Dictionary: jamarbina goose; yalbay big.", "0.86"),
    (10, "The cassowary is good.", "Kurranji ngulkurr.",
     "kurranji ngulkurr | cassowary good", "r143_state_cassowary_good", 2, js(["kurranji", "ngulkurr"]), js([]),
     "Kurranji cassowary in a second safe state frame.", "0.86"),
    (11, "The scrub turkey is big.", "Diwan yalbay.",
     "diwan yalbay | scrub.turkey big", "r143_state_scrub_turkey_big", 2, js(["diwan", "yalbay"]), js([]),
     "Diwan scrub turkey; no domestic-turkey or egg row.", "0.84"),
    (12, "The duck is big.", "Nili-nili yalbay.",
     "nili-nili yalbay | duck big", "r143_state_duck_big", 2, js(["nili-nili", "yalbay"]), js([]),
     "Nili-nili duck head kept whole.", "0.86"),
    (13, "The black crow is big.", "Kalbu yalbay.",
     "kalbu yalbay | black.crow big", "r143_state_black_crow_big", 2, js(["kalbu", "yalbay"]), js([]),
     "Kalbu black crow with yalbay big; no totem claim.", "0.84"),
    (14, "The Torres Strait pigeon is big.", "Wabul yalbay.",
     "wabul yalbay | Torres.Strait.pigeon big", "r143_state_pigeon_big", 2, js(["wabul", "yalbay"]), js([]),
     "Wabul pigeon sense only; no human/islander reading.", "0.84"),
    (15, "The white crane is good.", "Dulku ngulkurr.",
     "dulku ngulkurr | white.crane good", "r143_state_white_crane_good", 2, js(["dulku", "ngulkurr"]), js([]),
     "Dulku white crane with ngulkurr good.", "0.86"),

    (16, "The bird is standing there.", "Dikal yinyay janay.",
     "dikal yinyay jana-y | bird there stand-NONPAST", "r143_stand_bird_there", 3, js(["dikal", "yinyay", "jana-y"]), js([]),
     "Patz/corpus support for bird standing and yinyay there; target remains bare.", "0.84"),
    (17, "The cassowary is standing there.", "Kurranji yinyay janay.",
     "kurranji yinyay jana-y | cassowary there stand-NONPAST", "r143_stand_cassowary_there", 3, js(["kurranji", "yinyay", "jana-y"]), js([]),
     "Kurranji bare target in standing frame.", "0.84"),
    (18, "The scrub turkey is standing there.", "Diwan yinyay janay.",
     "diwan yinyay jana-y | scrub.turkey there stand-NONPAST", "r143_stand_scrub_turkey_there", 3, js(["diwan", "yinyay", "jana-y"]), js([]),
     "Diwan scrub turkey in standing frame; no egg row.", "0.84"),
    (19, "The Torres Strait pigeon is standing there.", "Wabul yinyay janay.",
     "wabul yinyay jana-y | Torres.Strait.pigeon there stand-NONPAST", "r143_stand_pigeon_there", 3, js(["wabul", "yinyay", "jana-y"]), js([]),
     "Wabul bird sense in standing frame.", "0.84"),
    (20, "The duck is standing there.", "Nili-nili yinyay janay.",
     "nili-nili yinyay jana-y | duck there stand-NONPAST", "r143_stand_duck_there", 3, js(["nili-nili", "yinyay", "jana-y"]), js([]),
     "Nili-nili duck in standing frame; head kept whole.", "0.84"),
    (21, "The black crow is standing there.", "Kalbu yinyay janay.",
     "kalbu yinyay jana-y | black.crow there stand-NONPAST", "r143_stand_black_crow_there", 3, js(["kalbu", "yinyay", "jana-y"]), js([]),
     "Kalbu black crow in standing frame.", "0.84"),
    (22, "The white crane is standing there.", "Dulku yinyay janay.",
     "dulku yinyay jana-y | white.crane there stand-NONPAST", "r143_stand_white_crane_there", 3, js(["dulku", "yinyay", "jana-y"]), js([]),
     "Dulku white crane in standing frame.", "0.84"),
    (23, "The parrot is standing there.", "Birrbirr yinyay janay.",
     "birrbirr yinyay jana-y | parrot.lorikeet there stand-NONPAST", "r143_stand_parrot_there", 3, js(["birrbirr", "yinyay", "jana-y"]), js([]),
     "Birrbirr parrot/lorikeet in standing frame; head kept whole.", "0.84"),
    (24, "The goose is standing there.", "Jamarbina yinyay janay.",
     "jamarbina yinyay jana-y | goose there stand-NONPAST", "r143_stand_goose_there", 3, js(["jamarbina", "yinyay", "jana-y"]), js([]),
     "Jamarbina goose in standing frame.", "0.84"),
    (25, "The cassowary is standing in the scrub.", "Kurranji madja-nga janay.",
     "kurranji madja-nga jana-y | cassowary scrub-LOC stand-NONPAST", "r143_stand_cassowary_scrub", 3, js(["kurranji", "madja", "jana-y"]), js([]),
     "Kurranji with support-only madja-nga scrub LOC.", "0.84"),
    (26, "The scrub turkey is standing in the scrub.", "Diwan madja-nga janay.",
     "diwan madja-nga jana-y | scrub.turkey scrub-LOC stand-NONPAST", "r143_stand_scrub_turkey_scrub", 3, js(["diwan", "madja", "jana-y"]), js([]),
     "Diwan scrub turkey with madja-nga support LOC.", "0.84"),
    (27, "The black crow is standing outside.", "Kalbu ngalkal-ba janay.",
     "kalbu ngalkal-ba jana-y | black.crow outside-LOC stand-NONPAST", "r143_stand_black_crow_outside", 3, js(["kalbu", "ngalkal", "jana-y"]), js([]),
     "Kalbu with support-only outside LOC.", "0.84"),
    (28, "The white crane is standing outside.", "Dulku ngalkal-ba janay.",
     "dulku ngalkal-ba jana-y | white.crane outside-LOC stand-NONPAST", "r143_stand_white_crane_outside", 3, js(["dulku", "ngalkal", "jana-y"]), js([]),
     "Dulku white crane with ngalkal-ba support.", "0.84"),
    (29, "The goose is standing outside.", "Jamarbina ngalkal-ba janay.",
     "jamarbina ngalkal-ba jana-y | goose outside-LOC stand-NONPAST", "r143_stand_goose_outside", 3, js(["jamarbina", "ngalkal", "jana-y"]), js([]),
     "Jamarbina goose with ngalkal-ba support.", "0.84"),
    (30, "The bird is standing at camp.", "Dikal bayan-ba janay.",
     "dikal bayan-ba jana-y | bird camp-LOC stand-NONPAST", "r143_stand_bird_camp", 3, js(["dikal", "bayan", "jana-y"]), js([]),
     "Dikal with support-only bayan-ba camp LOC.", "0.84"),
    (31, "The parrot is standing at camp.", "Birrbirr bayan-ba janay.",
     "birrbirr bayan-ba jana-y | parrot.lorikeet camp-LOC stand-NONPAST", "r143_stand_parrot_camp", 3, js(["birrbirr", "bayan", "jana-y"]), js([]),
     "Birrbirr bird head with bayan-ba support.", "0.84"),
    (32, "The Torres Strait pigeon is standing at camp.", "Wabul bayan-ba janay.",
     "wabul bayan-ba jana-y | Torres.Strait.pigeon camp-LOC stand-NONPAST", "r143_stand_pigeon_camp", 3, js(["wabul", "bayan", "jana-y"]), js([]),
     "Wabul pigeon with bayan-ba support.", "0.84"),

    (33, "Mother saw the bird.", "Ngamu-ngku dikal nyajin.",
     "ngamu-ngku dikal nyaji-ny | mother-ERG bird see-PAST", "r143_perception_mother_bird", 4, js(["ngamu", "dikal", "nyajil"]), js([]),
     "Common-noun A ERG; dikal bird object.", "0.84"),
    (34, "Father saw the cassowary.", "Nganjan-angka kurranji nyajin.",
     "nganjan-angka kurranji nyaji-ny | father-ERG cassowary see-PAST", "r143_perception_father_cassowary", 4, js(["nganjan", "kurranji", "nyajil"]), js([]),
     "Common-noun A ERG; kurranji object.", "0.84"),
    (35, "Grandmother saw the scrub turkey.", "Babi-ngka diwan nyajin.",
     "babi-ngka diwan nyaji-ny | grandmother-ERG scrub.turkey see-PAST", "r143_perception_grandmother_scrub_turkey", 4, js(["babi", "diwan", "nyajil"]), js([]),
     "Common-noun A ERG; diwan scrub turkey object.", "0.84"),
    (36, "The child saw the Torres Strait pigeon.", "Karrkay-ngku wabul nyajin.",
     "karrkay-ngku wabul nyaji-ny | child-ERG Torres.Strait.pigeon see-PAST", "r143_perception_child_pigeon", 4, js(["karrkay", "wabul", "nyajil"]), js([]),
     "Common-noun A ERG; wabul bird sense only.", "0.84"),
    (37, "Grandfather saw the duck.", "Kami-ngka nili-nili nyajin.",
     "kami-ngka nili-nili nyaji-ny | grandfather-ERG duck see-PAST", "r143_perception_grandfather_duck", 4, js(["kami", "nili-nili", "nyajil"]), js([]),
     "Common-noun A ERG; nili-nili duck head kept whole.", "0.84"),
    (38, "The woman saw the black crow.", "Jalbu-ngku kalbu nyajin.",
     "jalbu-ngku kalbu nyaji-ny | woman-ERG black.crow see-PAST", "r143_perception_woman_black_crow", 4, js(["jalbu", "kalbu", "nyajil"]), js([]),
     "Common-noun A ERG; kalbu black crow.", "0.84"),
    (39, "The man saw the white crane.", "Dingkar-angka dulku nyajin.",
     "dingkar-angka dulku nyaji-ny | man-ERG white.crane see-PAST", "r143_perception_man_white_crane", 4, js(["dingkar", "dulku", "nyajil"]), js([]),
     "Common-noun A ERG; dulku white crane.", "0.84"),
    (40, "I saw the parrot.", "Ngayu birrbirr nyajin.",
     "ngayu birrbirr nyaji-ny | I parrot.lorikeet see-PAST", "r143_perception_i_parrot", 4, js(["ngayu", "birrbirr", "nyajil"]), js([]),
     "Pronoun A bare; birrbirr bird head.", "0.84"),
    (41, "We saw the goose.", "Ngana jamarbina nyajin.",
     "ngana jamarbina nyaji-ny | we goose see-PAST", "r143_perception_we_goose", 4, js(["ngana", "jamarbina", "nyajil"]), js([]),
     "Pronoun A bare; jamarbina goose.", "0.84"),
    (42, "They saw the bird.", "Jana dikal nyajin.",
     "jana dikal nyaji-ny | they bird see-PAST", "r143_perception_they_bird", 4, js(["jana", "dikal", "nyajil"]), js([]),
     "Pronoun A bare; dikal generic bird object.", "0.84"),
    (43, "Mother saw the cassowary in the scrub.", "Ngamu-ngku kurranji madja-nga nyajin.",
     "ngamu-ngku kurranji madja-nga nyaji-ny | mother-ERG cassowary scrub-LOC see-PAST", "r143_perception_mother_cassowary_scrub", 4, js(["ngamu", "kurranji", "madja", "nyajil"]), js([]),
     "Common-noun A ERG; madja-nga support location.", "0.84"),
    (44, "Father saw the scrub turkey in the scrub.", "Nganjan-angka diwan madja-nga nyajin.",
     "nganjan-angka diwan madja-nga nyaji-ny | father-ERG scrub.turkey scrub-LOC see-PAST", "r143_perception_father_scrub_turkey_scrub", 4, js(["nganjan", "diwan", "madja", "nyajil"]), js([]),
     "Diwan scrub turkey in perception frame; no egg row.", "0.84"),
    (45, "Grandmother saw the Torres Strait pigeon outside.", "Babi-ngka wabul ngalkal-ba nyajin.",
     "babi-ngka wabul ngalkal-ba nyaji-ny | grandmother-ERG Torres.Strait.pigeon outside-LOC see-PAST", "r143_perception_grandmother_pigeon_outside", 4, js(["babi", "wabul", "ngalkal", "nyajil"]), js([]),
     "Wabul bird sense with outside support.", "0.84"),
    (46, "The child saw the duck outside.", "Karrkay-ngku nili-nili ngalkal-ba nyajin.",
     "karrkay-ngku nili-nili ngalkal-ba nyaji-ny | child-ERG duck outside-LOC see-PAST", "r143_perception_child_duck_outside", 4, js(["karrkay", "nili-nili", "ngalkal", "nyajil"]), js([]),
     "Nili-nili duck head kept whole; outside support.", "0.84"),
    (47, "Grandfather saw the black crow outside.", "Kami-ngka kalbu ngalkal-ba nyajin.",
     "kami-ngka kalbu ngalkal-ba nyaji-ny | grandfather-ERG black.crow outside-LOC see-PAST", "r143_perception_grandfather_black_crow_outside", 4, js(["kami", "kalbu", "ngalkal", "nyajil"]), js([]),
     "Kalbu black crow with outside support; no totem claim.", "0.84"),
    (48, "The woman saw the white crane at camp.", "Jalbu-ngku dulku bayan-ba nyajin.",
     "jalbu-ngku dulku bayan-ba nyaji-ny | woman-ERG white.crane camp-LOC see-PAST", "r143_perception_woman_white_crane_camp", 4, js(["jalbu", "dulku", "bayan", "nyajil"]), js([]),
     "Dulku white crane with camp support.", "0.84"),
    (49, "The man saw the parrot at camp.", "Dingkar-angka birrbirr bayan-ba nyajin.",
     "dingkar-angka birrbirr bayan-ba nyaji-ny | man-ERG parrot.lorikeet camp-LOC see-PAST", "r143_perception_man_parrot_camp", 4, js(["dingkar", "birrbirr", "bayan", "nyajil"]), js([]),
     "Birrbirr parrot/lorikeet with camp support.", "0.84"),
    (50, "I saw the goose there.", "Ngayu jamarbina yinyay nyajin.",
     "ngayu jamarbina yinyay nyaji-ny | I goose there see-PAST", "r143_perception_i_goose_there", 4, js(["ngayu", "jamarbina", "yinyay", "nyajil"]), js([]),
     "Pronoun A bare; jamarbina goose with yinyay deictic support.", "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("r143_"):
            issues.append(f"{label}: Rule 143 frame must start with r143_")
        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 143 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 head, gloss in STATE_HEADS.items():
            if has_token(low, head) and (head not in words or gloss not in analysis_low):
                issues.append(f"{label}: state head {head} must be logged and glossed as {gloss}")
        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 head in SUPPORT_HEADS:
            if has_token(low, head) and head not in words:
                issues.append(f"{label}: support/deictic head {head} 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 143")
        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:
            first = first_word(low)
            if first not in PRONOUN_A and first not in COMMON_A:
                issues.append(f"{label}: common-noun A in perception row should be an approved ERG-marked form")
        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 143 draft: bird-name lexical expansion after Rule 142", "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 143 source pass opened bird names only: dikal generic bird, kurranji cassowary, diwan scrub turkey, "
            "wabul Torres Strait Island pigeon, nili-nili duck, kalbu black crow, dulku white crane, birrbirr parrot/"
            "lorikeet, and jamarbina goose. Grammar/source readback supports bird-object perception, standing, support "
            "locations, and state predicates."
        ),
        command="dictionary.yaml structured readback; grammar.md bird/perception examples; sqlite lexeme and surface readback",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="lexical-scope",
        actor="claude-main",
        detail=(
            "Rule 143 admits bird-name rows only. It excludes eggs, nests, hunting, food, meat, eating, omens, messenger "
            "claims, totem claims, storm warnings, mythic claims, story claims, target case forms, instruments, and "
            "unresolved wakuka/jarruka polysemy."
        ),
        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_185.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="Static guards passed: selected bird sense for every target, 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_185.py static scan",
        artifact_path="batch_scripts/bank_185.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 143 bird-name lexical rows.",
        command="python3 batch_scripts/bank_185.py",
        artifact_path="batch_scripts/bank_185.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())
