#!/usr/bin/env python3
"""Bank Batch 397: Rule 355 complementary fish-set-F singular/third past perception."""
from __future__ import annotations
import ast,datetime as dt,json,re,sqlite3
from pathlib import Path
from bank_396 import DB,DENY_ENGLISH,DENY_KUKU,EVIDENCE as BASE_EVIDENCE,SUPPORT as BASE_SUPPORT,TARGET_ORDER,LABELS,GLOSSES,CONF,count_words,source_audit
from process_ledger import ensure_schema,record_event
LABEL="batch-397";NOW=dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00","Z");LOG="process_logs/batch-397-complementary-linter-clean-fish-set-f-singular-third-past-perception-after-rule354-process.md"
EXPECTED_TARGET={"kalngkan":10,"mangkurr-mangkurr":10,"dijarr":11,"julaji":11,"mawul":13,"balarri":29,"bayil":26,"dalmbal":26,"barrbal":31,"jalkay":31}
EXPECTED_SUPPORT={"ngayu":1531,"yundu":1127,"nyulu":802,"bula":598,"jana":1303,"yaluy":4762,"yinyay":4924,"nyajin":2800}
FRAMES=[("ngayu","yinyay","I saw","there","1sg"),("yundu","yaluy","You saw","here","2sg"),("nyulu","yinyay","He saw","there","3sg"),("bula","yaluy","The two of them saw","here","3du"),("jana","yinyay","They saw","there","3pl")];OPP={"yaluy":"yinyay","yinyay":"yaluy"}
TARGET_EVIDENCE={t:f"{BASE_EVIDENCE[t]} Rule355 changes deictic location only and retains every Rule354 source and sense boundary." for t in TARGET_ORDER};SUPPORT=f"{BASE_SUPPORT} Rule355 exactly complements Rule354."
def row(seq,en,ku,an,fr,words):
 t=words[1];return(seq,en,ku,an,fr,3,json.dumps(words,ensure_ascii=False),json.dumps([],ensure_ascii=False),f"{TARGET_EVIDENCE[t]} {SUPPORT}",CONF[t])
