fix(rectification): resolve typed focus answers deterministically
Independent Staging Quality Gate / validate (push) Successful in 19m52s
Independent Staging Quality Gate / publish (push) Successful in 53m47s

This commit is contained in:
Jesse_Chen
2026-08-27 20:31:05 +08:00
parent f3327235ea
commit 85db4591d3
24 changed files with 695 additions and 168 deletions
@@ -25,7 +25,7 @@ import { evaluateConvergence } from "../src/lib/rectification-agentic/core/conve
import { isDuplicateProbe } from "../src/lib/rectification-agentic/core/duplicate-probes.ts";
import { selectHighestGainProbe } from "../src/lib/rectification-agentic/core/select-probe.ts";
import { holdoutEventIds, splitHoldoutEvents } from "../src/lib/rectification-agentic/core/split-holdout.ts";
import type { ConflictProbe, InferenceCandidate, ProbeAnswer } from "../src/lib/rectification-agentic/core/types.ts";
import type { AnswerClass, ConflictProbe, InferenceCandidate, ProbeAnswer } from "../src/lib/rectification-agentic/core/types.ts";
function probe(input: {
id: string;
@@ -68,6 +68,27 @@ function candidates(scores: Readonly<Record<string, number>>): InferenceCandidat
}));
}
const DEFAULT_CHOICE_OPTIONS = [
{ key: "A", label: "明确发生", answer_class: "yes" },
{ key: "B", label: "部分符合", answer_class: "weak_yes" },
{ key: "C", label: "没有发生", answer_class: "no" },
{ key: "D", label: "无法确定", answer_class: "unsure" },
] as const;
function choiceSchemaFor(
target: Pick<ConflictProbe, "id" | "semantic_key" | "candidate_split_hash" | "question">,
options: readonly Readonly<{ key: string; label: string; answer_class: AnswerClass }>[] = DEFAULT_CHOICE_OPTIONS,
overrides: Readonly<Record<string, unknown>> = {},
): Readonly<Record<string, unknown>> {
return {
choice: { prompt: target.question, options },
probe_id: target.id,
semantic_key: target.semantic_key,
candidate_split_hash: target.candidate_split_hash,
...overrides,
};
}
test("discriminating result does not stay in event_collection", () => {
const state = buildInferenceState({
range_start: "04:50",
@@ -441,8 +462,7 @@ test("C without new evidence updates the posterior immediately and D only marks
});
const denied = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
status: "declined",
schema: { choice: { prompt: "2019 年前后有没有入职或职责加重?" }, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(denied.applied, true);
assert.equal(denied.answerClass, "no");
@@ -458,9 +478,8 @@ test("C without new evidence updates the posterior immediately and D only marks
const unsure = applyChoiceWithoutEvidence(state, {
choiceKey: "D",
status: "skipped",
userMessage: "D. 不记得 / 不确定",
schema: { choice: { prompt: "2019 年前后有没有入职或职责加重?" }, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(unsure.applied, true);
assert.equal(unsure.answerClass, "unsure");
@@ -510,11 +529,7 @@ test("A/B/C/D on a remaining-minute contrast probe moves the posterior", () => {
const before = posteriorMap(state.candidates);
const applied = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
schema: {
choice: { prompt: "那次考试有没有发挥失常?" },
probe_id: contrast.id,
semantic_key: contrast.semantic_key,
},
schema: choiceSchemaFor(contrast),
});
assert.equal(applied.applied, true);
assert.equal(applied.answerClass, "no");
@@ -553,15 +568,12 @@ test("two-way style C 都不像 does not promote the other minute group", () =>
const before = posteriorMap(state.candidates);
const applied = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
schema: {
choice: {
prompt: "这段关系更接近哪一种相处?",
option_c: "两边都不像",
},
choice_kind: "varga_style",
probe_id: contrast.id,
semantic_key: contrast.semantic_key,
},
schema: choiceSchemaFor(contrast, [
{ key: "A", label: "更接近第一种", answer_class: "yes" },
{ key: "B", label: "更接近第二种", answer_class: "weak_yes" },
{ key: "C", label: "两边都不像", answer_class: "unsure" },
{ key: "D", label: "无法确定", answer_class: "unsure" },
], { choice_kind: "varga_style" }),
});
assert.equal(applied.applied, true);
assert.equal(applied.answerClass, "unsure");
@@ -593,7 +605,10 @@ test("holdout and collection declines do not write a probe answer", () => {
const holdout = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
userMessage: `${HOLDOUT_MESSAGE_PREFIX}C. 没有明显发生`,
schema: { choice: { prompt: "盘外核对" }, scoring: false },
schema: {
choice: { prompt: "盘外核对", options: DEFAULT_CHOICE_OPTIONS },
scoring: false,
},
questionId: "relatives:family_event:holdout",
});
assert.equal(holdout.reason, "holdout");
@@ -603,7 +618,6 @@ test("holdout and collection declines do not write a probe answer", () => {
assert.equal(holdout.state.holdout_passed, false);
const collection = applyChoiceWithoutEvidence(state, {
status: "declined",
schema: { required: ["year"] },
});
assert.equal(collection.reason, "no_choice");
@@ -635,7 +649,7 @@ test("structured A/yes from a choice card moves the posterior", () => {
});
const after = applyChoiceWithoutEvidence(state, {
choiceKey: "A",
schema: { probe_id: conflict.id, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(after.applied, true);
assert.equal(after.answerClass, "yes");
@@ -844,7 +858,7 @@ test("evidence fingerprint can stay put while decision-state fingerprint and pos
});
const after = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
schema: { probe_id: conflict.id, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(after.applied, true);
assert.equal(after.state.revision, state.revision + 1);
@@ -922,26 +936,21 @@ test("persisted focus can answer a lower-gain probe while conflicting schema ide
const ledger = createLedger(state);
const firstD = applyChoiceWithoutEvidence(state, {
choiceKey: "D",
schema: { probe_id: conflict.id, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(firstD.applied, true);
const persistedFocus = applyChoiceWithoutEvidence(state, {
choiceKey: "D",
schema: {
probe_id: other.id,
semantic_key: other.semantic_key,
candidate_split_hash: other.candidate_split_hash,
},
schema: choiceSchemaFor(other),
});
assert.equal(persistedFocus.applied, true);
assert.equal(persistedFocus.probeId, other.id);
assert.equal(persistedFocus.state.answered_probes.at(-1)?.probe_id, other.id);
const contradictory = applyChoiceWithoutEvidence(state, {
choiceKey: "D",
schema: {
semantic_key: other.semantic_key,
schema: choiceSchemaFor(other, DEFAULT_CHOICE_OPTIONS, {
candidate_split_hash: conflict.candidate_split_hash,
},
}),
});
assert.equal(contradictory.reason, "stale_probe");
assert.deepEqual(posteriorMap(contradictory.state.candidates), posteriorMap(state.candidates));
@@ -980,11 +989,11 @@ test("persisted focus can answer a lower-gain probe while conflicting schema ide
const afterC = applyChoiceWithoutEvidence(state, {
choiceKey: "C",
schema: { probe_id: conflict.id, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
const superseded = applyChoiceWithoutEvidence(afterC.state, {
choiceKey: "D",
schema: { probe_id: conflict.id, semantic_key: conflict.semantic_key },
schema: choiceSchemaFor(conflict),
});
assert.equal(superseded.reason, "superseded");
assert.equal(superseded.state.revision, afterC.state.revision + 1);