#!/usr/bin/env python3
"""Bank batch-252: Rule 210 low-coverage water reference 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-252"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-252-low-coverage-water-reference-lexical-expansion-after-rule209-process.md"

TARGET_HEADS = {
    "balar": "high tide",
    "ngajal": "low tide",
    "walngka": "swamp",
    "yilki": "creek",
    "jirka": "spring",
    "yiri": "waterfall",
    "yirri": "running water",
    "marrka": "calm sea",
    "mulngku": "wave",
    "miral": "scum",
}
SUPPORT = {"yinya", "yaluy", "yinyay"}
DENY_KUKU = {
    "kari", "yala", "yanyu", "nyaji-l", "nyajil", "nyajin", "nuka", "nukal", "nukan",
    "mani", "manil", "manin", "muja", "mujal", "mujan", "dunga", "dungay", "dungan",
    "kada", "kaday", "kadan", "wuna", "wunay", "wunan", "bawa", "bawal", "bawan",
    "waju", "wajul", "wajun", "karrba", "karrbal", "karrban", "kuni", "kunil", "kunin",
    "daya", "dayal", "yirrka", "yirrkal", "yirrkan", "baja", "bajaku", "bana", "jalun",
    "dulngku", "ngarngkul", "jalungkarr", "kuyu", "kiju", "mukirr", "julul", "makarr",
    "baral", "bubu", "juku", "bayan", "balar-a", "ngajal-ba", "yilki-nga", "walngka-nga",
}
DENY_ENGLISH = (
    "place", "oaky", "okay", "salt pan", "whale oil", "fish", "fishing", "gather", "gathering",
    "oyster", "crab", "shellfish", "food", "eat", "eating", "route", "travel", "walk", "walking",
    "go", "going", "come", "coming", "danger", "drown", "swim", "swimming", "flood", "reef", "current",
    "flow", "flowing", "run", "running fast", "break", "breaking", "rough", "storm", "sound", "hear",
    "weather", "season", "ecology", "habitat", "owner", "own", "belongs", "use", "uses", "used", "using",
    "water source", "spring season", "falls down",
)


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


def row(
    seq: int,
    english: str,
    kuku: str,
    analysis: str,
    frame: str,
    words: list[str],
    evidence: str,
) -> tuple:
    return (seq, english, kuku, analysis, frame, 2, js(words), js([]), evidence, "0.86")


ROWS = [
    row(1, "The high tide is here.", "Balar yaluy.", "balar yaluy | high.tide here", "r210_deictic_here_balar", ["balar", "yaluy"], "Dictionary balar noun 'high tide'; place-name sense closed; Patz section 3.7 yaluy 'here'."),
    row(2, "The low tide is here.", "Ngajal yaluy.", "ngajal yaluy | low.tide here", "r210_deictic_here_ngajal", ["ngajal", "yaluy"], "Dictionary ngajal noun 'low tide'; Patz section 3.7 yaluy 'here'."),
    row(3, "The swamp is here.", "Walngka yaluy.", "walngka yaluy | swamp here", "r210_deictic_here_walngka", ["walngka", "yaluy"], "Dictionary walngka noun 'swamp'; Patz section 3.7 yaluy 'here'."),
    row(4, "The creek is here.", "Yilki yaluy.", "yilki yaluy | creek here", "r210_deictic_here_yilki", ["yilki", "yaluy"], "Dictionary yilki noun 'creek'; Patz section 3.7 yaluy 'here'."),
    row(5, "The spring is here.", "Jirka yaluy.", "jirka yaluy | spring here", "r210_deictic_here_jirka", ["jirka", "yaluy"], "Dictionary jirka noun 'spring' as water source; Patz section 3.7 yaluy 'here'."),
    row(6, "The waterfall is here.", "Yiri yaluy.", "yiri yaluy | waterfall here", "r210_deictic_here_yiri", ["yiri", "yaluy"], "Dictionary yiri noun 'waterfall'; Patz section 3.7 yaluy 'here'."),
    row(7, "The running water is here.", "Yirri yaluy.", "yirri yaluy | running.water here", "r210_deictic_here_yirri", ["yirri", "yaluy"], "Dictionary yirri noun 'running water'; Patz section 3.7 yaluy 'here'."),
    row(8, "The calm sea is here.", "Marrka yaluy.", "marrka yaluy | calm.sea here", "r210_deictic_here_marrka", ["marrka", "yaluy"], "Dictionary marrka noun 'calm sea'; salt-pan sense closed; Patz section 3.7 yaluy."),
    row(9, "The wave is here.", "Mulngku yaluy.", "mulngku yaluy | wave here", "r210_deictic_here_mulngku", ["mulngku", "yaluy"], "Dictionary mulngku noun 'wave'; Patz section 3.7 yaluy 'here'."),
    row(10, "The scum is here.", "Miral yaluy.", "miral yaluy | scum here", "r210_deictic_here_miral", ["miral", "yaluy"], "Dictionary miral noun 'scum on water'; whale-oil sense closed; Patz section 3.7 yaluy."),
    row(11, "The high tide is there.", "Balar yinyay.", "balar yinyay | high.tide there", "r210_deictic_there_balar", ["balar", "yinyay"], "Dictionary balar noun 'high tide'; Patz section 3.7 yinyay 'there'."),
    row(12, "The low tide is there.", "Ngajal yinyay.", "ngajal yinyay | low.tide there", "r210_deictic_there_ngajal", ["ngajal", "yinyay"], "Dictionary ngajal noun 'low tide'; Patz section 3.7 yinyay 'there'."),
    row(13, "The swamp is there.", "Walngka yinyay.", "walngka yinyay | swamp there", "r210_deictic_there_walngka", ["walngka", "yinyay"], "Dictionary walngka noun 'swamp'; Patz section 3.7 yinyay 'there'."),
    row(14, "The creek is there.", "Yilki yinyay.", "yilki yinyay | creek there", "r210_deictic_there_yilki", ["yilki", "yinyay"], "Dictionary yilki noun 'creek'; Patz section 3.7 yinyay 'there'."),
    row(15, "The spring is there.", "Jirka yinyay.", "jirka yinyay | spring there", "r210_deictic_there_jirka", ["jirka", "yinyay"], "Dictionary jirka noun 'spring' as water source; Patz section 3.7 yinyay."),
    row(16, "The waterfall is there.", "Yiri yinyay.", "yiri yinyay | waterfall there", "r210_deictic_there_yiri", ["yiri", "yinyay"], "Dictionary yiri noun 'waterfall'; Patz section 3.7 yinyay."),
    row(17, "The running water is there.", "Yirri yinyay.", "yirri yinyay | running.water there", "r210_deictic_there_yirri", ["yirri", "yinyay"], "Dictionary yirri noun 'running water'; Patz section 3.7 yinyay."),
    row(18, "The calm sea is there.", "Marrka yinyay.", "marrka yinyay | calm.sea there", "r210_deictic_there_marrka", ["marrka", "yinyay"], "Dictionary marrka noun 'calm sea'; salt-pan sense closed; Patz section 3.7 yinyay."),
    row(19, "The wave is there.", "Mulngku yinyay.", "mulngku yinyay | wave there", "r210_deictic_there_mulngku", ["mulngku", "yinyay"], "Dictionary mulngku noun 'wave'; Patz section 3.7 yinyay."),
    row(20, "The scum is there.", "Miral yinyay.", "miral yinyay | scum there", "r210_deictic_there_miral", ["miral", "yinyay"], "Dictionary miral noun 'scum on water'; whale-oil sense closed; Patz section 3.7 yinyay."),
    row(21, "That high tide is there.", "Yinya balar yinyay.", "yinya balar yinyay | that high.tide there", "r210_initial_that_there_balar", ["yinya", "balar", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; balar high tide; yinyay there."),
    row(22, "That low tide is there.", "Yinya ngajal yinyay.", "yinya ngajal yinyay | that low.tide there", "r210_initial_that_there_ngajal", ["yinya", "ngajal", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; ngajal low tide; yinyay there."),
    row(23, "That swamp is there.", "Yinya walngka yinyay.", "yinya walngka yinyay | that swamp there", "r210_initial_that_there_walngka", ["yinya", "walngka", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; walngka swamp; yinyay there."),
    row(24, "That creek is there.", "Yinya yilki yinyay.", "yinya yilki yinyay | that creek there", "r210_initial_that_there_yilki", ["yinya", "yilki", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; yilki creek; yinyay there."),
    row(25, "That spring is there.", "Yinya jirka yinyay.", "yinya jirka yinyay | that spring there", "r210_initial_that_there_jirka", ["yinya", "jirka", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; jirka spring; yinyay there."),
    row(26, "That waterfall is there.", "Yinya yiri yinyay.", "yinya yiri yinyay | that waterfall there", "r210_initial_that_there_yiri", ["yinya", "yiri", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; yiri waterfall; yinyay there."),
    row(27, "That running water is there.", "Yinya yirri yinyay.", "yinya yirri yinyay | that running.water there", "r210_initial_that_there_yirri", ["yinya", "yirri", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; yirri running water; yinyay there."),
    row(28, "That calm sea is there.", "Yinya marrka yinyay.", "yinya marrka yinyay | that calm.sea there", "r210_initial_that_there_marrka", ["yinya", "marrka", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; marrka calm sea; yinyay there."),
    row(29, "That wave is there.", "Yinya mulngku yinyay.", "yinya mulngku yinyay | that wave there", "r210_initial_that_there_mulngku", ["yinya", "mulngku", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; mulngku wave; yinyay there."),
    row(30, "That scum is there.", "Yinya miral yinyay.", "yinya miral yinyay | that scum there", "r210_initial_that_there_miral", ["yinya", "miral", "yinyay"], "Patz section 3.7 yinya can co-occur with a noun; miral scum; yinyay there."),
    row(31, "That high tide is there.", "Balar yinya yinyay.", "balar yinya yinyay | high.tide that there", "r210_postposed_that_there_balar", ["balar", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; balar high tide; yinyay there."),
    row(32, "That low tide is there.", "Ngajal yinya yinyay.", "ngajal yinya yinyay | low.tide that there", "r210_postposed_that_there_ngajal", ["ngajal", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; ngajal low tide; yinyay there."),
    row(33, "That swamp is there.", "Walngka yinya yinyay.", "walngka yinya yinyay | swamp that there", "r210_postposed_that_there_walngka", ["walngka", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; walngka swamp; yinyay there."),
    row(34, "That creek is there.", "Yilki yinya yinyay.", "yilki yinya yinyay | creek that there", "r210_postposed_that_there_yilki", ["yilki", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; yilki creek; yinyay there."),
    row(35, "That spring is there.", "Jirka yinya yinyay.", "jirka yinya yinyay | spring that there", "r210_postposed_that_there_jirka", ["jirka", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; jirka spring; yinyay there."),
    row(36, "That waterfall is there.", "Yiri yinya yinyay.", "yiri yinya yinyay | waterfall that there", "r210_postposed_that_there_yiri", ["yiri", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; yiri waterfall; yinyay there."),
    row(37, "That running water is there.", "Yirri yinya yinyay.", "yirri yinya yinyay | running.water that there", "r210_postposed_that_there_yirri", ["yirri", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; yirri running water; yinyay there."),
    row(38, "That calm sea is there.", "Marrka yinya yinyay.", "marrka yinya yinyay | calm.sea that there", "r210_postposed_that_there_marrka", ["marrka", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; marrka calm sea; yinyay there."),
    row(39, "That wave is there.", "Mulngku yinya yinyay.", "mulngku yinya yinyay | wave that there", "r210_postposed_that_there_mulngku", ["mulngku", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; mulngku wave; yinyay there."),
    row(40, "That scum is there.", "Miral yinya yinyay.", "miral yinya yinyay | scum that there", "r210_postposed_that_there_miral", ["miral", "yinya", "yinyay"], "Patz section 3.7 permits yinya after a noun; miral scum; yinyay there."),
    row(41, "That high tide is here.", "Yinya balar yaluy.", "yinya balar yaluy | that high.tide here", "r210_initial_that_here_balar", ["yinya", "balar", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; balar high tide; yaluy here."),
    row(42, "That low tide is here.", "Yinya ngajal yaluy.", "yinya ngajal yaluy | that low.tide here", "r210_initial_that_here_ngajal", ["yinya", "ngajal", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; ngajal low tide; yaluy here."),
    row(43, "That swamp is here.", "Yinya walngka yaluy.", "yinya walngka yaluy | that swamp here", "r210_initial_that_here_walngka", ["yinya", "walngka", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; walngka swamp; yaluy here."),
    row(44, "That creek is here.", "Yinya yilki yaluy.", "yinya yilki yaluy | that creek here", "r210_initial_that_here_yilki", ["yinya", "yilki", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; yilki creek; yaluy here."),
    row(45, "That spring is here.", "Yinya jirka yaluy.", "yinya jirka yaluy | that spring here", "r210_initial_that_here_jirka", ["yinya", "jirka", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; jirka spring; yaluy here."),
    row(46, "That waterfall is here.", "Yinya yiri yaluy.", "yinya yiri yaluy | that waterfall here", "r210_initial_that_here_yiri", ["yinya", "yiri", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; yiri waterfall; yaluy here."),
    row(47, "That running water is here.", "Yinya yirri yaluy.", "yinya yirri yaluy | that running.water here", "r210_initial_that_here_yirri", ["yinya", "yirri", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; yirri running water; yaluy here."),
    row(48, "That calm sea is here.", "Yinya marrka yaluy.", "yinya marrka yaluy | that calm.sea here", "r210_initial_that_here_marrka", ["yinya", "marrka", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; marrka calm sea; yaluy here."),
    row(49, "That wave is here.", "Yinya mulngku yaluy.", "yinya mulngku yaluy | that wave here", "r210_initial_that_here_mulngku", ["yinya", "mulngku", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; mulngku wave; yaluy here."),
    row(50, "That scum is here.", "Yinya miral yaluy.", "yinya miral yaluy | that scum here", "r210_initial_that_here_miral", ["yinya", "miral", "yaluy"], "Patz section 3.7 yinya can co-occur with a noun; miral scum; yaluy here."),
]


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


def has_target_morphology(low: str, head: str) -> bool:
    suffixes = "ngka|ngku|angka|nga|ngu|ba|mba|mbu|muny|darr|burr|bu|ka|nka|nku|ji|jirr|ku|mundu|amu|mu|iji|nda|ndu|anda|undu|unji|nji|kari|baka|baja|bungal|bungan|mal|man|rriny"
    return re.search(rf"(?<![a-z-]){re.escape(head)}-(?:{suffixes})\b", low) is not None


def english_has_denied(english_low: str, denied: str) -> bool:
    return denied in english_low if " " in denied else re.search(rf"\b{re.escape(denied)}\b", english_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_data in enumerate(ROWS, start=1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = row_data
        label = f"{LABEL}.{seq:02d}"
        low = kuku.lower()
        english_low = english.lower()
        words = json.loads(words_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("r210_"):
            issues.append(f"{label}: Rule 210 frame must start with r210_")
        if low in seen_ku:
            issues.append(f"{label}: duplicate Kuku surface within batch")
        seen_ku.add(low)
        if json.loads(loans_used):
            issues.append(f"{label}: no loans are admitted in Rule 210")
        for denied in DENY_KUKU:
            if has_token(low, denied):
                issues.append(f"{label}: denied carry-forward 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 210 target noun, found {targets}")
        for head in targets:
            selected = TARGET_HEADS[head]
            dotted = selected.replace("-", ".").replace(" ", ".")
            if head not in words:
                issues.append(f"{label}: target head {head} missing from words_used")
            if selected not in english_low:
                issues.append(f"{label}: English must include selected sense {selected!r}")
            if selected not in analysis and dotted not in analysis:
                issues.append(f"{label}: analysis must gloss {head} as {selected}")
            if has_target_morphology(low, head):
                issues.append(f"{label}: target head {head} has forbidden morphology")
        for support in SUPPORT:
            if has_token(low, support) and support not in words:
                issues.append(f"{label}: support {support} missing from words_used")
        if 1 <= seq <= 10 and (has_token(low, "yinya") or has_token(low, "yinyay") or not low.endswith(" yaluy.")):
            issues.append(f"{label}: rows 1-10 require TARGET yaluy")
        if 11 <= seq <= 20 and (has_token(low, "yinya") or has_token(low, "yaluy") or not low.endswith(" yinyay.")):
            issues.append(f"{label}: rows 11-20 require TARGET yinyay")
        if 21 <= seq <= 30 and (not low.startswith("yinya ") or has_token(low, "yaluy") or not low.endswith(" yinyay.")):
            issues.append(f"{label}: rows 21-30 require Yinya TARGET yinyay")
        if 31 <= seq <= 40 and (has_token(low, "yaluy") or not has_token(low, "yinya") or not low.endswith(" yinyay.") or low.startswith("yinya ")):
            issues.append(f"{label}: rows 31-40 require TARGET yinya yinyay")
        if 41 <= seq <= 50 and (not low.startswith("yinya ") or has_token(low, "yinyay") or not low.endswith(" yaluy.")):
            issues.append(f"{label}: rows 41-50 require Yinya TARGET yaluy")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied water-use/ecology/event field in English")
        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 210 draft: low-coverage water reference lexical expansion after Rule 209",
                "operator-guide-2026-07-08",
                "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

    db.executemany(
        """
        INSERT INTO sentences(
          batch_id, seq, english, kuku, analysis, frame, tier, words_used,
          loans_used, evidence, status, confidence, author, created_at
        )
        VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        [
            (batch_id, *row_data[:-1], "under_review", row_data[-1], "claude", NOW)
            for row_data in ROWS
        ],
    )
    verified_before, words_before = db.execute(
        """
        SELECT COUNT(*), SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
        FROM sentences
        WHERE status IN ('accepted','revised')
        """
    ).fetchone()
    record_event(
        db,
        batch_label=LABEL,
        phase="source",
        event_type="source-pass",
        actor="claude-main",
        detail="Checked Patz section 3.7 demonstrative frames plus dictionary entries for balar, ngajal, walngka, yilki, jirka, yiri, yirri, marrka, mulngku, and miral. Selected only bare water/coastal-reference readings.",
        command="structured dictionary.yaml readback plus grammar.md Patz section 3.7 checks",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="scope",
        event_type="candidate-rejection",
        actor="claude-main",
        detail="Rejected land-geography because Batches 227-228 already handled landform reference; deferred sky-weather and food for heavier condition/event/use traps; held ngarngkul, jalungkarr, and dulngku in reserve to avoid splash/flood/reef-gathering pressure.",
        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 over all 50 planned Rule 210 Kuku surfaces returned no accepted, revised, or under-review collisions.",
        command="sqlite3 duplicate-preflight over planned surfaces",
        output_excerpt="No collisions.",
        artifact_path=PROCESS_LOG,
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="preflight",
        event_type="static-guard-pass",
        actor="claude-main",
        detail="bank_252.py static guards passed: 50 explicit hand-authored rows, exactly one water target noun per row, support words recorded, correct five frame bands, no target morphology, no loans, and no denied water-use/ecology/event English/Kuku fields.",
        artifact_path="batch_scripts/bank_252.py",
        created_at=NOW,
    )
    db.commit()
    print(f"banked {LABEL}: rows=50, under_review=50, verified_before_close={verified_before}, words_before={words_before}")
    return 0


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