ROWS=[
row(1,"I saw the kalngkan small mullet there.","Ngayu kalngkan yinyay nyajin.","ngayu kalngkan yinyay nyajin | 1sg.NOM kalngkan.small-mullet there see.PST","r355_1sg_there_fish_f_kalngkan",["ngayu","kalngkan","yinyay","nyajin"]),
row(2,"I saw the mangkurr-mangkurr garfish there.","Ngayu mangkurr-mangkurr yinyay nyajin.","ngayu mangkurr-mangkurr yinyay nyajin | 1sg.NOM mangkurr-mangkurr.garfish there see.PST","r355_1sg_there_fish_f_mangkurr",["ngayu","mangkurr-mangkurr","yinyay","nyajin"]),
row(3,"I saw the dijarr freshwater perch there.","Ngayu dijarr yinyay nyajin.","ngayu dijarr yinyay nyajin | 1sg.NOM dijarr.freshwater-perch there see.PST","r355_1sg_there_fish_f_dijarr",["ngayu","dijarr","yinyay","nyajin"]),
row(4,"I saw the julaji freshwater catfish there.","Ngayu julaji yinyay nyajin.","ngayu julaji yinyay nyajin | 1sg.NOM julaji.freshwater-catfish there see.PST","r355_1sg_there_fish_f_julaji",["ngayu","julaji","yinyay","nyajin"]),
row(5,"I saw the mawul freshwater eel there.","Ngayu mawul yinyay nyajin.","ngayu mawul yinyay nyajin | 1sg.NOM mawul.freshwater-eel there see.PST","r355_1sg_there_fish_f_mawul",["ngayu","mawul","yinyay","nyajin"]),
row(6,"I saw the balarri queenfish there.","Ngayu balarri yinyay nyajin.","ngayu balarri yinyay nyajin | 1sg.NOM balarri.queenfish there see.PST","r355_1sg_there_fish_f_balarri",["ngayu","balarri","yinyay","nyajin"]),
row(7,"I saw the bayil jungle perch there.","Ngayu bayil yinyay nyajin.","ngayu bayil yinyay nyajin | 1sg.NOM bayil.jungle-perch there see.PST","r355_1sg_there_fish_f_bayil",["ngayu","bayil","yinyay","nyajin"]),
row(8,"I saw the dalmbal flathead there.","Ngayu dalmbal yinyay nyajin.","ngayu dalmbal yinyay nyajin | 1sg.NOM dalmbal.flathead there see.PST","r355_1sg_there_fish_f_dalmbal",["ngayu","dalmbal","yinyay","nyajin"]),
row(9,"I saw the barrbal black bream there.","Ngayu barrbal yinyay nyajin.","ngayu barrbal yinyay nyajin | 1sg.NOM barrbal.black-bream there see.PST","r355_1sg_there_fish_f_barrbal",["ngayu","barrbal","yinyay","nyajin"]),
row(10,"I saw the jalkay salmon there.","Ngayu jalkay yinyay nyajin.","ngayu jalkay yinyay nyajin | 1sg.NOM jalkay.salmon there see.PST","r355_1sg_there_fish_f_jalkay",["ngayu","jalkay","yinyay","nyajin"]),
row(11,"You saw the kalngkan small mullet here.","Yundu kalngkan yaluy nyajin.","yundu kalngkan yaluy nyajin | 2sg.NOM kalngkan.small-mullet here see.PST","r355_2sg_here_fish_f_kalngkan",["yundu","kalngkan","yaluy","nyajin"]),
row(12,"You saw the mangkurr-mangkurr garfish here.","Yundu mangkurr-mangkurr yaluy nyajin.","yundu mangkurr-mangkurr yaluy nyajin | 2sg.NOM mangkurr-mangkurr.garfish here see.PST","r355_2sg_here_fish_f_mangkurr",["yundu","mangkurr-mangkurr","yaluy","nyajin"]),
row(13,"You saw the dijarr freshwater perch here.","Yundu dijarr yaluy nyajin.","yundu dijarr yaluy nyajin | 2sg.NOM dijarr.freshwater-perch here see.PST","r355_2sg_here_fish_f_dijarr",["yundu","dijarr","yaluy","nyajin"]),
row(14,"You saw the julaji freshwater catfish here.","Yundu julaji yaluy nyajin.","yundu julaji yaluy nyajin | 2sg.NOM julaji.freshwater-catfish here see.PST","r355_2sg_here_fish_f_julaji",["yundu","julaji","yaluy","nyajin"]),
row(15,"You saw the mawul freshwater eel here.","Yundu mawul yaluy nyajin.","yundu mawul yaluy nyajin | 2sg.NOM mawul.freshwater-eel here see.PST","r355_2sg_here_fish_f_mawul",["yundu","mawul","yaluy","nyajin"]),
row(16,"You saw the balarri queenfish here.","Yundu balarri yaluy nyajin.","yundu balarri yaluy nyajin | 2sg.NOM balarri.queenfish here see.PST","r355_2sg_here_fish_f_balarri",["yundu","balarri","yaluy","nyajin"]),
row(17,"You saw the bayil jungle perch here.","Yundu bayil yaluy nyajin.","yundu bayil yaluy nyajin | 2sg.NOM bayil.jungle-perch here see.PST","r355_2sg_here_fish_f_bayil",["yundu","bayil","yaluy","nyajin"]),
row(18,"You saw the dalmbal flathead here.","Yundu dalmbal yaluy nyajin.","yundu dalmbal yaluy nyajin | 2sg.NOM dalmbal.flathead here see.PST","r355_2sg_here_fish_f_dalmbal",["yundu","dalmbal","yaluy","nyajin"]),
row(19,"You saw the barrbal black bream here.","Yundu barrbal yaluy nyajin.","yundu barrbal yaluy nyajin | 2sg.NOM barrbal.black-bream here see.PST","r355_2sg_here_fish_f_barrbal",["yundu","barrbal","yaluy","nyajin"]),
row(20,"You saw the jalkay salmon here.","Yundu jalkay yaluy nyajin.","yundu jalkay yaluy nyajin | 2sg.NOM jalkay.salmon here see.PST","r355_2sg_here_fish_f_jalkay",["yundu","jalkay","yaluy","nyajin"]),
row(21,"He saw the kalngkan small mullet there.","Nyulu kalngkan yinyay nyajin.","nyulu kalngkan yinyay nyajin | 3sg.NOM kalngkan.small-mullet there see.PST","r355_3sg_there_fish_f_kalngkan",["nyulu","kalngkan","yinyay","nyajin"]),
row(22,"He saw the mangkurr-mangkurr garfish there.","Nyulu mangkurr-mangkurr yinyay nyajin.","nyulu mangkurr-mangkurr yinyay nyajin | 3sg.NOM mangkurr-mangkurr.garfish there see.PST","r355_3sg_there_fish_f_mangkurr",["nyulu","mangkurr-mangkurr","yinyay","nyajin"]),
row(23,"He saw the dijarr freshwater perch there.","Nyulu dijarr yinyay nyajin.","nyulu dijarr yinyay nyajin | 3sg.NOM dijarr.freshwater-perch there see.PST","r355_3sg_there_fish_f_dijarr",["nyulu","dijarr","yinyay","nyajin"]),
row(24,"He saw the julaji freshwater catfish there.","Nyulu julaji yinyay nyajin.","nyulu julaji yinyay nyajin | 3sg.NOM julaji.freshwater-catfish there see.PST","r355_3sg_there_fish_f_julaji",["nyulu","julaji","yinyay","nyajin"]),
row(25,"He saw the mawul freshwater eel there.","Nyulu mawul yinyay nyajin.","nyulu mawul yinyay nyajin | 3sg.NOM mawul.freshwater-eel there see.PST","r355_3sg_there_fish_f_mawul",["nyulu","mawul","yinyay","nyajin"]),
row(26,"He saw the balarri queenfish there.","Nyulu balarri yinyay nyajin.","nyulu balarri yinyay nyajin | 3sg.NOM balarri.queenfish there see.PST","r355_3sg_there_fish_f_balarri",["nyulu","balarri","yinyay","nyajin"]),
row(27,"He saw the bayil jungle perch there.","Nyulu bayil yinyay nyajin.","nyulu bayil yinyay nyajin | 3sg.NOM bayil.jungle-perch there see.PST","r355_3sg_there_fish_f_bayil",["nyulu","bayil","yinyay","nyajin"]),
row(28,"He saw the dalmbal flathead there.","Nyulu dalmbal yinyay nyajin.","nyulu dalmbal yinyay nyajin | 3sg.NOM dalmbal.flathead there see.PST","r355_3sg_there_fish_f_dalmbal",["nyulu","dalmbal","yinyay","nyajin"]),
row(29,"He saw the barrbal black bream there.","Nyulu barrbal yinyay nyajin.","nyulu barrbal yinyay nyajin | 3sg.NOM barrbal.black-bream there see.PST","r355_3sg_there_fish_f_barrbal",["nyulu","barrbal","yinyay","nyajin"]),
row(30,"He saw the jalkay salmon there.","Nyulu jalkay yinyay nyajin.","nyulu jalkay yinyay nyajin | 3sg.NOM jalkay.salmon there see.PST","r355_3sg_there_fish_f_jalkay",["nyulu","jalkay","yinyay","nyajin"]),
row(31,"The two of them saw the kalngkan small mullet here.","Bula kalngkan yaluy nyajin.","bula kalngkan yaluy nyajin | 3du.NOM kalngkan.small-mullet here see.PST","r355_3du_here_fish_f_kalngkan",["bula","kalngkan","yaluy","nyajin"]),
row(32,"The two of them saw the mangkurr-mangkurr garfish here.","Bula mangkurr-mangkurr yaluy nyajin.","bula mangkurr-mangkurr yaluy nyajin | 3du.NOM mangkurr-mangkurr.garfish here see.PST","r355_3du_here_fish_f_mangkurr",["bula","mangkurr-mangkurr","yaluy","nyajin"]),
row(33,"The two of them saw the dijarr freshwater perch here.","Bula dijarr yaluy nyajin.","bula dijarr yaluy nyajin | 3du.NOM dijarr.freshwater-perch here see.PST","r355_3du_here_fish_f_dijarr",["bula","dijarr","yaluy","nyajin"]),
row(34,"The two of them saw the julaji freshwater catfish here.","Bula julaji yaluy nyajin.","bula julaji yaluy nyajin | 3du.NOM julaji.freshwater-catfish here see.PST","r355_3du_here_fish_f_julaji",["bula","julaji","yaluy","nyajin"]),
row(35,"The two of them saw the mawul freshwater eel here.","Bula mawul yaluy nyajin.","bula mawul yaluy nyajin | 3du.NOM mawul.freshwater-eel here see.PST","r355_3du_here_fish_f_mawul",["bula","mawul","yaluy","nyajin"]),
row(36,"The two of them saw the balarri queenfish here.","Bula balarri yaluy nyajin.","bula balarri yaluy nyajin | 3du.NOM balarri.queenfish here see.PST","r355_3du_here_fish_f_balarri",["bula","balarri","yaluy","nyajin"]),
row(37,"The two of them saw the bayil jungle perch here.","Bula bayil yaluy nyajin.","bula bayil yaluy nyajin | 3du.NOM bayil.jungle-perch here see.PST","r355_3du_here_fish_f_bayil",["bula","bayil","yaluy","nyajin"]),
row(38,"The two of them saw the dalmbal flathead here.","Bula dalmbal yaluy nyajin.","bula dalmbal yaluy nyajin | 3du.NOM dalmbal.flathead here see.PST","r355_3du_here_fish_f_dalmbal",["bula","dalmbal","yaluy","nyajin"]),
row(39,"The two of them saw the barrbal black bream here.","Bula barrbal yaluy nyajin.","bula barrbal yaluy nyajin | 3du.NOM barrbal.black-bream here see.PST","r355_3du_here_fish_f_barrbal",["bula","barrbal","yaluy","nyajin"]),
row(40,"The two of them saw the jalkay salmon here.","Bula jalkay yaluy nyajin.","bula jalkay yaluy nyajin | 3du.NOM jalkay.salmon here see.PST","r355_3du_here_fish_f_jalkay",["bula","jalkay","yaluy","nyajin"]),
row(41,"They saw the kalngkan small mullet there.","Jana kalngkan yinyay nyajin.","jana kalngkan yinyay nyajin | 3pl.NOM kalngkan.small-mullet there see.PST","r355_3pl_there_fish_f_kalngkan",["jana","kalngkan","yinyay","nyajin"]),
row(42,"They saw the mangkurr-mangkurr garfish there.","Jana mangkurr-mangkurr yinyay nyajin.","jana mangkurr-mangkurr yinyay nyajin | 3pl.NOM mangkurr-mangkurr.garfish there see.PST","r355_3pl_there_fish_f_mangkurr",["jana","mangkurr-mangkurr","yinyay","nyajin"]),
row(43,"They saw the dijarr freshwater perch there.","Jana dijarr yinyay nyajin.","jana dijarr yinyay nyajin | 3pl.NOM dijarr.freshwater-perch there see.PST","r355_3pl_there_fish_f_dijarr",["jana","dijarr","yinyay","nyajin"]),
row(44,"They saw the julaji freshwater catfish there.","Jana julaji yinyay nyajin.","jana julaji yinyay nyajin | 3pl.NOM julaji.freshwater-catfish there see.PST","r355_3pl_there_fish_f_julaji",["jana","julaji","yinyay","nyajin"]),
row(45,"They saw the mawul freshwater eel there.","Jana mawul yinyay nyajin.","jana mawul yinyay nyajin | 3pl.NOM mawul.freshwater-eel there see.PST","r355_3pl_there_fish_f_mawul",["jana","mawul","yinyay","nyajin"]),
row(46,"They saw the balarri queenfish there.","Jana balarri yinyay nyajin.","jana balarri yinyay nyajin | 3pl.NOM balarri.queenfish there see.PST","r355_3pl_there_fish_f_balarri",["jana","balarri","yinyay","nyajin"]),
row(47,"They saw the bayil jungle perch there.","Jana bayil yinyay nyajin.","jana bayil yinyay nyajin | 3pl.NOM bayil.jungle-perch there see.PST","r355_3pl_there_fish_f_bayil",["jana","bayil","yinyay","nyajin"]),
row(48,"They saw the dalmbal flathead there.","Jana dalmbal yinyay nyajin.","jana dalmbal yinyay nyajin | 3pl.NOM dalmbal.flathead there see.PST","r355_3pl_there_fish_f_dalmbal",["jana","dalmbal","yinyay","nyajin"]),
row(49,"They saw the barrbal black bream there.","Jana barrbal yinyay nyajin.","jana barrbal yinyay nyajin | 3pl.NOM barrbal.black-bream there see.PST","r355_3pl_there_fish_f_barrbal",["jana","barrbal","yinyay","nyajin"]),
row(50,"They saw the jalkay salmon there.","Jana jalkay yinyay nyajin.","jana jalkay yinyay nyajin | 3pl.NOM jalkay.salmon there see.PST","r355_3pl_there_fish_f_jalkay",["jana","jalkay","yinyay","nyajin"]),]
def literal_guard():
 m=ast.parse(Path(__file__).read_text());a=[n for n in m.body if isinstance(n,ast.Assign) and any(isinstance(t,ast.Name) and t.id=="ROWS" for t in n.targets)];issues=[]
 if len(a)!=1 or not isinstance(a[0].value,ast.List):return["ROWS"]
 if len(a[0].value.elts)!=50:issues.append("count")
 for i,e in enumerate(a[0].value.elts,1):
  if not isinstance(e,ast.Call) or not isinstance(e.func,ast.Name) or e.func.id!="row":issues.append(f"{i}:call");continue
  try:v=[ast.literal_eval(x) for x in e.args]
  except Exception:issues.append(f"{i}:literal");continue
  if len(v)!=6 or v[0]!=i:issues.append(f"{i}:args")
 return issues
