#!/usr/bin/env python3
"""Render the local EP1 report from authoritative snapshots and supplementary checks.
Never changes snapshots, runs, scores or credentials. Output works over file://.
"""
import hashlib, json, pathlib, runpy
ROOT=pathlib.Path(__file__).resolve().parents[1]
TEMPLATES=ROOT/'site/templates'
def main():
files=[ROOT/'data/experiments/exp-20260915-ep1-f005-docker.json',ROOT/'data/experiments/exp-20260918-ep1-f005-glm-probe1200.json']
snapshots=[json.loads(p.read_text()) for p in files]
evidence=json.loads((ROOT/'site/evidence/ep1-reference/comparison.json').read_text())
cases={c['id']:c for c in evidence['runs']}
rows=[]
for snap in snapshots:
for raw in snap['runs']:
if raw['task']!='ep1-f005':continue
u=raw.get('wire_backfill') or raw['official_usage']
net=u.get('net_input',u.get('input_tokens')); cache=u.get('cache_read',u.get('cache_read_tokens'))
row={
'id':raw['run_id'],'model':raw['model'],'attempt':raw['attempt'],'timestamp':raw['ts'],
'helper':cases[raw['run_id']]['helper'],'sourceSha256':cases[raw['run_id']]['sha256'],
'budget':raw['params']['timeout_s'],'seconds':raw['wall_s'],'score':raw['score'],'exit':raw['agent_exit'],
'calls':u['api_calls'],'net':net,'cache':cache,'output':u.get('output',u.get('output_tokens')),
'usageSource':'wire' if raw.get('wire_backfill') else 'official',
'testModified':raw['score_diag'].get('test_modified_by_model'),
'checks':cases[raw['run_id']]['checks'],'mechanism':cases[raw['run_id']]['mechanism'],
'diff':(ROOT/'site/evidence/ep1-reference'/f"{raw['run_id']}.diff").read_text(),
}
assert all(row[k] is not None for k in ['net','cache','calls','output'])
rows.append(row)
rows.sort(key=lambda r:(r['budget'],r['model'],r['attempt']))
main_runs=[r for r in rows if r['budget']==420]
passed=[r for r in rows if r['score']==1]
main_models=set(r['model'] for r in main_runs)
summary={
'mainRuns':len(main_runs),'mainModels':len(main_models),'mainPassed':sum(r['score']==1 for r in main_runs),
'mainTimeouts':sum(r['exit']==124 for r in main_runs),
'mainModelsBothPassed':sum(all(r['score']==1 for r in main_runs if r['model']==m) for m in main_models),
'allTaskRuns':len(rows),'passedRuns':len(passed),
'passedCopyPass':sum(r['checks']['api_key_copy']['pass'] for r in passed),
'checksRequests':sum(len(c['checks']) for c in cases.values()),
}
assert len(rows)==20 and len(main_runs)==18 and len(main_models)==9
assert summary['mainPassed']==14 and summary['passedCopyPass']==0
answer_review=json.loads((ROOT/'site/evidence/ep1-reference/answer-review.json').read_text())
assert {r['id'] for r in answer_review['runs']}=={r['id'] for r in rows}
assert sum(r['status']=='final' for r in answer_review['runs'])==16
for item in answer_review['runs']+answer_review['highlights']:
source=ROOT/item['source']
assert hashlib.sha256(source.read_bytes()).hexdigest()==item['sourceSha256'], str(source)
body=json.loads(source.read_text())['body']
visible=''
for line in body.splitlines():
if not line.startswith('data: '):continue
try: chunk=json.loads(line[6:])
except json.JSONDecodeError:continue
for choice in chunk.get('choices',[]):
visible+=choice.get('delta',choice.get('message',{})).get('content') or ''
assert item.get('quote',item.get('text')) in visible, str(source)
answer_corpus=json.loads((ROOT/'site/evidence/ep1-reference/answer-corpus.json').read_text())
answer_rubric=json.loads((ROOT/'site/evidence/ep1-reference/answer-rubric.json').read_text())
run_ids={r['id'] for r in rows}
assert {r['id'] for r in answer_corpus['runs']}==run_ids
assert {r['id'] for r in answer_rubric['runs']}==run_ids
parse_reply=runpy.run_path(str(ROOT/'scripts/extract-ep1-answers.py'))['visible_reply']
for run in answer_corpus['runs']:
for message in run['messages']:
source=ROOT/message['source']
assert hashlib.sha256(source.read_bytes()).hexdigest()==message['sourceSha256']
text,tools=parse_reply(source)
assert text==message['text'] and tools==message['hasToolCalls']
for item in answer_rubric['runs']:
messages=next(r['messages'] for r in answer_corpus['runs'] if r['id']==item['id'])
assert item['reviewedMessages']==len(messages)
assert all(e in messages for e in item['evidence'])
# 分层排名:应要求补充,只用主实验(420s 同预算)聚合,派生自快照 + 补测 + 审阅档位。
# 延时探针是另一种预算条件,混进来名次就不可比,故排除在外。
LOCATE={'explained':3,'proposed':2,'scoped_out':1,'not_observed':0}
LOCATE_LABEL={3:{'en':'Explained copy risk','zh':'解释复制风险'},2:{'en':'Proposed checking','zh':'提出排查'},1:{'en':'Scoped out after checking','zh':'检查后限定范围'},0:{'en':'Not stated explicitly','zh':'未见明确表述'}}
rubric_by={r['id']:r for r in answer_rubric['runs']}
by_model={}
for r in main_runs:
m=by_model.setdefault(r['model'],{'model':r['model'],'runs':[],'seconds':[],'locate':0,'mechanisms':set()})
m['runs'].append(r['id']); m['seconds'].append(r['seconds'])
m['locate']=max(m['locate'],LOCATE[rubric_by[r['id']]['copyRisk']])
m['mechanisms'].add(r['mechanism'])
ranking=[]
for m in by_model.values():
rr=[r for r in main_runs if r['model']==m['model']]
assert len(rr)==2, m['model']
ranking.append({
'model':m['model'],'runs':m['runs'],
'passed':sum(r['score'] for r in rr),
'seconds':round(sum(m['seconds'])/len(m['seconds']),1),
'calls':round(sum(r['calls'] for r in rr)/2,1),
'locate':m['locate'],'locateLabel':LOCATE_LABEL[m['locate']],
'copyPass':sum(r['checks']['api_key_copy']['pass'] and r['checks']['bearer_copy']['pass'] for r in rr),
'mechanisms':sorted(m['mechanisms']),
})
# 排序规则写死在这里,页面上原样公示,读者可据此自行重排。
ranking.sort(key=lambda r:(-r['passed'],-r['locate'],r['seconds']))
for i,r in enumerate(ranking,1): r['rank']=i
reference_checks=cases['upstream-reference']['checks']
# 运行日期范围:对应 OpenRouter 排行榜把数据新鲜度写在标题下方的做法,同样由脚本从快照取。
run_days=sorted(r['timestamp'][:10] for r in main_runs)
ranking_meta={
'rule':{'en':'Sorted by original-task passes (descending), then copy-boundary statement level (descending), then mean time over two runs (ascending). ','zh':'先按原题通过次数降序,再按复制边界表述档位降序,最后按两次耗时均值升序。'},
'budget':420,'runsPerModel':2,'models':len(ranking),
'runs':len(main_runs),'firstDay':run_days[0],'lastDay':run_days[-1],
'locateSource':{'en':'single-pass AI-assisted text review, no independent second reviewer','zh':'单遍 AI 辅助文本审阅,无独立第二审阅者'},
'byTime':[r['model'] for r in sorted([r for r in ranking if r['passed']==2],key=lambda r:r['seconds'])],
'copyWinners':sum(r['copyPass']>0 for r in ranking),
'referenceAllPass':all(c['pass'] for c in reference_checks.values()),
'locateLevels':LOCATE_LABEL,
}
assert ranking_meta['copyWinners']==0 and ranking_meta['referenceAllPass']
assert len(ranking)==9 and sum(r['passed'] for r in ranking)==summary['mainPassed']
# 首屏数据基准条:对应 OpenRouter 把数据新鲜度写在标题下的做法(openrouter.ai/rankings,2026-09-21 读取)。
# 全部字段由脚本从快照重算,不接受手填。
all_days=sorted(r['timestamp'][:10] for r in rows)
data_base={
'runs':len(rows),'models':len(main_models),'runsPerModel':2,
'budgets':sorted({r['budget'] for r in rows}),
'firstDay':all_days[0],'lastDay':all_days[-1],
'snapshots':[{'name':f.name,'label':lbl,'sha256':hashlib.sha256(f.read_bytes()).hexdigest()[:12]} for f,lbl in zip(files,({'en':'Main snapshot','zh':'主实验快照'},{'en':'Probe snapshot','zh':'探针快照'}))],
}
assert data_base['runs']==20 and data_base['models']==9 and data_base['budgets']==[420,1200]
zh_en=json.loads((TEMPLATES/'ep1-report-en.json').read_text())
needed={r[k] for r in answer_rubric['rules'] for k in ('state','rule')}|{answer_rubric['reviewer'],answer_rubric['scope']}
needed|={r[k] for r in answer_rubric['runs'] for k in ('copyLabel','completionStatement')}|{h['assessment'] for h in answer_review['highlights']}
assert needed<=zh_en.keys(), sorted(needed-zh_en.keys())
data={
'answerReview':answer_review,'answerCorpus':answer_corpus,'answerRubric':answer_rubric,
'runs':rows,'summary':summary,'ranking':ranking,'rankingMeta':ranking_meta,'fixture':snapshots[0]['fixture'],
'dataBase':data_base,
'reference':cases['upstream-reference'],'baseline':cases['baseline'],
'checkDate':evidence['generated_at'],'sdk':evidence['anthropic_version'],
'findings':json.loads((TEMPLATES/'ep1-findings.json').read_text()),
# English for the Chinese annotations kept in site/evidence (rubric, review); the
# evidence files stay as recorded, and a missing entry fails the build below.
'zhEn':zh_en,
'sourceHashes':{str(p.relative_to(ROOT)):hashlib.sha256(p.read_bytes()).hexdigest() for p in files},
}
payload=json.dumps(data,ensure_ascii=False,separators=(',',':')).replace('<','\\u003c').replace('>','\\u003e').replace('&','\\u0026')
i18n_head=''
for template,filename in [('ep1-report.html','ep1-douban.html'),('ep1-evidence.html','ep1-evidence.html')]:
out=(TEMPLATES/template).read_text().replace('/* REPORT_TOKENS */',(TEMPLATES/'ep1-tokens.css').read_text()).replace('/* REPORT_DATA */',payload).replace('',i18n_head)
(ROOT/'site/drafts'/filename).write_text(out)
print(json.dumps(summary,ensure_ascii=False))
if __name__=='__main__':main()