Files
InfoRSI/eval/round-zero/extract-16.ts
T
iomgaa 75d7cffd27 feat: flow 第 1 部分(骨架、研究开始、第零轮)与第零轮检验
按任务书 12 实现 src/flow:从账本现算下一步的纯函数、主循环、恢复入口、派模型任务和跑正式运行两个共用动作、冻结配置、基线、第零轮三个研究者阶段和入口分配,走到第 1 轮时停下。materials 导出 finalEntries、加 researcherId 和 StudyRecord。

审阅采纳两条:研究编号本机已用过时冻结前报错;docker run 没启动时写完结果就停下,不在本进程里无限起新编号。

eval/ 新增第零轮检验:起研究命令行、回放假模型、检验用 Jev 客户端、六问质量评估、16-real-1 回放、杀进程与两份同时跑。tsc 与 depcruise 也检查 eval/,新增 eval 只经 index.ts 用正式代码的规则。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:58:52 -04:00

72 lines
4.1 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 { copyFileSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { entryId } from "../../src/materials/index.ts";
import type { ReplayItem, ReplayScript } from "../lib/replay.ts";
// 从参考项目 16-real-1 的账本取第零轮的提交,按任务书 12 第 5.3 节转成新项目的格式,写成回放脚本。
// 原账本只读:先复制到临时目录,再以只读方式打开复制件,原目录里不写任何东西。
// node eval/round-zero/extract-16.ts
const SOURCE = "/home/iomgaa/rb-acceptance/16-real-1/board.sqlite";
export const REPLAY_FILE = "eval/round-zero/replay-16-real-1.json";
const PHASE_STAGE: Record<string, string> = { "entry-draft": "entryDraft", "entry-discuss": "discussion", "entry-claim": "claim" };
const copy = join(mkdtempSync(join(tmpdir(), "inforsi-rb16-")), "board.sqlite");
copyFileSync(SOURCE, copy);
const db = new DatabaseSync(copy, { readOnly: true });
type OldEntry = { name: string; startFrom: string; whyWorth: string };
const rows = db
.prepare(
`SELECT st.phase, st.id AS stage, s.actor, s.version, s.created_seq AS seq, s.payload
FROM submissions s JOIN stages st ON st.id = s.stage_id
WHERE st.phase IN ('entry-draft', 'entry-discuss', 'entry-claim') ORDER BY s.created_seq`,
)
.all() as Array<{ phase: string; stage: string; actor: string; version: number; seq: number; payload: string }>;
const fact = db.prepare("SELECT payload FROM facts WHERE kind = 'entry-assignment'").all() as Array<{ payload: string }>;
db.close();
if (fact.length !== 1) throw new Error(`entry-assignment 应有 1 条,实有 ${fact.length} 条`);
/** 当年入口 {name, startFrom, whyWorth} → 新项目 {name, body}。 */
const entry = (e: OldEntry) => ({ name: e.name, body: `从哪里开始看:${e.startFrom}\n\n为什么值得走:${e.whyWorth}` });
/** 当年入口编号 researcher-1-e4 → 新项目 e-researcher-1-4。 */
function newId(old: string): string {
const m = /^(researcher-\d+)-e(\d+)$/.exec(old);
if (m === null) throw new Error(`入口编号 ${old} 不是 researcher-<i>-e<n> 的写法`);
return entryId(m[1]!, Number(m[2]));
}
const models: Record<string, ReplayItem[]> = {};
for (const r of rows) {
const p = JSON.parse(r.payload) as { entries?: OldEntry[]; body?: string; choices?: string[] };
const payload =
r.phase === "entry-draft"
? { entries: p.entries!.map(entry) }
: r.phase === "entry-discuss"
? { body: p.body!, ...(p.entries === undefined ? {} : { entries: p.entries.map(entry) }) }
: { choices: p.choices!.map(newId) };
(models[r.actor] ??= []).push({ from: `${r.stage} ${PHASE_STAGE[r.phase]} seq ${r.seq} v${r.version}`, arguments: { payload } });
}
const old = JSON.parse(fact[0]!.payload) as { assignments: Array<{ researcher: string; entryId: string; rank: number | null }>; entries: Array<{ entryId: string; author: string }> };
const expected = old.assignments.map((a) => ({ researcher: a.researcher, entry: newId(a.entryId), rank: a.rank }));
const script: ReplayScript & { expected: typeof expected; expectedEntries: string[] } = {
source: `${SOURCE}(第零轮 submissions,created_seq ${rows[0]!.seq}–${rows.at(-1)!.seq})`,
models,
expected,
expectedEntries: old.entries.map((e) => newId(e.entryId)),
};
writeFileSync(REPLAY_FILE, `${JSON.stringify(script, null, 2)}\n`);
// 核对:三阶段各几份、作者、版本,以及当年的分配。
console.log(`共 ${rows.length} 份提交,序号 ${rows[0]!.seq}–${rows.at(-1)!.seq}`);
for (const phase of Object.keys(PHASE_STAGE)) {
const these = rows.filter((r) => r.phase === phase);
console.log(`${phase}:${these.length} 份;${these.map((r) => `${r.actor}@${r.seq}(v${r.version}${JSON.parse(r.payload).entries ? `,${JSON.parse(r.payload).entries.length} 个入口` : ""})`).join("、")}`);
}
console.log(`当年分配:${expected.map((a) => `${a.researcher} → ${a.entry}(第 ${a.rank} 志愿)`).join(";")}`);
console.log(`当年最终入口:${script.expectedEntries.join("、")}`);
console.log(`已写 ${REPLAY_FILE}`);