def scan_static():
 issues=literal_guard()
 if len(ROWS)!=50 or [r[0] for r in ROWS]!=list(range(1,51)) or len({r[1] for r in ROWS})!=50 or len({r[2] for r in ROWS})!=50:issues.append("inventory")
 for seq,en,ku,an,fr,_tier,wjson,ljson,ev,conf in ROWS:
  p,d,prefix,loc,person=FRAMES[(seq-1)//10];t=TARGET_ORDER[(seq-1)%10];w=json.loads(wjson);den=[x for x in DENY_KUKU if re.search(rf"(^|\s){re.escape(x)}([\s\.]|$)",ku.lower())];dr=[x for x in DENY_ENGLISH if re.search(rf"\b{re.escape(x)}\b",en.lower())]
  if json.loads(ljson) or den or dr:issues.append(f"{seq}:deny {den}/{dr}")
  if ku!=f"{p.capitalize()} {t} {d} nyajin." or en!=f"{prefix} the {LABELS[t]} {loc}." or an!=f"{p} {t} {d} nyajin | {person}.NOM {GLOSSES[t]} {loc} see.PST":issues.append(f"{seq}:fields")
  if w!=[p,t,d,"nyajin"] or not fr.startswith("r355_") or TARGET_EVIDENCE[t] not in ev or SUPPORT not in ev or conf!=CONF[t]:issues.append(f"{seq}:meta")
 return issues
def main():
 cards,hits=source_audit();issues=scan_static()
 if issues:raise SystemExit("\n".join(issues))
 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() or db.execute("SELECT COUNT(*) FROM sentences WHERE status='under_review'").fetchone()[0]:raise SystemExit("open")
 tc=count_words(db,TARGET_ORDER);sc=count_words(db,list(EXPECTED_SUPPORT))
 if tc!=EXPECTED_TARGET or sc!=EXPECTED_SUPPORT:raise SystemExit(f"coverage {tc}/{sc}")
 for seq,_en,ku,_an,_fr,_tier,wjson,_l,_ev,_c in ROWS:
  w=json.loads(wjson);ch=db.execute("SELECT b.label,s.seq 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')",(ku,)).fetchall();opp=f"{w[0].capitalize()} {w[1]} {OPP[w[2]]} nyajin.";oh=db.execute("SELECT b.label,s.seq 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')",(opp,)).fetchall()
  if ch or oh!=[("batch-396",seq)]:raise SystemExit(f"map {seq} {ch}/{oh}")
 verified,words=db.execute("SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1) FROM sentences WHERE status IN('accepted','revised')").fetchone()
 if (verified,words)!=(19897,134947):raise SystemExit(f"baseline {verified}/{words}")
 notes="COMPLEMENTARY LINTER-CLEAN FISH SET-F SINGULAR/THIRD PAST PERCEPTION AFTER RULE354 (Rule355). 50 exact complements; full 100-cell singular/third grid complete; source/sense boundaries retained; gap162->172."
 bid=db.execute("INSERT INTO batches(label,cheatsheet_version,guide_version,notes,created_at) VALUES(?,?,?,?,?)",(LABEL,"Rule355 under review","operator-guide-2026-07-10",notes,NOW)).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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",[(bid,seq,en,ku,an,fr,tier,w,l,ev,"under_review",conf,"project_approved_synthetic_pending_elder_verification","codex-main",NOW,NOW) for seq,en,ku,an,fr,tier,w,l,ev,conf in ROWS])
 events=[("source","source-audit-pass",f"Cards={cards};Patz={hits}; Rule354 hashes retained."),("planning","sense-preservation-audit","All fish meanings, whole-head and excluded-source-prose boundaries retained."),("planning","complement-map-audit","50 candidates absent; opposites exactly Batch396 seq1-50."),("planning","balance-audit","Gap162->172; inverse duplicate."),("preflight","coverage-audit",f"targets={tc};support={sc}."),("preflight","duplicate-preflight","50 selected unique/absent."),("preflight","static-guard-pass","AST 50 literal; source/sense/complement/linter contracts passed.")]
 for ph,et,d in events:record_event(db,batch_label=LABEL,phase=ph,event_type=et,actor="codex",detail=d,artifact_path=LOG,created_at=NOW)
 record_event(db,batch_label=LABEL,phase="bank",event_type="bank-success",actor="codex",detail=f"Banked 50 Rule355; verified {verified}; words {words}.",command="python3 batch_scripts/bank_397.py",artifact_path=LOG,created_at=NOW);db.commit();print("banked batch-397: 50 under_review");return 0
if __name__=="__main__":raise SystemExit(main())
