#!/usr/bin/env python3
import json,sys,re,hashlib
from pathlib import Path
p=Path(sys.argv[1]);d=json.loads(p.read_text());errs=[]
P='gacmai_20260717_184527';TITLE='Mùa Nở Không Có Tên';POV='Nguyễn Lam Chi';CAST=['Nguyễn Lam Chi','Trần Vĩnh Phúc','Lê Thu Nguyệt','Phạm Hữu Tường','Đỗ Khánh An','Vũ Minh Triết'];CONCEPT='e939b3867f10572ff43fa108dcd6a731380b017d17bcdfd8433af5b3ee7090c0'
if d.get('project_id')!=P:errs.append('project_id')
if d.get('title')!=TITLE:errs.append('title')
if d.get('concept_sha256')!=CONCEPT:errs.append('concept_sha256')
if d.get('cast')!=CAST or d.get('exact_cast')!=6:errs.append('cast')
if d.get('pov')!=POV:errs.append('pov')
if not 14500<=int(d.get('target_words',0))<=14750:errs.append('target_words')
if [x.get('id') for x in d.get('evidence',[])]!=[f'E{i:02d}' for i in range(1,10)]:errs.append('evidence_ids')
if any(not all(x.get(k) for k in ('setup','preservation','payoff','cannot_support','lawful_authority')) for x in d.get('evidence',[])):errs.append('evidence_fields')
if [x.get('id') for x in d.get('authorities',[])]!=[f'A{i:02d}' for i in range(1,7)]:errs.append('authority_ids')
if any(not x.get('scope') or not x.get('cannot_do') for x in d.get('authorities',[])):errs.append('authority_fields')
evidence_authority={x.get('id'):x.get('lawful_authority') for x in d.get('evidence',[])}
parts=d.get('parts',[])
if [x.get('part') for x in parts]!=list(range(1,13)):errs.append('part_ids')
beats=[];words=0
for part in parts:
 paras=part.get('paragraphs',[])
 if [x.get('paragraph') for x in paras]!=list(range(1,13)):errs.append(f"P{part.get('part')}_paragraph_ids")
 if not 1180<=int(part.get('target_words',0))<=1250:errs.append(f"P{part.get('part')}_target")
 words+=int(part.get('target_words',0))
 for x in paras:
  beat=(x.get('beat') or '').strip();beats.append(beat)
  if x.get('pov')!=POV:errs.append(f"P{part.get('part')}D{x.get('paragraph')}_pov")
  if not beat or not x.get('continuity_in') or not x.get('continuity_out'):errs.append(f"P{part.get('part')}D{x.get('paragraph')}_fields")
  if any(e not in {f'E{i:02d}' for i in range(1,10)} for e in x.get('evidence_ids',[])):errs.append('unknown_evidence')
  if any(a not in {f'A{i:02d}' for i in range(1,7)} for a in x.get('authority_ids',[])):errs.append('unknown_authority')
  for e in x.get('evidence_ids',[]):
   if e in evidence_authority and evidence_authority[e] not in x.get('authority_ids',[]):errs.append(f"P{part.get('part')}D{x.get('paragraph')}_{e}_authority_mismatch")
if len(beats)!=144 or len(set(beats))!=144:errs.append('beat_uniqueness')
if words!=int(d.get('target_words',0)):errs.append('part_word_sum')
blob=json.dumps({'parts':parts,'title':d.get('title')},ensure_ascii=False).lower();forbidden=['băng cassette','băng cối','đài phát thanh','phục chế âm thanh','siêu nhiên','ecg','bệnh viện','xăng dầu','dầu khí','placeholder','teaser']
if any(x in blob for x in forbidden):errs.append('forbidden_drift')
# Named-person guard: capitalized three-token names must be from the locked cast.
found=set(re.findall(r'\b[A-ZĐÀ-Ỹ][\wÀ-ỹĐđ]+\s+[A-ZĐÀ-Ỹ][\wÀ-ỹĐđ]+\s+[A-ZĐÀ-Ỹ][\wÀ-ỹĐđ]+\b',json.dumps(parts,ensure_ascii=False)))
extra=sorted(x for x in found if x not in CAST and not x.startswith(('Hội Đồng','Ngân Hàng','Cơ Quan','Mùa Nở')))
if extra:errs.append('extra_names:'+','.join(extra[:10]))
out={'status':'passed' if not errs else 'failed','sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'parts':len(parts),'paragraphs':len(beats),'unique_beats':len(set(beats)),'target_words':d.get('target_words'),'errors':errs};print(json.dumps(out,ensure_ascii=False));raise SystemExit(1 if errs else 0)
