#!/usr/bin/env python3
import json,hashlib,re,sys
from pathlib import Path
PROJECT='gacmai_20260718_075153'
SCAFFOLD=('dòng ghi riêng','placeholder','todo','ghi chú biên tập','nhịp kế tiếp','P01B','P02B','P03B','P04B','P05B','P06B','P07B','P08B','P09B','P10B','P11B','P12B')
def words(s):return len(re.findall(r'\b\w+\b',s,re.UNICODE))
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def main():
 if len(sys.argv)!=5:raise SystemExit('usage: validate_story_batch.py FILE START END OUTLINE_SHA')
 p=Path(sys.argv[1]);start,end=map(int,sys.argv[2:4]);outline_sha=sys.argv[4];d=json.loads(p.read_text());err=[]
 if d.get('project_id')!=PROJECT:err.append('project_id')
 if d.get('source_outline_sha256')!=outline_sha:err.append('outline_binding')
 if d.get('part_range')!=[start,end]:err.append('part_range')
 parts=d.get('parts',[])
 if [x.get('part_number',x.get('part')) for x in parts]!=list(range(start,end+1)):err.append('part_numbers')
 paras=[]
 for part in parts:
  q=part.get('paragraphs',[])
  if len(q)!=12:err.append(f"part_{part.get('part_number',part.get('part'))}_paragraphs={len(q)}")
  paras += [x.get('text','') if isinstance(x,dict) else str(x) for x in q]
 total=sum(words(x) for x in paras)
 if not 4400<=total<=5200:err.append(f'words={total}')
 first=sum(len(re.findall(r'(?<!\w)(?:tôi|chúng\s+tôi)(?!\w)',x,re.I)) for x in paras)
 # Dialogue may legitimately use first person; semantic audit decides narrator POV.
 blob='\n'.join(paras).lower()
 for s in SCAFFOLD:
  if s.lower() in blob:err.append(f'scaffold:{s}')
 if len(set(re.sub(r'\s+',' ',x.lower()).strip() for x in paras))!=len(paras):err.append('exact_duplicate_paragraph')
 out={'status':'passed' if not err else 'failed','path':str(p),'sha256':sha(p),'range':[start,end],'parts':len(parts),'paragraphs':len(paras),'words':total,'first_person':first,'errors':err};print(json.dumps(out,ensure_ascii=False,indent=2));raise SystemExit(0 if not err else 1)
if __name__=='__main__':main()
