Files
InfoRSI/tests/materials-belief.test.ts
iomgaa 13a7e60355 feat: 任务书 11,指标名加候选前缀、消融重做接着改、判重说明补关键设定
- 假设专用指标短名不能含"."(最长 64),冻结时由 materials 的 freezeProtocol 改为全名"候选编号.短名";可引用清单用全名,已占用清单只剩任务指标和两个保留键。
- 消融格核验不通过重做时,工作目录是上一次交的这一格代码的副本,能读的版本加上它,完整代码照旧列出;消融实现提示词同步改。
- jev 判重材料说明补 keySettings 一句。
- 结构.md 第八部分 vocab、materials、jev,WORK_LOG 同步更新。

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

243 lines
14 KiB
TypeScript
Raw Permalink 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 assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { Ledger } from "../src/ledger/index.ts";
import { beliefBook, cellNotRunEvent, queryMaterial } from "../src/materials/index.ts";
import { adopt, selectBestExpected, settleAblation, type BatchInput, type SettleInput } from "../src/rules/index.ts";
import { tempDir } from "./ledger-helpers.ts";
import { batch, judged, material, NONE, P_A1, P_A2, protocolOf, r, study, studyUpTo, TABLE_C1, Writer } from "./materials-helpers.ts";
// 信念本的留池、离池,按条文手算的数字,第零轮记录在第 1 轮起公开。
function fresh(t: import("node:test").TestContext): Writer {
const l = Ledger.open(join(tempDir(t), "ledger.db"));
t.after(() => l.close());
const w = new Writer(l);
w.l.append("code", { object: "base", version: "v-base", locations: null });
w.run("run-0", "baseline", 0, null, judged(0.96, "main"));
return w;
}
const adoptInput = (baseline: number, candidates: Array<{ id: string; score: number }>) => ({
baseline,
knownBest: 2.635983,
epsilon: 0.01,
direction: "maximize" as const,
candidates,
});
/** 第 1 轮冻结 r1-c1、r1-c2,选中 r1-c1、跑了没采纳:r1-c2 留到第 2 轮的候选池。 */
function roundOneNoAdoption(t: import("node:test").TestContext, before: (w: Writer) => void): Writer {
const w = fresh(t);
before(w);
w.protocol("r1-c1", protocolOf("r1-c1", P_A1, 1, "base", ["r1-a1-1"], ["c"]));
w.protocol("r1-c2", protocolOf("r1-c2", P_A2, 1, "base", ["r1-a2-1"], ["c"]));
const b1 = batch(1, 0.96, { "r1-c1": { a1: r(1.0, 1.1, 1.2) }, "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
assert.deepEqual(b1.output.chosen, ["r1-c1"]);
w.decision("batch", 1, b1.input, b1.output);
w.l.append("code", { object: "r1-c1", version: "v-c1", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-c1", judged(0.965, "main"));
const a1 = adoptInput(0.96, [{ id: "r1-c1", score: 0.965 }]);
w.decision("adopt", 1, a1, adopt(a1));
return w;
}
test("留池:第 1 轮没选中、第 2 轮入选跑了没采纳的候选,第 3 轮不再算留池(复现审阅意见 1)", (t) => {
const w = roundOneNoAdoption(t, () => undefined);
w.open("s2-prop", "proposal", 2, ["a1"]);
// 第 2 轮提案阶段(还没有本轮判重合并):r1-c2 没选中、基线没变,留在候选池。
assert.ok(beliefBook(w.l, study, 2).includes("r1-c2:没选中,留在本轮候选池;之前的合并范围不附"));
const b2 = batch(2, 0.96, { "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
w.decision("batch", 2, b2.input, b2.output);
w.l.append("code", { object: "r1-c2", version: "v-c2", locations: { factorLocations: [{ factor: "p1", locations: ["x.py"] }], safeguardLocations: [], unassigned: [] } });
w.run("run-2", "forward", 2, "r1-c2", judged(0.962, "main"));
const a2 = adoptInput(0.96, [{ id: "r1-c2", score: 0.962 }]);
w.decision("adopt", 2, a2, adopt(a2));
w.open("s3-prop", "proposal", 3, ["a1"]);
const book = beliefBook(w.l, study, 3);
assert.ok(!book.includes("留在本轮候选池"), "r1-c2 第 2 轮已经入选,不再留池");
assert.match(book, /- 第 1 轮选批[^\n]*\n(?:[^\n]*\n)*? - r1-c2:合并范围 /);
assert.ok(book.includes("离开候选池的旧候选**:没有"));
});
test("导出时(round 为 null)不藏任何候选的合并范围(复现复审意见 1)", (t) => {
const w = roundOneNoAdoption(t, () => undefined);
w.open("s2-prop", "proposal", 2, ["a1"]);
const book = beliefBook(w.l, study, null);
assert.ok(!book.includes("留在本轮候选池"));
assert.match(book, / - r1-c2:合并范围 /);
});
test("第二次预测:留池候选按 carried 判,不取它上一轮的第一次预测(复现复审意见 2)", (t) => {
const w = roundOneNoAdoption(t, (w) => {
w.open("s1-prop", "proposal", 1, ["a1"]);
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-1", P_A1);
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-2", P_A2);
w.close("s1-prop");
w.decision("dedup", 1, null, { fresh: [{ candidate: "r1-c1", proposals: ["r1-a1-1"] }, { candidate: "r1-c2", proposals: ["r1-a1-2"] }], carried: [] });
w.open("s1-fp", "firstPrediction", 1, ["a1"]);
w.sub("s1-fp", "a1", "firstPrediction", "r1-c2", null, { range: r(4.441, 4.442, 4.443), body: "上一轮的第一次" });
w.close("s1-fp");
w.open("s1-crit", "critique", 1, ["a1"]);
w.sub("s1-crit", "a1", "critique", "r1-a1-2", "r1-a1-2.a1.1", { target: "p1", body: "第 1 轮对乙方案的挑刺" });
w.close("s1-crit");
w.open("s1-resp", "response", 1, ["a1"]);
w.sub("s1-resp", "a1", "response", "r1-c2", null, { replies: [{ critique: "r1-a1-2.a1.1", reply: "第 1 轮的答复" }], changes: {}, clauses: ["c"], body: "第 1 轮的答复正文" });
w.close("s1-resp");
});
w.open("s2-prop", "proposal", 2, ["a1"]);
w.close("s2-prop");
w.decision("dedup", 2, null, { fresh: [], carried: [{ candidate: "r1-c2", proposals: [] }] });
w.open("s2-sp", "secondPrediction", 2, ["a1"]);
const m = material(w.l, "secondPrediction", "a1", "s2-sp", "r1-c2", NONE);
assert.ok(m.firstMessage.includes("(这是留池候选,本轮没有第一次预测)"));
assert.ok(!m.firstMessage.includes("4.442"));
// 留池候选沿用原来的冻结协议和回应(4.3 ①):答复从它第 1 轮的回应读。
for (const x of ["第 1 轮对乙方案的挑刺", "作者的答复:第 1 轮的答复", "第 1 轮的答复正文"]) assert.ok(m.firstMessage.includes(x), x);
});
test("离池:基线变了、从没入选的旧候选列在离开候选池里,不再写留池", (t) => {
const book = beliefBook(studyUpTo(t, "proposal2Open"), study, 2);
assert.ok(book.includes("离开候选池的旧候选**:r1-c2"));
assert.ok(!book.includes("留在本轮候选池"));
});
test("信念本的数按条文手算:少多少分、撑几成、世界比特、惊讶、战绩次数、标签", (t) => {
// 正向的三个候选、各人范围、本批基线 b 和 c3 的实测取自 tests/fixtures/rules/info.json 的 bits_forward_r1_real,
// 世界比特、惊讶的预期值是那里不看实现算出的独立值。
const fx = JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", "rules", "info.json"), "utf8")).scenarios[0];
const b: number = fx.input.b;
const full: number = fx.input.actual["r1-v1-c3"];
const w = fresh(t);
const input: BatchInput = {
candidates: Object.entries(fx.input.candidates as Record<string, Array<{ researcher: string; range: { q1: number; q5: number; q9: number } }>>).map(
([id, ranges]) => ({ id, ranges }),
),
baseline: b,
direction: "maximize",
epsilon: 0.01,
lambda: 0.1,
batchCap: 1,
balance: 9,
seed: "seed",
};
// 这里只要 c3 入选并跑过:选批记录的其余内容照规则算出,chosen 写成 c3。
w.decision("batch", 1, input, { ...selectBestExpected(input), chosen: ["r1-v1-c3"] });
w.protocol("r1-v1-c3", protocolOf("r1-v1-c3", P_A1, 1, "base", ["r1-a1-1"], ["c"]));
w.l.append("code", { object: "r1-v1-c3", version: "v-c3", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-v1-c3", judged(full, "main"));
const a = adoptInput(b, [{ id: "r1-v1-c3", score: full }]);
w.decision("adopt", 1, a, adopt(a));
w.run("run-2", "ablation", 1, "r1-v1-c3-p1", judged(1.3, "main"));
w.run("run-3", "ablation", 1, "r1-v1-c3-p2", judged(1.45, "main"));
const settle: SettleInput = {
baseline: b,
full,
direction: "maximize",
epsilon: 0.01,
knownBest: 2.635983,
lambda: 0.1,
factors: ["p1", "p2"],
cells: [
{ factor: "p1", outcome: { kind: "valid", score: 1.3 } },
{ factor: "p2", outcome: { kind: "valid", score: 1.45 } },
],
explanations: [
{ id: "r1-researcher-1-x1", author: "researcher-1", cells: [{ factor: "p1", range: r(1.25, 1.35, 1.45) }, { factor: "p2", range: r(1.5, 1.55, 1.6) }] },
{ id: "r1-researcher-2-x1", author: "researcher-2", cells: [{ factor: "p1", range: r(1.1, 1.2, 1.28) }, { factor: "p2", range: r(1.4, 1.44, 1.5) }] },
],
};
w.l.append("settlement", { round: 1, result: { candidate: "r1-v1-c3", input: settle, output: settleAblation(settle) } as never });
w.open("s2-prop", "proposal", 2, ["researcher-1"]);
const book = beliefBook(w.l, study, 2);
// 世界比特 13.018692667257758、惊讶 18.609640474436972(fixture),写 4 位有效数字。
assert.ok(book.includes("世界比特 13.02,惊讶 18.61 比特"), "正向 c3 的世界比特与惊讶");
// G = 1.552176048093269 − 0.9597642169962064 = 0.5924118311;关 p1:少 0.252176,撑 0.4257 → 43%;关 p2:少 0.102176,撑 0.1725 → 17%。
assert.ok(book.includes("关掉后 1.3,少 0.252176 分,撑 43%"));
assert.ok(book.includes("关掉后 1.45,少 0.102176 分,撑 17%"));
// Σ撑 = 0.598,相对交互量 −0.402 < −0.2:替代(方法.md 6.6)。
assert.ok(book.includes("替代:几个因子在做同一件事"));
// 正向:三人对 c3 的一成都在 2.1 以上,实测 1.552 低于一成:中位数偏高、穿过下沿,没包住。
for (const who of ["researcher-1", "researcher-2", "researcher-3"]) {
assert.ok(book.includes(`- ${who},正向 1 次:中位数偏高 1、偏低 0;穿过上沿 0、下沿 1;包住 0`), who);
}
// 消融:researcher-1 p1 1.3 落在 1.25–1.35(包住,中位数偏高)、p2 1.45 < 1.5(穿下沿,偏高);
// researcher-2 p1 1.3 > 1.28(穿上沿,偏低)、p2 1.45 在 1.44–1.5(包住,偏低)。
assert.ok(book.includes("- researcher-1,消融 2 次:中位数偏高 2、偏低 0;穿过上沿 0、下沿 1;包住 1"));
assert.ok(book.includes("- researcher-2,消融 2 次:中位数偏高 0、偏低 2;穿过上沿 1、下沿 0;包住 1"));
});
test("第零轮的入口、讨论、志愿、分配在第 1 轮起的研究记录里查得到(方法.md 4.2)", (t) => {
const m = material(studyUpTo(t, "proposalOpen"), "proposal", "a2", "s1-prop", null, NONE);
assert.ok(queryMaterial(m, "e-a1-1")!.includes("已被整份替换,作废"));
assert.ok(queryMaterial(m, "e-a2-1")!.includes("乙一起点"));
assert.ok(queryMaterial(m, "s0-d1.a1")!.includes("甲的讨论"));
assert.ok(queryMaterial(m, "s0-claim.a2")!.includes("e-a2-1"));
assert.ok(queryMaterial(m, "entry-assignment")!.includes("e-a1-3"));
});
test("第 2 轮解释的研究记录里查得到第 1 轮和第零轮的已公开记录", (t) => {
const m = material(studyUpTo(t, "explOpen2"), "explanation", "a1", "s2-expl", null, { changes: null, artifacts: {} });
const list = m.sections.find((s) => s.heading === "研究记录")!.text;
const ids = ["run-1", "run-2", "run-3", "r1-c1", "r1-a1-1", "r1-a2-1", "r1-a1-1.a2.1", "r1-a1-x1", "r1-a2-x1", "s1-wrap.a1", "s0-d1.a1", "s0-claim.a2", "entry-assignment"];
for (const id of ids) {
assert.ok(list.includes(`- ${id}:`), `研究记录清单里应有 ${id}`);
assert.notEqual(queryMaterial(m, id), null, id);
}
});
test("解释的现在交什么写明每人最多 M 条", (t) => {
const m = material(studyUpTo(t, "explOpen1"), "explanation", "a1", "s1-expl", null, { changes: null, artifacts: {} });
assert.ok(m.firstMessage.slice(m.firstMessage.lastIndexOf("# 现在交什么")).includes("最多 2 条"));
});
/** 第 1 轮冻结 r1-c1(两个因子)、选中、跑出 run1Judged、采纳。 */
function roundOneAdopted(t: import("node:test").TestContext, run1Judged: ReturnType<typeof judged>): Writer {
const w = fresh(t);
w.protocol("r1-c1", protocolOf("r1-c1", P_A1, 1, "base", ["r1-a1-1"], ["c"]));
const b1 = batch(1, 0.96, { "r1-c1": { a1: r(1.0, 1.1, 1.2) } });
w.decision("batch", 1, b1.input, b1.output);
w.l.append("code", { object: "r1-c1", version: "v-c1", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-c1", run1Judged);
const a1 = adoptInput(0.96, [{ id: "r1-c1", score: 1.2 }]);
w.decision("adopt", 1, a1, adopt(a1));
return w;
}
test("选批历史的每次运行后面原样写出任务的附加记录(合法化扣掉的分,方法.md 6.10 第 3 条)", (t) => {
const extra = { legalization: { applied: true, deduction: { status: "measured", value: 0.0012 } } };
const w = roundOneAdopted(t, { ...judged(1.2, "main"), extra });
const book = beliefBook(w.l, study, 1);
assert.ok(book.includes('任务的附加记录(含合法化,原样):{"legalization":{"applied":true,"deduction":{"status":"measured","value":0.0012}}}'), book);
});
test("消融格因核验不通过、重做次数用完而不跑:没有运行,证据不足的原因从事件读,信念本照样写得出", (t) => {
const w = roundOneAdopted(t, judged(1.2, "main"));
// p1 跑了,答案出自被关掉的 p1 本身:只照实写答案出处,不另加"本该关着"。p2 不跑。
w.l.append("code", { object: "r1-c1-p1", version: "v-c1-p1", locations: null });
w.run("run-2", "ablation", 1, "r1-c1-p1", judged(1.08, "p1"));
w.l.append("event", cellNotRunEvent("r1-c1-p2", "消融核验不通过,修复次数用完"));
const settle: SettleInput = {
baseline: 0.96,
full: 1.2,
direction: "maximize",
epsilon: 0.01,
knownBest: 2.635983,
lambda: 0.1,
factors: ["p1", "p2"],
cells: [
{ factor: "p1", outcome: { kind: "valid", score: 1.08 } },
{ factor: "p2", outcome: { kind: "insufficient" } },
],
explanations: [{ id: "r1-a1-x1", author: "a1", cells: [{ factor: "p1", range: r(1.0, 1.1, 1.15) }, { factor: "p2", range: r(1.1, 1.15, 1.19) }] }],
};
w.l.append("settlement", { round: 1, result: { candidate: "r1-c1", input: settle, output: settleAblation(settle) } as never });
const book = beliefBook(w.l, study, 2);
assert.ok(book.includes("- r1-c1-p2 停滞扰动(停滞扰动的设定):候选 r1-c1,基线 base;证据不足:消融核验不通过,修复次数用完;测前合并范围"), book);
// G = 1.2 − 0.96 = 0.24;关 p1 少 0.12,撑 0.5 → 50%。
assert.ok(book.includes("关掉后 1.08,少 0.12 分,撑 50%"), book);
assert.ok(book.includes("答案出自 p1"));
assert.ok(!book.includes("本该关着"));
});