#!/usr/bin/env python3
"""Close Batch 396 after explicit five-lens review."""
from __future__ import annotations
import datetime as dt,json,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-396";NOW=dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00","Z");LOG="process_logs/batch-396-linter-clean-fish-set-f-singular-third-past-perception-after-rule353-process.md";LENSES=("morphology","lexicon","syntax","fidelity","register")
NOTES="LINTER-CLEAN FISH SET-F SINGULAR/THIRD PAST PERCEPTION AFTER RULE353 (Rule354). 50 accepted; two-card/zero-Patz/empty-200-cell audits; source prose closed; gap172->162."
TARGETS=[("kalngkan","small mullet","size/habitat closed"),("mangkurr-mangkurr","garfish","whole head/shape closed"),("dijarr","freshwater perch","comparison/location closed"),("julaji","freshwater catfish","dialect comparison closed"),("mawul","freshwater eel","taboo/eating closed"),("balarri","queenfish","taxonomy closed"),("bayil","jungle perch","record/distribution closed"),("dalmbal","flathead","synonym/anatomy closed"),("barrbal","black bream","color predication closed"),("jalkay","salmon","species/migration closed")]
LESSONS=[("source","All ten final heads have two noun cards and zero Patz hits.","Rule354 source."),("process","Linter-aware selection excluded stop-final/rhotic-palatal/dialect-only/heavily polysemous heads.","Rule354 selection."),("process","All 200 cells were empty.","Rule354 matrix."),("process","Three-here/two-there changes gap172->162.","Rule354 balance."),("source","Patz licenses the shared pronoun/local/past/perception frame independently.","Rule354 grammar.")]+[("lexicon",f"{h} remains {g}; {n}.",f"Rule354 {h}.") for h,g,n in TARGETS]+[("morphology","Targets remain bare O.","Rule354 O."),("morphology","Reduplicated mangkurr-mangkurr remains an indivisible head.","Rule354 whole."),("morphology","Pronouns remain bare A.","Rule354 A."),("morphology","Locals/nyajin remain fixed.","Rule354 support."),("syntax","Rows1-10 Ngayu...yaluy.","Rule354 b1."),("syntax","Rows11-20 Yundu...yinyay.","Rule354 b2."),("syntax","Rows21-30 Nyulu...yaluy.","Rule354 b3."),("syntax","Rows31-40 Bula...yinyay.","Rule354 b4."),("syntax","Rows41-50 Jana...yaluy.","Rule354 b5."),("fidelity","English preserves participant,label,location,past seeing only.","Rule354 fidelity."),("fidelity","No location/taboo/synonym/habitat/capture/food/source/event proposition.","Rule354 exclude."),("register","Neutral elicitation register.","Rule354 register."),("process","AST proves 50 literal calls.","Rule354 authorship."),("process","Readback/lint/dup/integrity gate close.","Rule354 gates."),("process","Five lenses store 250 decisions.","Rule354 reviews."),("process","35 lessons/18 logs persist.","Rule354 storage."),("process","Export/books/live refresh.","Rule354 publish."),("process","Artifacts stay mounted.","Rule354 mounted."),("handoff","Rule355 requires exact complement audit.","Rule354 handoff."),("handoff","Provisional rejected fish heads remain excluded.","Rule354 rejects.")]
LEX=TARGETS+[("ngayu","first singular nominative","bare A"),("yundu","second singular nominative","bare A"),("nyulu","third singular nominative","bare A"),("bula","third dual nominative","bare A"),("jana","third plural nominative","bare A"),("yaluy","here","Yalanji local"),("yinyay","there","Yalanji local"),("nyajin","see; see.PST","past surface")]
def main():
 if len(LESSONS)!=35 or len(LEX)!=18:raise SystemExit(f"inventory {len(LESSONS)}/{len(LEX)}")
 db=sqlite3.connect(DB);db.execute("PRAGMA foreign_keys=ON");db.execute("BEGIN");ensure_schema(db);r=db.execute("SELECT id FROM batches WHERE label=?",(LABEL,)).fetchone()
 if not r:raise SystemExit("not banked")
 bid=r[0];c=db.execute("SELECT COUNT(*),COUNT(*) FILTER(WHERE status='under_review') FROM sentences WHERE batch_id=?",(bid,)).fetchone();total=db.execute("SELECT COUNT(*) FROM sentences WHERE status!='rejected'").fetchone()[0];before,wb=db.execute("SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1) FROM sentences WHERE status IN('accepted','revised')").fetchone()
 if c!=(50,50) or (total,before,wb)!=(19897,19847,134747):raise SystemExit(f"pre {c}/{total}/{before}/{wb}")
 ids=[x[0] for x in db.execute("SELECT id FROM sentences WHERE batch_id=? ORDER BY seq",(bid,))];db.executemany("INSERT INTO reviews(sentence_id,round,lens,verdict,issues,suggested_fix,reviewer,created_at) VALUES(?,?,?,?,?,?,?,?)",[(sid,1,l,"pass",None,None,"codex-main-five-lens",NOW) for sid in ids for l in LENSES]);db.executemany("INSERT INTO lessons(batch_id,category,lesson,guide_change,created_at) VALUES(?,?,?,?,?)",[(bid,c,l,g,NOW) for c,l,g in LESSONS])
 for h,g,n in LEX:
  p="pronoun" if h in {"ngayu","yundu","nyulu","bula","jana"} else "demonstrative" if h in {"yaluy","yinyay"} else "verb-tr" if h=="nyajin" else "noun";conj="l" if h=="nyajin" else None;s=json.dumps([{"sense":g,"definition":n,"status":"attested"}],ensure_ascii=False);a=json.dumps([{"source":LABEL,"ref":"five-lens close","quote":n}],ensure_ascii=False);note=f"batch-396: {n}"
  db.execute("""INSERT INTO lexemes(headword,phonemic,pos,conj_class,case_class,gloss,senses,attestation,allomorphy,morphology,collocations,usage_notes,antonyms_contrasts,corpus_freq,status,verified_at,notes) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(headword) DO UPDATE SET verified_at=excluded.verified_at,status=excluded.status,notes=CASE WHEN lexemes.notes IS NULL OR lexemes.notes='' THEN excluded.notes WHEN instr(lexemes.notes,?)>0 THEN lexemes.notes ELSE lexemes.notes||'; '||excluded.notes END""",(h,None,p,conj,"NA",g,s,a,None,None,None,n,None,0,"verified",NOW,note,LABEL));db.execute("INSERT INTO lexeme_log(headword,action,detail,created_at) VALUES(?,?,?,?)",(h,"batch-396-verify",n,NOW))
 for ph,et,d in [("lint","under-review-lint-green",f"Lint {total}/0; verified {before}."),("review","five-lens-pass","250/250 passed; linter-clean fish/source boundaries retained."),("review","review-detail","No location/taboo/synonym/habitat/capture/food/source/event drift."),("review","lesson-inventory","35 lessons."),("review","lexeme-source-log","18 logs.")]:record_event(db,batch_label=LABEL,phase=ph,event_type=et,actor="codex",detail=d,artifact_path=LOG,created_at=NOW)
 db.execute("UPDATE sentences SET status='accepted',updated_at=? WHERE batch_id=?",(NOW,bid));db.execute("UPDATE batches SET notes=?,cheatsheet_version=?,guide_version=? WHERE id=?",(NOTES,"Rule354 accepted","operator-guide-2026-07-10",bid));after,wa=db.execute("SELECT COUNT(*),SUM(LENGTH(kuku)-LENGTH(REPLACE(kuku,' ',''))+1) FROM sentences WHERE status IN('accepted','revised')").fetchone();rv=db.execute("SELECT COUNT(*) FROM reviews WHERE sentence_id IN(SELECT id FROM sentences WHERE batch_id=?)",(bid,)).fetchone()[0]
 if (after,wa,rv)!=(19897,134947,250):raise SystemExit(f"post {after}/{wa}/{rv}")
 record_event(db,batch_label=LABEL,phase="close",event_type="close-success",actor="codex",detail=f"accepted=50 reviews=250 lessons=35 logs=18 verified {before}->{after} words {wb}->{wa}.",command="python3 batch_scripts/close_396.py",artifact_path=LOG,created_at=NOW);db.commit();print(f"closed batch-396: {before}->{after}");return 0
if __name__=="__main__":raise SystemExit(main())
