#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Validate structured evidence, domain invariants, and human science approval. Pass a domain-specific topic.json containing claims, scenes and assertion checks. This deliberately refuses to claim a diagram has been proved correct by a string check. """ import argparse,json,re,sys from pathlib import Path from domain_router import route def review(topic, require_review=False): errors=[];warnings=[] title=topic.get('title','') domain=topic.get('topic',topic.get('domain')) resolved=route(title,domain if domain in ('astronomy','biology','physics','chemistry','geography','general') else None) ids=set() for c in topic.get('claims',[]): if isinstance(c,list): if len(c)<3:errors.append('claims entry incomplete');continue cid,statement,url=c[0:3] else: cid,statement,url=c.get('id'),c.get('statement'),c.get('sources',[]) if cid in ids:errors.append(f'duplicate claim id: {cid}') ids.add(cid) if not statement:errors.append(f'{cid}: missing statement') urls=[url] if isinstance(url,str) else url if not urls or not all(re.match(r'^https://',u) for u in urls):errors.append(f'{cid}: no attributable HTTPS source') if len(ids)<2:errors.append('At least two independent source-traced claims are needed') scenes=topic.get('scenes',[]) if len(scenes)<5:errors.append('Insufficient scenes; need observable mechanism and recap') for i,sc in enumerate(scenes): if isinstance(sc,list):continue if not sc.get('text') and not sc.get('lines'):errors.append(f'scene[{i}]: no voiced scientific explanation') if not sc.get('heading'):errors.append(f'scene[{i}]: missing teaching title') if not sc.get('role') and not sc.get('lines'):errors.append(f'scene[{i}]: missing voice role') tags=set(topic.get('mechanism_tags',[])) req=set(resolved['required_mechanism_tags']) if not tags: warnings.append('Mechanism tags not recorded. Must review scene-to-claim alignment by hand.') elif req-tags:errors.append('Missing domain-specific mechanisms: '+', '.join(sorted(req-tags))) approvals=topic.get('science_review',{}) if not approvals.get('visual_truth_approved'): warnings.append('Human reviewed diagram truth not signed') if require_review:errors.append('Publication requires explicit human diagram review') if not approvals.get('sources_opened'): warnings.append('Sources are listed but not marked as inspected') if require_review:errors.append('Publication requires opened sources') if not approvals.get('misconceptions_checked'): warnings.append('Misconception checklist not acknowledged') if require_review:errors.append('Publication requires misconception audit') result={'domain':resolved['domain'],'science_structural_pass':not errors,'manual_approval_required':True,'hard_errors':errors,'warnings':warnings,'required_diagram_invariants':resolved['required_mechanism_tags']} return result if __name__=='__main__': p=argparse.ArgumentParser();p.add_argument('--topic',required=True);p.add_argument('--publish',action='store_true');p.add_argument('--output');a=p.parse_args() result=review(json.loads(Path(a.topic).read_text(encoding='utf-8')),a.publish) if a.output:Path(a.output).write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps(result,ensure_ascii=False,indent=2));sys.exit(0 if result['science_structural_pass'] else 2)