Files
Jyotisha/frontend/tests/rectification-server-focus.test.ts
T
Jesse_Chen 85db4591d3
Independent Staging Quality Gate / validate (push) Successful in 19m52s
Independent Staging Quality Gate / publish (push) Successful in 53m47s
fix(rectification): resolve typed focus answers deterministically
2026-08-27 20:31:05 +08:00

433 lines
14 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import {
openQuestionFromPersistedFocus,
persistServerOwnedFocus,
shouldSkipDiscriminatorFollowup,
stableFollowupQuestionId,
} from "../src/lib/rectification-agentic/v9/server-focus.ts";
import { buildChoiceFrame, serverOwnedChoiceCopy } from "../src/lib/rectification-agentic/v9/choice-card.ts";
import type { MethodFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import type { EventProbeStyleOption } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
import { fakeAccounting, CASE_ID, FOCUS_ID, USER_ID, activeFocusFixture } from "./rectification-v9-test-support.ts";
const DYNAMIC_STYLE_OPTIONS = [
{ label: "明确发生且时间吻合", answer_class: "yes" },
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
{ label: "明确没有发生", answer_class: "no" },
{ label: "这段记不清楚", answer_class: "unsure" },
] as const;
function discriminatorFollowup(overrides: Partial<MethodFollowup> = {}): MethodFollowup {
const frame = buildChoiceFrame({
method_id: "dasha_events",
ask_theme: "dated_event",
domain: "education",
user_prompt_hint: "ask",
}, {
probes: [{
year: 2016,
year_label: "2016 年前后",
domain: "education",
event_family: "升学结果或学习环境出现明显变化",
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "2016 年前后升学结果或学习环境出现明显变化",
role: "distinguish",
information_gain: 0.4,
semantic_key: "education:2016",
style_options: DYNAMIC_STYLE_OPTIONS,
}],
});
assert.ok(frame);
return {
method_id: "dasha_events",
intent: "distinguish_candidates",
ask_theme: "dated_event",
domain: "education",
kind_hint: null,
user_prompt_hint: "ask",
must_not_label: false,
choice_frame: frame,
source: "event_probe",
information_gain: 0.4,
semantic_key: "education:2016",
probe_year: 2016,
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
],
...overrides,
};
}
function invalidStyleFollowup(
styleOptions: readonly EventProbeStyleOption[] | undefined,
eventFamily = "升学结果或学习环境出现明显变化",
): MethodFollowup {
const valid = discriminatorFollowup();
return {
...valid,
choice_frame: buildChoiceFrame({
method_id: "dasha_events",
ask_theme: "dated_event",
domain: "education",
user_prompt_hint: "ask",
}, {
probes: [{
year: 2016,
year_label: "2016 年前后",
domain: "education",
event_family: eventFamily,
source: "dasha_activation",
tracks: ["vimshottari", "narayana"],
tracks_agree: true,
unique_minute_claim: false,
user_meaning: "2016 年前后升学结果或学习环境出现明显变化",
role: "distinguish",
information_gain: 0.4,
semantic_key: "education:2016",
style_options: styleOptions,
}],
}),
};
}
async function assertInvalidChoiceSkipped(followup: MethodFollowup) {
const accounting = fakeAccounting({}, {
fallback: () => {
throw new Error("invalid choice frame must not call the database");
},
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: null,
decisionReceipt: null,
followup,
});
assert.equal(result.status, "invalid_choice_schema");
assert.equal(accounting.calls.length, 0);
}
function persistedFocus(questionId = "probe:education:2016") {
const copy = serverOwnedChoiceCopy(discriminatorFollowup().choice_frame!);
assert.ok(copy);
return {
id: FOCUS_ID,
caseId: CASE_ID,
questionId,
intent: "distinguish_candidates",
targetEvidenceId: null,
targetDomain: "education",
targetKind: null,
expectedAnswerSchema: {
choice: copy,
semantic_key: "education:2016",
candidate_split_hash: "education:2016",
},
status: "active" as const,
askedAt: "2026-08-27T00:00:00.000Z",
resolvedAt: null,
};
}
test("only a successfully persisted focus can expose its discriminator question", () => {
const focus = persistedFocus();
for (const status of ["created", "already_open"] as const) {
assert.deepEqual(openQuestionFromPersistedFocus({
status,
focus,
questionId: focus.questionId,
prompt: "2016 年前后 · 升学结果或学习环境出现明显变化",
}), {
question_id: focus.questionId,
prompt: "2016 年前后 · 升学结果或学习环境出现明显变化",
status,
});
}
});
test("an unpersisted, mismatched, or incomplete focus cannot expose a discriminator question", () => {
const focus = persistedFocus();
assert.equal(openQuestionFromPersistedFocus({
status: "duplicate_focus",
focus,
questionId: focus.questionId,
prompt: "不能展示",
}), null);
assert.equal(openQuestionFromPersistedFocus({
status: "created",
focus: { ...focus, questionId: "another-question" },
questionId: focus.questionId,
prompt: "不能展示",
}), null);
assert.equal(openQuestionFromPersistedFocus({
status: "created",
focus: { ...focus, expectedAnswerSchema: { choice: { prompt: "不完整" } } },
questionId: focus.questionId,
prompt: "不能展示",
}), null);
});
test("complete dynamic event options persist a server-owned focus", async () => {
const followup = discriminatorFollowup({ source: "precision_stage", information_gain: 0.2 });
const accounting = fakeAccounting({
set_agentic_rectification_conversation_focus: (_fn, args) => ({
id: FOCUS_ID,
case_id: CASE_ID,
question_id: args.p_question_id,
intent: args.p_intent,
target_evidence_id: null,
target_domain: args.p_target_domain,
target_kind: args.p_target_kind,
expected_answer_schema: args.p_expected_answer_schema,
status: "active",
asked_at: "2026-08-27T00:00:00.000Z",
resolved_at: null,
idempotent: false,
}),
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: null,
decisionReceipt: null,
followup,
});
assert.equal(result.status, "created");
assert.equal(accounting.calls.length, 1);
assert.deepEqual(result.focus?.expectedAnswerSchema.choice, {
prompt: "2016 年前后 · 升学结果或学习环境出现明显变化",
option_a: DYNAMIC_STYLE_OPTIONS[0].label,
option_b: DYNAMIC_STYLE_OPTIONS[1].label,
option_c: DYNAMIC_STYLE_OPTIONS[2].label,
option_d: DYNAMIC_STYLE_OPTIONS[3].label,
options: [
{ key: "A", label: DYNAMIC_STYLE_OPTIONS[0].label, answer_class: "yes" },
{ key: "B", label: DYNAMIC_STYLE_OPTIONS[1].label, answer_class: "weak_yes" },
{ key: "C", label: DYNAMIC_STYLE_OPTIONS[2].label, answer_class: "no" },
{ key: "D", label: DYNAMIC_STYLE_OPTIONS[3].label, answer_class: "unsure" },
],
});
});
test("missing, duplicate, incomplete, or illegal dynamic options skip focus persistence", async () => {
await assertInvalidChoiceSkipped(invalidStyleFollowup(undefined));
await assertInvalidChoiceSkipped(invalidStyleFollowup(DYNAMIC_STYLE_OPTIONS.slice(0, 3)));
await assertInvalidChoiceSkipped(invalidStyleFollowup([
...DYNAMIC_STYLE_OPTIONS,
{ label: "另一种明确发生", answer_class: "yes" },
]));
await assertInvalidChoiceSkipped(invalidStyleFollowup([
{ ...DYNAMIC_STYLE_OPTIONS[0], label: "08:12 左右发生" },
...DYNAMIC_STYLE_OPTIONS.slice(1),
]));
await assertInvalidChoiceSkipped(invalidStyleFollowup(DYNAMIC_STYLE_OPTIONS, " "));
});
test("does not ask an already-answered discriminator probe again", async () => {
const followup = discriminatorFollowup();
const active = activeFocusFixture({
expectedAnswerSchema: { probe_id: "education:2016", choice: serverOwnedChoiceCopy(followup.choice_frame!) },
});
const accounting = fakeAccounting({
set_agentic_rectification_conversation_focus: () => {
throw new Error("should not create a second focus");
},
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: {
...active,
id: FOCUS_ID,
caseId: CASE_ID,
questionId: stableFollowupQuestionId(followup),
intent: "distinguish_candidates",
targetEvidenceId: null,
targetDomain: "education",
targetKind: null,
expectedAnswerSchema: { probe_id: "education:2016" },
status: "active",
askedAt: "2026-08-24T00:00:00.000Z",
resolvedAt: null,
},
decisionReceipt: {
inference_state: {
answered_probes: [{ probe_id: "education:2016", semantic_key: "education:2016", answer_class: "yes" }],
probes: [],
},
},
followup,
});
assert.equal(result.status, "probe_already_answered");
assert.equal(accounting.calls.length, 0);
});
test("zero information gain does not open a discriminator", () => {
assert.equal(
shouldSkipDiscriminatorFollowup(discriminatorFollowup({ information_gain: 0 })),
"zero_information_gain",
);
});
test("recorded event quality cannot open a zero-information scoring card", () => {
assert.equal(
shouldSkipDiscriminatorFollowup(discriminatorFollowup({
choice_kind: "event_quality",
information_gain: 0,
})),
"zero_information_gain",
);
});
test("an unmatched scoring identity is skipped instead of being synthesized", async () => {
const followup = discriminatorFollowup({
semantic_key: "education.synthetic",
candidate_split_hash: "education:synthetic",
});
const accounting = fakeAccounting({
set_agentic_rectification_conversation_focus: () => {
throw new Error("must not persist an unmatched scoring focus");
},
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: null,
decisionReceipt: {
inference_state: {
algorithm_version: "birth-time-event-scoring-v1",
candidates: [
{ id: "05:00", birth_time: "05:00", posterior: 0.5 },
{ id: "05:20", birth_time: "05:20", posterior: 0.5 },
],
answered_probes: [],
probes: [{
id: "p-live",
semantic_key: "education.live",
candidate_split_hash: "education:live",
domain: "education",
year: 2016,
question: "live",
candidate_ids: ["05:00", "05:20"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] },
{ answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] },
],
information_gain: 0.2,
source: "dasha_activation",
}],
},
},
followup,
});
assert.equal(result.status, "invalid_choice_schema");
assert.equal(accounting.calls.length, 0);
});
test("duplicate focus conflict does not throw", async () => {
const followup = discriminatorFollowup({ source: "precision_stage", information_gain: 0.2 });
const accounting = fakeAccounting({
set_agentic_rectification_conversation_focus: () => {
throw new Error("agentic_rectification_focus_idempotency_conflict");
},
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: null,
decisionReceipt: null,
followup,
});
assert.equal(result.status, "duplicate_focus");
});
test("contrast probe is not replaced by an already-answered education quality probe", async () => {
const followup = discriminatorFollowup({
domain: "education",
ask_theme: "education_style",
information_gain: 0.16,
semantic_key: "varga.d24.05:00/05:06|05:07",
candidate_split_hash: "varga.d24.05:00/05:06|05:07",
probe_year: undefined,
});
const accounting = fakeAccounting({
set_agentic_rectification_conversation_focus: (_fn, args) => ({
id: FOCUS_ID,
case_id: CASE_ID,
question_id: args.p_question_id,
intent: args.p_intent,
target_evidence_id: null,
target_domain: args.p_target_domain,
target_kind: args.p_target_kind,
expected_answer_schema: args.p_expected_answer_schema,
status: "active",
asked_at: "2026-08-25T00:00:00.000Z",
resolved_at: null,
idempotent: false,
}),
});
const result = await persistServerOwnedFocus({
accounting: accounting.client,
userId: USER_ID,
caseId: CASE_ID,
activeFocus: null,
decisionReceipt: {
inference_state: buildInferenceState({
range_start: "05:00",
range_end: "05:20",
candidates: [
{ id: "05:00", time: "05:00", relative_support: 10 },
{ id: "05:20", time: "05:20", relative_support: 10 },
],
events: [],
probes: [{
id: "probe:education.2016",
semantic_key: "education.2016",
candidate_split_hash: "education:2016",
domain: "education",
year: 2016,
question: "发挥失常",
candidate_ids: [],
expected_outcomes: [],
information_gain: 0,
source: "known_event_quality",
}, {
id: "contrast:varga.d24.05:00/05:06|05:07",
semantic_key: "varga.d24.05:00/05:06|05:07",
candidate_split_hash: "varga.d24.05:00/05:06|05:07",
domain: "education",
year: 0,
question: "学业盘候选差异",
candidate_ids: ["05:00", "05:06", "05:07"],
expected_outcomes: [
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] },
{ answer_class: "no", supports: ["05:06", "05:07"], conflicts: ["05:00"] },
],
information_gain: 0.16,
source: "varga_contrast",
}],
}),
},
followup,
});
assert.equal(result.status, "created");
assert.ok(result.focus);
const schema = result.focus.expectedAnswerSchema;
assert.equal(schema.semantic_key, "varga.d24.05:00/05:06|05:07");
assert.notEqual(schema.probe_id, "probe:education.2016");
assert.match(String(schema.probe_id), /varga\.d24/);
});