Files
InfoRSI/eval/lib/diagnosis.ts
T

311 lines
11 KiB
TypeScript

import type { LedgerRecord } from "../../src/ledger/index.ts";
import {
Board,
diagnosisId,
type StudyRecord,
} from "../../src/materials/index.ts";
import {
covers,
improvement,
placement,
runBalance,
} from "../../src/rules/index.ts";
import { checkTaskFacts } from "../../src/tasks/index.ts";
import { fullAccounts } from "./full.ts";
import { frozenConfig } from "./forward.ts";
import { ofKind } from "./ledger.ts";
// 诊断开、消融关的完整研究(任务书 18 第三块)的账目与逐轮事实:只从账本记录重算,不作质量判断。
// 第 4 样"沿这个方向"这类判断由子 Agent 读材料标注,这里只给能由账本算出的列。
/** 消融关着时,每轮都应当有"按配置跳过"的七个步骤(src/config/steps.ts 的 off 清单)。 */
export const ABLATION_STEPS = [
"listCells",
"explanation",
"selectCells",
"ablationImplementation",
"ablationVerification",
"ablationRun",
"settle",
] as const;
const BAND = {
belowQ1: "<q1",
q1ToQ5: "[q1,q5)",
q5ToQ9: "[q5,q9]",
aboveQ9: ">q9",
} as const;
export function diagnosisAccounts(records: LedgerRecord[]) {
const base = fullAccounts(records);
const board = new Board(records);
const config = frozenConfig(records);
const facts = checkTaskFacts(
(ofKind(records, "config")[0]!.content as unknown as StudyRecord).task,
);
const events = ofKind(records, "event");
const opens = ofKind(records, "stageOpen");
const closes = ofKind(records, "stageClose");
const closeSeq = (stageId: string) =>
closes.find((c) => c.content.stageId === stageId)?.seq ?? null;
const planned = ofKind(records, "runPlanned");
const started = ofKind(records, "runStarted");
const results = ofKind(records, "runResult");
// 开过的轮次:有阶段、决定或按配置跳过事件的都算。
const roundIds = base.rounds.map((r) => r.round);
const rounds = roundIds.map((round) => {
const roundOf = base.rounds.find((r) => r.round === round)!;
const balanceBefore = runBalance(
config.runs,
board.runStatesBefore(round),
).balance;
const prefix = `r${round}-`;
const diagStages = opens.filter(
(o) =>
o.content.round === round &&
o.content.stageId.startsWith(`${prefix}diagnosis`),
);
const select = board.decision("selectDiagnosis", round) ?? null;
const dedup = board.decision("diagnosisDedup", round) ?? null;
const settlement = board.diagnosisSettlement(round) ?? null;
const settlementRecords = ofKind(records, "diagnosisSettlement").filter(
(r) => r.content.round === round,
);
const diagRuns = planned
.filter(
(p) => p.content.purpose === "diagnosis" && p.content.round === round,
)
.map((p) => ({
run: p.content.run,
planSeq: p.seq,
startSeqs: started
.filter((s) => s.content.run === p.content.run)
.map((s) => s.seq),
result: results.find((r) => r.content.run === p.content.run) ?? null,
}));
const anyDiagnosisRecord =
diagStages.length > 0 ||
select !== null ||
dedup !== null ||
diagRuns.length > 0 ||
settlementRecords.length > 0;
const merged = dedup === null ? [] : board.diagnosisExperiments(round);
const dropped = settlement?.dropped ?? [];
const verifications = board
.published("diagnosisVerification")
.filter((s) => s.round === round);
const repairs = board
.published("diagnosisRepair")
.filter((s) => s.round === round);
const predictionClose = closeSeq(`${prefix}diagnosisPrediction`);
const firstStart = diagRuns.flatMap((r) => r.startSeqs).sort((a, b) => a - b)[0];
const experiments = merged.map((e) => {
const row = settlement?.output.experiments.find(
(x) => x.experiment === e.id,
);
const judged = settlement?.judged.experiments.find((x) => x.id === e.id);
const skip = select?.output.experiments.find(
(x) => x.experiment === e.id,
);
return {
id: e.id,
name: e.name,
spot: e.spot,
authors: e.authors,
chosen: select?.output.chosen.includes(e.id) ?? false,
skipReason: skip?.reason ?? null,
skipBestNet: skip?.bestCombo?.net ?? null,
priorGain: select?.output.priorGains.find((g) => g.experiment === e.id)
?.gain ?? null,
outcome: row?.outcome ?? null,
score:
judged !== undefined &&
judged.verdict === "valid" &&
judged.score.status === "measured"
? judged.score.value
: null,
scoreStatus: judged?.score.status ?? null,
gain: row?.gain ?? null,
merged: row?.merged ?? null,
ending: judged?.ending ?? null,
wallSeconds:
judged?.wallSeconds.status === "measured"
? judged.wallSeconds.value
: null,
worldBits: row?.worldBits ?? null,
surprise:
row?.surprise?.bits.status === "measured"
? row.surprise.bits.value
: null,
dropped: dropped.find((d) => d.experiment === e.id)?.reason ?? null,
};
});
// 第 5 样:每位作者每条解释对每个有效实验的三个数,实测落点;合并范围的落点。
const predictionRows =
settlement === null
? []
: settlement.input.explanations.flatMap((x) =>
settlement.input.experiments.flatMap((e) => {
if (e.outcome.kind !== "valid") return [];
const range = x.experiments.find(
(y) => y.experiment === e.experiment,
)!.range;
return [
{
explanation: x.id,
author: x.author,
experiment: e.experiment,
range,
actual: e.outcome.score,
band: BAND[placement(range, e.outcome.score)],
covered: covers(range, e.outcome.score),
},
];
}),
);
const mergedRows =
settlement === null
? []
: settlement.input.experiments.flatMap((e) => {
if (e.outcome.kind !== "valid") return [];
const range = settlement.output.experiments.find(
(x) => x.experiment === e.experiment,
)!.merged;
return [
{
experiment: e.experiment,
range,
actual: e.outcome.score,
band: BAND[placement(range, e.outcome.score)],
covered: covers(range, e.outcome.score),
},
];
});
const medians = (
settlement?.output.experiments.map((x) => x.merged.q5) ?? []
).sort((a, b) => a - b);
const experimentGap =
medians.length < 2
? null
: {
maxMinusMin: medians.at(-1)! - medians[0]!,
minPairwise: Math.min(
...medians.slice(1).map((m, i) => m - medians[i]!),
),
};
const batch = board.decision("batch", round) ?? null;
const candMedians = (batch?.output.candidates ?? [])
.flatMap((c) => (c.merged === null ? [] : [c.merged.q5]))
.sort((a, b) => a - b);
const candidateGap =
candMedians.length < 2
? null
: {
maxMinusMin: candMedians.at(-1)! - candMedians[0]!,
minPairwise: Math.min(
...candMedians.slice(1).map((m, i) => m - candMedians[i]!),
),
};
// 本轮用于第 4 样的正向侧:每个候选相对本轮基线的改进量。
const baseline = board.baselineOf(round);
const baselineScore = board.baselineScore(baseline);
const forward = roundOf.candidates.map((c) => ({
candidate: c.candidate,
score: c.score,
gain:
c.score === null
? null
: improvement(baselineScore, c.score, facts.finalMetric.direction),
}));
// 本轮诊断结算按轮次唯一;运行扣次:确认启动的诊断运行数。
const ablationAbsent =
opens.every(
(o) =>
o.content.round !== round ||
!["explanation", "ablationImplementation", "ablationVerification"].includes(
o.content.stage,
),
) &&
planned.every(
(p) => p.content.purpose !== "ablation" || p.content.round !== round,
) &&
board.decision("cells", round) === undefined &&
board.decision("bet", round) === undefined &&
board.settlement(round) === undefined;
const skippedSteps = ABLATION_STEPS.map((step) =>
events.some(
(e) =>
e.content.event === "skippedByConfig" &&
e.content.objects[0] === `${prefix}${step}`,
),
);
return {
round,
baseline,
baselineScore,
balanceBefore,
expectedDiagnosis: balanceBefore >= 2,
anyDiagnosisRecord,
diagnosed: select?.output.diagnose ?? false,
chosen: select?.output.chosen ?? [],
chosenNet: select?.output.chosenNet ?? null,
combos: select?.output.combos ?? [],
g0: select?.output.g0 ?? null,
selectParams: select?.output.params ?? null,
experiments,
verificationCount: verifications.length,
repairCount: repairs.length,
droppedCount: dropped.length,
ranCount: settlement?.judged.experiments.length ?? 0,
runs: diagRuns,
charged: diagRuns.filter((r) => r.startSeqs.length > 0).length,
settlementCount: settlementRecords.length,
direction: settlement?.output.direction ?? null,
judgments: settlement?.output.judgments ?? null,
predictionRows,
mergedRows,
experimentGap,
candidateGap,
forward,
predictionCloseSeq: predictionClose,
firstDiagnosisStartSeq: firstStart ?? null,
predictionBeforeStart:
firstStart === undefined
? null
: predictionClose !== null && predictionClose < firstStart,
ablationAbsent,
ablationSkipEventsAll: skippedSteps.every(Boolean),
diagnosisNotRunEvents: events.filter(
(e) =>
e.content.event === "diagnosisNotRun" &&
e.content.objects.includes(diagnosisId(round)),
).length,
};
});
const checks = {
...base.checks,
diagnosisChargedEqualsStarts: rounds.every(
(r) => r.runs.length === 0 || r.charged === r.runs.filter((x) => x.startSeqs.length > 0).length,
),
oneDiagnosisSettlementPerRound: rounds.every((r) => r.settlementCount <= 1),
noDiagnosisBelowTwo: rounds.every(
(r) => r.expectedDiagnosis || !r.anyDiagnosisRecord,
),
diagnosisWhenBalanceAtLeastTwo: rounds.every(
(r) => !r.expectedDiagnosis || r.anyDiagnosisRecord,
),
predictionBeforeStart: rounds.every((r) => r.predictionBeforeStart !== false),
ablationOffClean: rounds.every((r) => r.ablationAbsent),
ablationSkipEvents: rounds.every((r) => r.ablationSkipEventsAll),
diagnosisRunPurposeCount:
planned.filter((p) => p.content.purpose === "diagnosis").length ===
rounds.reduce((n, r) => n + r.runs.length, 0),
};
return { ...base, diagnosisRounds: rounds, diagnosisChecks: checks };
}