fix(rectification): resolve typed focus answers deterministically
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user