#!/usr/bin/env python3
"""Bank batch-320: Rule 278 visible water/landform non-singular nonpast pronoun perception."""

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-320"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-320-visible-water-landform-nonsingular-nonpast-pronoun-perception-after-rule277-process.md"

TARGET_HEADS = {
    "bana": "water",
    "dajali": "deep water",
    "jalun": "sea",
    "jirka": "spring",
    "jukar": "sand",
    "manjal": "mountain",
    "wawubaja": "river",
    "yilki": "creek",
    "yiri": "waterfall",
    "yirri": "running water",
}
TARGET_ORDER = ["bana", "dajali", "jalun", "jirka", "jukar", "manjal", "wawubaja", "yilki", "yiri", "yirri"]
PRONOUN_SUPPORT = {
    "ngali": "1du.EXCL.NOM",
    "ngaliny": "1du.INCL.NOM",
    "yubal": "2du.NOM",
    "yurra": "2pl.NOM",
    "jana": "3pl.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajil"}
DENY_KUKU = {
    "banabila", "jalungkarr", "jirimandi", "marrka", "mukurr-mukurr", "yalkan", "yikin",
    "yirri-damal", "yiril", "yiman", "kalki", "kubirri", "bujung", "wawu-balangaji",
    "ngalingan", "ngalinyan", "yubalan", "bulangan", "ngalininy", "ngalinyiny", "yubaliny",
    "bulaniny", "ngalinka", "ngalinyanka", "yubalanka", "bulanka", "ngalinda", "ngalinyanda",
    "yubalanda", "bulanda", "ngalindamuny", "ngalinyandamuny", "yubalandamuny", "bulandamuny",
    "ngalinji", "ngalinyanji", "yubalanji", "bulanji", "ngalinga", "ngalinyanga", "yubalanga",
    "bulanga", "nganjinan", "nganangan", "yurrangan", "ianangan", "janangan", "nganjininy",
    "ngananiny", "yurraniny", "iananiny", "jananiny", "nganjinanka", "ngananka", "yurranka",
    "iananka", "jananka", "nganjinanda", "ngananda", "yurranda", "iananda", "jananda",
    "nganjinandamuny", "nganandamuny", "yurrandamuny", "ianandamuny", "janandamuny",
    "nganjinanji", "ngananji", "yurranji", "iananji", "jananji", "nganjinanga", "ngananga",
    "yurranga", "iananga", "jananga", "nyajin", "nyaji-ny", "nyaji-nka", "nyaji-l-nyaji",
    "yinyangka", "yinyayngka", "yinyamba", "yinyamuny", "yalungku", "yaluyngku",
    "yaluymbu", "yaluyundu", "yalamuny", "yalanji",
}
DENY_ENGLISH = (
    "saw", "look", "looked", "watch", "watched", "watching", "listen", "listening", "hearing", "heard",
    "drink", "drank", "drinking", "get ", "got ", "getting", "fill", "filled", "wash", "washed",
    "washing", "swim", "swam", "swimming", "cross", "crossed", "crossing", "travel", "route",
    "road", "flood", "tide", "salt pan", "whale oil", "season", "place", "story", "spirit",
    "fish", "shell", "snake", "crocodile", "plant", "tree", "ecology", "danger", "own", "owner",
    "source", "use", "uses", "used", "case", "compound", "event",
)


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, 3, js(words), js([]), evidence, "0.86")


