Files
InfoRSI/eval/route/extract.ts
T

70 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"));