import assert from "node:assert/strict"; import { test } from "node:test"; import { ablationCellLimit, assignEntries, decisionSeed, forwardBatchSize, runBalance, seededRandom, seededSample, type RunState, } from "../src/rules/index.ts"; // 预期值来源写在每条测试旁边。 test("第七部分扣次示例:运行次数 13、每批上限 2,逐行余额、每轮 k 与消融上限", () => { // 来源:方法.md 第七部分示例表。每一行先按当时余额算出 k 或消融上限,再把这些运行记成确认启动。 const total = 13; const cap = 2; const runs: RunState[] = []; const spend = (n: number): number => { for (let i = 0; i < n; i++) runs.push("started"); return runBalance(total, runs).balance; }; assert.equal(spend(1), 12); // 基线 assert.equal(forwardBatchSize(cap, 12), 2); assert.equal(spend(2), 10); // 第 1 轮正向 assert.equal(ablationCellLimit(10), 9); assert.equal(spend(2), 8); // 第 1 轮消融选中 2 格 assert.equal(forwardBatchSize(cap, 8), 2); assert.equal(spend(2), 6); // 第 2 轮正向 assert.equal(forwardBatchSize(cap, 6), 2); assert.equal(spend(2), 4); // 第 3 轮正向 assert.equal(ablationCellLimit(4), 3); assert.equal(spend(0), 4); // 第 3 轮消融一格都不测 assert.equal(forwardBatchSize(cap, 4), 2); assert.equal(spend(2), 2); // 第 4 轮正向 assert.equal(forwardBatchSize(cap, 2), 2); assert.equal(spend(2), 0); // 第 5 轮正向,余额用完 assert.equal(runBalance(total, runs).charged, 13); }); test("第七部分:确认启动的扣,确认没启动的退还,已预留还没结果的先占着名额", () => { assert.deepEqual(runBalance(5, ["started", "reserved", "notStarted", "started"]), { charged: 2, reserved: 1, balance: 2, }); assert.deepEqual(runBalance(3, []), { charged: 0, reserved: 0, balance: 3 }); }); test("第七部分:余额 0、1、2 时的正向批量与消融上限", () => { // 手算:k = min(2, 余额);消融上限 = 余额 − 1,余额为 0 或 1 时是 0。 assert.deepEqual([0, 1, 2].map((b) => forwardBatchSize(2, b)), [0, 1, 2]); assert.deepEqual([0, 1, 2].map(ablationCellLimit), [0, 0, 1]); }); test("4.2 入口分配第 1、2 条:只有一个人选就给他;几人同选时作者优先", () => { // 手算:e2 被 r1、r2 同时选,作者 r2 在其中 → r2;e3 只有 r3 选 → r3;第 2 轮 r1 选 e1 → r1,第 2 志愿。 assert.deepEqual( assignEntries({ researchers: [ { id: "r1", choices: ["e2", "e1"] }, { id: "r2", choices: ["e2", "e3"] }, { id: "r3", choices: ["e3", "e1"] }, ], entries: [ { id: "e1", author: "r1" }, { id: "e2", author: "r2" }, { id: "e3", author: "r3" }, { id: "e4", author: "r1" }, ], }), [ { researcher: "r1", entry: "e1", rank: 2 }, { researcher: "r2", entry: "e2", rank: 1 }, { researcher: "r3", entry: "e3", rank: 1 }, ], ); }); test("4.2 入口分配第 2、3 条:作者不在其中给编号最小的;志愿用完的人按编号拿剩下编号最小的入口", () => { // 手算:e3(作者 r3)被 r1、r2 同选,r3 不在其中 → r1。r3 选 e1 → r3。 // r2 志愿用完;剩下 e2、e4 → r2 拿 e2,rank 为 null。 assert.deepEqual( assignEntries({ researchers: [ { id: "r1", choices: ["e3"] }, { id: "r2", choices: ["e3"] }, { id: "r3", choices: ["e1"] }, ], entries: [ { id: "e1", author: "r1" }, { id: "e2", author: "r2" }, { id: "e3", author: "r3" }, { id: "e4", author: "r1" }, ], }), [ { researcher: "r1", entry: "e3", rank: 1 }, { researcher: "r2", entry: "e2", rank: null }, { researcher: "r3", entry: "e1", rank: 1 }, ], ); // 两个人都落空:按编号各拿剩下的编号最小的。 assert.deepEqual( assignEntries({ researchers: [ { id: "r1", choices: ["e1"] }, { id: "r2", choices: ["e1"] }, { id: "r3", choices: ["e1"] }, ], entries: [ { id: "e1", author: "r9" }, { id: "e2", author: "r9" }, { id: "e3", author: "r9" }, ], }).map((a) => [a.researcher, a.entry, a.rank]), [ ["r1", "e1", 1], ["r2", "e2", null], ["r3", "e3", null], ], ); }); test("4.2 N = 1 时直接分第一志愿", () => { assert.deepEqual( assignEntries({ researchers: [{ id: "r1", choices: ["e3", "e1"] }], entries: [ { id: "e1", author: "r1" }, { id: "e2", author: "r1" }, { id: "e3", author: "r1" }, ], }), [{ researcher: "r1", entry: "e3", rank: 1 }], ); }); test("第九部分:种子 = hash({ studyId, round, version })", () => { // 预期哈希用命令行 printf '%s' '{"round":1,"studyId":"s1","version":1}' | sha256sum 算出。 assert.equal( decisionSeed({ studyId: "s1", round: 1, version: 1 }), "0fcf4530be51b098065a8f4a8774afc2c0be8f3a6de9b3b670bd26396d2c18ea", ); }); test("第九部分:同一个种子永远得到同一串数;抽样只取决于种子和编号集合", () => { const a = seededRandom("seed"); const b = seededRandom("seed"); const xs = Array.from({ length: 50 }, () => a.uniformInt(7)); assert.deepEqual( Array.from({ length: 50 }, () => b.uniformInt(7)), xs, ); assert.ok(xs.every((x) => Number.isInteger(x) && x >= 0 && x < 7)); assert.deepEqual(seededSample(["c", "a", "b", "d"], 2, "k"), seededSample(["d", "b", "a", "c"], 2, "k")); assert.deepEqual(seededSample(["c", "a", "b"], 3, "k"), ["a", "b", "c"]); assert.deepEqual(seededSample(["c", "a", "b"], 0, "k"), []); });