EVIDENCE = {
    "bana": "Dictionary lines 1264-1272 give bana as noun 'water'. Rule 278 selects visible water reference/perception only. Patz lines 2577-2586 and 2592-2603 source bare non-singular nominative pronouns; lines 2674 and 2682-2686 source inclusive/exclusive and non-singular forms; lines 2838-2845 source yaluy/yinyay; and lines 3043-3049 source nyaji-l 'see' nonpast.",
    "dajali": "Dictionary lines 4510-4518 give dajali as noun 'deep water'. Rule 278 selects visible deep-water reference only.",
    "jalun": "Dictionary lines 7612-7620 give jalun as noun 'sea, large body of water'. Rule 278 selects sea reference only and leaves flood rows closed.",
    "jirka": "Dictionary lines 9803-9813 give jirka as noun 'spring' and commentary reads it as a water source rather than season. Rule 278 selects visible water-source spring only, not season.",
    "jukar": "Dictionary lines 10254-10262 give jukar as noun 'sand'. Rule 278 selects visible sand reference only.",
    "manjal": "Dictionary lines 17832-17840 give manjal as noun 'mountain'. Rule 278 selects visible mountain reference only.",
    "wawubaja": "Dictionary lines 26813-26821 give wawubaja as noun 'river'. Rule 278 selects visible river reference only.",
    "yilki": "Dictionary lines 30031-30039 give yilki as noun 'creek'. Rule 278 selects visible creek reference only.",
    "yiri": "Dictionary lines 30410-30418 give yiri as noun 'waterfall'. Rule 278 selects visible waterfall reference only.",
    "yirri": "Dictionary lines 30507-30515 give yirri as noun 'running water'. Rule 278 selects visible running-water reference only and leaves yirri-damal 'leak' closed.",
}

