#!/usr/bin/env python3
"""Bank batch-317: Rule 275 visible sky/celestial past 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-317"
NOW = dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
PROCESS_LOG = "process_logs/batch-317-visible-sky-celestial-past-pronoun-perception-after-rule274-process.md"

TARGET_HEADS = {
    "balbay": "lightning",
    "binyu": "shooting star",
    "dawar": "star",
    "jiri": "sky",
    "kaba": "rain",
    "kija": "moon",
    "kurrinkurrin": "mackerel sky",
    "kurrubijal": "morning star",
    "ngurrban": "cloud",
    "yiril": "rainbow",
}
TARGET_ORDER = [
    "balbay", "binyu", "dawar", "jiri", "kaba",
    "kija", "kurrinkurrin", "kurrubijal", "ngurrban", "yiril",
]
PRONOUN_SUPPORT = {
    "ngayu": "1sg.NOM",
    "yundu": "2sg.NOM",
    "nyulu": "3sg.NOM",
    "bula": "3du.NOM",
    "ngana": "1pl.INC.NOM",
}
SUPPORT = set(PRONOUN_SUPPORT) | {"yaluy", "yinyay", "nyajin"}
DENY_KUKU = {
    "jabulbina", "jarramali", "muyar", "wadil", "wungar", "wungar-dakal", "wungar-daray",
    "wungar-jalamal", "wungar-jarra", "wungar-wandil", "wungar-wangkar", "jiri-bajaku",
    "jirimali", "jirimandi", "jiriwu", "jiriy", "kijandaku", "kijar-kijar", "dalbarrinji",
    "dalbarrin", "balban", "ngurrbal", "ngurrku", "yiringkal", "nyajil", "nyaji-nka",
    "nyaji-nyaku", "nyaji-l-nyaji", "nyajiny", "nganya", "yununy", "yununiny", "nyunguny",
    "nyunguniny", "bulangan", "nganangan", "ngananiny", "ngananka", "ngananda", "nganandamuny",
    "ngananji", "ngananga", "ngaykunku", "yununku", "nyungunku", "ngaykundu", "yunundu",
    "nyungundu", "ngaykundumuny", "yunundumuny", "nyungundumuny", "ngaykunji", "yununji",
    "nyungunji", "ngayku", "yunu", "nyunju", "yinyangka", "yinyayngka", "yinyamba", "yinyamuny",
    "yalungku", "yaluyngku", "yaluymbu", "yaluyundu", "yalamuny", "yalanji",
}
DENY_ENGLISH = (
    "thunder", "wind", "weather", "cold", "storm", "strike", "struck", "spirit", "dead",
    "death", "die", "died", "omen", "presage", "roaring meg", "falls", "place", "story",
    "created", "women", "rain forest", "rainforest", "plant", "ecology", "sun", "sunrise",
    "sunset", "midday", "fine day", "cloudless", "sky-mob", "angel", "heaven", "case",
    "compound", "watch", "look", "listening", "hearing", "event", "use", "uses", "used",
)


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 = {
    "balbay": "Dictionary lines 903-911 give balbay as noun 'lightning' and include a source example about lightning striking. Rule 275 selects visible lightning reference/perception only and leaves strike-event prose closed. Patz lines 2577-2586 and 2592-2603 source bare nominative pronouns; lines 2838-2845 source yaluy/yinyay; lines 2568-2571 give nyaji-ny see-PAST; and lines 3043-3049 source nyaji-l 'see'.",
    "binyu": "Dictionary lines 3066-3074 give binyu as noun 'shooting star'. Rule 275 selects the visible celestial object only and leaves death-spirit and death-omen belief prose closed.",
    "dawar": "Dictionary lines 5377-5382 give dawar as noun 'star'. Rule 275 uses bare star reference only.",
    "jiri": "Dictionary lines 9723-9730 give jiri as noun 'sky' and 'heaven'. Rule 275 selects visible sky only and leaves heaven, fine-day compounds, sky-mob, and place compounds closed.",
    "kaba": "Dictionary lines 11674-11680 give kaba as noun 'rain'. Rule 275 selects visible rain only and leaves rainforest, plant, and ecological prose closed.",
    "kija": "Dictionary lines 14146-14155 give kija as noun 'moon' and Roaring Meg Falls as a moon-story site. Rule 275 selects visible moon only and leaves place/story/creation prose closed.",
    "kurrinkurrin": "Dictionary lines 16409-16417 give kurrinkurrin as noun 'mackeral sky'. Rule 275 normalizes English to mackerel sky and treats the reduplicated form as a lexicalized target, not productive reduplication.",
    "kurrubijal": "Dictionary lines 16560-16567 give kurrubijal as noun 'morning star', the planet Venus as morning star.",
    "ngurrban": "Dictionary lines 23888-23893 give ngurrban as noun 'cloud'. Rule 275 selects cloud reference only.",
    "yiril": "Dictionary lines 30422-30427 give yiril as noun 'rainbow'. Rule 275 selects rainbow reference only.",
}

ROWS = [
    row(1, "I saw the lightning here.", "Ngayu balbay yaluy nyajin.", "ngayu balbay yaluy nyajin | 1sg.NOM lightning here see.PAST", "r275_1sg_visible_sky_here_saw_balbay", ["ngayu", "balbay", "yaluy", "nyajin"], EVIDENCE["balbay"]),
    row(2, "I saw the shooting star here.", "Ngayu binyu yaluy nyajin.", "ngayu binyu yaluy nyajin | 1sg.NOM shooting.star here see.PAST", "r275_1sg_visible_sky_here_saw_binyu", ["ngayu", "binyu", "yaluy", "nyajin"], EVIDENCE["binyu"]),
    row(3, "I saw the star here.", "Ngayu dawar yaluy nyajin.", "ngayu dawar yaluy nyajin | 1sg.NOM star here see.PAST", "r275_1sg_visible_sky_here_saw_dawar", ["ngayu", "dawar", "yaluy", "nyajin"], EVIDENCE["dawar"]),
    row(4, "I saw the sky here.", "Ngayu jiri yaluy nyajin.", "ngayu jiri yaluy nyajin | 1sg.NOM sky here see.PAST", "r275_1sg_visible_sky_here_saw_jiri", ["ngayu", "jiri", "yaluy", "nyajin"], EVIDENCE["jiri"]),
    row(5, "I saw the rain here.", "Ngayu kaba yaluy nyajin.", "ngayu kaba yaluy nyajin | 1sg.NOM rain here see.PAST", "r275_1sg_visible_sky_here_saw_kaba", ["ngayu", "kaba", "yaluy", "nyajin"], EVIDENCE["kaba"]),
    row(6, "I saw the moon here.", "Ngayu kija yaluy nyajin.", "ngayu kija yaluy nyajin | 1sg.NOM moon here see.PAST", "r275_1sg_visible_sky_here_saw_kija", ["ngayu", "kija", "yaluy", "nyajin"], EVIDENCE["kija"]),
    row(7, "I saw the mackerel sky here.", "Ngayu kurrinkurrin yaluy nyajin.", "ngayu kurrinkurrin yaluy nyajin | 1sg.NOM mackerel.sky here see.PAST", "r275_1sg_visible_sky_here_saw_kurrinkurrin", ["ngayu", "kurrinkurrin", "yaluy", "nyajin"], EVIDENCE["kurrinkurrin"]),
    row(8, "I saw the morning star here.", "Ngayu kurrubijal yaluy nyajin.", "ngayu kurrubijal yaluy nyajin | 1sg.NOM morning.star here see.PAST", "r275_1sg_visible_sky_here_saw_kurrubijal", ["ngayu", "kurrubijal", "yaluy", "nyajin"], EVIDENCE["kurrubijal"]),
    row(9, "I saw the cloud here.", "Ngayu ngurrban yaluy nyajin.", "ngayu ngurrban yaluy nyajin | 1sg.NOM cloud here see.PAST", "r275_1sg_visible_sky_here_saw_ngurrban", ["ngayu", "ngurrban", "yaluy", "nyajin"], EVIDENCE["ngurrban"]),
    row(10, "I saw the rainbow here.", "Ngayu yiril yaluy nyajin.", "ngayu yiril yaluy nyajin | 1sg.NOM rainbow here see.PAST", "r275_1sg_visible_sky_here_saw_yiril", ["ngayu", "yiril", "yaluy", "nyajin"], EVIDENCE["yiril"]),
    row(11, "You saw the lightning there.", "Yundu balbay yinyay nyajin.", "yundu balbay yinyay nyajin | 2sg.NOM lightning there see.PAST", "r275_2sg_visible_sky_there_saw_balbay", ["yundu", "balbay", "yinyay", "nyajin"], EVIDENCE["balbay"]),
    row(12, "You saw the shooting star there.", "Yundu binyu yinyay nyajin.", "yundu binyu yinyay nyajin | 2sg.NOM shooting.star there see.PAST", "r275_2sg_visible_sky_there_saw_binyu", ["yundu", "binyu", "yinyay", "nyajin"], EVIDENCE["binyu"]),
    row(13, "You saw the star there.", "Yundu dawar yinyay nyajin.", "yundu dawar yinyay nyajin | 2sg.NOM star there see.PAST", "r275_2sg_visible_sky_there_saw_dawar", ["yundu", "dawar", "yinyay", "nyajin"], EVIDENCE["dawar"]),
    row(14, "You saw the sky there.", "Yundu jiri yinyay nyajin.", "yundu jiri yinyay nyajin | 2sg.NOM sky there see.PAST", "r275_2sg_visible_sky_there_saw_jiri", ["yundu", "jiri", "yinyay", "nyajin"], EVIDENCE["jiri"]),
    row(15, "You saw the rain there.", "Yundu kaba yinyay nyajin.", "yundu kaba yinyay nyajin | 2sg.NOM rain there see.PAST", "r275_2sg_visible_sky_there_saw_kaba", ["yundu", "kaba", "yinyay", "nyajin"], EVIDENCE["kaba"]),
    row(16, "You saw the moon there.", "Yundu kija yinyay nyajin.", "yundu kija yinyay nyajin | 2sg.NOM moon there see.PAST", "r275_2sg_visible_sky_there_saw_kija", ["yundu", "kija", "yinyay", "nyajin"], EVIDENCE["kija"]),
    row(17, "You saw the mackerel sky there.", "Yundu kurrinkurrin yinyay nyajin.", "yundu kurrinkurrin yinyay nyajin | 2sg.NOM mackerel.sky there see.PAST", "r275_2sg_visible_sky_there_saw_kurrinkurrin", ["yundu", "kurrinkurrin", "yinyay", "nyajin"], EVIDENCE["kurrinkurrin"]),
    row(18, "You saw the morning star there.", "Yundu kurrubijal yinyay nyajin.", "yundu kurrubijal yinyay nyajin | 2sg.NOM morning.star there see.PAST", "r275_2sg_visible_sky_there_saw_kurrubijal", ["yundu", "kurrubijal", "yinyay", "nyajin"], EVIDENCE["kurrubijal"]),
    row(19, "You saw the cloud there.", "Yundu ngurrban yinyay nyajin.", "yundu ngurrban yinyay nyajin | 2sg.NOM cloud there see.PAST", "r275_2sg_visible_sky_there_saw_ngurrban", ["yundu", "ngurrban", "yinyay", "nyajin"], EVIDENCE["ngurrban"]),
    row(20, "You saw the rainbow there.", "Yundu yiril yinyay nyajin.", "yundu yiril yinyay nyajin | 2sg.NOM rainbow there see.PAST", "r275_2sg_visible_sky_there_saw_yiril", ["yundu", "yiril", "yinyay", "nyajin"], EVIDENCE["yiril"]),
    row(21, "He saw the lightning here.", "Nyulu balbay yaluy nyajin.", "nyulu balbay yaluy nyajin | 3sg.NOM lightning here see.PAST", "r275_3sg_visible_sky_here_saw_balbay", ["nyulu", "balbay", "yaluy", "nyajin"], EVIDENCE["balbay"]),
    row(22, "He saw the shooting star here.", "Nyulu binyu yaluy nyajin.", "nyulu binyu yaluy nyajin | 3sg.NOM shooting.star here see.PAST", "r275_3sg_visible_sky_here_saw_binyu", ["nyulu", "binyu", "yaluy", "nyajin"], EVIDENCE["binyu"]),
    row(23, "He saw the star here.", "Nyulu dawar yaluy nyajin.", "nyulu dawar yaluy nyajin | 3sg.NOM star here see.PAST", "r275_3sg_visible_sky_here_saw_dawar", ["nyulu", "dawar", "yaluy", "nyajin"], EVIDENCE["dawar"]),
    row(24, "He saw the sky here.", "Nyulu jiri yaluy nyajin.", "nyulu jiri yaluy nyajin | 3sg.NOM sky here see.PAST", "r275_3sg_visible_sky_here_saw_jiri", ["nyulu", "jiri", "yaluy", "nyajin"], EVIDENCE["jiri"]),
    row(25, "He saw the rain here.", "Nyulu kaba yaluy nyajin.", "nyulu kaba yaluy nyajin | 3sg.NOM rain here see.PAST", "r275_3sg_visible_sky_here_saw_kaba", ["nyulu", "kaba", "yaluy", "nyajin"], EVIDENCE["kaba"]),
    row(26, "He saw the moon here.", "Nyulu kija yaluy nyajin.", "nyulu kija yaluy nyajin | 3sg.NOM moon here see.PAST", "r275_3sg_visible_sky_here_saw_kija", ["nyulu", "kija", "yaluy", "nyajin"], EVIDENCE["kija"]),
    row(27, "He saw the mackerel sky here.", "Nyulu kurrinkurrin yaluy nyajin.", "nyulu kurrinkurrin yaluy nyajin | 3sg.NOM mackerel.sky here see.PAST", "r275_3sg_visible_sky_here_saw_kurrinkurrin", ["nyulu", "kurrinkurrin", "yaluy", "nyajin"], EVIDENCE["kurrinkurrin"]),
    row(28, "He saw the morning star here.", "Nyulu kurrubijal yaluy nyajin.", "nyulu kurrubijal yaluy nyajin | 3sg.NOM morning.star here see.PAST", "r275_3sg_visible_sky_here_saw_kurrubijal", ["nyulu", "kurrubijal", "yaluy", "nyajin"], EVIDENCE["kurrubijal"]),
    row(29, "He saw the cloud here.", "Nyulu ngurrban yaluy nyajin.", "nyulu ngurrban yaluy nyajin | 3sg.NOM cloud here see.PAST", "r275_3sg_visible_sky_here_saw_ngurrban", ["nyulu", "ngurrban", "yaluy", "nyajin"], EVIDENCE["ngurrban"]),
    row(30, "He saw the rainbow here.", "Nyulu yiril yaluy nyajin.", "nyulu yiril yaluy nyajin | 3sg.NOM rainbow here see.PAST", "r275_3sg_visible_sky_here_saw_yiril", ["nyulu", "yiril", "yaluy", "nyajin"], EVIDENCE["yiril"]),
    row(31, "The two of them saw the lightning there.", "Bula balbay yinyay nyajin.", "bula balbay yinyay nyajin | 3du.NOM lightning there see.PAST", "r275_3du_visible_sky_there_saw_balbay", ["bula", "balbay", "yinyay", "nyajin"], EVIDENCE["balbay"]),
    row(32, "The two of them saw the shooting star there.", "Bula binyu yinyay nyajin.", "bula binyu yinyay nyajin | 3du.NOM shooting.star there see.PAST", "r275_3du_visible_sky_there_saw_binyu", ["bula", "binyu", "yinyay", "nyajin"], EVIDENCE["binyu"]),
    row(33, "The two of them saw the star there.", "Bula dawar yinyay nyajin.", "bula dawar yinyay nyajin | 3du.NOM star there see.PAST", "r275_3du_visible_sky_there_saw_dawar", ["bula", "dawar", "yinyay", "nyajin"], EVIDENCE["dawar"]),
    row(34, "The two of them saw the sky there.", "Bula jiri yinyay nyajin.", "bula jiri yinyay nyajin | 3du.NOM sky there see.PAST", "r275_3du_visible_sky_there_saw_jiri", ["bula", "jiri", "yinyay", "nyajin"], EVIDENCE["jiri"]),
    row(35, "The two of them saw the rain there.", "Bula kaba yinyay nyajin.", "bula kaba yinyay nyajin | 3du.NOM rain there see.PAST", "r275_3du_visible_sky_there_saw_kaba", ["bula", "kaba", "yinyay", "nyajin"], EVIDENCE["kaba"]),
    row(36, "The two of them saw the moon there.", "Bula kija yinyay nyajin.", "bula kija yinyay nyajin | 3du.NOM moon there see.PAST", "r275_3du_visible_sky_there_saw_kija", ["bula", "kija", "yinyay", "nyajin"], EVIDENCE["kija"]),
    row(37, "The two of them saw the mackerel sky there.", "Bula kurrinkurrin yinyay nyajin.", "bula kurrinkurrin yinyay nyajin | 3du.NOM mackerel.sky there see.PAST", "r275_3du_visible_sky_there_saw_kurrinkurrin", ["bula", "kurrinkurrin", "yinyay", "nyajin"], EVIDENCE["kurrinkurrin"]),
    row(38, "The two of them saw the morning star there.", "Bula kurrubijal yinyay nyajin.", "bula kurrubijal yinyay nyajin | 3du.NOM morning.star there see.PAST", "r275_3du_visible_sky_there_saw_kurrubijal", ["bula", "kurrubijal", "yinyay", "nyajin"], EVIDENCE["kurrubijal"]),
    row(39, "The two of them saw the cloud there.", "Bula ngurrban yinyay nyajin.", "bula ngurrban yinyay nyajin | 3du.NOM cloud there see.PAST", "r275_3du_visible_sky_there_saw_ngurrban", ["bula", "ngurrban", "yinyay", "nyajin"], EVIDENCE["ngurrban"]),
    row(40, "The two of them saw the rainbow there.", "Bula yiril yinyay nyajin.", "bula yiril yinyay nyajin | 3du.NOM rainbow there see.PAST", "r275_3du_visible_sky_there_saw_yiril", ["bula", "yiril", "yinyay", "nyajin"], EVIDENCE["yiril"]),
    row(41, "We saw the lightning there.", "Ngana balbay yinyay nyajin.", "ngana balbay yinyay nyajin | 1pl.INC.NOM lightning there see.PAST", "r275_1pl_incl_visible_sky_there_saw_balbay", ["ngana", "balbay", "yinyay", "nyajin"], EVIDENCE["balbay"]),
    row(42, "We saw the shooting star there.", "Ngana binyu yinyay nyajin.", "ngana binyu yinyay nyajin | 1pl.INC.NOM shooting.star there see.PAST", "r275_1pl_incl_visible_sky_there_saw_binyu", ["ngana", "binyu", "yinyay", "nyajin"], EVIDENCE["binyu"]),
    row(43, "We saw the star there.", "Ngana dawar yinyay nyajin.", "ngana dawar yinyay nyajin | 1pl.INC.NOM star there see.PAST", "r275_1pl_incl_visible_sky_there_saw_dawar", ["ngana", "dawar", "yinyay", "nyajin"], EVIDENCE["dawar"]),
    row(44, "We saw the sky there.", "Ngana jiri yinyay nyajin.", "ngana jiri yinyay nyajin | 1pl.INC.NOM sky there see.PAST", "r275_1pl_incl_visible_sky_there_saw_jiri", ["ngana", "jiri", "yinyay", "nyajin"], EVIDENCE["jiri"]),
    row(45, "We saw the rain there.", "Ngana kaba yinyay nyajin.", "ngana kaba yinyay nyajin | 1pl.INC.NOM rain there see.PAST", "r275_1pl_incl_visible_sky_there_saw_kaba", ["ngana", "kaba", "yinyay", "nyajin"], EVIDENCE["kaba"]),
    row(46, "We saw the moon there.", "Ngana kija yinyay nyajin.", "ngana kija yinyay nyajin | 1pl.INC.NOM moon there see.PAST", "r275_1pl_incl_visible_sky_there_saw_kija", ["ngana", "kija", "yinyay", "nyajin"], EVIDENCE["kija"]),
    row(47, "We saw the mackerel sky there.", "Ngana kurrinkurrin yinyay nyajin.", "ngana kurrinkurrin yinyay nyajin | 1pl.INC.NOM mackerel.sky there see.PAST", "r275_1pl_incl_visible_sky_there_saw_kurrinkurrin", ["ngana", "kurrinkurrin", "yinyay", "nyajin"], EVIDENCE["kurrinkurrin"]),
    row(48, "We saw the morning star there.", "Ngana kurrubijal yinyay nyajin.", "ngana kurrubijal yinyay nyajin | 1pl.INC.NOM morning.star there see.PAST", "r275_1pl_incl_visible_sky_there_saw_kurrubijal", ["ngana", "kurrubijal", "yinyay", "nyajin"], EVIDENCE["kurrubijal"]),
    row(49, "We saw the cloud there.", "Ngana ngurrban yinyay nyajin.", "ngana ngurrban yinyay nyajin | 1pl.INC.NOM cloud there see.PAST", "r275_1pl_incl_visible_sky_there_saw_ngurrban", ["ngana", "ngurrban", "yinyay", "nyajin"], EVIDENCE["ngurrban"]),
    row(50, "We saw the rainbow there.", "Ngana yiril yinyay nyajin.", "ngana yiril yinyay nyajin | 1pl.INC.NOM rainbow there see.PAST", "r275_1pl_incl_visible_sky_there_saw_yiril", ["ngana", "yiril", "yinyay", "nyajin"], EVIDENCE["yiril"]),
]


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("r275_"):
            issues.append(f"{label}: frame must start with r275_")
        if " | " not in analysis or "see.PAST" not in analysis:
            issues.append(f"{label}: analysis missing gloss separator or see.PAST")
        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 " saw " not in f" {english_low} ":
            issues.append(f"{label}: English must use past 'saw'")
        if "nyajin" not in words or not low.endswith(" nyajin."):
            issues.append(f"{label}: row must end with nyajin")
        if 1 <= seq <= 10 and (not low.startswith("ngayu ") or not low.endswith(" yaluy nyajin.")):
            issues.append(f"{label}: rows 1-10 require Ngayu TARGET yaluy nyajin")
        if 11 <= seq <= 20 and (not low.startswith("yundu ") or not low.endswith(" yinyay nyajin.")):
            issues.append(f"{label}: rows 11-20 require Yundu TARGET yinyay nyajin")
        if 21 <= seq <= 30 and (not low.startswith("nyulu ") or not low.endswith(" yaluy nyajin.")):
            issues.append(f"{label}: rows 21-30 require Nyulu TARGET yaluy nyajin")
        if 31 <= seq <= 40 and (not low.startswith("bula ") or not low.endswith(" yinyay nyajin.")):
            issues.append(f"{label}: rows 31-40 require Bula TARGET yinyay nyajin")
        if 41 <= seq <= 50 and (not low.startswith("ngana ") or not low.endswith(" yinyay nyajin.")):
            issues.append(f"{label}: rows 41-50 require Ngana TARGET yinyay nyajin")
        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 sky/weather source-prose 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 ["ngayu", "yundu", "nyulu", "bula", "ngana", "yaluy", "yinyay", "nyajin"]}
    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 SKY/CELESTIAL PAST PRONOUN PERCEPTION SWEEP (Rule 275). 50 under-review rows over "
        "balbay lightning, binyu shooting star, dawar star, jiri sky, kaba rain, kija moon, "
        "kurrinkurrin mackerel sky, kurrubijal morning star, ngurrban cloud, and yiril rainbow. "
        "Only bare visual reference/perception rows are opened; thunder, wind, weather-entering, cold, storms, "
        "lightning-strike events, death-spirit/death-omen prose, moon place/story prose, rainforest/ecology, "
        "sun/sunrise/sunset/midday compounds, target case, pronoun case, demonstrative case, target compounds, "
        "productive reduplication, perception compounds, and support inflection remain closed."
    )
    cur = db.execute(
        "INSERT INTO batches(label, cheatsheet_version, guide_version, notes, created_at) VALUES(?,?,?,?,?)",
        (LABEL, "Rule 275 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="Read dictionary entries for balbay, binyu, dawar, jiri, kaba, kija, kurrinkurrin, kurrubijal, ngurrban, and yiril plus Patz pronoun, demonstrative, and nyaji-l/nyaji-ny 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 visible sky/celestial targets only because they fit a visual nyajin frame; excluded wungar after a duplicate preflight collision and excluded thunder, wind, and weather because they are not safe visual 'saw' targets.",
        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="Initial grid found one collision with Ngana wungar yinyay nyajin. from Batch 195; final 50 Rule 275 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 nominative pronouns, bare yaluy/yinyay, final nyajin, no loans, no denied Kuku forms, no thunder/wind/weather/sun/story/event/source-prose drift.",
        command="python3 -m py_compile batch_scripts/bank_317.py && python3 - <<'PY' import bank_317; print(bank_317.scan_static()) PY",
        output_excerpt="issues 0",
        artifact_path="batch_scripts/bank_317.py",
        created_at=NOW,
    )
    record_event(
        db,
        batch_label=LABEL,
        phase="bank",
        event_type="bank-success",
        actor="claude-main",
        detail=f"Banked 50 Rule 275 rows as under_review. Verified total remains {verified_before}; Kuku word count remains {words_before}.",
        command="python3 batch_scripts/bank_317.py",
        output_excerpt=f"banked batch-317: 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-317: 50 rows under_review")
    return 0


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