b7be524cdc
新增单份串行启动入口、首个正向运行杀进程恢复证据、逐候选审阅导出与账目统计;参考只读副本分数对照。456项免费测试通过,尚未启动三份真实验收。同步结构说明与当前工作状态。
217 lines
7.4 KiB
TypeScript
217 lines
7.4 KiB
TypeScript
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
||
import { join, resolve } from "node:path";
|
||
import type { LedgerRecord } from "../../src/ledger/index.ts";
|
||
import {
|
||
Board,
|
||
validScore,
|
||
type TaskMaterial,
|
||
} from "../../src/materials/index.ts";
|
||
import {
|
||
forwardAccounts,
|
||
frozenConfig,
|
||
jsonFile,
|
||
scoreBand,
|
||
} from "../lib/forward.ts";
|
||
import { finalSubmissions, ofKind, readLedger } from "../lib/ledger.ts";
|
||
import { lightCheckUsage } from "../lib/light-checks.ts";
|
||
import { referenceScores } from "../lib/reference-scores.ts";
|
||
|
||
// 五项表中可计算的字段。路线与审阅结论故意不自动猜,由父会话按候选审阅包填写报告。
|
||
// node eval/forward/stats.ts <全新绝对输出目录> <五份参考副本根目录> <检验目录>...
|
||
const [output, referenceRoot, ...dirs] = process.argv.slice(2);
|
||
if (
|
||
!output ||
|
||
!referenceRoot ||
|
||
dirs.length === 0 ||
|
||
output !== resolve(output)
|
||
)
|
||
throw new Error(
|
||
"用法:node eval/forward/stats.ts <全新绝对输出目录> <参考副本根目录> <检验目录>...",
|
||
);
|
||
mkdirSync(output);
|
||
const summaries = dirs.map((dir) => {
|
||
const studyDir = join(dir, "study");
|
||
const records = readLedger(studyDir);
|
||
const board = new Board(records);
|
||
const config = frozenConfig(records);
|
||
const accounts = forwardAccounts(records);
|
||
const batch = board.decision("batch", 1)!;
|
||
const finals = finalSubmissions(records);
|
||
const scores = accounts.candidates.map((c) => {
|
||
const p = board.protocol(c.candidate);
|
||
const merged = batch.output.candidates.find(
|
||
(b) => b.id === c.candidate,
|
||
)!.merged;
|
||
const secondPredictions = finals.flatMap((s) =>
|
||
s.content.kind === "secondPrediction" &&
|
||
s.content.target === c.candidate &&
|
||
board.stage(s.content.stageId).round === 1
|
||
? [
|
||
{
|
||
researcher: s.content.author,
|
||
q5: s.content.content.range.q5,
|
||
seq: s.seq,
|
||
},
|
||
]
|
||
: [],
|
||
);
|
||
const authorEstimates = p.proposals.map((id) => {
|
||
const proposal = finals.find(
|
||
(s) => s.content.kind === "proposal" && s.content.item === id,
|
||
)!;
|
||
const seal = ofKind(records, "seal").find(
|
||
(s) => s.content.proposal === proposal.seq,
|
||
)!;
|
||
return {
|
||
proposal: id,
|
||
author: proposal.content.author,
|
||
range: seal.content.range,
|
||
proposalSeq: proposal.seq,
|
||
sealSeq: seal.seq,
|
||
};
|
||
});
|
||
const runs = c.runs.map((r) => {
|
||
const result = r.results[0] ?? null;
|
||
const score = validScore(result?.content.judged ?? null);
|
||
return {
|
||
run: r.plan.content.run,
|
||
planSeq: r.plan.seq,
|
||
started: r.starts.length > 0,
|
||
result,
|
||
score,
|
||
band: scoreBand(score, merged),
|
||
measuredMinusQ5: score === null ? null : score - merged.q5,
|
||
};
|
||
});
|
||
return {
|
||
study: config.studyId,
|
||
candidate: c.candidate,
|
||
name: p.factors.map((f) => f.name).join(";"),
|
||
merged,
|
||
secondPredictions,
|
||
authorEstimates,
|
||
runs,
|
||
adopted: board.decision("adopt", 1)?.output.adopted === c.candidate,
|
||
verificationCount: c.verificationCount,
|
||
repairCount: c.repairCount,
|
||
started: c.started,
|
||
manualRequired:
|
||
"路线按冻结协议归类;完整高上限路线单列 measuredMinusQ5;忠实度及误判见独立审阅回复",
|
||
};
|
||
});
|
||
const lightChecks = ofKind(records, "callStarted")
|
||
.filter((c) =>
|
||
["implementation", "repair"].includes(
|
||
(c.content.materials as TaskMaterial).kind,
|
||
),
|
||
)
|
||
.map((c) => ({
|
||
call: c.content.call,
|
||
seq: c.seq,
|
||
...lightCheckUsage(
|
||
join(studyDir, "calls", c.content.call, "sessions"),
|
||
config.lightChecks.maxCount,
|
||
),
|
||
}));
|
||
let kill = null;
|
||
if (existsSync(join(dir, "kill.json"))) {
|
||
const evidence = JSON.parse(
|
||
readFileSync(join(dir, "kill.json"), "utf8"),
|
||
) as { run: string; candidate: string; beforeKillSeq: number };
|
||
type Container = {
|
||
Id: string;
|
||
State: { StartedAt: string; Running: boolean };
|
||
};
|
||
const snapshots = ["before", "after-kill", "after-resume"].map(
|
||
(part) =>
|
||
JSON.parse(
|
||
readFileSync(join(dir, `container-${part}.json`), "utf8"),
|
||
) as Container,
|
||
);
|
||
const target = accounts.runs.find(
|
||
(r) => r.plan.content.run === evidence.run,
|
||
)!;
|
||
const candidatePlans = accounts.runs.filter(
|
||
(r) =>
|
||
r.plan.content.purpose === "forward" &&
|
||
r.plan.content.object === evidence.candidate,
|
||
);
|
||
const atKill = JSON.parse(
|
||
readFileSync(join(dir, "ledger-at-kill.json"), "utf8"),
|
||
) as LedgerRecord[];
|
||
const killSeq = atKill.at(-1)!.seq;
|
||
kill = {
|
||
evidence,
|
||
killSeq,
|
||
snapshots,
|
||
charged: target.starts.length,
|
||
resultSeq: target.results[0]?.seq ?? null,
|
||
checks: {
|
||
sameContainer:
|
||
snapshots[0]!.State.Running &&
|
||
snapshots.every(
|
||
(c) =>
|
||
c.Id === snapshots[0]!.Id &&
|
||
c.State.StartedAt === snapshots[0]!.State.StartedAt,
|
||
),
|
||
chargedOnceNoRerun:
|
||
target.starts.length === 1 && candidatePlans.length === 1,
|
||
resultAndContinued:
|
||
!ofKind(atKill, "runResult").some(
|
||
(r) => r.content.run === evidence.run,
|
||
) &&
|
||
target.results.length === 1 &&
|
||
target.results[0]!.seq > killSeq &&
|
||
accounts.checks.adoptedAndStopped,
|
||
},
|
||
};
|
||
}
|
||
return {
|
||
dir,
|
||
study: config.studyId,
|
||
scores,
|
||
accounts,
|
||
lightChecks,
|
||
kill,
|
||
pass:
|
||
Object.values(accounts.checks).every(Boolean) &&
|
||
lightChecks.every((c) => c.pass) &&
|
||
(kill === null || Object.values(kill.checks).every(Boolean)),
|
||
};
|
||
});
|
||
const references = [
|
||
"16-real-1",
|
||
"15-real-1",
|
||
"15-tight-2",
|
||
"14-real-1",
|
||
"12-real-2",
|
||
].flatMap((name) => referenceScores(join(referenceRoot, name, "board.sqlite")));
|
||
jsonFile(join(output, "stats.json"), summaries);
|
||
jsonFile(
|
||
join(output, "predictions-scores.json"),
|
||
summaries.flatMap((s) => s.scores),
|
||
);
|
||
jsonFile(join(output, "reference-scores.json"), references);
|
||
jsonFile(join(output, "score-comparison.json"), {
|
||
references,
|
||
ours: summaries.flatMap((s) => s.scores),
|
||
caveat:
|
||
"参考项目提示词带题目具体做法、方法版本也不同;保留无效成绩及重复尝试,不作公平因果比较",
|
||
});
|
||
jsonFile(join(output, "manual-review.json"), {
|
||
implementation:
|
||
"每个已启动候选一个全新上下文:协议逐条、基线、最终代码、concerns、位置表;逐项漏做/额外改进/位置表准确性和代码位置",
|
||
verification:
|
||
"同一审阅者再读各次核验及对应版本:误放行/误拒、修复是否仅改指出处;保存原始回复",
|
||
route:
|
||
"逐候选按任务书15附录一路线归类,并单列完整高上限路线的实测-q5;不自动按关键词贴标签",
|
||
comparison:
|
||
"复用eval/route导出、items、thinking、verify-ranges、原四问Jev及原阅读问卷,填A–J;与任务15三次历史结果并排,标历史批次",
|
||
serial:
|
||
"核对各目录launches.jsonl、machine.jsonl和父会话观察;上一份全部结束后才起下一份;不宣称快照排除任意外部进程",
|
||
});
|
||
console.log(
|
||
`统计:${output};程序检查${summaries.every((s) => s.pass) ? "通过" : "未通过"};质量判断仍待独立阅读。`,
|
||
);
|
||
if (!summaries.every((s) => s.pass)) process.exitCode = 1;
|