ROWS = [
    row(1, "We two, not you, see the water here.", "Ngali bana yaluy nyajil.", "ngali bana yaluy nyajil | 1du.EXCL.NOM water here see.NPST", "r278_1du_excl_water_landform_here_see_bana", ["ngali", "bana", "yaluy", "nyajil"], EVIDENCE["bana"]),
    row(2, "We two, not you, see the deep water here.", "Ngali dajali yaluy nyajil.", "ngali dajali yaluy nyajil | 1du.EXCL.NOM deep.water here see.NPST", "r278_1du_excl_water_landform_here_see_dajali", ["ngali", "dajali", "yaluy", "nyajil"], EVIDENCE["dajali"]),
    row(3, "We two, not you, see the sea here.", "Ngali jalun yaluy nyajil.", "ngali jalun yaluy nyajil | 1du.EXCL.NOM sea here see.NPST", "r278_1du_excl_water_landform_here_see_jalun", ["ngali", "jalun", "yaluy", "nyajil"], EVIDENCE["jalun"]),
    row(4, "We two, not you, see the spring here.", "Ngali jirka yaluy nyajil.", "ngali jirka yaluy nyajil | 1du.EXCL.NOM spring here see.NPST", "r278_1du_excl_water_landform_here_see_jirka", ["ngali", "jirka", "yaluy", "nyajil"], EVIDENCE["jirka"]),
    row(5, "We two, not you, see the sand here.", "Ngali jukar yaluy nyajil.", "ngali jukar yaluy nyajil | 1du.EXCL.NOM sand here see.NPST", "r278_1du_excl_water_landform_here_see_jukar", ["ngali", "jukar", "yaluy", "nyajil"], EVIDENCE["jukar"]),
    row(6, "We two, not you, see the mountain here.", "Ngali manjal yaluy nyajil.", "ngali manjal yaluy nyajil | 1du.EXCL.NOM mountain here see.NPST", "r278_1du_excl_water_landform_here_see_manjal", ["ngali", "manjal", "yaluy", "nyajil"], EVIDENCE["manjal"]),
    row(7, "We two, not you, see the river here.", "Ngali wawubaja yaluy nyajil.", "ngali wawubaja yaluy nyajil | 1du.EXCL.NOM river here see.NPST", "r278_1du_excl_water_landform_here_see_wawubaja", ["ngali", "wawubaja", "yaluy", "nyajil"], EVIDENCE["wawubaja"]),
    row(8, "We two, not you, see the creek here.", "Ngali yilki yaluy nyajil.", "ngali yilki yaluy nyajil | 1du.EXCL.NOM creek here see.NPST", "r278_1du_excl_water_landform_here_see_yilki", ["ngali", "yilki", "yaluy", "nyajil"], EVIDENCE["yilki"]),
    row(9, "We two, not you, see the waterfall here.", "Ngali yiri yaluy nyajil.", "ngali yiri yaluy nyajil | 1du.EXCL.NOM waterfall here see.NPST", "r278_1du_excl_water_landform_here_see_yiri", ["ngali", "yiri", "yaluy", "nyajil"], EVIDENCE["yiri"]),
    row(10, "We two, not you, see the running water here.", "Ngali yirri yaluy nyajil.", "ngali yirri yaluy nyajil | 1du.EXCL.NOM running.water here see.NPST", "r278_1du_excl_water_landform_here_see_yirri", ["ngali", "yirri", "yaluy", "nyajil"], EVIDENCE["yirri"]),
    row(11, "You and I see the water there.", "Ngaliny bana yinyay nyajil.", "ngaliny bana yinyay nyajil | 1du.INCL.NOM water there see.NPST", "r278_1du_incl_water_landform_there_see_bana", ["ngaliny", "bana", "yinyay", "nyajil"], EVIDENCE["bana"]),
    row(12, "You and I see the deep water there.", "Ngaliny dajali yinyay nyajil.", "ngaliny dajali yinyay nyajil | 1du.INCL.NOM deep.water there see.NPST", "r278_1du_incl_water_landform_there_see_dajali", ["ngaliny", "dajali", "yinyay", "nyajil"], EVIDENCE["dajali"]),
    row(13, "You and I see the sea there.", "Ngaliny jalun yinyay nyajil.", "ngaliny jalun yinyay nyajil | 1du.INCL.NOM sea there see.NPST", "r278_1du_incl_water_landform_there_see_jalun", ["ngaliny", "jalun", "yinyay", "nyajil"], EVIDENCE["jalun"]),
    row(14, "You and I see the spring there.", "Ngaliny jirka yinyay nyajil.", "ngaliny jirka yinyay nyajil | 1du.INCL.NOM spring there see.NPST", "r278_1du_incl_water_landform_there_see_jirka", ["ngaliny", "jirka", "yinyay", "nyajil"], EVIDENCE["jirka"]),
    row(15, "You and I see the sand there.", "Ngaliny jukar yinyay nyajil.", "ngaliny jukar yinyay nyajil | 1du.INCL.NOM sand there see.NPST", "r278_1du_incl_water_landform_there_see_jukar", ["ngaliny", "jukar", "yinyay", "nyajil"], EVIDENCE["jukar"]),
    row(16, "You and I see the mountain there.", "Ngaliny manjal yinyay nyajil.", "ngaliny manjal yinyay nyajil | 1du.INCL.NOM mountain there see.NPST", "r278_1du_incl_water_landform_there_see_manjal", ["ngaliny", "manjal", "yinyay", "nyajil"], EVIDENCE["manjal"]),
    row(17, "You and I see the river there.", "Ngaliny wawubaja yinyay nyajil.", "ngaliny wawubaja yinyay nyajil | 1du.INCL.NOM river there see.NPST", "r278_1du_incl_water_landform_there_see_wawubaja", ["ngaliny", "wawubaja", "yinyay", "nyajil"], EVIDENCE["wawubaja"]),
    row(18, "You and I see the creek there.", "Ngaliny yilki yinyay nyajil.", "ngaliny yilki yinyay nyajil | 1du.INCL.NOM creek there see.NPST", "r278_1du_incl_water_landform_there_see_yilki", ["ngaliny", "yilki", "yinyay", "nyajil"], EVIDENCE["yilki"]),
    row(19, "You and I see the waterfall there.", "Ngaliny yiri yinyay nyajil.", "ngaliny yiri yinyay nyajil | 1du.INCL.NOM waterfall there see.NPST", "r278_1du_incl_water_landform_there_see_yiri", ["ngaliny", "yiri", "yinyay", "nyajil"], EVIDENCE["yiri"]),
    row(20, "You and I see the running water there.", "Ngaliny yirri yinyay nyajil.", "ngaliny yirri yinyay nyajil | 1du.INCL.NOM running.water there see.NPST", "r278_1du_incl_water_landform_there_see_yirri", ["ngaliny", "yirri", "yinyay", "nyajil"], EVIDENCE["yirri"]),
    row(21, "You two see the water here.", "Yubal bana yaluy nyajil.", "yubal bana yaluy nyajil | 2du.NOM water here see.NPST", "r278_2du_water_landform_here_see_bana", ["yubal", "bana", "yaluy", "nyajil"], EVIDENCE["bana"]),
    row(22, "You two see the deep water here.", "Yubal dajali yaluy nyajil.", "yubal dajali yaluy nyajil | 2du.NOM deep.water here see.NPST", "r278_2du_water_landform_here_see_dajali", ["yubal", "dajali", "yaluy", "nyajil"], EVIDENCE["dajali"]),
    row(23, "You two see the sea here.", "Yubal jalun yaluy nyajil.", "yubal jalun yaluy nyajil | 2du.NOM sea here see.NPST", "r278_2du_water_landform_here_see_jalun", ["yubal", "jalun", "yaluy", "nyajil"], EVIDENCE["jalun"]),
    row(24, "You two see the spring here.", "Yubal jirka yaluy nyajil.", "yubal jirka yaluy nyajil | 2du.NOM spring here see.NPST", "r278_2du_water_landform_here_see_jirka", ["yubal", "jirka", "yaluy", "nyajil"], EVIDENCE["jirka"]),
    row(25, "You two see the sand here.", "Yubal jukar yaluy nyajil.", "yubal jukar yaluy nyajil | 2du.NOM sand here see.NPST", "r278_2du_water_landform_here_see_jukar", ["yubal", "jukar", "yaluy", "nyajil"], EVIDENCE["jukar"]),
    row(26, "You two see the mountain here.", "Yubal manjal yaluy nyajil.", "yubal manjal yaluy nyajil | 2du.NOM mountain here see.NPST", "r278_2du_water_landform_here_see_manjal", ["yubal", "manjal", "yaluy", "nyajil"], EVIDENCE["manjal"]),
    row(27, "You two see the river here.", "Yubal wawubaja yaluy nyajil.", "yubal wawubaja yaluy nyajil | 2du.NOM river here see.NPST", "r278_2du_water_landform_here_see_wawubaja", ["yubal", "wawubaja", "yaluy", "nyajil"], EVIDENCE["wawubaja"]),
    row(28, "You two see the creek here.", "Yubal yilki yaluy nyajil.", "yubal yilki yaluy nyajil | 2du.NOM creek here see.NPST", "r278_2du_water_landform_here_see_yilki", ["yubal", "yilki", "yaluy", "nyajil"], EVIDENCE["yilki"]),
    row(29, "You two see the waterfall here.", "Yubal yiri yaluy nyajil.", "yubal yiri yaluy nyajil | 2du.NOM waterfall here see.NPST", "r278_2du_water_landform_here_see_yiri", ["yubal", "yiri", "yaluy", "nyajil"], EVIDENCE["yiri"]),
    row(30, "You two see the running water here.", "Yubal yirri yaluy nyajil.", "yubal yirri yaluy nyajil | 2du.NOM running.water here see.NPST", "r278_2du_water_landform_here_see_yirri", ["yubal", "yirri", "yaluy", "nyajil"], EVIDENCE["yirri"]),
    row(31, "You all see the water there.", "Yurra bana yinyay nyajil.", "yurra bana yinyay nyajil | 2pl.NOM water there see.NPST", "r278_2pl_water_landform_there_see_bana", ["yurra", "bana", "yinyay", "nyajil"], EVIDENCE["bana"]),
    row(32, "You all see the deep water there.", "Yurra dajali yinyay nyajil.", "yurra dajali yinyay nyajil | 2pl.NOM deep.water there see.NPST", "r278_2pl_water_landform_there_see_dajali", ["yurra", "dajali", "yinyay", "nyajil"], EVIDENCE["dajali"]),
    row(33, "You all see the sea there.", "Yurra jalun yinyay nyajil.", "yurra jalun yinyay nyajil | 2pl.NOM sea there see.NPST", "r278_2pl_water_landform_there_see_jalun", ["yurra", "jalun", "yinyay", "nyajil"], EVIDENCE["jalun"]),
    row(34, "You all see the spring there.", "Yurra jirka yinyay nyajil.", "yurra jirka yinyay nyajil | 2pl.NOM spring there see.NPST", "r278_2pl_water_landform_there_see_jirka", ["yurra", "jirka", "yinyay", "nyajil"], EVIDENCE["jirka"]),
    row(35, "You all see the sand there.", "Yurra jukar yinyay nyajil.", "yurra jukar yinyay nyajil | 2pl.NOM sand there see.NPST", "r278_2pl_water_landform_there_see_jukar", ["yurra", "jukar", "yinyay", "nyajil"], EVIDENCE["jukar"]),
    row(36, "You all see the mountain there.", "Yurra manjal yinyay nyajil.", "yurra manjal yinyay nyajil | 2pl.NOM mountain there see.NPST", "r278_2pl_water_landform_there_see_manjal", ["yurra", "manjal", "yinyay", "nyajil"], EVIDENCE["manjal"]),
    row(37, "You all see the river there.", "Yurra wawubaja yinyay nyajil.", "yurra wawubaja yinyay nyajil | 2pl.NOM river there see.NPST", "r278_2pl_water_landform_there_see_wawubaja", ["yurra", "wawubaja", "yinyay", "nyajil"], EVIDENCE["wawubaja"]),
    row(38, "You all see the creek there.", "Yurra yilki yinyay nyajil.", "yurra yilki yinyay nyajil | 2pl.NOM creek there see.NPST", "r278_2pl_water_landform_there_see_yilki", ["yurra", "yilki", "yinyay", "nyajil"], EVIDENCE["yilki"]),
    row(39, "You all see the waterfall there.", "Yurra yiri yinyay nyajil.", "yurra yiri yinyay nyajil | 2pl.NOM waterfall there see.NPST", "r278_2pl_water_landform_there_see_yiri", ["yurra", "yiri", "yinyay", "nyajil"], EVIDENCE["yiri"]),
    row(40, "You all see the running water there.", "Yurra yirri yinyay nyajil.", "yurra yirri yinyay nyajil | 2pl.NOM running.water there see.NPST", "r278_2pl_water_landform_there_see_yirri", ["yurra", "yirri", "yinyay", "nyajil"], EVIDENCE["yirri"]),
    row(41, "They see the water here.", "Jana bana yaluy nyajil.", "jana bana yaluy nyajil | 3pl.NOM water here see.NPST", "r278_3pl_water_landform_here_see_bana", ["jana", "bana", "yaluy", "nyajil"], EVIDENCE["bana"]),
    row(42, "They see the deep water here.", "Jana dajali yaluy nyajil.", "jana dajali yaluy nyajil | 3pl.NOM deep.water here see.NPST", "r278_3pl_water_landform_here_see_dajali", ["jana", "dajali", "yaluy", "nyajil"], EVIDENCE["dajali"]),
    row(43, "They see the sea here.", "Jana jalun yaluy nyajil.", "jana jalun yaluy nyajil | 3pl.NOM sea here see.NPST", "r278_3pl_water_landform_here_see_jalun", ["jana", "jalun", "yaluy", "nyajil"], EVIDENCE["jalun"]),
    row(44, "They see the spring here.", "Jana jirka yaluy nyajil.", "jana jirka yaluy nyajil | 3pl.NOM spring here see.NPST", "r278_3pl_water_landform_here_see_jirka", ["jana", "jirka", "yaluy", "nyajil"], EVIDENCE["jirka"]),
    row(45, "They see the sand here.", "Jana jukar yaluy nyajil.", "jana jukar yaluy nyajil | 3pl.NOM sand here see.NPST", "r278_3pl_water_landform_here_see_jukar", ["jana", "jukar", "yaluy", "nyajil"], EVIDENCE["jukar"]),
    row(46, "They see the mountain here.", "Jana manjal yaluy nyajil.", "jana manjal yaluy nyajil | 3pl.NOM mountain here see.NPST", "r278_3pl_water_landform_here_see_manjal", ["jana", "manjal", "yaluy", "nyajil"], EVIDENCE["manjal"]),
    row(47, "They see the river here.", "Jana wawubaja yaluy nyajil.", "jana wawubaja yaluy nyajil | 3pl.NOM river here see.NPST", "r278_3pl_water_landform_here_see_wawubaja", ["jana", "wawubaja", "yaluy", "nyajil"], EVIDENCE["wawubaja"]),
    row(48, "They see the creek here.", "Jana yilki yaluy nyajil.", "jana yilki yaluy nyajil | 3pl.NOM creek here see.NPST", "r278_3pl_water_landform_here_see_yilki", ["jana", "yilki", "yaluy", "nyajil"], EVIDENCE["yilki"]),
    row(49, "They see the waterfall here.", "Jana yiri yaluy nyajil.", "jana yiri yaluy nyajil | 3pl.NOM waterfall here see.NPST", "r278_3pl_water_landform_here_see_yiri", ["jana", "yiri", "yaluy", "nyajil"], EVIDENCE["yiri"]),
    row(50, "They see the running water here.", "Jana yirri yaluy nyajil.", "jana yirri yaluy nyajil | 3pl.NOM running.water here see.NPST", "r278_3pl_water_landform_here_see_yirri", ["jana", "yirri", "yaluy", "nyajil"], EVIDENCE["yirri"]),
]


