eval: 完整研究的运行、统计与审阅导出(任务书 17 第三块的检验代码)
- eval/full/run.ts:配置照 eval/round-zero/research.json 只换编号,真实跑到停下; launches/machine/timeline/outcome 照任务书 16 惯例;--kill-after-first-ablation-start 在第一个消融运行确认启动后、结果落账前 SIGKILL 控制进程,恢复续跑到底, kill-report.json 写三条检查(同一容器不重跑、只扣一次、结果照常写入)。 - eval/lib/full.ts:账目核对 fullAccounts——逐轮走向(候选分数、采纳、选格、每格 核验重做次数与扣次、结算、押的因子)加 12 项程序账目检查(阶段开关成对、 扣次=确认启动数、余额为 0、解释冻结早于首格启动、每轮最多一条结算、解封在 收尾关闭之后且覆盖全部选中候选、跑不起来/不跑的格零扣次、第 2 轮起基线链 重算、停在"导出还没写"等)与 CPU/墙钟比(<0.9 列出并附同时段负载,只列事实)。 - eval/full/stats.ts、export.ts:统计输出与按轮按格的审阅材料导出。 - 不沿用 eval/forward/acceptance.ts 的"启动区间互不重叠"(这次 3 份同时跑)。 - tests/full-eval.test.ts 4 条:真实账本 + rules 真实计算走完一轮含消融的研究, 撑几成与配合关系标签按方法.md 6.6 手算对照。npm run check 通过,npm test 476 条通过。
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import {
|
||||
Board,
|
||||
cellId,
|
||||
explanationId,
|
||||
protocolMetrics,
|
||||
splitCell,
|
||||
type TaskMaterial,
|
||||
} from "../../src/materials/index.ts";
|
||||
import { ablationOutcome } from "../../src/submissions/index.ts";
|
||||
import { checkoutVersion, compareVersions } from "../../src/workspace/index.ts";
|
||||
import { jsonFile } from "../lib/forward.ts";
|
||||
import { finalSubmissions, ofKind, readLedger } from "../lib/ledger.ts";
|
||||
|
||||
// 任务书 17 第 3.2 节研究质量评估的审阅材料:解释、选格决定、消融格(协议、位置表、代码与 diff、实现与核验往来)、
|
||||
// 结算、收尾、下一轮提案。只导出事实和原文,不替审阅者下结论。
|
||||
// node eval/full/export.ts <研究输出目录> <全新绝对审阅目录>
|
||||
const [dir, output, ...extra] = process.argv.slice(2);
|
||||
if (!dir || !output || extra.length || output !== resolve(output)) {
|
||||
throw new Error(
|
||||
"用法:node eval/full/export.ts <研究输出目录> <全新绝对审阅目录>",
|
||||
);
|
||||
}
|
||||
mkdirSync(output); // 不覆盖以前的审阅材料。
|
||||
const study = join(dir, "study");
|
||||
const records = readLedger(study);
|
||||
const board = new Board(records);
|
||||
const repo = join(study, "repo.git");
|
||||
const finals = finalSubmissions(records);
|
||||
const roundOf = (stageId: string) => board.stage(stageId).round;
|
||||
const roundIds = [
|
||||
...new Set(ofKind(records, "decision").map((d) => d.content.round)),
|
||||
]
|
||||
.filter((r) => r >= 1)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const checkout = (version: string, path: string) => {
|
||||
mkdirSync(path);
|
||||
checkoutVersion(repo, version, path);
|
||||
};
|
||||
const json = (v: unknown) => `${JSON.stringify(v, null, 2)}\n`;
|
||||
|
||||
const index = [];
|
||||
for (const round of roundIds) {
|
||||
const roundDir = join(output, `round-${round}`);
|
||||
mkdirSync(roundDir);
|
||||
|
||||
// 本轮提案(下一轮提案在 round-<r+1> 里,审阅"收尾→接力"时把两份对着读)。
|
||||
const proposals = finals.filter(
|
||||
(s) =>
|
||||
s.content.kind === "proposal" &&
|
||||
roundOf(s.content.stageId) === round,
|
||||
);
|
||||
jsonFile(join(roundDir, "proposals.json"), proposals);
|
||||
writeFileSync(
|
||||
join(roundDir, "proposals.txt"),
|
||||
proposals
|
||||
.map(
|
||||
(s) =>
|
||||
`${"=".repeat(20)} ${s.content.item}(${s.content.author},记录 ${s.seq})\n${json(s.content.content)}`,
|
||||
)
|
||||
.join("\n"),
|
||||
);
|
||||
|
||||
// 解释(任务书 17 第 3.2 节第 1 样):作者、正文、每格三个数。
|
||||
const explanations = finals.filter(
|
||||
(s) =>
|
||||
s.content.kind === "explanation" &&
|
||||
roundOf(s.content.stageId) === round,
|
||||
);
|
||||
jsonFile(join(roundDir, "explanations.json"), explanations);
|
||||
writeFileSync(
|
||||
join(roundDir, "explanations.txt"),
|
||||
explanations
|
||||
.flatMap((s) =>
|
||||
(
|
||||
s.content.content as {
|
||||
explanations: Array<{
|
||||
body: string;
|
||||
cells?: Array<{ factor: string; range: unknown }>;
|
||||
}>;
|
||||
}
|
||||
).explanations.map(
|
||||
(e, i) =>
|
||||
`${"=".repeat(20)} ${explanationId(round, s.content.author, i + 1)}(${s.content.author} 的第 ${i + 1} 条,记录 ${s.seq})\n${e.body}\n三个数:${(e.cells ?? []).map((c) => `${c.factor} ${JSON.stringify(c.range)}`).join(";")}`,
|
||||
),
|
||||
)
|
||||
.join("\n\n"),
|
||||
);
|
||||
|
||||
// 选格决定全文(第 2 样):因子、合并范围、各组合的信息价值/成本/净值、选中与没选的原因都在这条决定里。
|
||||
const cellsDecision =
|
||||
ofKind(records, "decision").find(
|
||||
(d) => d.content.decision === "cells" && d.content.round === round,
|
||||
) ?? null;
|
||||
const chosenCells = board.decision("cells", round)?.output.chosen ?? null;
|
||||
jsonFile(join(roundDir, "cells-decision.json"), cellsDecision);
|
||||
|
||||
// 结算全文(第 4 样):结算记录和同一事务里押的因子决定。
|
||||
const settlement =
|
||||
ofKind(records, "settlement").find((s) => s.content.round === round) ??
|
||||
null;
|
||||
const bet =
|
||||
ofKind(records, "decision").find(
|
||||
(d) => d.content.decision === "bet" && d.content.round === round,
|
||||
) ?? null;
|
||||
jsonFile(join(roundDir, "settlement.json"), { settlement, bet });
|
||||
|
||||
// 收尾全文(第 5 样)。
|
||||
const wrapUps = finals.filter(
|
||||
(s) =>
|
||||
s.content.kind === "wrapUp" && roundOf(s.content.stageId) === round,
|
||||
);
|
||||
jsonFile(join(roundDir, "wrapups.json"), wrapUps);
|
||||
writeFileSync(
|
||||
join(roundDir, "wrapups.txt"),
|
||||
wrapUps
|
||||
.map(
|
||||
(s) =>
|
||||
`${"=".repeat(20)} ${s.content.author}(记录 ${s.seq})\n${(s.content.content as { body: string }).body}`,
|
||||
)
|
||||
.join("\n\n"),
|
||||
);
|
||||
|
||||
// 消融格(第 3 样):冻结协议、位置表、完整代码与格代码及 diff、alsoOff、concerns、每次核验逐项结论与退回、重做记录。
|
||||
const cells = [];
|
||||
if (chosenCells !== null) {
|
||||
const adopt = board.decision("adopt", round)!;
|
||||
const candidate = adopt.output.adopted!;
|
||||
const full = board.code(candidate);
|
||||
const baseline = board.code(board.baselineOf(round));
|
||||
for (const key of chosenCells) {
|
||||
const cell = cellId(candidate, key);
|
||||
const cellDir = join(roundDir, "cells", cell);
|
||||
mkdirSync(cellDir, { recursive: true });
|
||||
jsonFile(join(cellDir, "protocol.json"), {
|
||||
record: ofKind(records, "protocol")
|
||||
.filter((p) => p.content.candidate === candidate)
|
||||
.at(-1),
|
||||
resolvedMetrics: protocolMetrics(
|
||||
board,
|
||||
board.protocol(candidate),
|
||||
),
|
||||
factorLocations: full.locations,
|
||||
baselineVersion: baseline.version,
|
||||
fullVersion: full.version,
|
||||
});
|
||||
checkout(full.version, join(cellDir, "full"));
|
||||
checkout(baseline.version, join(cellDir, "baseline"));
|
||||
// 每次实现和代码版本对号(同一事务里先写提交、紧接着写代码;交"跑不起来"时没有代码记录)。
|
||||
const impls = finals.filter(
|
||||
(s) =>
|
||||
s.content.kind === "ablationImplementation" &&
|
||||
s.content.target === cell,
|
||||
);
|
||||
const codes = ofKind(records, "code").filter(
|
||||
(c) => c.content.object === cell,
|
||||
);
|
||||
const attempts = impls.map((s) => ({
|
||||
submission: s,
|
||||
code: codes.find((c) => c.seq === s.seq + 1) ?? null,
|
||||
}));
|
||||
jsonFile(join(cellDir, "implementations.json"), attempts);
|
||||
const verifications = finals
|
||||
.filter(
|
||||
(s) =>
|
||||
s.content.kind === "ablationVerification" &&
|
||||
s.content.target === cell,
|
||||
)
|
||||
.map((s) => {
|
||||
const impl =
|
||||
impls.filter((x) => x.seq < s.seq).at(-1) ?? null;
|
||||
return {
|
||||
submission: s,
|
||||
judgedImplSeq: impl?.seq ?? null,
|
||||
outcome:
|
||||
impl === null
|
||||
? null
|
||||
: ablationOutcome(
|
||||
s.content.content as Parameters<
|
||||
typeof ablationOutcome
|
||||
>[0],
|
||||
key,
|
||||
(
|
||||
impl.content.content as {
|
||||
alsoOff: string[];
|
||||
}
|
||||
).alsoOff,
|
||||
),
|
||||
};
|
||||
});
|
||||
jsonFile(join(cellDir, "verifications.json"), verifications);
|
||||
jsonFile(
|
||||
join(cellDir, "rejections.json"),
|
||||
ofKind(records, "rejection").filter(
|
||||
(r) => r.content.target === cell,
|
||||
),
|
||||
);
|
||||
const lastCode = codes.at(-1) ?? null;
|
||||
if (lastCode !== null) {
|
||||
checkout(lastCode.content.version, join(cellDir, "cell"));
|
||||
jsonFile(
|
||||
join(cellDir, "diff.json"),
|
||||
compareVersions(
|
||||
repo,
|
||||
full.version,
|
||||
lastCode.content.version,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (const attempt of attempts) {
|
||||
if (attempt.code === null) continue;
|
||||
const path = join(cellDir, `code-${attempt.code.seq}`);
|
||||
checkout(attempt.code.content.version, path);
|
||||
jsonFile(
|
||||
join(cellDir, `diff-${attempt.code.seq}.json`),
|
||||
compareVersions(
|
||||
repo,
|
||||
full.version,
|
||||
attempt.code.content.version,
|
||||
),
|
||||
);
|
||||
}
|
||||
jsonFile(
|
||||
join(cellDir, "calls.json"),
|
||||
ofKind(records, "callStarted")
|
||||
.filter((c) => {
|
||||
const material = c.content.materials as TaskMaterial;
|
||||
return (
|
||||
material.object === cell &&
|
||||
[
|
||||
"ablationImplementation",
|
||||
"ablationVerification",
|
||||
].includes(material.kind)
|
||||
);
|
||||
})
|
||||
.map((c) => ({
|
||||
record: c,
|
||||
sessions: join(
|
||||
study,
|
||||
"calls",
|
||||
c.content.call,
|
||||
"sessions",
|
||||
),
|
||||
})),
|
||||
);
|
||||
cells.push({
|
||||
cell,
|
||||
factor: splitCell(cell).key,
|
||||
dir: cellDir,
|
||||
ran: lastCode !== null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
join(roundDir, "review.txt"),
|
||||
`任务书17第3.2节,只读本目录,中文直接回复,不写文件。\n` +
|
||||
`第1样(解释):读 explanations.txt、cells-decision.json、settlement.json。看不到消融结果地回答:机制是否具体、写了什么情况下不成立没有、三个数和正文是否一致、范围宽窄有没有依据、同轮几人的解释有没有真正不同、"没有具体机制判断"用得诚不诚实;再对 settlement.json 看每条解释在每格被确认还是否定。\n` +
|
||||
`第2样(选格):读 cells-decision.json,列出各格合并范围、各组合信息价值/成本/净值、选中和没选的原因。\n` +
|
||||
`第3样(消融实现与核验,每格一个全新上下文):读 cells/<格>/ 下的 protocol.json、full/、cell/、diff.json、implementations.json、verifications.json、rejections.json。判断:是不是只关了指定的因子;保障原样保留没有、关掉的因子的指标写 null 没有;答案路径标签和关掉以后的分支对不对得上;核验员每次逐项结论判得对不对;重做的只改了被指出的地方没有。\n` +
|
||||
`第5样(收尾与接力):读 wrapups.txt、settlement.json,再对着 round-${round + 1}/proposals.txt(没有就是最后一轮)看下一轮提案用上了哪些发现。\n`,
|
||||
);
|
||||
index.push({
|
||||
round,
|
||||
dir: roundDir,
|
||||
hasAblation: chosenCells !== null,
|
||||
cells,
|
||||
});
|
||||
}
|
||||
jsonFile(join(output, "index.json"), { study: dir, rounds: index });
|
||||
console.log(`审阅材料:${output}`);
|
||||
Reference in New Issue
Block a user