fix(rectification): enforce persisted convergence budgets
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { EFFECTIVE_ANSWER_SAFETY_CAP } from "../src/lib/birth-time-dynamic-stop-policy.ts";
|
||||
import { DEFAULT_MAX_DISCRIMINATION_ROUNDS } from "../src/lib/rectification-agentic/core/types.ts";
|
||||
import { decideRectification } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
|
||||
import { RECTIFICATION_POLICY } from "../src/lib/rectification-policy.ts";
|
||||
import type { CandidateDiscriminatorProbe } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
||||
|
||||
const PROBE: CandidateDiscriminatorProbe = {
|
||||
probeId: "probe-1",
|
||||
candidateSetVersion: "set-1",
|
||||
question: "这件事更接近哪一种情况?",
|
||||
expectedOutcomes: [
|
||||
{
|
||||
outcomeId: "yes",
|
||||
supportsCandidateIds: ["05:00"],
|
||||
conflictsCandidateIds: ["05:06", "05:07"],
|
||||
},
|
||||
{
|
||||
outcomeId: "no",
|
||||
supportsCandidateIds: ["05:06", "05:07"],
|
||||
conflictsCandidateIds: ["05:00"],
|
||||
},
|
||||
],
|
||||
candidateSplitHash: "05:00|05:06|05:07",
|
||||
informationGain: 0.5,
|
||||
sourceFeatures: [{ technique: "test", calculationResultId: null }],
|
||||
domain: "career",
|
||||
year: 2020,
|
||||
semanticKey: "career.2020.test",
|
||||
};
|
||||
|
||||
const BASE_INPUT = {
|
||||
methodCoverageAll: true,
|
||||
trainingGateOpen: true,
|
||||
candidateScores: [
|
||||
{ time: "05:00", score: 34 },
|
||||
{ time: "05:06", score: 33 },
|
||||
{ time: "05:07", score: 33 },
|
||||
],
|
||||
discriminatorProbe: PROBE,
|
||||
holdoutValidation: "unavailable" as const,
|
||||
};
|
||||
|
||||
function decideWithBudget(budget: {
|
||||
inferenceRounds?: number;
|
||||
effectiveAnswerCount?: number;
|
||||
plateauRounds?: number;
|
||||
}) {
|
||||
return decideRectification({ ...BASE_INPUT, ...budget });
|
||||
}
|
||||
|
||||
test("every persisted discrimination budget terminates before asking another probe", () => {
|
||||
for (const budget of [
|
||||
{ inferenceRounds: DEFAULT_MAX_DISCRIMINATION_ROUNDS },
|
||||
{ effectiveAnswerCount: EFFECTIVE_ANSWER_SAFETY_CAP },
|
||||
{ plateauRounds: RECTIFICATION_POLICY.maxPlateauRounds },
|
||||
]) {
|
||||
const decision = decideWithBudget(budget);
|
||||
assert.equal(decision.nextAction, "complete_with_range");
|
||||
assert.equal(decision.sessionOutcome, "completed_with_range");
|
||||
assert.notEqual(decision.nextAction, "ask_candidate_discriminator");
|
||||
}
|
||||
});
|
||||
|
||||
test("repeated declined or unsure answers reach the existing plateau terminal", () => {
|
||||
const decision = decideWithBudget({
|
||||
inferenceRounds: 0,
|
||||
effectiveAnswerCount: RECTIFICATION_POLICY.maxPlateauRounds,
|
||||
plateauRounds: RECTIFICATION_POLICY.maxPlateauRounds,
|
||||
});
|
||||
assert.equal(decision.nextAction, "complete_with_range");
|
||||
assert.equal(decision.sessionOutcome, "completed_with_range");
|
||||
});
|
||||
|
||||
test("exhausted discrimination still delivers the credible candidate range", () => {
|
||||
const decision = decideWithBudget({
|
||||
inferenceRounds: DEFAULT_MAX_DISCRIMINATION_ROUNDS,
|
||||
effectiveAnswerCount: 0,
|
||||
plateauRounds: 0,
|
||||
});
|
||||
assert.equal(decision.resultStatus, "completed_with_range");
|
||||
assert.equal(decision.sessionOutcome, "completed_with_range");
|
||||
assert.equal(decision.canOfferRange, true);
|
||||
assert.equal(decision.canAdopt, true);
|
||||
assert.deepEqual(decision.credibleRange, ["05:00", "05:07"]);
|
||||
});
|
||||
|
||||
test("additional score evidence never widens the credible range", () => {
|
||||
const before = decideRectification({
|
||||
...BASE_INPUT,
|
||||
candidateScores: [
|
||||
{ time: "05:00", score: 34 },
|
||||
{ time: "05:06", score: 33 },
|
||||
{ time: "05:07", score: 33 },
|
||||
],
|
||||
inferenceRounds: DEFAULT_MAX_DISCRIMINATION_ROUNDS,
|
||||
});
|
||||
const after = decideRectification({
|
||||
...BASE_INPUT,
|
||||
candidateScores: [
|
||||
{ time: "05:00", score: 42 },
|
||||
{ time: "05:06", score: 33 },
|
||||
{ time: "05:07", score: 33 },
|
||||
],
|
||||
inferenceRounds: DEFAULT_MAX_DISCRIMINATION_ROUNDS,
|
||||
});
|
||||
|
||||
const width = (range: readonly [string, string] | null) => {
|
||||
assert.ok(range);
|
||||
const toMinutes = (time: string) => Number(time.slice(0, 2)) * 60 + Number(time.slice(3, 5));
|
||||
return toMinutes(range[1]) - toMinutes(range[0]);
|
||||
};
|
||||
|
||||
assert.ok(width(after.credibleRange) <= width(before.credibleRange));
|
||||
});
|
||||
Reference in New Issue
Block a user