Spaces:
Sleeping
Sleeping
File size: 3,780 Bytes
c12eb19 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Structural evidence and diagram QA gate. Humans must still verify real physics."""
import argparse,json,re,sys,xml.etree.ElementTree as ET
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
def run(strict=True):
d=json.loads((ROOT/'story.json').read_text(encoding='utf-8'))
errors=[];warnings=[]
cl=d.get('claims',[])
if not cl:errors.append('缺少科学主张及对应证据 claims')
ids=set()
for c in cl:
key=c.get('id')
if not key or key in ids:errors.append('科学主张 ID 不唯一或为空')
ids.add(key)
if not c.get('statement'):errors.append(f'{key}: 主张内容不能为空')
if not c.get('sources') or any(not re.match(r'^https?://',x) for x in c.get('sources',[])):
errors.append(f'{key}: 至少一条可核实的 HTTP(S) 来源')
if not c.get('diagram_truth'):errors.append(f'{key}: 必须列明示意图不容颠倒的科学关系')
if c.get('source_checked') is not True:
warnings.append(f'{key}: 来源尚未由制作者人工/浏览器核对;不得标记已审稿')
covered=set()
scenes=d.get('scenes',[])
if len(scenes)<3:errors.append('科普视频至少 3 幕:现象、原理、总结')
for i,sc in enumerate(scenes):
sceneid=sc.get('id',f'#{i}')
links=sc.get('claim_ids',[])
if not links:errors.append(f'{sceneid}: 缺少 claim_ids')
for link in links:
if link not in ids:errors.append(f'{sceneid}: 未知科学主张 {link}')
covered.add(link)
art=sc.get('diagram_svg') or sc.get('image')
if not art:
errors.append(f'{sceneid}: 缺少 diagram_svg/image,不能仅凭标题渲染')
elif sc.get('diagram_svg'):
loc=(ROOT/sc['diagram_svg']).resolve()
if not loc.exists():errors.append(f'{sceneid}: SVG 不存在:{sc["diagram_svg"]}')
else:
txt=loc.read_text(encoding='utf-8')
if 'data-placeholder="true"' in txt:
errors.append(f'{sceneid}: SVG 还是占位画面,禁止发布')
try: ET.fromstring(txt)
except ET.ParseError: errors.append(f'{sceneid}: SVG 语法损坏')
if not sc.get('visual_truth'):
errors.append(f'{sceneid}: 缺少物理关系/方向/颜色/空间约束 visual_truth')
if len(sc.get('heading',''))>22:warnings.append(f'{sceneid}: 标题过长,检查竖屏适配')
if len(sc.get('subtitle',''))>36:warnings.append(f'{sceneid}: 说明文字过长,易与字幕重叠')
for key in ids-covered: warnings.append(f'{key}: 尚未在分镜中展示该科学主张')
v=d.get('voices',{})
for role,cfg in v.items():
if not isinstance(cfg,dict) or not cfg.get('speaker'):errors.append(f'{role}: 声音配置不完整')
report={'science_gate_pass':not errors and (not strict or not warnings), 'hard_errors':errors,'human_review_pending':warnings,
'scientific_truth_manually_verified':False,
'claims':len(cl),'scenes':len(scenes),
'note':'本脚本仅检查证据链和示意图规格,不能自动证明光路或公式正确。'}
(ROOT/'output').mkdir(exist_ok=True)
(ROOT/'output/science-audit.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(report,ensure_ascii=False,indent=2))
return 0 if report['science_gate_pass'] else 2
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--allow-unreviewed',action='store_true',help='Only structural drafting; never count as science approval');a=p.parse_args()
sys.exit(run(strict=not a.allow_unreviewed))
|