a0ce55f066
Showing a choice card is no longer treated as completion. Distinguish probes require real candidate groups, holdout stays out of scoring, and ordinary sessions can finish with a credible range instead of an exact-minute gate. Co-authored-by: Cursor <cursoragent@cursor.com>
218 lines
7.8 KiB
TypeScript
218 lines
7.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { applyChoiceWithoutEvidence } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
|
|
import { applyHoldoutAnswer, buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
|
import { unionStillValidRange } from "../src/lib/rectification-agentic/core/credible-range.ts";
|
|
import { decideRectification } from "../src/lib/rectification-agentic/core/rectification-decision.ts";
|
|
import { posteriorMap } from "../src/lib/rectification-agentic/core/decision-fingerprint.ts";
|
|
import { holdoutEventIds } from "../src/lib/rectification-agentic/core/split-holdout.ts";
|
|
import { selectDiscriminatorProbe, buildCandidateContrastPacket } from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
|
|
import type { ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts";
|
|
|
|
function probe(input: {
|
|
id: string;
|
|
domain: string;
|
|
year: number;
|
|
yesSupports: readonly string[];
|
|
noSupports: readonly string[];
|
|
}): ConflictProbe {
|
|
return {
|
|
id: input.id,
|
|
semantic_key: `${input.domain}.${input.year}`,
|
|
candidate_split_hash: `${input.yesSupports.join(",")}|${input.noSupports.join(",")}`,
|
|
domain: input.domain,
|
|
year: input.year,
|
|
question: `${input.year} 年前后是否发生过相关前事?`,
|
|
candidate_ids: [...input.yesSupports, ...input.noSupports],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: input.yesSupports, conflicts: [] },
|
|
{ answer_class: "no", supports: input.noSupports, conflicts: [] },
|
|
{ answer_class: "unsure", supports: [], conflicts: [] },
|
|
],
|
|
information_gain: 0.4,
|
|
source: "dasha_boundary",
|
|
};
|
|
}
|
|
|
|
test("credible range unions every still-valid cluster, not only rank=1", () => {
|
|
const range = unionStillValidRange([
|
|
{
|
|
id: "05:00",
|
|
time: "05:00",
|
|
cluster_range: ["05:00", "05:02"],
|
|
prior_score: 12,
|
|
posterior_score: 34,
|
|
probability: 0.34,
|
|
status: "active",
|
|
rank: 1,
|
|
strong_conflict_count: 0,
|
|
},
|
|
{
|
|
id: "05:20",
|
|
time: "05:20",
|
|
cluster_range: ["05:20", "05:22"],
|
|
prior_score: 12,
|
|
posterior_score: 33,
|
|
probability: 0.33,
|
|
status: "active",
|
|
rank: 2,
|
|
strong_conflict_count: 0,
|
|
},
|
|
{
|
|
id: "05:40",
|
|
time: "05:40",
|
|
cluster_range: ["05:40", "05:40"],
|
|
prior_score: 4,
|
|
posterior_score: 4,
|
|
probability: 0.04,
|
|
status: "active",
|
|
rank: 3,
|
|
strong_conflict_count: 0,
|
|
},
|
|
]);
|
|
assert.deepEqual(range, ["05:00", "05:22"]);
|
|
});
|
|
|
|
test("hidden case walks collection through holdout to a range or representative close", () => {
|
|
const first = probe({
|
|
id: "p-career",
|
|
domain: "career",
|
|
year: 2015,
|
|
yesSupports: ["05:00"],
|
|
noSupports: ["05:20"],
|
|
});
|
|
const collected = buildInferenceState({
|
|
range_start: "04:50",
|
|
range_end: "05:30",
|
|
candidates: [
|
|
{ id: "05:00", time: "05:00", relative_support: 10 },
|
|
{ id: "05:20", time: "05:20", relative_support: 4 },
|
|
],
|
|
events: [
|
|
{ id: "e1", domain: "education", year: 2016, precision: "month" },
|
|
{ id: "e2", domain: "career", year: 2018, precision: "year" },
|
|
{ id: "e3", domain: "relationship", year: 2021, precision: "year" },
|
|
{ id: "e4", domain: "family", year: 2023, precision: "year" },
|
|
],
|
|
probes: [first],
|
|
});
|
|
const holdoutIds = holdoutEventIds(collected.events);
|
|
assert.equal(holdoutIds.size, 1);
|
|
assert.ok(collected.events.some((item) => item.usage === "training"));
|
|
assert.ok(!collected.probes.some((item) => {
|
|
const holdout = collected.events.find((row) => row.usage === "holdout");
|
|
return holdout !== undefined && item.domain === holdout.domain && item.year === holdout.year;
|
|
}));
|
|
|
|
const packet = buildCandidateContrastPacket({
|
|
candidateSetVersion: collected.candidate_set_id,
|
|
calculationResultId: "11111111-1111-4111-8111-111111111111",
|
|
engineProbes: collected.probes.map((item) => ({
|
|
semantic_key: item.semantic_key,
|
|
candidate_split_hash: item.candidate_split_hash,
|
|
domain: item.domain,
|
|
year: item.year,
|
|
user_meaning: item.question,
|
|
information_gain: item.information_gain,
|
|
expected_outcomes: item.expected_outcomes,
|
|
candidate_ids: item.candidate_ids,
|
|
})),
|
|
});
|
|
const discriminator = selectDiscriminatorProbe(packet);
|
|
assert.ok(discriminator);
|
|
assert.ok(discriminator.informationGain > 0);
|
|
assert.ok(discriminator.expectedOutcomes.length >= 2);
|
|
const mapped = new Set(discriminator.expectedOutcomes.flatMap((row) => [
|
|
...row.supportsCandidateIds,
|
|
...row.conflictsCandidateIds,
|
|
]));
|
|
assert.ok(mapped.size >= 2);
|
|
|
|
const before = posteriorMap(collected.candidates);
|
|
const answered = applyChoiceWithoutEvidence(collected, {
|
|
choiceKey: "A",
|
|
schema: { probe_id: first.id, semantic_key: first.semantic_key },
|
|
});
|
|
assert.equal(answered.applied, true);
|
|
assert.notDeepEqual(posteriorMap(answered.state.candidates), before);
|
|
|
|
const scores = answered.state.candidates.map((item) => ({
|
|
time: item.time,
|
|
score: item.posterior_score,
|
|
}));
|
|
const afterAnswers = decideRectification({
|
|
methodCoverageAll: true,
|
|
candidateScores: scores,
|
|
holdoutValidation: "not_started",
|
|
});
|
|
assert.equal(afterAnswers.nextAction, "ask_holdout_validation");
|
|
assert.equal(afterAnswers.canConfirmExactMinute, false);
|
|
|
|
const holdoutPassed = applyHoldoutAnswer(answered.state, "yes");
|
|
assert.equal(holdoutPassed.holdout_passed, true);
|
|
assert.deepEqual(posteriorMap(holdoutPassed.candidates), posteriorMap(answered.state.candidates));
|
|
const closed = decideRectification({
|
|
methodCoverageAll: true,
|
|
candidateScores: holdoutPassed.candidates.map((item) => ({
|
|
time: item.time,
|
|
score: item.posterior_score,
|
|
})),
|
|
holdoutValidation: "passed",
|
|
inferenceCredibleRange: holdoutPassed.credible_range,
|
|
});
|
|
assert.ok(closed.nextAction === "ready_to_adopt" || closed.nextAction === "complete_with_range");
|
|
assert.equal(closed.canAdopt, true);
|
|
assert.equal(closed.canConfirmExactMinute, false);
|
|
assert.ok(closed.credibleRange);
|
|
assert.notEqual(closed.sessionOutcome, "discriminate_candidates");
|
|
});
|
|
|
|
test("holdout failure returns to candidate discrimination", () => {
|
|
const retry = probe({
|
|
id: "p-retry",
|
|
domain: "career",
|
|
year: 2018,
|
|
yesSupports: ["05:00"],
|
|
noSupports: ["05:20"],
|
|
});
|
|
const state = buildInferenceState({
|
|
range_start: "04:50",
|
|
range_end: "05:30",
|
|
candidates: [
|
|
{ id: "05:00", time: "05:00", relative_support: 40 },
|
|
{ id: "05:20", time: "05:20", relative_support: 12 },
|
|
],
|
|
events: [
|
|
{ id: "e1", domain: "education", year: 2016, precision: "month" },
|
|
{ id: "e2", domain: "career", year: 2018, precision: "year" },
|
|
{ id: "e3", domain: "relationship", year: 2021, precision: "year" },
|
|
{ id: "e4", domain: "family", year: 2023, precision: "year" },
|
|
],
|
|
probes: [retry],
|
|
});
|
|
const failed = applyHoldoutAnswer(state, "no");
|
|
assert.equal(failed.holdout_passed, false);
|
|
const retryPacket = buildCandidateContrastPacket({
|
|
candidateSetVersion: failed.candidate_set_id,
|
|
calculationResultId: "11111111-1111-4111-8111-111111111111",
|
|
engineProbes: [{
|
|
semantic_key: retry.semantic_key,
|
|
candidate_split_hash: retry.candidate_split_hash,
|
|
domain: retry.domain,
|
|
year: retry.year,
|
|
user_meaning: retry.question,
|
|
information_gain: retry.information_gain,
|
|
expected_outcomes: retry.expected_outcomes,
|
|
candidate_ids: retry.candidate_ids,
|
|
}],
|
|
});
|
|
const next = decideRectification({
|
|
methodCoverageAll: true,
|
|
candidateScores: failed.candidates.map((item) => ({ time: item.time, score: item.posterior_score })),
|
|
discriminatorProbe: selectDiscriminatorProbe(retryPacket),
|
|
holdoutValidation: "failed",
|
|
});
|
|
assert.equal(next.nextAction, "ask_candidate_discriminator");
|
|
});
|