eval: 完成任务书15路线对照与先想后读核查

This commit is contained in:
2026-10-04 01:31:50 -04:00
parent 18235cc9b1
commit 856845cd71
11 changed files with 448 additions and 4 deletions
+69
View File
@@ -0,0 +1,69 @@
import { writeFileSync } from "node:fs";
import { pyDumps, pyRepr, readRun, type J } from "./py.ts";
// 任务书 15 第 4.1 节:把一次运行的第零轮和第 1 轮导出成一份纯文本,供子 Agent 按附录一问卷阅读。
// 逐行移植对照材料里的 extract.py,输出逐字节一致(含原脚本的写法:结尾不带换行;
// 认领入口名是从调用记录的 JSON 文字里用正则抠的,名字里的引号、换行会带着 JSON 转义)。
// node eval/route/extract.ts <运行目录名> <输出文件>
// 运行目录是 /home/iomgaa/inforsi-eval/<运行目录名>/study/ledger.sqlite,只读打开。
const [run, out] = process.argv.slice(2);
if (run === undefined || out === undefined) throw new Error("用法:node eval/route/extract.ts <运行目录名> <输出文件>");
const rows = readRun(run);
const L: string[] = [];
const subs: J[] = rows.filter(([k]) => k === "submission").map(([, c]) => c);
const decisions = (name: string): J[] => rows.filter(([k, c]) => k === "decision" && c.decision === name).map(([, c]) => c);
L.push(`# 运行 ${run}\n`);
L.push("## 第零轮 一、闭门写的入口(每人按自己写的先后)\n");
for (const s of subs) {
if (s.kind !== "entry") continue;
L.push(`### ${s.author}(第 ${s.version} 版)`);
s.content.entries.forEach((e: J, i: number) => L.push(`#### 第 ${i + 1} 个:${e.name}\n${e.body}\n`));
}
L.push("## 第零轮 二、讨论(按提交先后)\n");
for (const s of subs) {
if (s.kind !== "discussion") continue;
L.push(`### ${s.author}(${s.stageId},第 ${s.version} 版)\n${s.content.body ?? ""}\n`);
if (s.content.entries?.length) {
L.push("换成的新入口清单:");
s.content.entries.forEach((e: J, i: number) => L.push(`#### 新第 ${i + 1} 个:${e.name}\n${e.body}\n`));
}
}
// 认领时的最终入口编号:从第一条认领调用的记录(材料里的入口全文)里抠出来。
const names = new Map<string, [string, string]>();
for (const [k, c] of rows) {
if (k !== "callStarted") continue;
const txt = pyDumps(c, false);
if (!txt.includes("r0-claim")) continue;
for (const m of txt.matchAll(/### 入口 (e-researcher-\d-\d+):(.*?)(作者 (researcher-\d))/g)) names.set(m[1]!, [m[2]!, m[3]!]);
break;
}
L.push("## 第零轮 三、认领时的最终入口编号\n");
for (const [i, [n, a]] of names) L.push(`- ${i}(作者 ${a}):${n}`);
L.push("\n## 第零轮 四、志愿与分配\n");
for (const s of subs) if (s.kind === "claim") L.push(`- ${s.author} 志愿:${s.content.choices.join(" > ")}`);
for (const c of decisions("entries")) L.push("分配:" + pyDumps(c.output, false));
const props = subs.filter((s) => s.kind === "proposal");
if (props.length > 0) {
L.push("\n## 第 1 轮 一、提案全文\n");
for (const s of props) {
L.push(`### ${s.item}(作者 ${s.author})\n${s.content.body}\n`);
L.push("因子:");
for (const f of s.content.factors) L.push(`- ${f.name}:${f.method}`);
L.push("");
}
for (const c of decisions("dedup")) L.push("判重:" + pyDumps(c.output, false));
for (const kind of ["firstPrediction", "secondPrediction"]) {
L.push(`\n## 第 1 轮 ${kind} 三个数(作者→候选)`);
for (const s of subs) if (s.kind === kind) L.push(`- ${s.author} → ${s.target}:${pyRepr(s.content.range)}`);
}
for (const c of decisions("batch")) {
const merged = c.output.candidates.map((x: J) => [x.id, x.merged]);
L.push("\n选批:入选 " + pyRepr(c.output.chosen) + ";合并范围 " + pyDumps(merged, true));
}
L.push("\n## 附:第二次预测正文(看研究者如何判断风险)\n");
for (const s of subs) if (s.kind === "secondPrediction") L.push(`### ${s.author} → ${s.target}\n${s.content.body}\n`);
}
writeFileSync(out, L.join("\n"));