fix(rectification): keep candidate state coherent
This commit is contained in:
@@ -23,7 +23,6 @@ import {
|
||||
import {
|
||||
CASE_ID,
|
||||
CANDIDATE_ID,
|
||||
EVIDENCE_ID,
|
||||
FOCUS_ID,
|
||||
RESULT_ID,
|
||||
SECOND_CANDIDATE_ID,
|
||||
@@ -577,10 +576,7 @@ test("D9 differ keeps sign names for the type-table report and still forbids uni
|
||||
evidence: CLASSIC_COVERAGE,
|
||||
observations,
|
||||
});
|
||||
assert.equal(plan.next_followup?.source, "varga_observation");
|
||||
assert.equal(plan.next_followup?.ask_theme, "relationship_style");
|
||||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /自己写一句追问/);
|
||||
assert.equal(plan.next_followup?.choice_frame?.choice_mode, "A/B/C/D");
|
||||
assert.equal(plan.next_followup, null);
|
||||
assert.doesNotMatch(JSON.stringify(plan), UNIQUE_MINUTE_COPY);
|
||||
});
|
||||
|
||||
@@ -1077,6 +1073,61 @@ test("public tool surface stays at 13 and new cases bind 10.0.12", () => {
|
||||
assert.doesNotMatch(skill, /KP 政策跳过不挡提出门/);
|
||||
});
|
||||
|
||||
test("Mastra hides active candidates when the receipt range excludes one of them", () => {
|
||||
const candidates = [
|
||||
{ candidateId: CANDIDATE_ID, time: "05:00", rank: 1, relativeSupport: 20, tiedMinuteCount: 1 },
|
||||
{ candidateId: SECOND_CANDIDATE_ID, time: "05:07", rank: 2, relativeSupport: 15, tiedMinuteCount: 1 },
|
||||
];
|
||||
const inferenceState = {
|
||||
algorithm_version: "rectification-inference-v1",
|
||||
candidate_set_id: "05:00-05:07:05:00,05:07",
|
||||
revision: 2,
|
||||
phase: "discrimination",
|
||||
result_status: "credible_range",
|
||||
range_start: "05:00",
|
||||
range_end: "05:07",
|
||||
candidates: [
|
||||
{ id: CANDIDATE_ID, time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 20, posterior_score: 20, probability: 0.57, status: "active", rank: 1, strong_conflict_count: 0 },
|
||||
{ id: SECOND_CANDIDATE_ID, time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 15, posterior_score: 15, probability: 0.43, status: "active", rank: 2, strong_conflict_count: 0 },
|
||||
],
|
||||
events: [], probes: [], answered_probes: [], rounds: [], entropy: 0.98,
|
||||
representative_time: "05:00",
|
||||
credible_range: ["05:00", "05:07"],
|
||||
};
|
||||
const latest = {
|
||||
resultId: RESULT_ID,
|
||||
candidates,
|
||||
selectionAllowed: true,
|
||||
confirmationAllowed: false,
|
||||
representativeTime: "05:00",
|
||||
selectedTime: null,
|
||||
selectionKind: null,
|
||||
algorithmVersion: "rectification-v5",
|
||||
decisionReceipt: { inference_state: inferenceState },
|
||||
};
|
||||
const session = {
|
||||
methods: buildMethodFollowupPlan({ evidence: CLASSIC_COVERAGE }).methods,
|
||||
userStopped: true,
|
||||
candidateScores: candidates.map((item) => ({ time: item.time, score: item.relativeSupport })),
|
||||
holdoutValidation: "passed" as const,
|
||||
snapshotCurrent: true,
|
||||
trainingGateOpen: true,
|
||||
};
|
||||
const valid = latestResultToolProjection(latest, session);
|
||||
assert.deepEqual((valid.candidates as typeof candidates).map((item) => item.time), ["05:00", "05:07"]);
|
||||
assert.equal(valid.selection_allowed, true);
|
||||
|
||||
const invalid = latestResultToolProjection({
|
||||
...latest,
|
||||
decisionReceipt: {
|
||||
inference_state: { ...inferenceState, credible_range: ["05:00", "05:00"] },
|
||||
},
|
||||
}, session);
|
||||
assert.deepEqual(invalid.candidates, []);
|
||||
assert.equal(invalid.representative_time, null);
|
||||
assert.equal(invalid.selection_allowed, false);
|
||||
});
|
||||
|
||||
test("family then occupation then horary follow the method plan without appearance or marks", () => {
|
||||
const afterFamily = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
@@ -1295,6 +1346,34 @@ test("accepted representative time without remaining probes hands off to consult
|
||||
assert.doesNotMatch(JSON.stringify({ plan, action }), UNIQUE_MINUTE_COPY);
|
||||
});
|
||||
|
||||
test("confirmed relationship evidence skips generic D9 followups unless a real probe is bound", () => {
|
||||
const evidence = CLASSIC_COVERAGE;
|
||||
const precision = buildMethodFollowupPlan({ evidence, precisionStage: "d9_refine" });
|
||||
const varga = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
observations: [{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" }],
|
||||
});
|
||||
assert.notEqual(precision.next_followup?.domain, "relationship");
|
||||
assert.notEqual(varga.next_followup?.domain, "relationship");
|
||||
|
||||
const probed = buildMethodFollowupPlan({
|
||||
evidence,
|
||||
precisionStage: "d9_refine",
|
||||
eventProbes: [{
|
||||
...CAREER_CONFLICT_PROBE,
|
||||
year: 2021,
|
||||
year_label: "2021 年前后",
|
||||
domain: "relationship",
|
||||
event_family: "关系状态或相处方式明显变化",
|
||||
semantic_key: "relationship.2021.dasha_activation",
|
||||
candidate_split_hash: "set-test:relationship:2021",
|
||||
}],
|
||||
});
|
||||
assert.equal(probed.next_followup?.domain, "relationship");
|
||||
assert.equal(probed.next_followup?.source, "event_probe");
|
||||
assert.equal(probed.next_followup?.semantic_key, "relationship.2021.dasha_activation");
|
||||
});
|
||||
|
||||
test("d9_refine after relationship still asks uncovered career first", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
@@ -1691,10 +1770,17 @@ test("coverage-complete tie with no remaining split offers a provisional range",
|
||||
assert.equal(action.id, "offer_provisional_range");
|
||||
});
|
||||
|
||||
test("没有了 is a user stop", () => {
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "没有了" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "没了" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "想不起来了" }]), true);
|
||||
test("only explicit rectification exit language is a global user stop", () => {
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "没有" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "没有了" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "想不起来了" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "结束校正" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "我不想继续了" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "直接给结果吧" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "就到这里" }]), true);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "我不是不想继续,只是想先补工作" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "不要直接给结果,我还想补一件事" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "还没到这里结束,我继续说" }]), false);
|
||||
assert.equal(latestUserStoppedCollecting([{ role: "user", text: "还有一件升学" }]), false);
|
||||
assert.equal(conversationalSessionOutcome({
|
||||
selectionAllowed: true,
|
||||
@@ -2034,7 +2120,7 @@ test("user stop with selection_allowed may offer the escape hatch", async () =>
|
||||
turns: [{
|
||||
id: TURN_ID,
|
||||
role: "user",
|
||||
text: "暂时想不到了",
|
||||
text: "结束校正",
|
||||
status: "completed",
|
||||
created_at: "2026-08-12T10:00:00.000Z",
|
||||
completed_at: "2026-08-12T10:00:05.000Z",
|
||||
@@ -2047,6 +2133,28 @@ test("user stop with selection_allowed may offer the escape hatch", async () =>
|
||||
{ candidate_id: CANDIDATE_ID, rank: 1, time: "04:48", relative_support: 58, tied_minute_count: 1 },
|
||||
{ candidate_id: SECOND_CANDIDATE_ID, rank: 2, time: "04:49", relative_support: 42, tied_minute_count: 2 },
|
||||
],
|
||||
decisionReceipt: {
|
||||
inference_state: {
|
||||
algorithm_version: "rectification-inference-v1",
|
||||
candidate_set_id: "04:48-04:49:04:48,04:49",
|
||||
revision: 1,
|
||||
phase: "discrimination",
|
||||
result_status: "credible_range",
|
||||
range_start: "04:48",
|
||||
range_end: "04:49",
|
||||
candidates: [
|
||||
{ id: CANDIDATE_ID, time: "04:48", cluster_range: ["04:48", "04:48"], prior_score: 58, posterior_score: 58, probability: 0.58, status: "active", rank: 1, strong_conflict_count: 0 },
|
||||
{ id: SECOND_CANDIDATE_ID, time: "04:49", cluster_range: ["04:49", "04:49"], prior_score: 42, posterior_score: 42, probability: 0.42, status: "active", rank: 2, strong_conflict_count: 0 },
|
||||
],
|
||||
events: [],
|
||||
probes: [],
|
||||
answered_probes: [],
|
||||
rounds: [],
|
||||
entropy: 0.98,
|
||||
representative_time: "04:48",
|
||||
credible_range: ["04:48", "04:49"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
transition_agentic_rectification_case_status: () => ({
|
||||
|
||||
Reference in New Issue
Block a user