revert: 撤掉 pi-lens 的两次纯格式重排(fdda4f0、2ca6da1)
用户 2026-10-05 同意撤掉;之后执行用 Claude Code 会话,不再有自动格式化。撤掉后 check 通过、477 条测试全过,vocab 回到 1131 行。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+95
-458
@@ -1,30 +1,9 @@
|
|||||||
import type { RoundStepId, StudyStepId } from "../config/index.ts";
|
import type { RoundStepId, StudyStepId } from "../config/index.ts";
|
||||||
import type { Content, LedgerRecord } from "../ledger/index.ts";
|
import type { Content, LedgerRecord } from "../ledger/index.ts";
|
||||||
import {
|
import { Board, CELL_NOT_RUN, cellId, DECISION_KINDS, experimenterId, factorKey, FORWARD_NOT_RUN, ORIGINAL_BASELINE, researcherId, splitCell, verifierId } from "../materials/index.ts";
|
||||||
Board,
|
import { compareIds, listCells, runBalance, sharedSettings } from "../rules/index.ts";
|
||||||
CELL_NOT_RUN,
|
|
||||||
cellId,
|
|
||||||
DECISION_KINDS,
|
|
||||||
experimenterId,
|
|
||||||
factorKey,
|
|
||||||
FORWARD_NOT_RUN,
|
|
||||||
ORIGINAL_BASELINE,
|
|
||||||
researcherId,
|
|
||||||
splitCell,
|
|
||||||
verifierId,
|
|
||||||
} from "../materials/index.ts";
|
|
||||||
import {
|
|
||||||
compareIds,
|
|
||||||
listCells,
|
|
||||||
runBalance,
|
|
||||||
sharedSettings,
|
|
||||||
} from "../rules/index.ts";
|
|
||||||
import { ablationOutcome, verificationOutcome } from "../submissions/index.ts";
|
import { ablationOutcome, verificationOutcome } from "../submissions/index.ts";
|
||||||
import {
|
import { SUBMISSION_KINDS, type Stage, type SubmissionKind } from "../vocab/index.ts";
|
||||||
SUBMISSION_KINDS,
|
|
||||||
type Stage,
|
|
||||||
type SubmissionKind,
|
|
||||||
} from "../vocab/index.ts";
|
|
||||||
import { readFrozen, type Frozen } from "./frozen.ts";
|
import { readFrozen, type Frozen } from "./frozen.ts";
|
||||||
|
|
||||||
// "下一步做什么"(docs/结构.md 第二部分"几条边界"):步骤的先后只写在这里。
|
// "下一步做什么"(docs/结构.md 第二部分"几条边界"):步骤的先后只写在这里。
|
||||||
@@ -40,20 +19,14 @@ const ROUND_ZERO_STAGES = {
|
|||||||
discussion1: { stage: "discussion", kind: "discussion" },
|
discussion1: { stage: "discussion", kind: "discussion" },
|
||||||
discussion2: { stage: "discussion", kind: "discussion" },
|
discussion2: { stage: "discussion", kind: "discussion" },
|
||||||
claim: { stage: "claim", kind: "claim" },
|
claim: { stage: "claim", kind: "claim" },
|
||||||
} as const satisfies Partial<
|
} as const satisfies Partial<Record<StudyStepId, { stage: Stage; kind: string }>>;
|
||||||
Record<StudyStepId, { stage: Stage; kind: string }>
|
|
||||||
>;
|
|
||||||
type RoundZeroStep = keyof typeof ROUND_ZERO_STAGES;
|
type RoundZeroStep = keyof typeof ROUND_ZERO_STAGES;
|
||||||
|
|
||||||
/** 第 r 轮的研究者阶段:步骤名就是阶段名(vocab STAGES)。 */
|
/** 第 r 轮的研究者阶段:步骤名就是阶段名(vocab STAGES)。 */
|
||||||
type RoundStageStep = Extract<
|
type RoundStageStep = Extract<RoundStepId, "proposal" | "firstPrediction" | "critique" | "response" | "secondPrediction">;
|
||||||
RoundStepId,
|
|
||||||
"proposal" | "firstPrediction" | "critique" | "response" | "secondPrediction"
|
|
||||||
>;
|
|
||||||
|
|
||||||
/** 阶段编号由 flow 起:r<轮>-<步骤>,一个步骤一个阶段。第 r 轮按配置跳过的步骤也用这个编号记"按配置跳过"。 */
|
/** 阶段编号由 flow 起:r<轮>-<步骤>,一个步骤一个阶段。第 r 轮按配置跳过的步骤也用这个编号记"按配置跳过"。 */
|
||||||
export const stageIdOf = (round: number, step: string): string =>
|
export const stageIdOf = (round: number, step: string): string => `r${round}-${step}`;
|
||||||
`r${round}-${step}`;
|
|
||||||
/** 基线运行编号:没启动就起下一个编号重跑(docs/结构.md 第四部分)。 */
|
/** 基线运行编号:没启动就起下一个编号重跑(docs/结构.md 第四部分)。 */
|
||||||
export const baselineRunId = (n: number): string => `base-${n}`;
|
export const baselineRunId = (n: number): string => `base-${n}`;
|
||||||
|
|
||||||
@@ -68,24 +41,14 @@ export const EVENTS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** 一个任务:一个人对一个对象交一种提交;提案和第零轮任务没有对象。 */
|
/** 一个任务:一个人对一个对象交一种提交;提案和第零轮任务没有对象。 */
|
||||||
export type TaskRef = {
|
export type TaskRef = { actor: string; kind: SubmissionKind; object: string | null };
|
||||||
actor: string;
|
|
||||||
kind: SubmissionKind;
|
|
||||||
object: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Next =
|
export type Next =
|
||||||
| { do: "freezeConfig" }
|
| { do: "freezeConfig" }
|
||||||
| { do: "saveBaseline" }
|
| { do: "saveBaseline" }
|
||||||
| { do: "baselineRun"; run: string }
|
| { do: "baselineRun"; run: string }
|
||||||
| { do: "skip"; step: string; reason: string }
|
| { do: "skip"; step: string; reason: string }
|
||||||
| {
|
| { do: "openStage"; stageId: string; stage: Stage; round: number; roster: string[] }
|
||||||
do: "openStage";
|
|
||||||
stageId: string;
|
|
||||||
stage: Stage;
|
|
||||||
round: number;
|
|
||||||
roster: string[];
|
|
||||||
}
|
|
||||||
| { do: "closeStage"; stageId: string }
|
| { do: "closeStage"; stageId: string }
|
||||||
| { do: "dispatch"; stageId: string; tasks: TaskRef[] }
|
| { do: "dispatch"; stageId: string; tasks: TaskRef[] }
|
||||||
| { do: "assignEntries" }
|
| { do: "assignEntries" }
|
||||||
@@ -109,11 +72,7 @@ export function nextStep(records: readonly LedgerRecord[]): Next {
|
|||||||
const view = new View(records);
|
const view = new View(records);
|
||||||
for (const step of frozen.steps.study) {
|
for (const step of frozen.steps.study) {
|
||||||
const next =
|
const next =
|
||||||
step.status === "skip"
|
step.status === "skip" ? skipNext(step.id, step.reason, view) : step.id === "rounds" ? roundsNext(view, frozen) : studyStepNext(step.id, view, frozen);
|
||||||
? skipNext(step.id, step.reason, view)
|
|
||||||
: step.id === "rounds"
|
|
||||||
? roundsNext(view, frozen)
|
|
||||||
: studyStepNext(step.id, view, frozen);
|
|
||||||
if (next !== null) return next;
|
if (next !== null) return next;
|
||||||
}
|
}
|
||||||
throw new Error("步骤清单走完了却没有停下:export 一步应当停下");
|
throw new Error("步骤清单走完了却没有停下:export 一步应当停下");
|
||||||
@@ -121,17 +80,11 @@ export function nextStep(records: readonly LedgerRecord[]): Next {
|
|||||||
|
|
||||||
/** 按配置跳过的步骤 → 一条"按配置跳过"事件,写明步骤和原因。 */
|
/** 按配置跳过的步骤 → 一条"按配置跳过"事件,写明步骤和原因。 */
|
||||||
function skipNext(step: string, reason: string, view: View): Next | null {
|
function skipNext(step: string, reason: string, view: View): Next | null {
|
||||||
return view.events(EVENTS.skipped).some((e) => e.objects[0] === step)
|
return view.events(EVENTS.skipped).some((e) => e.objects[0] === step) ? null : { do: "skip", step, reason };
|
||||||
? null
|
|
||||||
: { do: "skip", step, reason };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 整份研究的一个要执行的步骤还要做什么;做完了交回 null。 */
|
/** 整份研究的一个要执行的步骤还要做什么;做完了交回 null。 */
|
||||||
function studyStepNext(
|
function studyStepNext(step: Exclude<StudyStepId, "rounds">, view: View, frozen: Frozen): Next | null {
|
||||||
step: Exclude<StudyStepId, "rounds">,
|
|
||||||
view: View,
|
|
||||||
frozen: Frozen,
|
|
||||||
): Next | null {
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case "freezeConfig":
|
case "freezeConfig":
|
||||||
return null;
|
return null;
|
||||||
@@ -142,21 +95,11 @@ function studyStepNext(
|
|||||||
case "discussion2":
|
case "discussion2":
|
||||||
case "claim": {
|
case "claim": {
|
||||||
const { stage, kind } = ROUND_ZERO_STAGES[step];
|
const { stage, kind } = ROUND_ZERO_STAGES[step];
|
||||||
return stageNext(
|
return stageNext(stageIdOf(0, step), stage, 0, (roster) => roster.map((actor) => ({ actor, kind, object: null })), view, frozen, researchers(frozen));
|
||||||
stageIdOf(0, step),
|
|
||||||
stage,
|
|
||||||
0,
|
|
||||||
(roster) => roster.map((actor) => ({ actor, kind, object: null })),
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
researchers(frozen),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case "assignEntries":
|
case "assignEntries":
|
||||||
// 分配 → 第 0 轮的入口分配决定(decision,种类 entries)。
|
// 分配 → 第 0 轮的入口分配决定(decision,种类 entries)。
|
||||||
return view.board.decision("entries", 0) === undefined
|
return view.board.decision("entries", 0) === undefined ? { do: "assignEntries" } : null;
|
||||||
? { do: "assignEntries" }
|
|
||||||
: null;
|
|
||||||
case "export":
|
case "export":
|
||||||
// 余额用完、一轮接一轮走完后走到这里:导出还没写,停下(任务书 17 第 2.1 节)。
|
// 余额用完、一轮接一轮走完后走到这里:导出还没写,停下(任务书 17 第 2.1 节)。
|
||||||
return { do: "halt", reason: "导出还没写", objects: [] };
|
return { do: "halt", reason: "导出还没写", objects: [] };
|
||||||
@@ -173,16 +116,9 @@ function roundsNext(view: View, frozen: Frozen): Next | null {
|
|||||||
// 余额为 0 不开【下一轮】;已经开始的轮次照常走完——最后一次正向用掉全部余额时,这一轮的采纳、
|
// 余额为 0 不开【下一轮】;已经开始的轮次照常走完——最后一次正向用掉全部余额时,这一轮的采纳、
|
||||||
// 消融(各格记“没测:剩余次数不够”)、收尾与解封照样做(方法.md 第七部分“什么时候开下一轮”)。
|
// 消融(各格记“没测:剩余次数不够”)、收尾与解封照样做(方法.md 第七部分“什么时候开下一轮”)。
|
||||||
// 一轮在账本里留下的第一处痕迹是阶段记录(或按配置跳过事件、决定记录),此前这一轮还不算开始。
|
// 一轮在账本里留下的第一处痕迹是阶段记录(或按配置跳过事件、决定记录),此前这一轮还不算开始。
|
||||||
if (
|
if (!roundStarted(view, round) && runBalance(frozen.config.runs, view.board.runStates()).balance === 0) return null;
|
||||||
!roundStarted(view, round) &&
|
|
||||||
runBalance(frozen.config.runs, view.board.runStates()).balance === 0
|
|
||||||
)
|
|
||||||
return null;
|
|
||||||
for (const step of frozen.steps.round) {
|
for (const step of frozen.steps.round) {
|
||||||
const next =
|
const next = step.status === "skip" ? skipNext(stageIdOf(round, step.id), step.reason, view) : roundStepNext(step.id, round, view, frozen);
|
||||||
step.status === "skip"
|
|
||||||
? skipNext(stageIdOf(round, step.id), step.reason, view)
|
|
||||||
: roundStepNext(step.id, round, view, frozen);
|
|
||||||
if (next !== null) return next;
|
if (next !== null) return next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,29 +127,13 @@ function roundsNext(view: View, frozen: Frozen): Next | null {
|
|||||||
/** 第 round 轮开始了没有:看账本里有没有这一轮的阶段、跳过事件或决定。 */
|
/** 第 round 轮开始了没有:看账本里有没有这一轮的阶段、跳过事件或决定。 */
|
||||||
function roundStarted(view: View, round: number): boolean {
|
function roundStarted(view: View, round: number): boolean {
|
||||||
const prefix = `r${round}-`;
|
const prefix = `r${round}-`;
|
||||||
if ([...view.board.stages.keys()].some((id) => id.startsWith(prefix)))
|
if ([...view.board.stages.keys()].some((id) => id.startsWith(prefix))) return true;
|
||||||
return true;
|
if (DECISION_KINDS.some((kind) => view.board.decision(kind, round) !== undefined)) return true;
|
||||||
if (
|
return view.events(EVENTS.skipped).some((e) => typeof e.objects[0] === "string" && e.objects[0].startsWith(prefix));
|
||||||
DECISION_KINDS.some(
|
|
||||||
(kind) => view.board.decision(kind, round) !== undefined,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
return view
|
|
||||||
.events(EVENTS.skipped)
|
|
||||||
.some(
|
|
||||||
(e) =>
|
|
||||||
typeof e.objects[0] === "string" && e.objects[0].startsWith(prefix),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 第 round 轮的一个要执行的步骤还要做什么;做完了交回 null。 */
|
/** 第 round 轮的一个要执行的步骤还要做什么;做完了交回 null。 */
|
||||||
function roundStepNext(
|
function roundStepNext(step: RoundStepId, round: number, view: View, frozen: Frozen): Next | null {
|
||||||
step: RoundStepId,
|
|
||||||
round: number,
|
|
||||||
view: View,
|
|
||||||
frozen: Frozen,
|
|
||||||
): Next | null {
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case "openRound":
|
case "openRound":
|
||||||
case "carryOverPool":
|
case "carryOverPool":
|
||||||
@@ -224,55 +144,25 @@ function roundStepNext(
|
|||||||
case "critique":
|
case "critique":
|
||||||
case "response":
|
case "response":
|
||||||
case "secondPrediction":
|
case "secondPrediction":
|
||||||
return stageNext(
|
return stageNext(stageIdOf(round, step), step, round, (roster) => roundTasks(step, round, roster, view.board), view, frozen, researchers(frozen));
|
||||||
stageIdOf(round, step),
|
|
||||||
step,
|
|
||||||
round,
|
|
||||||
(roster) => roundTasks(step, round, roster, view.board),
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
researchers(frozen),
|
|
||||||
);
|
|
||||||
case "dedupe":
|
case "dedupe":
|
||||||
// 判重合并 → 本轮的判重合并决定(decision,种类 dedup)。
|
// 判重合并 → 本轮的判重合并决定(decision,种类 dedup)。
|
||||||
return view.board.decision("dedup", round) === undefined
|
return view.board.decision("dedup", round) === undefined ? { do: "dedupe", round } : null;
|
||||||
? { do: "dedupe", round }
|
|
||||||
: null;
|
|
||||||
case "freezeProtocol": {
|
case "freezeProtocol": {
|
||||||
// 冻结协议 → 本轮每个新候选都有冻结协议记录(protocol)。全部候选在同一个事务里写;
|
// 冻结协议 → 本轮每个新候选都有冻结协议记录(protocol)。全部候选在同一个事务里写;
|
||||||
// 这一步排在回应阶段关闭之后,materials 把冻结协议的出现当作"已公开",早写会泄露。
|
// 这一步排在回应阶段关闭之后,materials 把冻结协议的出现当作"已公开",早写会泄露。
|
||||||
if (
|
if (frozen.steps.round.some((s) => s.id === "response" && s.status === "skip")) {
|
||||||
frozen.steps.round.some(
|
return { do: "halt", reason: "配置关掉了挑刺与回应时怎么冻结协议还没写:submissions 的 protocolBody 只收回应和统一", objects: [] };
|
||||||
(s) => s.id === "response" && s.status === "skip",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
do: "halt",
|
|
||||||
reason:
|
|
||||||
"配置关掉了挑刺与回应时怎么冻结协议还没写:submissions 的 protocolBody 只收回应和统一",
|
|
||||||
objects: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
const frozenIds = new Set(view.board.protocols().map((p) => p.candidate));
|
const frozenIds = new Set(view.board.protocols().map((p) => p.candidate));
|
||||||
return freshOf(view.board, round).every((c) => frozenIds.has(c.candidate))
|
return freshOf(view.board, round).every((c) => frozenIds.has(c.candidate)) ? null : { do: "freezeProtocols", round };
|
||||||
? null
|
|
||||||
: { do: "freezeProtocols", round };
|
|
||||||
}
|
}
|
||||||
case "selectBatch":
|
case "selectBatch":
|
||||||
// 选批 → 本轮的选批决定(decision,种类 batch)。
|
// 选批 → 本轮的选批决定(decision,种类 batch)。
|
||||||
return view.board.decision("batch", round) === undefined
|
return view.board.decision("batch", round) === undefined ? { do: "selectBatch", round } : null;
|
||||||
? { do: "selectBatch", round }
|
|
||||||
: null;
|
|
||||||
case "implementation": {
|
case "implementation": {
|
||||||
const candidates = view.board.decision("batch", round)!.output.chosen;
|
const candidates = view.board.decision("batch", round)!.output.chosen;
|
||||||
return assignedStage(
|
return assignedStage("implementation", round, "implementation", candidates, view, frozen);
|
||||||
"implementation",
|
|
||||||
round,
|
|
||||||
"implementation",
|
|
||||||
candidates,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case "verification":
|
case "verification":
|
||||||
case "repair":
|
case "repair":
|
||||||
@@ -284,94 +174,42 @@ function roundStepNext(
|
|||||||
// 不另写记录:整批运行已结束。采纳决定使结果整批公开,信息账从预测和结果现算。
|
// 不另写记录:整批运行已结束。采纳决定使结果整批公开,信息账从预测和结果现算。
|
||||||
return null;
|
return null;
|
||||||
case "adopt":
|
case "adopt":
|
||||||
return view.board.decision("adopt", round) === undefined
|
return view.board.decision("adopt", round) === undefined ? { do: "adopt", round } : null;
|
||||||
? { do: "adopt", round }
|
|
||||||
: null;
|
|
||||||
case "listCells":
|
case "listCells":
|
||||||
// 不写记录:格从采纳决定和冻结协议现算(方法.md 4.3 ⑫),格编号用 materials 的 cellId。
|
// 不写记录:格从采纳决定和冻结协议现算(方法.md 4.3 ⑫),格编号用 materials 的 cellId。
|
||||||
return null;
|
return null;
|
||||||
case "explanation": {
|
case "explanation": {
|
||||||
// ⑭:这一轮有消融(采纳了、且被采纳改法有两个以上因子)才开解释阶段;否则直接到收尾(方法.md 4.3 ⑫)。
|
// ⑭:这一轮有消融(采纳了、且被采纳改法有两个以上因子)才开解释阶段;否则直接到收尾(方法.md 4.3 ⑫)。
|
||||||
// 解释 → 这一轮解释阶段的关闭记录。
|
// 解释 → 这一轮解释阶段的关闭记录。
|
||||||
const adopted =
|
const adopted = view.board.decision("adopt", round)?.output.adopted ?? null;
|
||||||
view.board.decision("adopt", round)?.output.adopted ?? null;
|
if (adopted === null || view.board.protocol(adopted).factors.length < 2) return null;
|
||||||
if (adopted === null || view.board.protocol(adopted).factors.length < 2)
|
if (frozen.steps.round.some((s) => s.id === "listCells" && s.status === "skip")) {
|
||||||
return null;
|
return { do: "halt", reason: "配置关掉了消融、又采纳了多因子改法:不交三个数的解释还没写(cells 只按完整流程全开实现,做步骤开关时再补)", objects: [] };
|
||||||
if (
|
|
||||||
frozen.steps.round.some(
|
|
||||||
(s) => s.id === "listCells" && s.status === "skip",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
do: "halt",
|
|
||||||
reason:
|
|
||||||
"配置关掉了消融、又采纳了多因子改法:不交三个数的解释还没写(cells 只按完整流程全开实现,做步骤开关时再补)",
|
|
||||||
objects: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return stageNext(
|
return stageNext(stageIdOf(round, "explanation"), "explanation", round, (roster) => roster.map((actor) => ({ actor, kind: "explanation" as const, object: null })), view, frozen, researchers(frozen));
|
||||||
stageIdOf(round, "explanation"),
|
|
||||||
"explanation",
|
|
||||||
round,
|
|
||||||
(roster) =>
|
|
||||||
roster.map((actor) => ({
|
|
||||||
actor,
|
|
||||||
kind: "explanation" as const,
|
|
||||||
object: null,
|
|
||||||
})),
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
researchers(frozen),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case "selectCells":
|
case "selectCells":
|
||||||
// ⑮ → 本轮的选格决定(decision,种类 cells)。没选中的格不另写事件:原因都在这条决定里。
|
// ⑮ → 本轮的选格决定(decision,种类 cells)。没选中的格不另写事件:原因都在这条决定里。
|
||||||
if (ablationOf(view.board, round) === null) return null;
|
if (ablationOf(view.board, round) === null) return null;
|
||||||
return view.board.decision("cells", round) === undefined
|
return view.board.decision("cells", round) === undefined ? { do: "selectCells", round } : null;
|
||||||
? { do: "selectCells", round }
|
|
||||||
: null;
|
|
||||||
case "ablationImplementation":
|
case "ablationImplementation":
|
||||||
case "ablationVerification":
|
case "ablationVerification":
|
||||||
// ⑯ → 每个选中的格:交了"跑不起来",或最近一次核验通过,或重做次数用完、有"不跑"事件。
|
// ⑯ → 每个选中的格:交了"跑不起来",或最近一次核验通过,或重做次数用完、有"不跑"事件。
|
||||||
return ablationOf(view.board, round) === null
|
return ablationOf(view.board, round) === null ? null : ablationCellsNext(round, view, frozen);
|
||||||
? null
|
|
||||||
: ablationCellsNext(round, view, frozen);
|
|
||||||
case "ablationRun":
|
case "ablationRun":
|
||||||
// ⑯ → 每个通过核验的格,最近一次消融运行有结果,而且不是"没启动"。
|
// ⑯ → 每个通过核验的格,最近一次消融运行有结果,而且不是"没启动"。
|
||||||
return ablationOf(view.board, round) === null
|
return ablationOf(view.board, round) === null ? null : ablationRunNext(round, view);
|
||||||
? null
|
|
||||||
: ablationRunNext(round, view);
|
|
||||||
case "settle":
|
case "settle":
|
||||||
// ⑰ → 本轮的结算记录(settlement),以及同一事务里的押的因子决定(decision,种类 bet)。
|
// ⑰ → 本轮的结算记录(settlement),以及同一事务里的押的因子决定(decision,种类 bet)。
|
||||||
if (ablationOf(view.board, round) === null) return null;
|
if (ablationOf(view.board, round) === null) return null;
|
||||||
return view.board.settlement(round) === undefined ||
|
return view.board.settlement(round) === undefined || view.board.decision("bet", round) === undefined ? { do: "settle", round } : null;
|
||||||
view.board.decision("bet", round) === undefined
|
|
||||||
? { do: "settle", round }
|
|
||||||
: null;
|
|
||||||
case "wrapUp": {
|
case "wrapUp": {
|
||||||
// ⑱ → 收尾阶段的关闭记录,以及本轮选中的每个候选都有解封记录。
|
// ⑱ → 收尾阶段的关闭记录,以及本轮选中的每个候选都有解封记录。
|
||||||
const stage = stageNext(
|
const stage = stageNext(stageIdOf(round, "wrapUp"), "wrapUp", round, (roster) => roster.map((actor) => ({ actor, kind: "wrapUp" as const, object: null })), view, frozen, researchers(frozen));
|
||||||
stageIdOf(round, "wrapUp"),
|
|
||||||
"wrapUp",
|
|
||||||
round,
|
|
||||||
(roster) =>
|
|
||||||
roster.map((actor) => ({
|
|
||||||
actor,
|
|
||||||
kind: "wrapUp" as const,
|
|
||||||
object: null,
|
|
||||||
})),
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
researchers(frozen),
|
|
||||||
);
|
|
||||||
if (stage !== null) return stage;
|
if (stage !== null) return stage;
|
||||||
// 轮末解封(方法.md 4.3 ⑱):只在收尾阶段关闭之后写,提前写会泄露。本轮全部解封写在同一个事务里。
|
// 轮末解封(方法.md 4.3 ⑱):只在收尾阶段关闭之后写,提前写会泄露。本轮全部解封写在同一个事务里。
|
||||||
const chosen = view.board.decision("batch", round)!.output.chosen;
|
const chosen = view.board.decision("batch", round)!.output.chosen;
|
||||||
return chosen.every((c) =>
|
return chosen.every((c) => view.of("unseal").some((u) => u.candidate === c)) ? null : { do: "unsealChosen", round };
|
||||||
view.of("unseal").some((u) => u.candidate === c),
|
|
||||||
)
|
|
||||||
? null
|
|
||||||
: { do: "unsealChosen", round };
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`第 ${round} 轮的 ${step} 没有对应的下一步写法`);
|
throw new Error(`第 ${round} 轮的 ${step} 没有对应的下一步写法`);
|
||||||
@@ -379,19 +217,13 @@ function roundStepNext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 第 round 轮判重合并出的新候选,以及各合并了哪几条原提案。 */
|
/** 第 round 轮判重合并出的新候选,以及各合并了哪几条原提案。 */
|
||||||
export function freshOf(
|
export function freshOf(board: Board, round: number): Array<{ candidate: string; proposals: string[] }> {
|
||||||
board: Board,
|
|
||||||
round: number,
|
|
||||||
): Array<{ candidate: string; proposals: string[] }> {
|
|
||||||
return board.decision("dedup", round)!.output.fresh;
|
return board.decision("dedup", round)!.output.fresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 第 round 轮的候选池:新候选加留池候选(方法.md 4.3 ①⑦)。第二次预测和选批都用这一份。 */
|
/** 第 round 轮的候选池:新候选加留池候选(方法.md 4.3 ①⑦)。第二次预测和选批都用这一份。 */
|
||||||
export function poolOf(board: Board, round: number): string[] {
|
export function poolOf(board: Board, round: number): string[] {
|
||||||
return [
|
return [...freshOf(board, round).map((c) => c.candidate), ...board.carried(round)];
|
||||||
...freshOf(board, round).map((c) => c.candidate),
|
|
||||||
...board.carried(round),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -402,34 +234,17 @@ export function poolOf(board: Board, round: number): string[] {
|
|||||||
* - 回应与统一:每个新候选一个任务,一条原提案的由作者交回应,几条合成的由其中编号最小的作者交统一(方法.md 4.3 ③⑥);
|
* - 回应与统一:每个新候选一个任务,一条原提案的由作者交回应,几条合成的由其中编号最小的作者交统一(方法.md 4.3 ③⑥);
|
||||||
* - 第二次预测:每人 × 候选池里每个候选。
|
* - 第二次预测:每人 × 候选池里每个候选。
|
||||||
*/
|
*/
|
||||||
function roundTasks(
|
function roundTasks(step: RoundStageStep, round: number, roster: readonly string[], board: Board): TaskRef[] {
|
||||||
step: RoundStageStep,
|
|
||||||
round: number,
|
|
||||||
roster: readonly string[],
|
|
||||||
board: Board,
|
|
||||||
): TaskRef[] {
|
|
||||||
const each = (kind: TaskRef["kind"], objects: readonly string[]): TaskRef[] =>
|
const each = (kind: TaskRef["kind"], objects: readonly string[]): TaskRef[] =>
|
||||||
roster.flatMap((actor) =>
|
roster.flatMap((actor) => objects.map((object) => ({ actor, kind, object })));
|
||||||
objects.map((object) => ({ actor, kind, object })),
|
|
||||||
);
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case "proposal":
|
case "proposal":
|
||||||
return roster.map((actor) => ({ actor, kind: "proposal", object: null }));
|
return roster.map((actor) => ({ actor, kind: "proposal", object: null }));
|
||||||
case "firstPrediction":
|
case "firstPrediction":
|
||||||
return each(
|
return each("firstPrediction", freshOf(board, round).map((c) => c.candidate));
|
||||||
"firstPrediction",
|
|
||||||
freshOf(board, round).map((c) => c.candidate),
|
|
||||||
);
|
|
||||||
case "critique": {
|
case "critique": {
|
||||||
const proposals = board
|
const proposals = board.published("proposal").filter((p) => p.round === round).sort((a, b) => compareIds(a.item!, b.item!));
|
||||||
.published("proposal")
|
return roster.flatMap((actor) => proposals.filter((p) => p.author !== actor).map((p) => ({ actor, kind: "critique" as const, object: p.item! })));
|
||||||
.filter((p) => p.round === round)
|
|
||||||
.sort((a, b) => compareIds(a.item!, b.item!));
|
|
||||||
return roster.flatMap((actor) =>
|
|
||||||
proposals
|
|
||||||
.filter((p) => p.author !== actor)
|
|
||||||
.map((p) => ({ actor, kind: "critique" as const, object: p.item! })),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case "response":
|
case "response":
|
||||||
return freshOf(board, round).map((c) => ({
|
return freshOf(board, round).map((c) => ({
|
||||||
@@ -447,28 +262,16 @@ function roundTasks(
|
|||||||
* 没启动的起新编号重跑;判为不算数的停下(方法.md 4.1)。
|
* 没启动的起新编号重跑;判为不算数的停下(方法.md 4.1)。
|
||||||
*/
|
*/
|
||||||
function baselineNext(view: View): Next | null {
|
function baselineNext(view: View): Next | null {
|
||||||
if (!view.of("code").some((c) => c.object === ORIGINAL_BASELINE))
|
if (!view.of("code").some((c) => c.object === ORIGINAL_BASELINE)) return { do: "saveBaseline" };
|
||||||
return { do: "saveBaseline" };
|
|
||||||
const plans = view.of("runPlanned").filter((p) => p.purpose === "baseline");
|
const plans = view.of("runPlanned").filter((p) => p.purpose === "baseline");
|
||||||
const last = plans.at(-1);
|
const last = plans.at(-1);
|
||||||
if (last === undefined) return { do: "baselineRun", run: baselineRunId(1) };
|
if (last === undefined) return { do: "baselineRun", run: baselineRunId(1) };
|
||||||
const result = view.of("runResult").find((r) => r.run === last.run);
|
const result = view.of("runResult").find((r) => r.run === last.run);
|
||||||
if (result === undefined)
|
if (result === undefined) throw new Error(`基线运行 ${last.run} 登记了还没有结果:恢复入口应当已经核对过`);
|
||||||
throw new Error(
|
if (result.process.ending === "notStarted") return { do: "baselineRun", run: baselineRunId(plans.length + 1) };
|
||||||
`基线运行 ${last.run} 登记了还没有结果:恢复入口应当已经核对过`,
|
|
||||||
);
|
|
||||||
if (result.process.ending === "notStarted")
|
|
||||||
return { do: "baselineRun", run: baselineRunId(plans.length + 1) };
|
|
||||||
if (result.judged?.verdict === "valid") return null;
|
if (result.judged?.verdict === "valid") return null;
|
||||||
const why =
|
const why = result.judged === null ? result.process.ending : result.judged.verdictReason;
|
||||||
result.judged === null
|
return { do: "halt", reason: `基线运行 ${last.run} 判为不算数(${why}),不能当基线`, objects: [last.run] };
|
||||||
? result.process.ending
|
|
||||||
: result.judged.verdictReason;
|
|
||||||
return {
|
|
||||||
do: "halt",
|
|
||||||
reason: `基线运行 ${last.run} 判为不算数(${why}),不能当基线`,
|
|
||||||
objects: [last.run],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -497,8 +300,7 @@ function stageNext(
|
|||||||
for (const t of todo) {
|
for (const t of todo) {
|
||||||
const last = view.lastCall(stageId, t);
|
const last = view.lastCall(stageId, t);
|
||||||
// 没有调用、被用户停下、出错(含恢复时补写的"进程中断")都派;没交的只在开发入口开着时派(方法.md 第九部分"开发入口")。
|
// 没有调用、被用户停下、出错(含恢复时补写的"进程中断")都派;没交的只在开发入口开着时派(方法.md 第九部分"开发入口")。
|
||||||
if (last?.status === "notSubmitted" && !frozen.config.devEntry)
|
if (last?.status === "notSubmitted" && !frozen.config.devEntry) stuck.push({ call: last.call, reason: last.reason });
|
||||||
stuck.push({ call: last.call, reason: last.reason });
|
|
||||||
else tasks.push(t);
|
else tasks.push(t);
|
||||||
}
|
}
|
||||||
if (tasks.length > 0) return { do: "dispatch", stageId, tasks };
|
if (tasks.length > 0) return { do: "dispatch", stageId, tasks };
|
||||||
@@ -510,95 +312,44 @@ function stageNext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 研究者阶段的冻结名单。实验员、核验员阶段则按本阶段的任务列名单。 */
|
/** 研究者阶段的冻结名单。实验员、核验员阶段则按本阶段的任务列名单。 */
|
||||||
const researchers = (frozen: Frozen): string[] =>
|
const researchers = (frozen: Frozen): string[] => frozen.config.models.researchers.map((_, i) => researcherId(i));
|
||||||
frozen.config.models.researchers.map((_, i) => researcherId(i));
|
|
||||||
|
|
||||||
/** 正向的实现、修复、核验共用阶段开关和派发规则,每个候选固定一个人。 */
|
/** 正向的实现、修复、核验共用阶段开关和派发规则,每个候选固定一个人。 */
|
||||||
function assignedStage(
|
function assignedStage(
|
||||||
step: string,
|
step: string,
|
||||||
round: number,
|
round: number,
|
||||||
kind:
|
kind: "implementation" | "repair" | "verification" | "ablationImplementation" | "ablationVerification",
|
||||||
| "implementation"
|
|
||||||
| "repair"
|
|
||||||
| "verification"
|
|
||||||
| "ablationImplementation"
|
|
||||||
| "ablationVerification",
|
|
||||||
candidates: readonly string[],
|
candidates: readonly string[],
|
||||||
view: View,
|
view: View,
|
||||||
frozen: Frozen,
|
frozen: Frozen,
|
||||||
): Next | null {
|
): Next | null {
|
||||||
const actorOf =
|
const actorOf = SUBMISSION_KINDS[kind].role === "verifier" ? verifierId : experimenterId;
|
||||||
SUBMISSION_KINDS[kind].role === "verifier" ? verifierId : experimenterId;
|
const tasks = candidates.map((object) => ({ actor: actorOf(object), kind, object }));
|
||||||
const tasks = candidates.map((object) => ({
|
return stageNext(stageIdOf(round, step), SUBMISSION_KINDS[kind].stage, round, () => tasks, view, frozen, tasks.map((t) => t.actor));
|
||||||
actor: actorOf(object),
|
|
||||||
kind,
|
|
||||||
object,
|
|
||||||
}));
|
|
||||||
return stageNext(
|
|
||||||
stageIdOf(round, step),
|
|
||||||
SUBMISSION_KINDS[kind].stage,
|
|
||||||
round,
|
|
||||||
() => tasks,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
tasks.map((t) => t.actor),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ⑨:实现全部关闭后开核验;核验全部关闭后,失败的才修复。每次只重验上次修复的候选。
|
* ⑨:实现全部关闭后开核验;核验全部关闭后,失败的才修复。每次只重验上次修复的候选。
|
||||||
* 第几次由已开关的阶段推出来;次数用完先写不跑事件,不登记运行(方法.md 4.3 ⑨)。
|
* 第几次由已开关的阶段推出来;次数用完先写不跑事件,不登记运行(方法.md 4.3 ⑨)。
|
||||||
*/
|
*/
|
||||||
function verificationNext(
|
function verificationNext(round: number, view: View, frozen: Frozen): Next | null {
|
||||||
round: number,
|
|
||||||
view: View,
|
|
||||||
frozen: Frozen,
|
|
||||||
): Next | null {
|
|
||||||
let candidates = view.board.decision("batch", round)!.output.chosen;
|
let candidates = view.board.decision("batch", round)!.output.chosen;
|
||||||
for (let n = 1; candidates.length > 0; n++) {
|
for (let n = 1; candidates.length > 0; n++) {
|
||||||
const verificationId = stageIdOf(round, `verification-${n}`);
|
const verificationId = stageIdOf(round, `verification-${n}`);
|
||||||
const verify = assignedStage(
|
const verify = assignedStage(`verification-${n}`, round, "verification", candidates, view, frozen);
|
||||||
`verification-${n}`,
|
|
||||||
round,
|
|
||||||
"verification",
|
|
||||||
candidates,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
);
|
|
||||||
if (verify !== null) return verify;
|
if (verify !== null) return verify;
|
||||||
const failed = view.board
|
const failed = view.board.published("verification")
|
||||||
.published("verification")
|
.filter((s) => s.stageId === verificationId && !verificationOutcome(s.content).passed);
|
||||||
.filter(
|
|
||||||
(s) =>
|
|
||||||
s.stageId === verificationId &&
|
|
||||||
!verificationOutcome(s.content).passed,
|
|
||||||
);
|
|
||||||
if (n > frozen.config.maxRepairs) {
|
if (n > frozen.config.maxRepairs) {
|
||||||
for (const s of failed) {
|
for (const s of failed) {
|
||||||
if (
|
if (view.events(FORWARD_NOT_RUN).some((e) => e.objects.includes(s.target!))) continue;
|
||||||
view
|
return { do: "forwardNotRun", candidate: s.target!, reason: `第 ${n} 次核验仍不通过,修复次数上限 ${frozen.config.maxRepairs} 已用完:${JSON.stringify(verificationOutcome(s.content))};${s.content.body}` };
|
||||||
.events(FORWARD_NOT_RUN)
|
|
||||||
.some((e) => e.objects.includes(s.target!))
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
return {
|
|
||||||
do: "forwardNotRun",
|
|
||||||
candidate: s.target!,
|
|
||||||
reason: `第 ${n} 次核验仍不通过,修复次数上限 ${frozen.config.maxRepairs} 已用完:${JSON.stringify(verificationOutcome(s.content))};${s.content.body}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
candidates = failed.map((s) => s.target!);
|
candidates = failed.map((s) => s.target!);
|
||||||
if (candidates.length === 0) return null;
|
if (candidates.length === 0) return null;
|
||||||
const repair = assignedStage(
|
const repair = assignedStage(`repair-${n}`, round, "repair", candidates, view, frozen);
|
||||||
`repair-${n}`,
|
|
||||||
round,
|
|
||||||
"repair",
|
|
||||||
candidates,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
);
|
|
||||||
if (repair !== null) return repair;
|
if (repair !== null) return repair;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -607,29 +358,12 @@ function verificationNext(
|
|||||||
/** ⑩:按选批顺序一次只交回一个运行;没启动的下次启动起新编号,不会把半批提前公开。 */
|
/** ⑩:按选批顺序一次只交回一个运行;没启动的下次启动起新编号,不会把半批提前公开。 */
|
||||||
function forwardNext(round: number, view: View): Next | null {
|
function forwardNext(round: number, view: View): Next | null {
|
||||||
for (const candidate of view.board.decision("batch", round)!.output.chosen) {
|
for (const candidate of view.board.decision("batch", round)!.output.chosen) {
|
||||||
if (view.events(FORWARD_NOT_RUN).some((e) => e.objects.includes(candidate)))
|
if (view.events(FORWARD_NOT_RUN).some((e) => e.objects.includes(candidate))) continue;
|
||||||
continue;
|
const runs = view.board.runs().filter((r) => r.plan.purpose === "forward" && r.plan.round === round && r.plan.object === candidate);
|
||||||
const runs = view.board
|
|
||||||
.runs()
|
|
||||||
.filter(
|
|
||||||
(r) =>
|
|
||||||
r.plan.purpose === "forward" &&
|
|
||||||
r.plan.round === round &&
|
|
||||||
r.plan.object === candidate,
|
|
||||||
);
|
|
||||||
const last = runs.at(-1);
|
const last = runs.at(-1);
|
||||||
if (last !== undefined && last.result === null)
|
if (last !== undefined && last.result === null) throw new Error(`正向运行 ${last.plan.run} 登记了还没有结果:恢复入口应当已经核对过`);
|
||||||
throw new Error(
|
if (last !== undefined && last.result!.process.ending !== "notStarted") continue;
|
||||||
`正向运行 ${last.plan.run} 登记了还没有结果:恢复入口应当已经核对过`,
|
return { do: "forwardRun", round, candidate, run: `r${round}-forward-${candidate}-${runs.length + 1}` };
|
||||||
);
|
|
||||||
if (last !== undefined && last.result!.process.ending !== "notStarted")
|
|
||||||
continue;
|
|
||||||
return {
|
|
||||||
do: "forwardRun",
|
|
||||||
round,
|
|
||||||
candidate,
|
|
||||||
run: `r${round}-forward-${candidate}-${runs.length + 1}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -638,74 +372,40 @@ function forwardNext(round: number, view: View): Next | null {
|
|||||||
* 这一轮做不做消融、消融哪个候选的哪几个因子(方法.md 4.3 ⑫):采纳了、且被采纳改法有两个以上因子。
|
* 这一轮做不做消融、消融哪个候选的哪几个因子(方法.md 4.3 ⑫):采纳了、且被采纳改法有两个以上因子。
|
||||||
* 没采纳或只采纳了单因子改法时 ⑬–⑰ 都不做:这几步不写记录,从采纳决定和冻结协议就算得出。
|
* 没采纳或只采纳了单因子改法时 ⑬–⑰ 都不做:这几步不写记录,从采纳决定和冻结协议就算得出。
|
||||||
*/
|
*/
|
||||||
export function ablationOf(
|
export function ablationOf(board: Board, round: number): { candidate: string; keys: string[] } | null {
|
||||||
board: Board,
|
|
||||||
round: number,
|
|
||||||
): { candidate: string; keys: string[] } | null {
|
|
||||||
const adopted = board.decision("adopt", round)?.output.adopted ?? null;
|
const adopted = board.decision("adopt", round)?.output.adopted ?? null;
|
||||||
if (adopted === null) return null;
|
if (adopted === null) return null;
|
||||||
const keys = listCells(
|
const keys = listCells(board.protocol(adopted).factors.map((_, i) => factorKey(i)));
|
||||||
board.protocol(adopted).factors.map((_, i) => factorKey(i)),
|
|
||||||
);
|
|
||||||
return keys.length < 2 ? null : { candidate: adopted, keys };
|
return keys.length < 2 ? null : { candidate: adopted, keys };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选格决定选中要测的格(对象编号用 materials 的 cellId),按因子编号排。 */
|
/** 选格决定选中要测的格(对象编号用 materials 的 cellId),按因子编号排。 */
|
||||||
export function chosenCells(board: Board, round: number): string[] {
|
export function chosenCells(board: Board, round: number): string[] {
|
||||||
const { candidate } = ablationOf(board, round)!;
|
const { candidate } = ablationOf(board, round)!;
|
||||||
return board
|
return board.decision("cells", round)!.output.chosen.map((key) => cellId(candidate, key));
|
||||||
.decision("cells", round)!
|
|
||||||
.output.chosen.map((key) => cellId(candidate, key));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 一格最近一次已公开的消融实现提交(阶段关闭即公开);没有为 undefined。 */
|
/** 一格最近一次已公开的消融实现提交(阶段关闭即公开);没有为 undefined。 */
|
||||||
export function lastAblationImpl(board: Board, cell: string) {
|
export function lastAblationImpl(board: Board, cell: string) {
|
||||||
return board
|
return board.published("ablationImplementation").filter((s) => s.target === cell).at(-1);
|
||||||
.published("ablationImplementation")
|
|
||||||
.filter((s) => s.target === cell)
|
|
||||||
.at(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选中的格里"活着"的(没交"跑不起来"、没有"不跑"事件),以及每格实际关掉的全部因子(指定的加实验员报的一起关的)。 */
|
/** 选中的格里"活着"的(没交"跑不起来"、没有"不跑"事件),以及每格实际关掉的全部因子(指定的加实验员报的一起关的)。 */
|
||||||
export function aliveCells(
|
export function aliveCells(board: Board, events: readonly Content<"event">[], round: number): Array<{ cell: string; off: string[] }> {
|
||||||
board: Board,
|
|
||||||
events: readonly Content<"event">[],
|
|
||||||
round: number,
|
|
||||||
): Array<{ cell: string; off: string[] }> {
|
|
||||||
return chosenCells(board, round)
|
return chosenCells(board, round)
|
||||||
.map((cell) => ({ cell, impl: lastAblationImpl(board, cell) }))
|
.map((cell) => ({ cell, impl: lastAblationImpl(board, cell) }))
|
||||||
.filter(
|
.filter((x) => x.impl !== undefined && x.impl.content.cannotRun === null && !events.some((e) => e.event === CELL_NOT_RUN && e.objects.includes(x.cell)))
|
||||||
(x) =>
|
.map((x) => ({ cell: x.cell, off: [splitCell(x.cell).key, ...x.impl!.content.alsoOff] }));
|
||||||
x.impl !== undefined &&
|
|
||||||
x.impl.content.cannotRun === null &&
|
|
||||||
!events.some(
|
|
||||||
(e) => e.event === CELL_NOT_RUN && e.objects.includes(x.cell),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((x) => ({
|
|
||||||
cell: x.cell,
|
|
||||||
off: [splitCell(x.cell).key, ...x.impl!.content.alsoOff],
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 要跑消融运行的格:活着的格里同设置的第一格(方法.md 6.6"一起关":几格实际关掉的因子完全相同就是同一种设置,
|
* 要跑消融运行的格:活着的格里同设置的第一格(方法.md 6.6"一起关":几格实际关掉的因子完全相同就是同一种设置,
|
||||||
* 沿用第一格的结果,不重跑、不重复扣次、不重复记账)。
|
* 沿用第一格的结果,不重跑、不重复扣次、不重复记账)。
|
||||||
*/
|
*/
|
||||||
export function cellsToRun(
|
export function cellsToRun(board: Board, events: readonly Content<"event">[], round: number): string[] {
|
||||||
board: Board,
|
|
||||||
events: readonly Content<"event">[],
|
|
||||||
round: number,
|
|
||||||
): string[] {
|
|
||||||
const alive = aliveCells(board, events, round);
|
const alive = aliveCells(board, events, round);
|
||||||
const sameAs = new Map(
|
const sameAs = new Map(sharedSettings(alive.map((x) => ({ factor: splitCell(x.cell).key, off: x.off }))).map((x) => [x.factor, x.sameAs]));
|
||||||
sharedSettings(
|
return alive.filter((x) => sameAs.get(splitCell(x.cell).key) === null).map((x) => x.cell);
|
||||||
alive.map((x) => ({ factor: splitCell(x.cell).key, off: x.off })),
|
|
||||||
).map((x) => [x.factor, x.sameAs]),
|
|
||||||
);
|
|
||||||
return alive
|
|
||||||
.filter((x) => sameAs.get(splitCell(x.cell).key) === null)
|
|
||||||
.map((x) => x.cell);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -713,60 +413,26 @@ export function cellsToRun(
|
|||||||
* 从它上一次交的这一格代码接着改,每次只重验刚重做的格。阶段编号按"第几次"排,第几次从已开关的阶段数出来,不另存。
|
* 从它上一次交的这一格代码接着改,每次只重验刚重做的格。阶段编号按"第几次"排,第几次从已开关的阶段数出来,不另存。
|
||||||
* 交了"跑不起来"的格不核验、不运行、不扣次;核验不通过、次数用完写"不跑"事件,不登记运行(方法.md 4.3 ⑯、6.6)。
|
* 交了"跑不起来"的格不核验、不运行、不扣次;核验不通过、次数用完写"不跑"事件,不登记运行(方法.md 4.3 ⑯、6.6)。
|
||||||
*/
|
*/
|
||||||
function ablationCellsNext(
|
function ablationCellsNext(round: number, view: View, frozen: Frozen): Next | null {
|
||||||
round: number,
|
let cells = chosenCells(view.board, round).filter((cell) => !view.events(CELL_NOT_RUN).some((e) => e.objects.includes(cell)));
|
||||||
view: View,
|
|
||||||
frozen: Frozen,
|
|
||||||
): Next | null {
|
|
||||||
let cells = chosenCells(view.board, round).filter(
|
|
||||||
(cell) => !view.events(CELL_NOT_RUN).some((e) => e.objects.includes(cell)),
|
|
||||||
);
|
|
||||||
for (let n = 1; cells.length > 0; n++) {
|
for (let n = 1; cells.length > 0; n++) {
|
||||||
const impl = assignedStage(
|
const impl = assignedStage(`ablationImplementation-${n}`, round, "ablationImplementation", cells, view, frozen);
|
||||||
`ablationImplementation-${n}`,
|
|
||||||
round,
|
|
||||||
"ablationImplementation",
|
|
||||||
cells,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
);
|
|
||||||
if (impl !== null) return impl;
|
if (impl !== null) return impl;
|
||||||
const runnable = cells.filter(
|
const runnable = cells.filter((cell) => lastAblationImpl(view.board, cell)!.content.cannotRun === null);
|
||||||
(cell) => lastAblationImpl(view.board, cell)!.content.cannotRun === null,
|
|
||||||
);
|
|
||||||
const failed: Array<{ cell: string; reason: string }> = [];
|
const failed: Array<{ cell: string; reason: string }> = [];
|
||||||
if (runnable.length > 0) {
|
if (runnable.length > 0) {
|
||||||
const ver = assignedStage(
|
const ver = assignedStage(`ablationVerification-${n}`, round, "ablationVerification", runnable, view, frozen);
|
||||||
`ablationVerification-${n}`,
|
|
||||||
round,
|
|
||||||
"ablationVerification",
|
|
||||||
runnable,
|
|
||||||
view,
|
|
||||||
frozen,
|
|
||||||
);
|
|
||||||
if (ver !== null) return ver;
|
if (ver !== null) return ver;
|
||||||
for (const s of view.board
|
for (const s of view.board.published("ablationVerification").filter((x) => x.stageId === stageIdOf(round, `ablationVerification-${n}`))) {
|
||||||
.published("ablationVerification")
|
const outcome = ablationOutcome(s.content, splitCell(s.target!).key, lastAblationImpl(view.board, s.target!)!.content.alsoOff);
|
||||||
.filter(
|
|
||||||
(x) => x.stageId === stageIdOf(round, `ablationVerification-${n}`),
|
|
||||||
)) {
|
|
||||||
const outcome = ablationOutcome(
|
|
||||||
s.content,
|
|
||||||
splitCell(s.target!).key,
|
|
||||||
lastAblationImpl(view.board, s.target!)!.content.alsoOff,
|
|
||||||
);
|
|
||||||
if (!outcome.passed) {
|
if (!outcome.passed) {
|
||||||
failed.push({
|
failed.push({ cell: s.target!, reason: `第 ${n} 次核验仍不通过,修复次数上限 ${frozen.config.maxRepairs} 已用完:${JSON.stringify({ leftOn: outcome.leftOn, wronglyOff: outcome.wronglyOff })}` });
|
||||||
cell: s.target!,
|
|
||||||
reason: `第 ${n} 次核验仍不通过,修复次数上限 ${frozen.config.maxRepairs} 已用完:${JSON.stringify({ leftOn: outcome.leftOn, wronglyOff: outcome.wronglyOff })}`,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (n > frozen.config.maxRepairs) {
|
if (n > frozen.config.maxRepairs) {
|
||||||
for (const f of failed) {
|
for (const f of failed) {
|
||||||
if (view.events(CELL_NOT_RUN).some((e) => e.objects.includes(f.cell)))
|
if (view.events(CELL_NOT_RUN).some((e) => e.objects.includes(f.cell))) continue;
|
||||||
continue;
|
|
||||||
return { do: "cellNotRun", cell: f.cell, reason: f.reason };
|
return { do: "cellNotRun", cell: f.cell, reason: f.reason };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -780,27 +446,11 @@ function ablationCellsNext(
|
|||||||
/** ⑯ 消融运行:按格编号顺序一次只交回一个运行(一份研究里的消融运行一个接一个跑,同正向);没启动的下次启动起新编号。 */
|
/** ⑯ 消融运行:按格编号顺序一次只交回一个运行(一份研究里的消融运行一个接一个跑,同正向);没启动的下次启动起新编号。 */
|
||||||
function ablationRunNext(round: number, view: View): Next | null {
|
function ablationRunNext(round: number, view: View): Next | null {
|
||||||
for (const cell of cellsToRun(view.board, view.of("event"), round)) {
|
for (const cell of cellsToRun(view.board, view.of("event"), round)) {
|
||||||
const runs = view.board
|
const runs = view.board.runs().filter((r) => r.plan.purpose === "ablation" && r.plan.round === round && r.plan.object === cell);
|
||||||
.runs()
|
|
||||||
.filter(
|
|
||||||
(r) =>
|
|
||||||
r.plan.purpose === "ablation" &&
|
|
||||||
r.plan.round === round &&
|
|
||||||
r.plan.object === cell,
|
|
||||||
);
|
|
||||||
const last = runs.at(-1);
|
const last = runs.at(-1);
|
||||||
if (last !== undefined && last.result === null)
|
if (last !== undefined && last.result === null) throw new Error(`消融运行 ${last.plan.run} 登记了还没有结果:恢复入口应当已经核对过`);
|
||||||
throw new Error(
|
if (last !== undefined && last.result!.process.ending !== "notStarted") continue;
|
||||||
`消融运行 ${last.plan.run} 登记了还没有结果:恢复入口应当已经核对过`,
|
return { do: "ablationRun", round, cell, run: `r${round}-ablation-${cell}-${runs.length + 1}` };
|
||||||
);
|
|
||||||
if (last !== undefined && last.result!.process.ending !== "notStarted")
|
|
||||||
continue;
|
|
||||||
return {
|
|
||||||
do: "ablationRun",
|
|
||||||
round,
|
|
||||||
cell,
|
|
||||||
run: `r${round}-ablation-${cell}-${runs.length + 1}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -816,9 +466,7 @@ class View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
of<K extends LedgerRecord["kind"]>(kind: K): Array<Content<K>> {
|
of<K extends LedgerRecord["kind"]>(kind: K): Array<Content<K>> {
|
||||||
return (
|
return (this.#records.filter((r) => r.kind === kind) as Array<LedgerRecord<K>>).map((r) => r.content);
|
||||||
this.#records.filter((r) => r.kind === kind) as Array<LedgerRecord<K>>
|
|
||||||
).map((r) => r.content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
events(event: string): Array<Content<"event">> {
|
events(event: string): Array<Content<"event">> {
|
||||||
@@ -827,10 +475,7 @@ class View {
|
|||||||
|
|
||||||
/** 一个任务做完了:这个人在这个阶段对这个对象有一份被收下的提交(任务书 14 第 2.3 节)。 */
|
/** 一个任务做完了:这个人在这个阶段对这个对象有一份被收下的提交(任务书 14 第 2.3 节)。 */
|
||||||
done(stageId: string, t: TaskRef): boolean {
|
done(stageId: string, t: TaskRef): boolean {
|
||||||
return this.of("submission").some(
|
return this.of("submission").some((s) => s.stageId === stageId && s.author === t.actor && s.target === t.object);
|
||||||
(s) =>
|
|
||||||
s.stageId === stageId && s.author === t.actor && s.target === t.object,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -839,19 +484,11 @@ class View {
|
|||||||
*/
|
*/
|
||||||
lastCall(stageId: string, t: TaskRef): Content<"callEnded"> | null {
|
lastCall(stageId: string, t: TaskRef): Content<"callEnded"> | null {
|
||||||
const started = this.of("callStarted")
|
const started = this.of("callStarted")
|
||||||
.filter(
|
.filter((c) => c.stageId === stageId && c.actor === t.actor && (c.materials as { object: string | null }).object === t.object)
|
||||||
(c) =>
|
|
||||||
c.stageId === stageId &&
|
|
||||||
c.actor === t.actor &&
|
|
||||||
(c.materials as { object: string | null }).object === t.object,
|
|
||||||
)
|
|
||||||
.at(-1);
|
.at(-1);
|
||||||
if (started === undefined) return null;
|
if (started === undefined) return null;
|
||||||
const ended = this.of("callEnded").find((e) => e.call === started.call);
|
const ended = this.of("callEnded").find((e) => e.call === started.call);
|
||||||
if (ended === undefined)
|
if (ended === undefined) throw new Error(`模型调用 ${started.call} 开始了还没有结束记录:恢复入口应当已经补写`);
|
||||||
throw new Error(
|
|
||||||
`模型调用 ${started.call} 开始了还没有结束记录:恢复入口应当已经补写`,
|
|
||||||
);
|
|
||||||
return ended;
|
return ended;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-103
@@ -1,13 +1,5 @@
|
|||||||
import type { Content } from "../../ledger/index.ts";
|
import type { Content } from "../../ledger/index.ts";
|
||||||
import {
|
import { Board, cellId, CELL_NOT_RUN, explanationId, protocolMetrics, splitCell, validScore } from "../../materials/index.ts";
|
||||||
Board,
|
|
||||||
cellId,
|
|
||||||
CELL_NOT_RUN,
|
|
||||||
explanationId,
|
|
||||||
protocolMetrics,
|
|
||||||
splitCell,
|
|
||||||
validScore,
|
|
||||||
} from "../../materials/index.ts";
|
|
||||||
import {
|
import {
|
||||||
chooseBet,
|
chooseBet,
|
||||||
decisionSeed,
|
decisionSeed,
|
||||||
@@ -38,11 +30,7 @@ import type { Study } from "../study.ts";
|
|||||||
export function selectCells(s: Study, round: number): void {
|
export function selectCells(s: Study, round: number): void {
|
||||||
const board = new Board(s.ledger.records());
|
const board = new Board(s.ledger.records());
|
||||||
const { config, steps } = s.frozen;
|
const { config, steps } = s.frozen;
|
||||||
const seed = decisionSeed({
|
const seed = decisionSeed({ studyId: config.studyId, round, version: SEED_VERSIONS.cells });
|
||||||
studyId: config.studyId,
|
|
||||||
round,
|
|
||||||
version: SEED_VERSIONS.cells,
|
|
||||||
});
|
|
||||||
const input: CellRuleInput = {
|
const input: CellRuleInput = {
|
||||||
...modelInput(board, s, round),
|
...modelInput(board, s, round),
|
||||||
balance: runBalance(config.runs, board.runStates()).balance,
|
balance: runBalance(config.runs, board.runStates()).balance,
|
||||||
@@ -51,8 +39,7 @@ export function selectCells(s: Study, round: number): void {
|
|||||||
decay: config.flow.decay,
|
decay: config.flow.decay,
|
||||||
seed,
|
seed,
|
||||||
};
|
};
|
||||||
const rule = steps.round.find((x) => x.id === "selectCells")!.rule!
|
const rule = steps.round.find((x) => x.id === "selectCells")!.rule!.name as CellRuleName;
|
||||||
.name as CellRuleName;
|
|
||||||
const { rule: used, ...output } = runCellRule(rule, input);
|
const { rule: used, ...output } = runCellRule(rule, input);
|
||||||
// SAFETY: rules 是纯计算,输入输出都是纯 Json 值;账本在写入时校验 Json。
|
// SAFETY: rules 是纯计算,输入输出都是纯 Json 值;账本在写入时校验 Json。
|
||||||
s.ledger.append("decision", {
|
s.ledger.append("decision", {
|
||||||
@@ -68,30 +55,14 @@ export function selectCells(s: Study, round: number): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** ⑯ 消融运行:用核验过的这一格快照,复用正式运行的登记、扣次、判分入口。完整代码和基线的分数取本批已有的运行结果,不重跑。 */
|
/** ⑯ 消融运行:用核验过的这一格快照,复用正式运行的登记、扣次、判分入口。完整代码和基线的分数取本批已有的运行结果,不重跑。 */
|
||||||
export function ablationRun(
|
export function ablationRun(s: Study, round: number, cell: string, run: string): Promise<RunOutcome> {
|
||||||
s: Study,
|
|
||||||
round: number,
|
|
||||||
cell: string,
|
|
||||||
run: string,
|
|
||||||
): Promise<RunOutcome> {
|
|
||||||
const board = new Board(s.ledger.records());
|
const board = new Board(s.ledger.records());
|
||||||
return formalRun(
|
return formalRun(s, { run, purpose: "ablation", round, object: cell }, board.code(cell).version, ablationMetrics(board, splitCell(cell).candidate));
|
||||||
s,
|
|
||||||
{ run, purpose: "ablation", round, object: cell },
|
|
||||||
board.code(cell).version,
|
|
||||||
ablationMetrics(board, splitCell(cell).candidate),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 消融格正式运行登记的指标:照被采纳候选的冻结协议取;消融格里假设专用指标缺失一律只记缺失、不作废这一格(方法.md 第五部分"指标")。 */
|
/** 消融格正式运行登记的指标:照被采纳候选的冻结协议取;消融格里假设专用指标缺失一律只记缺失、不作废这一格(方法.md 第五部分"指标")。 */
|
||||||
export function ablationMetrics(
|
export function ablationMetrics(board: Board, candidate: string): HypothesisMetric[] {
|
||||||
board: Board,
|
return protocolMetrics(board, board.protocol(candidate)).map((m) => ({ ...m, missing: "recordMissing" }));
|
||||||
candidate: string,
|
|
||||||
): HypothesisMetric[] {
|
|
||||||
return protocolMetrics(board, board.protocol(candidate)).map((m) => ({
|
|
||||||
...m,
|
|
||||||
missing: "recordMissing",
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,37 +73,21 @@ export function ablationMetrics(
|
|||||||
export function settle(s: Study, round: number): void {
|
export function settle(s: Study, round: number): void {
|
||||||
const board = new Board(s.ledger.records());
|
const board = new Board(s.ledger.records());
|
||||||
const { candidate, keys } = ablationOf(board, round)!;
|
const { candidate, keys } = ablationOf(board, round)!;
|
||||||
const events = s.ledger
|
const events = s.ledger.records().flatMap((r) => (r.kind === "event" ? [r.content] : []));
|
||||||
.records()
|
|
||||||
.flatMap((r) => (r.kind === "event" ? [r.content] : []));
|
|
||||||
const model = modelInput(board, s, round);
|
const model = modelInput(board, s, round);
|
||||||
const input: SettleInput = {
|
const input: SettleInput = { ...model, cells: keys.map((key) => ({ factor: key, outcome: outcomeOf(board, events, round, candidate, key) })) };
|
||||||
...model,
|
|
||||||
cells: keys.map((key) => ({
|
|
||||||
factor: key,
|
|
||||||
outcome: outcomeOf(board, events, round, candidate, key),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
const output = settleAblation(input);
|
const output = settleAblation(input);
|
||||||
const betInput = { ...model, cells: input.cells };
|
const betInput = { ...model, cells: input.cells };
|
||||||
const bet = chooseBet(betInput);
|
const bet = chooseBet(betInput);
|
||||||
// SAFETY: rules 是纯计算,结算和决定的输入输出都是纯 Json 值;账本在写入时校验 Json。
|
// SAFETY: rules 是纯计算,结算和决定的输入输出都是纯 Json 值;账本在写入时校验 Json。
|
||||||
s.ledger.transaction(() => {
|
s.ledger.transaction(() => {
|
||||||
s.ledger.append("settlement", {
|
s.ledger.append("settlement", { round, result: { candidate, input, output } as unknown as Json });
|
||||||
round,
|
|
||||||
result: { candidate, input, output } as unknown as Json,
|
|
||||||
});
|
|
||||||
s.ledger.append("decision", {
|
s.ledger.append("decision", {
|
||||||
decision: "bet",
|
decision: "bet",
|
||||||
round,
|
round,
|
||||||
rule: "chooseBet",
|
rule: "chooseBet",
|
||||||
ruleVersion: 1,
|
ruleVersion: 1,
|
||||||
params: {
|
params: { direction: model.direction, epsilon: model.epsilon, knownBest: model.knownBest, lambda: model.lambda },
|
||||||
direction: model.direction,
|
|
||||||
epsilon: model.epsilon,
|
|
||||||
knownBest: model.knownBest,
|
|
||||||
lambda: model.lambda,
|
|
||||||
},
|
|
||||||
seed: null,
|
seed: null,
|
||||||
input: betInput as unknown as Json,
|
input: betInput as unknown as Json,
|
||||||
output: bet as unknown as Json,
|
output: bet as unknown as Json,
|
||||||
@@ -141,40 +96,19 @@ export function settle(s: Study, round: number): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 一格的结局(方法.md 6.6"一格的几种结局"):没测、跑不起来、证据不足、一起关、有效分数。 */
|
/** 一格的结局(方法.md 6.6"一格的几种结局"):没测、跑不起来、证据不足、一起关、有效分数。 */
|
||||||
function outcomeOf(
|
function outcomeOf(board: Board, events: readonly Content<"event">[], round: number, candidate: string, key: string): CellOutcome {
|
||||||
board: Board,
|
if (!board.decision("cells", round)!.output.chosen.includes(key)) return { kind: "untested" };
|
||||||
events: readonly Content<"event">[],
|
|
||||||
round: number,
|
|
||||||
candidate: string,
|
|
||||||
key: string,
|
|
||||||
): CellOutcome {
|
|
||||||
if (!board.decision("cells", round)!.output.chosen.includes(key))
|
|
||||||
return { kind: "untested" };
|
|
||||||
const cell = cellId(candidate, key);
|
const cell = cellId(candidate, key);
|
||||||
const impl = lastAblationImpl(board, cell)!;
|
const impl = lastAblationImpl(board, cell)!;
|
||||||
if (impl.content.cannotRun !== null) return { kind: "cannotRun" };
|
if (impl.content.cannotRun !== null) return { kind: "cannotRun" };
|
||||||
if (events.some((e) => e.event === CELL_NOT_RUN && e.objects.includes(cell)))
|
if (events.some((e) => e.event === CELL_NOT_RUN && e.objects.includes(cell))) return { kind: "insufficient" };
|
||||||
return { kind: "insufficient" };
|
|
||||||
const off = [key, ...impl.content.alsoOff];
|
const off = [key, ...impl.content.alsoOff];
|
||||||
// 几格实际关掉的因子完全相同时,沿用同设置第一格的运行结果(不重跑,方法.md 6.6"一起关")。
|
// 几格实际关掉的因子完全相同时,沿用同设置第一格的运行结果(不重跑,方法.md 6.6"一起关")。
|
||||||
const alive = aliveCells(board, events, round);
|
const alive = aliveCells(board, events, round);
|
||||||
const sameAs = new Map(
|
const sameAs = new Map(sharedSettings(alive.map((x) => ({ factor: splitCell(x.cell).key, off: x.off }))).map((x) => [x.factor, x.sameAs]));
|
||||||
sharedSettings(
|
|
||||||
alive.map((x) => ({ factor: splitCell(x.cell).key, off: x.off })),
|
|
||||||
).map((x) => [x.factor, x.sameAs]),
|
|
||||||
);
|
|
||||||
const first = sameAs.get(key);
|
const first = sameAs.get(key);
|
||||||
const ranCell =
|
const ranCell = first === null || first === undefined ? cell : cellId(candidate, first);
|
||||||
first === null || first === undefined ? cell : cellId(candidate, first);
|
const run = board.runs().filter((r) => r.plan.purpose === "ablation" && r.plan.round === round && r.plan.object === ranCell).at(-1)!;
|
||||||
const run = board
|
|
||||||
.runs()
|
|
||||||
.filter(
|
|
||||||
(r) =>
|
|
||||||
r.plan.purpose === "ablation" &&
|
|
||||||
r.plan.round === round &&
|
|
||||||
r.plan.object === ranCell,
|
|
||||||
)
|
|
||||||
.at(-1)!;
|
|
||||||
const score = validScore(run.result!.judged);
|
const score = validScore(run.result!.judged);
|
||||||
if (off.length > 1) return { kind: "joint", off, score };
|
if (off.length > 1) return { kind: "joint", off, score };
|
||||||
return score === null ? { kind: "insufficient" } : { kind: "valid", score };
|
return score === null ? { kind: "insufficient" } : { kind: "valid", score };
|
||||||
@@ -191,21 +125,11 @@ export function unsealChosen(s: Study, round: number): void {
|
|||||||
const chosen = board.decision("batch", round)!.output.chosen;
|
const chosen = board.decision("batch", round)!.output.chosen;
|
||||||
s.ledger.transaction(() => {
|
s.ledger.transaction(() => {
|
||||||
for (const candidate of chosen) {
|
for (const candidate of chosen) {
|
||||||
if (
|
if (records.some((r) => r.kind === "unseal" && r.content.candidate === candidate)) continue;
|
||||||
records.some(
|
|
||||||
(r) => r.kind === "unseal" && r.content.candidate === candidate,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
const seals = board.proposalsOf(candidate).map((id) => {
|
const seals = board.proposalsOf(candidate).map((id) => {
|
||||||
const seq = board.proposal(id).seq;
|
const seq = board.proposal(id).seq;
|
||||||
const seal = records.find(
|
const seal = records.find((r) => r.kind === "seal" && r.content.proposal === seq);
|
||||||
(r) => r.kind === "seal" && r.content.proposal === seq,
|
if (seal === undefined) throw new Error(`提案 ${id}(记录 ${seq})没有封存记录:提案的作者自评在写入提案时一并封存`);
|
||||||
);
|
|
||||||
if (seal === undefined)
|
|
||||||
throw new Error(
|
|
||||||
`提案 ${id}(记录 ${seq})没有封存记录:提案的作者自评在写入提案时一并封存`,
|
|
||||||
);
|
|
||||||
return seal.seq;
|
return seal.seq;
|
||||||
});
|
});
|
||||||
s.ledger.append("unseal", { candidate, seals });
|
s.ledger.append("unseal", { candidate, seals });
|
||||||
@@ -234,11 +158,5 @@ function explanationsOf(board: Board, round: number): AblationExplanation[] {
|
|||||||
return board
|
return board
|
||||||
.published("explanation")
|
.published("explanation")
|
||||||
.filter((x) => x.round === round)
|
.filter((x) => x.round === round)
|
||||||
.flatMap((x) =>
|
.flatMap((x) => x.content.explanations.map((e, i) => ({ id: explanationId(round, x.author, i + 1), author: x.author, cells: e.cells! })));
|
||||||
x.content.explanations.map((e, i) => ({
|
|
||||||
id: explanationId(round, x.author, i + 1),
|
|
||||||
author: x.author,
|
|
||||||
cells: e.cells!,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-55
@@ -31,7 +31,7 @@ export const Range = Type.Object(
|
|||||||
...CLOSED,
|
...CLOSED,
|
||||||
description:
|
description:
|
||||||
"三个数,按分数原值写,不管越大越好还是越小越好。必须严格递增 q1 < q5 < q9,认为几乎不变就写一个很窄的范围。" +
|
"三个数,按分数原值写,不管越大越好还是越小越好。必须严格递增 q1 < q5 < q9,认为几乎不变就写一个很窄的范围。" +
|
||||||
'没有"不知道"这个选项:拿不准就把 q1 和 q9 拉开',
|
"没有\"不知道\"这个选项:拿不准就把 q1 和 q9 拉开",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
export type Range = Static<typeof Range>;
|
export type Range = Static<typeof Range>;
|
||||||
@@ -55,15 +55,14 @@ const ClauseKey = Type.String({
|
|||||||
|
|
||||||
const CodeLocation = Type.String({
|
const CodeLocation = Type.String({
|
||||||
pattern: "^[^:\\s]+(:[1-9][0-9]*(-[1-9][0-9]*)?)?$",
|
pattern: "^[^:\\s]+(:[1-9][0-9]*(-[1-9][0-9]*)?)?$",
|
||||||
description:
|
description: "代码位置:\"文件\"、\"文件:行号\"或\"文件:起-止\",行号以本次提交代码为准。一个值只写一个连续范围,不带括号标签;多处位置拆开写",
|
||||||
'代码位置:"文件"、"文件:行号"或"文件:起-止",行号以本次提交代码为准。一个值只写一个连续范围,不带括号标签;多处位置拆开写',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CONCERNS = Type.String({
|
const CONCERNS = Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
"照冻结原文实现、但你认为会出问题的地方,尤其是某条保障会挡掉或改变什么。" +
|
"照冻结原文实现、但你认为会出问题的地方,尤其是某条保障会挡掉或改变什么。" +
|
||||||
'它会转给研究者,不阻止运行,也不改动协议。没有就写"无"',
|
"它会转给研究者,不阻止运行,也不改动协议。没有就写\"无\"",
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -80,7 +79,7 @@ const Entry = Type.Object(
|
|||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
"Markdown:从哪里开始看(先读哪段代码、先问哪个问题、先质疑哪个设定)、为什么值得有人去走。" +
|
"Markdown:从哪里开始看(先读哪段代码、先问哪个问题、先质疑哪个设定)、为什么值得有人去走。" +
|
||||||
'只写起点,不写"应该改成什么、能到多少分"',
|
"只写起点,不写\"应该改成什么、能到多少分\"",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -89,8 +88,7 @@ const Entry = Type.Object(
|
|||||||
const ENTRIES = Type.Array(Entry, {
|
const ENTRIES = Type.Array(Entry, {
|
||||||
minItems: 2,
|
minItems: 2,
|
||||||
maxItems: 3,
|
maxItems: 3,
|
||||||
description:
|
description: "2–3 个入口,彼此尽量不重叠;想不出第三个就交两个。编号由程序按顺序分配",
|
||||||
"2–3 个入口,彼此尽量不重叠;想不出第三个就交两个。编号由程序按顺序分配",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EntrySubmission = Type.Object(
|
export const EntrySubmission = Type.Object(
|
||||||
@@ -110,8 +108,7 @@ export const DiscussionSubmission = Type.Object(
|
|||||||
}),
|
}),
|
||||||
entries: Type.Optional({
|
entries: Type.Optional({
|
||||||
...ENTRIES,
|
...ENTRIES,
|
||||||
description:
|
description: "可选:整份替换你自己的入口清单(2–3 个)。不换就不交这个字段",
|
||||||
"可选:整份替换你自己的入口清单(2–3 个)。不换就不交这个字段",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -145,8 +142,7 @@ const Factor = Type.Object(
|
|||||||
}),
|
}),
|
||||||
keySettings: Type.String({
|
keySettings: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "关键设定:决定这个因子效果的设定值,例如\"统计窗口 1000 次查找、每次最多删四分之一\";没有写\"无\"",
|
||||||
'关键设定:决定这个因子效果的设定值,例如"统计窗口 1000 次查找、每次最多删四分之一";没有写"无"',
|
|
||||||
}),
|
}),
|
||||||
offBehavior: Type.Optional(
|
offBehavior: Type.Optional(
|
||||||
Type.String({
|
Type.String({
|
||||||
@@ -184,7 +180,7 @@ const METRICS = Type.Object(
|
|||||||
}),
|
}),
|
||||||
reused: Type.Array(Id, {
|
reused: Type.Array(Id, {
|
||||||
description:
|
description:
|
||||||
'引用已登记的假设专用指标,照材料里已登记指标清单的全名("候选编号.短名")写;没有就交空数组。' +
|
"引用已登记的假设专用指标,照材料里已登记指标清单的全名(\"候选编号.短名\")写;没有就交空数组。" +
|
||||||
"要测的量清单里已经有,就引用它,不换个名字重新登记:两个名字测同一个东西,会被当成两份独立的证据",
|
"要测的量清单里已经有,就引用它,不换个名字重新登记:两个名字测同一个东西,会被当成两份独立的证据",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -192,7 +188,7 @@ const METRICS = Type.Object(
|
|||||||
...CLOSED,
|
...CLOSED,
|
||||||
description:
|
description:
|
||||||
"指标登记:这个候选要采集哪些假设专用指标。最终指标和常规辅助指标程序每次都会采集,不用在这里写。" +
|
"指标登记:这个候选要采集哪些假设专用指标。最终指标和常规辅助指标程序每次都会采集,不用在这里写。" +
|
||||||
'假设专用指标由候选代码在运行当中算出,候选代码拿不到基线的成绩,"比基线好多少"这类量测不到。' +
|
"假设专用指标由候选代码在运行当中算出,候选代码拿不到基线的成绩,\"比基线好多少\"这类量测不到。" +
|
||||||
"协议在某种情形下会让某个指标写 null(例如某条分支没走到、某个因子被关掉),就别给它选 invalidateRun,否则那次正式运行白跑",
|
"协议在某种情形下会让某个指标写 null(例如某条分支没走到、某个因子被关掉),就别给它选 invalidateRun,否则那次正式运行白跑",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -203,7 +199,7 @@ const PROPOSAL_BODY = Type.String({
|
|||||||
"提案正文,Markdown,带小标题:每个因子为什么可能有效;" +
|
"提案正文,Markdown,带小标题:每个因子为什么可能有效;" +
|
||||||
"每条保障做什么、属于拦 / 补 / 改哪一种、自检时怎么想的;" +
|
"每条保障做什么、属于拦 / 补 / 改哪一种、自检时怎么想的;" +
|
||||||
"协议(不许改什么、随机性怎么处理、何时停、什么样的运行不算数);因子之间的关系。" +
|
"协议(不许改什么、随机性怎么处理、何时停、什么样的运行不算数);因子之间的关系。" +
|
||||||
'某方面不适用写"不适用:原因"。协议里不要求实验员按正式规模运行程序来核对什么,正式规模只由程序跑。' +
|
"某方面不适用写\"不适用:原因\"。协议里不要求实验员按正式规模运行程序来核对什么,正式规模只由程序跑。" +
|
||||||
"不写自己估的分数或提升量;引用已测到的分数要写明出自哪次运行;" +
|
"不写自己估的分数或提升量;引用已测到的分数要写明出自哪次运行;" +
|
||||||
"文献和别人报告的数值只当起点,说明与本任务的区别;从第 2 轮起说明哪些新的观测影响了这条提案",
|
"文献和别人报告的数值只当起点,说明与本任务的区别;从第 2 轮起说明哪些新的观测影响了这条提案",
|
||||||
});
|
});
|
||||||
@@ -249,8 +245,7 @@ export const CritiqueSubmission = Type.Object(
|
|||||||
{
|
{
|
||||||
target: Type.String({
|
target: Type.String({
|
||||||
pattern: "^(whole|p[1-9][0-9]*)$",
|
pattern: "^(whole|p[1-9][0-9]*)$",
|
||||||
description:
|
description: "针对谁:整条提案写 whole,针对某个因子写它的编号(p1、p2……)",
|
||||||
"针对谁:整条提案写 whole,针对某个因子写它的编号(p1、p2……)",
|
|
||||||
}),
|
}),
|
||||||
body: Type.String({
|
body: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
@@ -285,8 +280,7 @@ const REPLIES = Type.Array(
|
|||||||
CLOSED,
|
CLOSED,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
description:
|
description: "材料\"本任务要答复的挑刺\"里列出的每一份挑刺各答复一条;没有挑刺交空数组",
|
||||||
'材料"本任务要答复的挑刺"里列出的每一份挑刺各答复一条;没有挑刺交空数组',
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -294,7 +288,7 @@ const CLAUSES = Type.Array(
|
|||||||
Type.String({
|
Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
'一条实现条款:实验员要做的一件事,写到不用猜就能实现。开头写位置:"文件"、"文件:行号"或"文件:函数名",' +
|
"一条实现条款:实验员要做的一件事,写到不用猜就能实现。开头写位置:\"文件\"、\"文件:行号\"或\"文件:函数名\"," +
|
||||||
"只写一个行号或一个函数名、不写范围,文件和函数要在基线代码里真实存在",
|
"只写一个行号或一个函数名、不写范围,文件和函数要在基线代码里真实存在",
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@@ -325,8 +319,7 @@ export const ResponseSubmission = Type.Object(
|
|||||||
clauses: CLAUSES,
|
clauses: CLAUSES,
|
||||||
body: Type.String({
|
body: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "答复正文,Markdown:逐条答复之外要补充的话。没有挑刺、也没有要补充的,写\"无\"",
|
||||||
'答复正文,Markdown:逐条答复之外要补充的话。没有挑刺、也没有要补充的,写"无"',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -349,8 +342,7 @@ export const UnificationSubmission = Type.Object(
|
|||||||
}),
|
}),
|
||||||
coverage: Type.String({
|
coverage: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "这份原提案在统一后的协议里哪里体现;没采纳的部分写明为什么",
|
||||||
"这份原提案在统一后的协议里哪里体现;没采纳的部分写明为什么",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -415,29 +407,25 @@ export const ImplementationSubmission = Type.Object(
|
|||||||
CLOSED,
|
CLOSED,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
description:
|
description: "不属于任何因子或保障的改动,按本次代码相对本批基线的完整差异列出,并说明服务哪项要求;没有就交空数组",
|
||||||
"不属于任何因子或保障的改动,按本次代码相对本批基线的完整差异列出,并说明服务哪项要求;没有就交空数组",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
concerns: CONCERNS,
|
concerns: CONCERNS,
|
||||||
body: Type.String({
|
body: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "实现说明,Markdown:位置表之外需要说明的事。修复时逐条回应退回意见:改在哪里,或以协议原文和代码位置说明为什么不改。协议要求自行求解核数而未执行的,说明数值留给正式实验核对,不声称已经验证。没有要补充的就写\"无\"",
|
||||||
'实现说明,Markdown:位置表之外需要说明的事。修复时逐条回应退回意见:改在哪里,或以协议原文和代码位置说明为什么不改。协议要求自行求解核数而未执行的,说明数值留给正式实验核对,不声称已经验证。没有要补充的就写"无"',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
);
|
);
|
||||||
|
|
||||||
const FOUND = Type.Boolean({
|
const FOUND = Type.Boolean({
|
||||||
description:
|
description: "找到代码且实际行为符合该项要求写 true;找不到、找到但不一致或材料不足以核清,写 false(记为没落实)",
|
||||||
"找到代码且实际行为符合该项要求写 true;找不到、找到但不一致或材料不足以核清,写 false(记为没落实)",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const WHERE = Type.String({
|
const WHERE = Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "代码位置和足以复核的依据,对应冻结要求。没落实时区分具体偏差与核不了:写明看过哪里、哪里不符或还缺什么,不把猜测写成事实",
|
||||||
"代码位置和足以复核的依据,对应冻结要求。没落实时区分具体偏差与核不了:写明看过哪里、哪里不符或还缺什么,不把猜测写成事实",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const VerificationSubmission = Type.Object(
|
export const VerificationSubmission = Type.Object(
|
||||||
@@ -486,37 +474,32 @@ export const VerificationSubmission = Type.Object(
|
|||||||
{
|
{
|
||||||
location: Type.String({
|
location: Type.String({
|
||||||
...CodeLocation,
|
...CodeLocation,
|
||||||
description:
|
description: "材料列出的改动块位置,逐字照抄位置本身,不带展示用的括号标签,不合并不同改动块",
|
||||||
"材料列出的改动块位置,逐字照抄位置本身,不带展示用的括号标签,不合并不同改动块",
|
|
||||||
}),
|
}),
|
||||||
outOfScope: Type.Boolean({
|
outOfScope: Type.Boolean({
|
||||||
description:
|
description: "超出冻结协议、改了最终指标的测量或校验、未按登记定义采集或用占位数掩盖缺测,写 true;落实冻结要求所需的辅助代码写 false",
|
||||||
"超出冻结协议、改了最终指标的测量或校验、未按登记定义采集或用占位数掩盖缺测,写 true;落实冻结要求所需的辅助代码写 false",
|
|
||||||
}),
|
}),
|
||||||
reason: Type.String({
|
reason: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "指出该块代码及对应的冻结要求,说明它为什么必要或哪里超出要求",
|
||||||
"指出该块代码及对应的冻结要求,说明它为什么必要或哪里超出要求",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
description:
|
description: "材料里列出的没有归属的改动,逐条判断是否越界;材料说没有就交空数组",
|
||||||
"材料里列出的没有归属的改动,逐条判断是否越界;材料说没有就交空数组",
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: Type.String({
|
body: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
"说明,Markdown:逐项结果之外需要说明的事。材料里列出的同一行既属于因子又属于保障的地方,在这里各写一句提醒(只是提醒,不判越界)。" +
|
"说明,Markdown:逐项结果之外需要说明的事。材料里列出的同一行既属于因子又属于保障的地方,在这里各写一句提醒(只是提醒,不判越界)。" +
|
||||||
'没有要补充的就写"无"',
|
"没有要补充的就写\"无\"",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...CLOSED,
|
...CLOSED,
|
||||||
description:
|
description: "只交逐项结果,通过与否由程序推出:任何一项没落实或判为越界,就退回实验员修复",
|
||||||
"只交逐项结果,通过与否由程序推出:任何一项没落实或判为越界,就退回实验员修复",
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -530,7 +513,7 @@ const Explanation = Type.Object(
|
|||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
"Markdown:机制是什么、哪个因子在出力、什么情况下不成立、因子之间在效果上怎么互相依赖" +
|
"Markdown:机制是什么、哪个因子在出力、什么情况下不成立、因子之间在效果上怎么互相依赖" +
|
||||||
'(例如"没有 p2,p3 就没用",当作预测交给消融检验)、结论覆盖哪些设定、没覆盖哪些。' +
|
"(例如\"没有 p2,p3 就没用\",当作预测交给消融检验)、结论覆盖哪些设定、没覆盖哪些。" +
|
||||||
"没有具体的机制判断就直说",
|
"没有具体的机制判断就直说",
|
||||||
}),
|
}),
|
||||||
cells: Type.Optional(
|
cells: Type.Optional(
|
||||||
@@ -558,8 +541,7 @@ export const ExplanationSubmission = Type.Object(
|
|||||||
{
|
{
|
||||||
explanations: Type.Array(Explanation, {
|
explanations: Type.Array(Explanation, {
|
||||||
minItems: 1,
|
minItems: 1,
|
||||||
description:
|
description: "一般交一条就够,真有第二个机制判断时再交第二条——比如你的主判断若错了最可能是哪种错法,答得出来就写成第二条,写明哪一格能把两条分开;条数上限见材料",
|
||||||
"一般交一条就够,真有第二个机制判断时再交第二条——比如你的主判断若错了最可能是哪种错法,答得出来就写成第二条,写明哪一格能把两条分开;条数上限见材料",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -580,8 +562,7 @@ export const AblationImplementationSubmission = Type.Object(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...CLOSED,
|
...CLOSED,
|
||||||
description:
|
description: "从完整代码出发,把指定的因子换回关掉时的做法,其余因子和全部保障照留",
|
||||||
"从完整代码出发,把指定的因子换回关掉时的做法,其余因子和全部保障照留",
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -592,13 +573,11 @@ export const AblationVerificationSubmission = Type.Object(
|
|||||||
{
|
{
|
||||||
factor: FactorKey,
|
factor: FactorKey,
|
||||||
off: Type.Boolean({
|
off: Type.Boolean({
|
||||||
description:
|
description: "这份代码里这个因子是关着的写 true,照完整版本保留写 false",
|
||||||
"这份代码里这个因子是关着的写 true,照完整版本保留写 false",
|
|
||||||
}),
|
}),
|
||||||
where: Type.String({
|
where: Type.String({
|
||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description: "依据:在哪几行看到的。不属于任何单个因子的总体核对说明(怎么核的、行号怎么对上的),写进被关掉因子那一项的依据里",
|
||||||
"依据:在哪几行看到的。不属于任何单个因子的总体核对说明(怎么核的、行号怎么对上的),写进被关掉因子那一项的依据里",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@@ -620,7 +599,7 @@ export const WrapUpSubmission = Type.Object(
|
|||||||
...TEXT,
|
...TEXT,
|
||||||
description:
|
description:
|
||||||
"收尾,Markdown:什么被支持了、什么被挑战了、下一步想往哪走:沿某个因子继续,或换一条更可能拿最高分的路线。" +
|
"收尾,Markdown:什么被支持了、什么被挑战了、下一步想往哪走:沿某个因子继续,或换一条更可能拿最高分的路线。" +
|
||||||
'推广结论时写清它覆盖哪些设定。某方面没有写"没有:为什么"。' +
|
"推广结论时写清它覆盖哪些设定。某方面没有写\"没有:为什么\"。" +
|
||||||
"原样放进下一轮的提案材料,所有人都读得到",
|
"原样放进下一轮的提案材料,所有人都读得到",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -648,6 +627,4 @@ export const SUBMISSION_SCHEMAS = {
|
|||||||
} as const satisfies Record<SubmissionKind, TSchema>;
|
} as const satisfies Record<SubmissionKind, TSchema>;
|
||||||
|
|
||||||
/** 某种提交的内容类型,例如 Submission<"proposal">。 */
|
/** 某种提交的内容类型,例如 Submission<"proposal">。 */
|
||||||
export type Submission<K extends SubmissionKind> = Static<
|
export type Submission<K extends SubmissionKind> = Static<(typeof SUBMISSION_SCHEMAS)[K]>;
|
||||||
(typeof SUBMISSION_SCHEMAS)[K]
|
|
||||||
>;
|
|
||||||
|
|||||||
+184
-1151
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user