def has_token(low: str, token: str) -> bool:
    return re.search(rf"(?<![a-z-]){re.escape(token)}(?![a-z-])", 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.strip())}\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: set[str] = set()
    for expected, data in enumerate(ROWS, 1):
        seq, english, kuku, analysis, frame, _tier, words_used, loans_used, evidence, confidence = 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")
        if low in seen:
            issues.append(f"{label}: duplicate surface inside batch")
        seen.add(low)
        if not frame.startswith("r278_"):
            issues.append(f"{label}: frame must start with r278_")
        if " | " not in analysis or "see.NPST" not in analysis:
            issues.append(f"{label}: analysis missing gloss separator or see.NPST")
        if json.loads(loans_used):
            issues.append(f"{label}: loans_used must be empty")
        denied = [bad for bad in DENY_KUKU if has_token(low, bad)]
        if denied:
            issues.append(f"{label}: denied Kuku token(s) present: {denied}")
        targets = [head for head in TARGET_HEADS if has_token(low, head)]
        if len(targets) != 1:
            issues.append(f"{label}: expected one 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 re.search(rf"(?<![a-z-]){re.escape(head)}-[a-z]+", low):
                issues.append(f"{label}: target head {head} has forbidden suffix/compound")
        pronouns = [pronoun for pronoun in PRONOUN_SUPPORT if has_token(low, pronoun)]
        if len(pronouns) != 1:
            issues.append(f"{label}: expected one selected bare nominative pronoun, found {pronouns}")
        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 " see " not in f" {english_low} ":
            issues.append(f"{label}: English must use nonpast 'see'")
        if "nyajil" not in words or not low.endswith(" nyajil."):
            issues.append(f"{label}: row must end with nyajil")
        if 1 <= seq <= 10 and (not low.startswith("ngali ") or not low.endswith(" yaluy nyajil.")):
            issues.append(f"{label}: rows 1-10 require Ngali TARGET yaluy nyajil")
        if 11 <= seq <= 20 and (not low.startswith("ngaliny ") or not low.endswith(" yinyay nyajil.")):
            issues.append(f"{label}: rows 11-20 require Ngaliny TARGET yinyay nyajil")
        if 21 <= seq <= 30 and (not low.startswith("yubal ") or not low.endswith(" yaluy nyajil.")):
            issues.append(f"{label}: rows 21-30 require Yubal TARGET yaluy nyajil")
        if 31 <= seq <= 40 and (not low.startswith("yurra ") or not low.endswith(" yinyay nyajil.")):
            issues.append(f"{label}: rows 31-40 require Yurra TARGET yinyay nyajil")
        if 41 <= seq <= 50 and (not low.startswith("jana ") or not low.endswith(" yaluy nyajil.")):
            issues.append(f"{label}: rows 41-50 require Jana TARGET yaluy nyajil")
        if len(low.split()) != 4:
            issues.append(f"{label}: expected four Kuku tokens")
        if any(english_has_denied(english_low, bad) for bad in DENY_ENGLISH):
            issues.append(f"{label}: denied water/landform source-prose or tense field in English")
        if not evidence or not confidence:
            issues.append(f"{label}: missing evidence/confidence")
    return issues


def count_token(db: sqlite3.Connection, token: str) -> int:
    rows = db.execute("SELECT kuku FROM sentences WHERE status IN ('accepted','revised')").fetchall()
    return sum(1 for (kuku,) in rows if has_token(kuku.lower(), token))


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)
    if db.execute("SELECT 1 FROM batches WHERE label=?", (LABEL,)).fetchone():
        raise SystemExit(f"{LABEL} already exists")
    duplicates = []
    for _seq, _english, kuku, *_rest in ROWS:
        hit = db.execute(
            """
            SELECT b.label, s.seq, s.status
            FROM sentences s JOIN batches b ON b.id=s.batch_id
            WHERE lower(s.kuku)=lower(?) AND s.status IN ('accepted','revised','under_review')
            """,
            (kuku,),
        ).fetchone()
        if hit:
            duplicates.append((kuku, hit))
    if duplicates:
        for kuku, hit in duplicates:
            print(f"duplicate: {kuku} -> {hit}")
        raise SystemExit(f"{LABEL}: duplicate preflight failed")

    target_counts = {head: count_token(db, head) for head in TARGET_ORDER}
    support_counts = {head: count_token(db, head) for head in ["ngali", "ngaliny", "yubal", "yurra", "jana", "yaluy", "yinyay", "nyajil"]}
    verified_before, words_before = db.execute(
        """
        SELECT COUNT(*), SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1)
        FROM sentences
        WHERE status IN ('accepted','revised')
        """
    ).fetchone()

    notes = (
        "VISIBLE WATER/LANDFORM NON-SINGULAR NONPAST PRONOUN PERCEPTION SWEEP (Rule 278). 50 under-review rows over "
        "bana water, dajali deep water, jalun sea, jirka spring, jukar sand, manjal mountain, wawubaja river, yilki "
        "creek, yiri waterfall, and yirri running water with bare nominative ngali, ngaliny, yubal, yurra, and jana. "
        "Only bare visual reference/perception rows are opened; pronoun case, water use, travel, route, flood, tide, "
        "place names, source-use, ecology, story, target case, demonstrative case, compounds, productive reduplication, "
        "perception compounds, past/purposive/reduplicated perception drift, and support inflection remain closed."
    )
    cur = db.execute(
        "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
        (LABEL, "Rule 278 under review", "operator-guide-2026-07-09", notes, NOW),
    )
    batch_id = cur.lastrowid
    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", NOW, NOW,
            )
            for seq, english, kuku, analysis, frame, tier, words_used, loans_used, evidence, confidence in ROWS
        ],
    )
    record_event(db, batch_label=LABEL, phase="source", event_type="source-readback", actor="claude-main", detail="Reread dictionary source lines for water/landform targets and Patz non-singular pronoun, demonstrative, and nyaji-l nonpast support before authoring.", artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="planning", event_type="debt-sweep-scope", actor="claude-main", detail="Selected a nonpast counterpart to Rule 277 over the same visible water/landform targets; opened nyajil only as sourced nonpast perception and no water-use, travel, route, place-name, flood/tide, ecology, or event prose.", artifact_path=PROCESS_LOG, created_at=NOW)
    record_event(db, batch_label=LABEL, phase="preflight", event_type="coverage-audit", actor="claude-main", detail=f"Token-aware accepted/revised coverage before banking: targets={target_counts}; support={support_counts}.", 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 the final 50 Rule 278 surfaces returned no accepted, revised, or under-review 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="Static guard passed: 50 rows, one target each, bare non-singular nominative pronouns, bare yaluy/yinyay, final nyajil, no loans, no denied Kuku forms, no water-use/travel/place-name/event/source-prose drift.", command="python3 -m py_compile batch_scripts/bank_320.py && python3 - <<'PY' import bank_320; print(bank_320.scan_static()) PY", output_excerpt="issues 0", artifact_path="batch_scripts/bank_320.py", created_at=NOW)
    record_event(db, batch_label=LABEL, phase="bank", event_type="bank-success", actor="claude-main", detail=f"Banked 50 Rule 278 rows as under_review. Verified total remains {verified_before}; Kuku word count remains {words_before}.", command="python3 batch_scripts/bank_320.py", output_excerpt=f"banked batch-320: 50 rows under_review; verified_before={verified_before}; kuku_words_before={words_before}", artifact_path=PROCESS_LOG, created_at=NOW)
    db.commit()
    print("banked batch-320: 50 rows under_review")
    return 0


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