078c1cdc0d
A spoken no neither scored nor declined coverage, so relatives never closed and free-text turns left current_question null. Prefer a same-domain yearless scoring card before an unscoreable collect, resolve explicit collect denials as declined, and persist the next followup after an idle agent turn. Co-authored-by: Cursor <cursoragent@cursor.com>
1521 lines
54 KiB
TypeScript
1521 lines
54 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
buildChoiceFrame,
|
|
choiceCardUserMessage,
|
|
HOLDOUT_MESSAGE_PREFIX,
|
|
isHoldoutVerificationQuote,
|
|
lifePeriodLabel,
|
|
preferConcreteChoicePrompt,
|
|
mergeChoiceCard,
|
|
parseAgentChoiceCopy,
|
|
parseRectificationChoiceCard,
|
|
serverOwnedChoiceCopy,
|
|
} from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
|
import { buildMethodFollowupPlan, projectRectificationChoiceCard } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
|
import { choiceCardFromCaseDossier } from "../src/lib/rectification-agentic/v9/interview-state.ts";
|
|
import { QUALITY_STYLE_OPTIONS } from "../src/lib/rectification-agentic/v9/probe-question-contract.ts";
|
|
import type { DiscriminatingEventProbe } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
|
|
|
|
const DYNAMIC_STYLE_OPTIONS = [
|
|
{ label: "明确发生且时间吻合", answer_class: "yes" },
|
|
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
|
|
{ label: "明确没有发生", answer_class: "no" },
|
|
{ label: "这段记不清楚", answer_class: "unsure" },
|
|
] as const;
|
|
|
|
const SAMPLE_COPY = {
|
|
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: DYNAMIC_STYLE_OPTIONS.map((option, index) => ({
|
|
key: (["A", "B", "C", "D"] as const)[index]!,
|
|
label: option.label,
|
|
answer_class: option.answer_class,
|
|
})),
|
|
} as const;
|
|
|
|
const FOCUS_ID = "abababab-abab-4bab-8bab-abababababab";
|
|
|
|
const MOVE_PROBE: DiscriminatingEventProbe = {
|
|
year: 2016,
|
|
year_label: "2016 年前后",
|
|
domain: "relocation",
|
|
event_family: "搬家、离乡或长期异地",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2016 年前后。事件家族:搬家、离乡或长期异地。请写成一句自然语言是/否题。不得改年份。",
|
|
role: "distinguish",
|
|
information_gain: 0.4,
|
|
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"] },
|
|
],
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
};
|
|
|
|
const IDENTITY_PROBE: DiscriminatingEventProbe = {
|
|
...MOVE_PROBE,
|
|
semantic_key: "relocation.2016",
|
|
candidate_split_hash: "relocation.2016:05:00|05:20",
|
|
};
|
|
|
|
function identityCard(schemaOverrides: Record<string, unknown> = {}) {
|
|
return projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
eventProbes: [IDENTITY_PROBE],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
questionId: "persisted-question-id",
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relocation",
|
|
targetKind: "relocation_change",
|
|
expectedAnswerSchema: {
|
|
choice: SAMPLE_COPY,
|
|
semantic_key: IDENTITY_PROBE.semantic_key,
|
|
candidate_split_hash: IDENTITY_PROBE.candidate_split_hash,
|
|
...schemaOverrides,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
test("choice frames ask one biographical event from a server probe, not competing charts", () => {
|
|
const frame = buildChoiceFrame(
|
|
{
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
},
|
|
{
|
|
observations: [
|
|
{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" },
|
|
{ layer: "d10", candidates_differ: true, ask_theme: "career_style" },
|
|
{ layer: "d4", candidates_differ: true, ask_theme: "home_change" },
|
|
],
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [MOVE_PROBE],
|
|
},
|
|
);
|
|
assert.ok(frame);
|
|
assert.equal(frame.period, "2016 年前后");
|
|
assert.match(frame.why, /年份锁定/);
|
|
assert.match(frame.why, /请写成/);
|
|
assert.doesNotMatch(frame.why, /^2016 年前后,有没有明显/);
|
|
assert.equal(frame.option_a_hint, DYNAMIC_STYLE_OPTIONS[0].label);
|
|
assert.equal(frame.option_b_hint, DYNAMIC_STYLE_OPTIONS[1].label);
|
|
assert.doesNotMatch(frame.option_a_hint + frame.option_b_hint, /更像哪一件|可能性/);
|
|
assert.doesNotMatch(frame.why, /更像哪一件/);
|
|
assert.equal(/外貌|疤痕|胎记/.test(frame.option_a_hint + frame.option_b_hint), false);
|
|
assert.equal(frame.scoring, true);
|
|
assert.equal(mergeChoiceCard(frame, null), null);
|
|
});
|
|
|
|
test("choice frames keep the engine month lock instead of collapsing to a year", () => {
|
|
const frame = buildChoiceFrame(
|
|
{
|
|
method_id: "d10_career",
|
|
ask_theme: "dated_event",
|
|
domain: "career",
|
|
user_prompt_hint: "unused",
|
|
},
|
|
{
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [{
|
|
...MOVE_PROBE,
|
|
year: 2018,
|
|
month: 3,
|
|
year_label: "2018 年 3 月前后",
|
|
domain: "career",
|
|
event_family: "入职、升职或职责明显加重",
|
|
source: "dasha_boundary",
|
|
user_meaning: "时间范围锁定 2018 年 3 月前后;领域锁定 career。",
|
|
semantic_key: "career.2018.03.dasha_boundary",
|
|
}],
|
|
},
|
|
);
|
|
assert.ok(frame);
|
|
assert.equal(frame.period, "2018 年 3 月前后");
|
|
assert.match(frame.prompt, /2018 年 3 月前后/);
|
|
assert.match(frame.why, /2018 年 3 月前后/);
|
|
assert.doesNotMatch(frame.period, /^2018 年前后$/);
|
|
});
|
|
|
|
test("server-owned card names the event family, not a generic 有没有这件事", () => {
|
|
const frame = buildChoiceFrame(
|
|
{
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
},
|
|
{ probes: [MOVE_PROBE] },
|
|
);
|
|
assert.ok(frame);
|
|
if (!frame) assert.fail("expected dynamic choice frame");
|
|
const copy = serverOwnedChoiceCopy(frame!);
|
|
assert.ok(copy);
|
|
assert.match(copy.prompt, /搬家、离乡或长期异地/);
|
|
assert.doesNotMatch(copy.prompt, /有没有这件事/);
|
|
assert.doesNotMatch(copy.prompt, /有没有明显/);
|
|
});
|
|
|
|
test("the visible card uses Agent copy for A/B/C/D, not the server hint", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, {
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [MOVE_PROBE],
|
|
});
|
|
assert.ok(frame);
|
|
const card = mergeChoiceCard(frame, SAMPLE_COPY);
|
|
assert.equal(card?.prompt, SAMPLE_COPY.prompt);
|
|
assert.equal(card?.options[0]?.label, SAMPLE_COPY.option_a);
|
|
assert.equal(card?.options[1]?.label, SAMPLE_COPY.option_b);
|
|
assert.equal(card?.options[2]?.label, SAMPLE_COPY.option_c);
|
|
assert.equal(card?.options[3]?.label, SAMPLE_COPY.option_d);
|
|
assert.equal(card?.stop_label, frame.stop_label);
|
|
});
|
|
|
|
test("holdout messages are prefixed and do not count as scoring quotes", () => {
|
|
const frame = buildChoiceFrame(
|
|
{
|
|
method_id: "oos_blind",
|
|
ask_theme: "oos_blind",
|
|
domain: "family",
|
|
user_prompt_hint: "unused",
|
|
},
|
|
{
|
|
scoring: false,
|
|
probes: [{
|
|
...MOVE_PROBE,
|
|
domain: "family",
|
|
event_family: "家人结婚、添丁或住院等重要变动",
|
|
user_meaning: "盘外核对:只核对家人事件,不计入候选评分。",
|
|
}],
|
|
},
|
|
);
|
|
assert.ok(frame);
|
|
const card = mergeChoiceCard(frame, {
|
|
...SAMPLE_COPY,
|
|
prompt: "家人这条线还没用过,有没有一件记得大概年份的变化?",
|
|
});
|
|
assert.ok(card);
|
|
const message = choiceCardUserMessage(card, "A");
|
|
assert.match(message, new RegExp(HOLDOUT_MESSAGE_PREFIX));
|
|
assert.equal(isHoldoutVerificationQuote(message), true);
|
|
assert.equal(isHoldoutVerificationQuote("A. 认真关系进入或结婚"), false);
|
|
assert.equal(card.scoring, false);
|
|
assert.match(frame.why, /盘外核对/);
|
|
});
|
|
|
|
test("reverse-verify after adopt scores the predicted event and invites 改选 on mismatch", () => {
|
|
const frame = buildChoiceFrame(
|
|
{
|
|
method_id: "reverse_verify",
|
|
ask_theme: "career_style",
|
|
domain: "career",
|
|
user_prompt_hint: "unused",
|
|
},
|
|
{
|
|
probes: [{
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "career",
|
|
event_family: "入职、升职或职责明显加重",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "按当前采用时间核对 2018 年前后的事业变化;若不吻合可以改选。",
|
|
role: "reverse_verify",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}],
|
|
},
|
|
);
|
|
assert.ok(frame);
|
|
assert.equal(frame.period, "2018 年前后");
|
|
assert.equal(frame.scoring, true);
|
|
assert.match(frame.why, /按当前采用时间核对/);
|
|
assert.match(frame.why, /改选/);
|
|
assert.doesNotMatch(frame.why, /盘外核对/);
|
|
const card = mergeChoiceCard(frame, SAMPLE_COPY);
|
|
assert.ok(card);
|
|
assert.equal(card.scoring, true);
|
|
assert.doesNotMatch(choiceCardUserMessage(card, "A"), new RegExp(HOLDOUT_MESSAGE_PREFIX));
|
|
});
|
|
|
|
test("life period only reuses years from the same domain", () => {
|
|
assert.equal(lifePeriodLabel([
|
|
{ status: "confirmed", datePrecision: "unknown", occurredFrom: null, occurredTo: null },
|
|
]), "那段时间");
|
|
assert.equal(lifePeriodLabel([
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
{ status: "confirmed", domain: "career", datePrecision: "year", occurredFrom: "2020-06-01", occurredTo: null },
|
|
], "education"), "2016 年前后");
|
|
assert.equal(lifePeriodLabel([
|
|
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
|
], "relocation"), "那段时间");
|
|
assert.equal(lifePeriodLabel([
|
|
{ status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: null },
|
|
], "career"), "2020 年 4 月前后");
|
|
assert.equal(lifePeriodLabel([
|
|
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: null },
|
|
], "relationship"), "2024 年 8 月前后");
|
|
});
|
|
|
|
test("choice frame requires a real non-empty event family", () => {
|
|
const missingFamily = { ...MOVE_PROBE, event_family: undefined } as unknown as DiscriminatingEventProbe;
|
|
assert.equal(buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, { probes: [missingFamily] }), null);
|
|
assert.equal(buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, { probes: [{ ...MOVE_PROBE, event_family: " " }] }), null);
|
|
});
|
|
|
|
test("yearless D24 quality cards do not borrow a recorded education year", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d5_education",
|
|
ask_theme: "education_style",
|
|
domain: "education",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "event_quality",
|
|
semantic_key: "varga.d24.05:00/05:07",
|
|
style_options: QUALITY_STYLE_OPTIONS,
|
|
}, {
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [{
|
|
...MOVE_PROBE,
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "education",
|
|
event_family: "学业或考试发挥失常、压力特别大",
|
|
choice_kind: "event_quality",
|
|
semantic_key: "varga.d24.05:00/05:07",
|
|
style_options: QUALITY_STYLE_OPTIONS,
|
|
}],
|
|
});
|
|
assert.ok(frame);
|
|
assert.doesNotMatch(frame.period, /2016/);
|
|
assert.doesNotMatch(frame.prompt, /2016/);
|
|
});
|
|
|
|
test("yearless family cards attach a scoring frame without borrowing another domain's year", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "relatives",
|
|
ask_theme: "family_event",
|
|
domain: "family",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "existence",
|
|
semantic_key: "varga.d12.05:00/05:07",
|
|
}, {
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [{
|
|
...MOVE_PROBE,
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "family",
|
|
event_family: "家人结婚、添丁或住院",
|
|
choice_kind: "existence",
|
|
semantic_key: "varga.d12.05:00/05:07",
|
|
}],
|
|
});
|
|
assert.ok(frame);
|
|
assert.doesNotMatch(frame.period, /2016/);
|
|
assert.doesNotMatch(frame.prompt, /2016/);
|
|
assert.match(frame.neither_label + frame.option_a_hint + frame.option_b_hint, /明确没有发生|家人/);
|
|
});
|
|
|
|
test("yearless family cards do not borrow a recorded family year", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "relatives",
|
|
ask_theme: "family_event",
|
|
domain: "family",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "existence",
|
|
semantic_key: "varga.d12.05:00/05:07",
|
|
}, {
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
probes: [{
|
|
...MOVE_PROBE,
|
|
year: 0,
|
|
year_label: "当前这几个候选",
|
|
domain: "family",
|
|
event_family: "家人结婚、添丁或住院",
|
|
choice_kind: "existence",
|
|
semantic_key: "varga.d12.05:00/05:07",
|
|
}],
|
|
});
|
|
assert.ok(frame);
|
|
assert.doesNotMatch(frame.period, /2018/);
|
|
assert.doesNotMatch(frame.prompt, /2018/);
|
|
});
|
|
|
|
test("GET upgrades a persisted placeholder stem to the recorded year lock", () => {
|
|
assert.equal(
|
|
preferConcreteChoicePrompt(
|
|
"2016 年前后,有没有学业或考试发挥失常、压力特别大的时候?",
|
|
"当前这几个候选 · 学业、考试发挥或学习压力出现明显变化",
|
|
),
|
|
"2016 年前后,有没有学业或考试发挥失常、压力特别大的时候?",
|
|
);
|
|
assert.equal(
|
|
preferConcreteChoicePrompt(
|
|
"2016 年前后,有没有学业或考试发挥失常、压力特别大的时候?",
|
|
"2016 年前后 · 学业、考试发挥或学习压力出现明显变化",
|
|
),
|
|
"2016 年前后,有没有学业或考试发挥失常、压力特别大的时候?",
|
|
);
|
|
assert.equal(
|
|
preferConcreteChoicePrompt(
|
|
"2018 年前后,有没有开始一段认真关系、分手或结婚?",
|
|
"2018 年前后,有没有认真关系进入、结束或关系观明显转变?",
|
|
),
|
|
"2018 年前后,有没有开始一段认真关系、分手或结婚?",
|
|
);
|
|
assert.equal(
|
|
preferConcreteChoicePrompt(
|
|
"2018 年前后,有没有家人结婚、添丁或住院?",
|
|
"那段时间,有没有家人相关的明显变化?",
|
|
),
|
|
"2018 年前后,有没有家人结婚、添丁或住院?",
|
|
);
|
|
});
|
|
|
|
test("choice frame completes existence options and rejects illegal or non-renderable varga styles", () => {
|
|
const build = (style_options: DiscriminatingEventProbe["style_options"], extra: Partial<DiscriminatingEventProbe> = {}) => buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, { probes: [{ ...MOVE_PROBE, style_options, ...extra }] });
|
|
|
|
assert.ok(build(DYNAMIC_STYLE_OPTIONS.slice(0, 3)));
|
|
assert.ok(build(undefined));
|
|
assert.equal(build([
|
|
{ label: "巨蟹相处主动热情", answer_class: "yes" },
|
|
], { choice_kind: "varga_style" }), null);
|
|
});
|
|
|
|
test("python-shaped existence probe without style_options still builds a four-option card", () => {
|
|
const { style_options: _unused, ...pythonProbe } = MOVE_PROBE;
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, { probes: [pythonProbe] });
|
|
assert.ok(frame);
|
|
assert.equal(frame.option_a_answer_class, "yes");
|
|
assert.equal(frame.option_b_answer_class, "weak_yes");
|
|
assert.equal(frame.option_c_answer_class, "no");
|
|
assert.equal(frame.option_d_answer_class, "unsure");
|
|
assert.equal(frame.unsure_label, "这段记不清楚");
|
|
});
|
|
|
|
test("style options without a real event probe do not generate a card", () => {
|
|
assert.equal(buildChoiceFrame({
|
|
method_id: "d10_career",
|
|
ask_theme: "career_style",
|
|
domain: "career",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "varga_style",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}), null);
|
|
});
|
|
|
|
test("agent choice copy rejects appearance, scars, clock times, and incomplete C/D", () => {
|
|
assert.equal(parseAgentChoiceCopy({
|
|
choice: { prompt: "你长得更像哪一种?", option_a: "外貌偏圆", option_b: "有疤痕", option_c: "都没有", option_d: "不记得" },
|
|
}), null);
|
|
assert.equal(parseAgentChoiceCopy({
|
|
...SAMPLE_COPY,
|
|
prompt: "08:12 换升时发生了什么?",
|
|
}), null);
|
|
assert.equal(parseAgentChoiceCopy({
|
|
prompt: SAMPLE_COPY.prompt,
|
|
option_a: SAMPLE_COPY.option_a,
|
|
option_b: SAMPLE_COPY.option_b,
|
|
}), null);
|
|
assert.equal(parseAgentChoiceCopy({
|
|
...SAMPLE_COPY,
|
|
option_c: SAMPLE_COPY.option_a,
|
|
}), null);
|
|
assert.ok(parseAgentChoiceCopy(SAMPLE_COPY));
|
|
});
|
|
|
|
test("GET card without a persisted focus UUID is not tappable", () => {
|
|
const distinguish = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
eventProbes: [MOVE_PROBE],
|
|
candidateScores: [
|
|
{ time: "05:00", score: 34 },
|
|
{ time: "05:20", score: 33 },
|
|
],
|
|
activeFocus: {
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relocation",
|
|
targetKind: "home_change",
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.equal(distinguish, null);
|
|
});
|
|
|
|
test("GET hides a legacy known-event quality focus and resumes evidence collection", () => {
|
|
const card = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-09-01",
|
|
occurredTo: null,
|
|
}],
|
|
eventClarificationProbes: [{
|
|
year: 2016,
|
|
year_label: "2016 年前后",
|
|
domain: "education",
|
|
event_family: "升学结果或学习环境出现明显变化",
|
|
source: "known_event_quality",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "已知事件质量",
|
|
role: "clarify",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
phase: "event_clarification",
|
|
choice_kind: "event_quality",
|
|
semantic_key: "education.2016",
|
|
candidate_split_hash: "education:2016",
|
|
candidate_ids: [],
|
|
expected_outcomes: [],
|
|
information_gain: 0,
|
|
}],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "clarify_event",
|
|
targetDomain: "education",
|
|
targetKind: null,
|
|
expectedAnswerSchema: {
|
|
choice: SAMPLE_COPY,
|
|
probe_id: "probe:education.2016",
|
|
semantic_key: "education.2016",
|
|
candidate_split_hash: "education:2016",
|
|
scoring: true,
|
|
choice_kind: "event_quality",
|
|
},
|
|
},
|
|
});
|
|
assert.equal(card, null);
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "month",
|
|
occurredFrom: "2016-09-01",
|
|
occurredTo: null,
|
|
}],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "clarify_event",
|
|
targetDomain: "education",
|
|
targetKind: null,
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.equal(plan.next_followup?.method_id, "d9_relationship");
|
|
assert.equal(plan.next_followup?.choice_frame, null);
|
|
});
|
|
|
|
test("GET card stays hidden on an empty ledger even if the Agent already wrote choice copy", () => {
|
|
const withoutCopy = projectRectificationChoiceCard({ evidence: [] });
|
|
assert.equal(withoutCopy, null);
|
|
const leftoverOpening = projectRectificationChoiceCard({
|
|
evidence: [],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "collect_method_evidence",
|
|
targetDomain: null,
|
|
targetKind: null,
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.equal(leftoverOpening, null);
|
|
const distinguish = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
}],
|
|
observations: [{ layer: "d9", candidates_differ: true, ask_theme: "relationship_style" }],
|
|
eventProbes: [{
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "relationship",
|
|
event_family: "认真关系进入、结束或关系观明显转变",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2018 年前后。事件家族:认真关系进入、结束或关系观明显转变。请写成一句自然语言是/否题。不得改年份。",
|
|
role: "distinguish",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
information_gain: 0.4,
|
|
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"] },
|
|
],
|
|
}],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relationship",
|
|
targetKind: "relationship_change",
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.equal(distinguish?.prompt, SAMPLE_COPY.prompt);
|
|
assert.equal(distinguish?.options[2]?.label, SAMPLE_COPY.option_c);
|
|
assert.ok(parseRectificationChoiceCard(distinguish));
|
|
});
|
|
|
|
test("opening follow-up asks for a dated event in natural language, not a choice card", () => {
|
|
const plan = buildMethodFollowupPlan({ evidence: [] });
|
|
assert.equal(plan.next_followup?.method_id, "dasha_events");
|
|
assert.equal(plan.next_followup?.choice_frame, null);
|
|
assert.doesNotMatch(plan.next_followup?.user_prompt_hint ?? "", /A\/B\/C\/D/);
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /自然语言/);
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /不要调用 set-focus/);
|
|
});
|
|
|
|
test("distinguish follow-up copy forbids competing-chart ranking", () => {
|
|
const plan = buildMethodFollowupPlan({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "horary",
|
|
datePrecision: "day",
|
|
occurredFrom: "2024-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
precisionStage: "d4_refine",
|
|
eventProbes: [MOVE_PROBE],
|
|
birthDate: "1997-08-08",
|
|
});
|
|
assert.equal(plan.next_followup?.choice_frame?.period, "2016 年前后");
|
|
assert.doesNotMatch(plan.next_followup?.choice_frame?.option_a_hint ?? "", /更像哪一件|两套盘|可能性/);
|
|
assert.doesNotMatch(plan.next_followup?.choice_frame?.option_b_hint ?? "", /更像哪一件|两套盘|可能性/);
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /自己写一句追问/);
|
|
assert.match(plan.next_followup?.user_prompt_hint ?? "", /不得发明年份/);
|
|
});
|
|
|
|
test("GET A/B card stays visible after coverage when candidates remain tied", () => {
|
|
const card = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "draft",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
eventKind: "occupation_note",
|
|
}],
|
|
eventProbes: [MOVE_PROBE],
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
candidateScores: [
|
|
{ time: "05:00", score: 34 },
|
|
{ time: "05:01", score: 33 },
|
|
{ time: "05:02", score: 33 },
|
|
],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relocation",
|
|
targetKind: "home_change",
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.ok(card);
|
|
assert.equal(card.prompt, SAMPLE_COPY.prompt);
|
|
});
|
|
|
|
test("GET A/B card stays hidden once candidates are separated and ready to offer", () => {
|
|
const card = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "draft",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
eventKind: "occupation_note",
|
|
}],
|
|
eventProbes: [MOVE_PROBE],
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
holdoutValidation: "passed",
|
|
candidateScores: [
|
|
{ time: "05:00", score: 62 },
|
|
{ time: "05:01", score: 22 },
|
|
{ time: "05:02", score: 16 },
|
|
],
|
|
});
|
|
assert.equal(card, null);
|
|
});
|
|
|
|
test("GET reverse-verify card still appears after a time is accepted", () => {
|
|
const card = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "day",
|
|
occurredFrom: "2024-04-07",
|
|
occurredTo: null,
|
|
}],
|
|
accepted: true,
|
|
sessionOutcome: "adopt_representative",
|
|
selectionAllowed: true,
|
|
proposeAllowed: true,
|
|
eventProbes: [{
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "career",
|
|
event_family: "入职、升职或职责明显加重",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2018 年前后。请写成一句自然语言,问是否入职或职责加重。",
|
|
role: "reverse_verify",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "reverse_verify",
|
|
targetDomain: "career",
|
|
targetKind: "career_change",
|
|
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
|
},
|
|
});
|
|
assert.equal(card?.prompt, SAMPLE_COPY.prompt);
|
|
assert.ok(parseRectificationChoiceCard(card));
|
|
});
|
|
|
|
test("GET choice_card stays after coverage when remaining minutes still split on D24", () => {
|
|
const card = choiceCardFromCaseDossier({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
summary: "2016 年大学入学",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "draft",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
eventKind: "occupation_note",
|
|
summary: "医疗器械算法,第三个(技术执行)",
|
|
}],
|
|
conversationSummary: {
|
|
activeFocus: {
|
|
id: "abababab-abab-4bab-8bab-abababababab",
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "education",
|
|
targetKind: "education_milestone",
|
|
expectedAnswerSchema: {
|
|
choice: SAMPLE_COPY,
|
|
probe_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",
|
|
},
|
|
},
|
|
declinedSkippedTopics: [],
|
|
},
|
|
latestResult: {
|
|
resultId: "55555555-5555-4555-8555-555555555555",
|
|
selectionAllowed: true,
|
|
candidates: [
|
|
{ time: "05:00", relativeSupport: 34 },
|
|
{ time: "05:06", relativeSupport: 33 },
|
|
{ time: "05:07", relativeSupport: 33 },
|
|
],
|
|
decisionReceipt: {
|
|
propose_allowed: true,
|
|
discriminating_event_probes: [{
|
|
year: 2017,
|
|
year_label: "2017 年前后",
|
|
domain: "education",
|
|
event_family: "学习方向或升学环境出现明显变化",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2017 年前后,核对学习方向或升学环境变化。",
|
|
role: "distinguish",
|
|
phase: "candidate_discriminator",
|
|
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",
|
|
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"] },
|
|
],
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}],
|
|
window_scan: {
|
|
scanned: true,
|
|
d9_lagna_count: 1,
|
|
d10_lagna_count: 3,
|
|
d10_candidates_differ: true,
|
|
d10_sign_names: ["巨蟹座", "狮子座", "处女座"],
|
|
d24_candidates_differ: true,
|
|
transitions: [
|
|
{ layer: "d10", at: "05:00" },
|
|
{ layer: "d10", at: "05:15" },
|
|
{ layer: "d24", at: "05:00" },
|
|
{ layer: "d24", at: "05:06" },
|
|
],
|
|
},
|
|
inference_state: {
|
|
algorithm_version: "rectification-inference-v1",
|
|
candidate_set_id: "05:00-05:07:05:00,05:06,05:07",
|
|
revision: 1,
|
|
phase: "discrimination",
|
|
result_status: "discriminating",
|
|
range_start: "05:00",
|
|
range_end: "05:07",
|
|
candidates: [
|
|
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 34, posterior_score: 34, probability: 0.34, status: "active", rank: 1, strong_conflict_count: 0 },
|
|
{ id: "05:06", time: "05:06", cluster_range: ["05:06", "05:06"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 2, strong_conflict_count: 0 },
|
|
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 3, strong_conflict_count: 0 },
|
|
],
|
|
events: [],
|
|
answered_probes: [{
|
|
probe_id: "probe:education.2016",
|
|
semantic_key: "education.2016",
|
|
candidate_split_hash: "education.2016",
|
|
answer_class: "yes",
|
|
classified_from: "evidence",
|
|
}],
|
|
probes: [{
|
|
id: "probe:education.2016",
|
|
semantic_key: "education.2016",
|
|
candidate_split_hash: "education.2016",
|
|
domain: "education",
|
|
year: 2016,
|
|
question: "2016 年是否入学?",
|
|
candidate_ids: ["05:00", "05:06", "05:07"],
|
|
information_gain: 0,
|
|
source: "known_event_quality",
|
|
expected_outcomes: [],
|
|
}],
|
|
rounds: [],
|
|
entropy: 1,
|
|
representative_time: "05:00",
|
|
credible_range: ["05:00", "05:07"],
|
|
},
|
|
},
|
|
},
|
|
case: { acceptedTime: null },
|
|
turns: [],
|
|
});
|
|
assert.ok(card);
|
|
assert.equal(card.probe_id, "contrast:varga.d24.05:00/05:06|05:07");
|
|
assert.equal(card.prompt, SAMPLE_COPY.prompt);
|
|
});
|
|
|
|
test("GET shows an engine-dated career dasha instead of locking D24 to the recorded education year", () => {
|
|
const careerKey = "career.2012.11.dasha_boundary";
|
|
const careerCopy = {
|
|
prompt: "2012 年 11 月前后,有没有入职、升职或职责明显加重?",
|
|
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: DYNAMIC_STYLE_OPTIONS.map((option, index) => ({
|
|
key: (["A", "B", "C", "D"] as const)[index]!,
|
|
label: option.label,
|
|
answer_class: option.answer_class,
|
|
})),
|
|
};
|
|
const inferenceCandidates = [
|
|
{ id: "04:47", time: "04:47", cluster_range: ["04:47", "04:47"] as const, prior_score: 5, posterior_score: 5, probability: 0.05, status: "active", rank: 6, strong_conflict_count: 0 },
|
|
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"] as const, prior_score: 21, posterior_score: 21, probability: 0.21, status: "active", rank: 1, strong_conflict_count: 0 },
|
|
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"] as const, prior_score: 18, posterior_score: 18, probability: 0.18, status: "active", rank: 2, strong_conflict_count: 0 },
|
|
{ id: "05:12", time: "05:12", cluster_range: ["05:12", "05:12"] as const, prior_score: 18, posterior_score: 18, probability: 0.18, status: "active", rank: 3, strong_conflict_count: 0 },
|
|
{ id: "05:14", time: "05:14", cluster_range: ["05:14", "05:14"] as const, prior_score: 17, posterior_score: 17, probability: 0.17, status: "active", rank: 4, strong_conflict_count: 0 },
|
|
{ id: "05:15", time: "05:15", cluster_range: ["05:15", "05:15"] as const, prior_score: 3, posterior_score: 3, probability: 0.03, status: "active", rank: 9, strong_conflict_count: 0 },
|
|
];
|
|
const candidateSetId = "04:45-05:15:04:47,05:00,05:07,05:12,05:14,05:15";
|
|
const d24Key = "varga.d24.04:47/04:51|04:53/04:59/05:00/05:07|05:12/05:14|05:15";
|
|
const d24Split = `${candidateSetId}:${d24Key}`;
|
|
const d24Outcomes = [
|
|
{ answer_class: "yes", supports: ["04:47"], conflicts: ["05:00", "05:07", "05:12", "05:14", "05:15"] },
|
|
{ answer_class: "weak_yes", supports: ["05:00", "05:07"], conflicts: ["04:47", "05:12", "05:14", "05:15"] },
|
|
{ answer_class: "no", supports: ["05:12", "05:14"], conflicts: ["04:47", "05:00", "05:07", "05:15"] },
|
|
{ answer_class: "unsure", supports: [], conflicts: [] },
|
|
];
|
|
const card = choiceCardFromCaseDossier({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: "2016-01-01",
|
|
eventKind: "education_start",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "month",
|
|
occurredFrom: "2020-04-01",
|
|
occurredTo: "2020-04-01",
|
|
eventKind: "career_entry",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "month",
|
|
occurredFrom: "2020-10-01",
|
|
occurredTo: "2020-10-01",
|
|
eventKind: "career_exit",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "day",
|
|
occurredFrom: "2024-04-07",
|
|
occurredTo: null,
|
|
eventKind: "career_entry",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "month",
|
|
occurredFrom: "2024-05-01",
|
|
occurredTo: null,
|
|
eventKind: "relationship_start",
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "day",
|
|
occurredFrom: "2024-08-08",
|
|
occurredTo: null,
|
|
eventKind: "relationship_end",
|
|
}],
|
|
conversationSummary: {
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
questionId: `probe:${careerKey}`,
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "career",
|
|
targetKind: null,
|
|
expectedAnswerSchema: {
|
|
choice: careerCopy,
|
|
probe_id: `event:${careerKey}`,
|
|
semantic_key: careerKey,
|
|
candidate_split_hash: "career.2012.11",
|
|
choice_kind: "existence",
|
|
},
|
|
},
|
|
declinedSkippedTopics: [],
|
|
},
|
|
latestResult: {
|
|
resultId: "result-d24-open",
|
|
selectionAllowed: true,
|
|
confirmationAllowed: false,
|
|
candidates: inferenceCandidates.map((item) => ({
|
|
time: item.time,
|
|
rank: item.rank,
|
|
relativeSupport: item.posterior_score,
|
|
})),
|
|
decisionReceipt: {
|
|
discriminating_event_probes: [{
|
|
year: 2012,
|
|
year_label: "2012 年 11 月前后",
|
|
domain: "career",
|
|
event_family: "入职、升职或职责明显加重",
|
|
source: "dasha_boundary",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "时间范围锁定 2012 年 11 月前后。",
|
|
role: "distinguish",
|
|
phase: "candidate_discriminator",
|
|
information_gain: 1.3356,
|
|
semantic_key: careerKey,
|
|
candidate_split_hash: "career.2012.11",
|
|
candidate_ids: ["04:47", "05:00", "05:15"],
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["04:47"], conflicts: ["05:00", "05:15"] },
|
|
{ answer_class: "weak_yes", supports: ["04:47"], conflicts: ["05:00", "05:15"] },
|
|
{ answer_class: "no", supports: ["05:00", "05:15"], conflicts: ["04:47"] },
|
|
{ answer_class: "unsure", supports: [], conflicts: [] },
|
|
],
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}],
|
|
inference_state: {
|
|
algorithm_version: "rectification-inference-v1",
|
|
candidate_set_id: candidateSetId,
|
|
revision: 1,
|
|
phase: "discrimination",
|
|
result_status: "discriminating",
|
|
range_start: "04:45",
|
|
range_end: "05:15",
|
|
candidates: inferenceCandidates,
|
|
events: [],
|
|
probes: [{
|
|
id: `contrast:${d24Key}`,
|
|
year: 0,
|
|
domain: "education",
|
|
source: "varga_contrast",
|
|
question: "引擎给出的区分机会绑定 D24。",
|
|
semantic_key: d24Key,
|
|
candidate_ids: inferenceCandidates.map((item) => item.time),
|
|
information_gain: 2.503258334775646,
|
|
expected_outcomes: d24Outcomes,
|
|
candidate_split_hash: d24Split,
|
|
}],
|
|
answered_probes: [],
|
|
rounds: [],
|
|
entropy: 2.0,
|
|
representative_time: "05:00",
|
|
credible_range: ["05:00", "05:14"],
|
|
},
|
|
},
|
|
},
|
|
case: { acceptedTime: null, status: "collecting_evidence" },
|
|
turns: [],
|
|
});
|
|
assert.ok(card);
|
|
assert.equal(card.focus_id, FOCUS_ID);
|
|
assert.match(card.prompt, /2012 年 11 月前后,有没有入职、升职或职责明显加重?/);
|
|
assert.doesNotMatch(card.prompt, /2016 年前后/);
|
|
assert.doesNotMatch(card.prompt, /当前这几个候选/);
|
|
assert.doesNotMatch(card.prompt, / · /);
|
|
assert.equal(card.options.length, 4);
|
|
assert.equal(card.options.every((option) => option.role === "primary"), true);
|
|
});
|
|
|
|
test("GET choice_card stays hidden without a persisted focus after 没有了", () => {
|
|
const card = choiceCardFromCaseDossier({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2018-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "family",
|
|
datePrecision: "year",
|
|
occurredFrom: "2020-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "draft",
|
|
domain: "occupation",
|
|
datePrecision: "unknown",
|
|
occurredFrom: null,
|
|
occurredTo: null,
|
|
eventKind: "occupation_note",
|
|
}],
|
|
conversationSummary: {
|
|
activeFocus: null,
|
|
declinedSkippedTopics: [],
|
|
},
|
|
latestResult: {
|
|
selectionAllowed: true,
|
|
candidates: [
|
|
{ time: "05:00", relativeSupport: 34 },
|
|
{ time: "05:06", relativeSupport: 33 },
|
|
{ time: "05:07", relativeSupport: 33 },
|
|
],
|
|
decisionReceipt: { propose_allowed: true },
|
|
},
|
|
case: { acceptedTime: null },
|
|
turns: [{ role: "user", text: "没有了" }],
|
|
});
|
|
assert.equal(card, null);
|
|
});
|
|
|
|
test("dynamic option labels canonicalize to yes/weak_yes/no/unsure order", () => {
|
|
const styleOptions = [
|
|
{ label: "明确没有发生", answer_class: "no" },
|
|
{ label: "这段记不清楚", answer_class: "unsure" },
|
|
{ label: "明确发生且时间吻合", answer_class: "yes" },
|
|
{ label: "发生过但程度较弱", answer_class: "weak_yes" },
|
|
] as const;
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "existence",
|
|
style_options: styleOptions,
|
|
}, { probes: [{ ...MOVE_PROBE, style_options: styleOptions }] });
|
|
assert.ok(frame);
|
|
const copy = serverOwnedChoiceCopy(frame);
|
|
assert.deepEqual(copy?.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" },
|
|
]);
|
|
});
|
|
|
|
test("choice card requires the persisted focus to match the discriminator identity", () => {
|
|
assert.equal(identityCard({ semantic_key: "relationship.2016" }), null);
|
|
assert.equal(identityCard({ candidate_split_hash: "different-split" }), null);
|
|
const card = identityCard();
|
|
assert.ok(card);
|
|
assert.equal(card.focus_id, FOCUS_ID);
|
|
assert.equal(card.question_id, "persisted-question-id");
|
|
});
|
|
|
|
test("GET card prompt keeps the agent's year-locked question", () => {
|
|
const fallback = {
|
|
...SAMPLE_COPY,
|
|
prompt: "2018 年前后 · 认真关系进入、结束或关系观明显转变",
|
|
};
|
|
const card = projectRectificationChoiceCard({
|
|
evidence: [{
|
|
status: "confirmed",
|
|
domain: "education",
|
|
datePrecision: "year",
|
|
occurredFrom: "2016-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "relationship",
|
|
datePrecision: "year",
|
|
occurredFrom: "2017-01-01",
|
|
occurredTo: null,
|
|
}, {
|
|
status: "confirmed",
|
|
domain: "career",
|
|
datePrecision: "year",
|
|
occurredFrom: "2019-01-01",
|
|
occurredTo: null,
|
|
}],
|
|
eventProbes: [{
|
|
year: 2018,
|
|
year_label: "2018 年前后",
|
|
domain: "relationship",
|
|
event_family: "认真关系进入、结束或关系观明显转变",
|
|
source: "dasha_activation",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2018 年前后。",
|
|
role: "reverse_verify",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
}],
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relationship",
|
|
targetKind: "relationship_change",
|
|
expectedAnswerSchema: { choice: fallback, probe_id: "relationship.2018" },
|
|
},
|
|
latestAssistantText: "记下了。2018 年前后,有没有一段认真的关系开始或结束?",
|
|
});
|
|
assert.ok(card);
|
|
assert.equal(card.prompt, "2018 年前后,有没有一段认真的关系开始或结束?");
|
|
assert.doesNotMatch(card.prompt, /有没有明显认真关系进入/);
|
|
});
|
|
|
|
test("career event_quality uses the supplied dynamic labels", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d10_career",
|
|
ask_theme: "career_style",
|
|
domain: "career",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "event_quality",
|
|
}, {
|
|
probes: [{
|
|
year: 2020,
|
|
year_label: "2020 年前后",
|
|
domain: "career",
|
|
event_family: "入职、升职或职责明显加重",
|
|
source: "known_event_quality",
|
|
tracks: ["vimshottari", "narayana"],
|
|
tracks_agree: true,
|
|
unique_minute_claim: false,
|
|
user_meaning: "年份锁定 2020 年前后。已有相关经历。请写成一句自然语言,问入职、升职或职责明显加重有没有发生过。不得改年份。",
|
|
role: "distinguish",
|
|
style_options: DYNAMIC_STYLE_OPTIONS,
|
|
choice_kind: "event_quality",
|
|
}],
|
|
});
|
|
assert.ok(frame);
|
|
const copy = serverOwnedChoiceCopy(frame);
|
|
assert.ok(copy);
|
|
assert.match(copy.prompt, /2020 年前后/);
|
|
assert.match(copy.prompt, /入职、升职或职责明显加重/);
|
|
assert.match(copy.prompt, /有没有/);
|
|
assert.doesNotMatch(copy.prompt, / · /);
|
|
assert.equal(copy.option_a, DYNAMIC_STYLE_OPTIONS[0].label);
|
|
assert.doesNotMatch(copy.option_a, /失常/);
|
|
});
|
|
|
|
test("dasha existence cards lock the event and use supplied dynamic labels", () => {
|
|
const frame = buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
choice_kind: "existence",
|
|
}, { probes: [MOVE_PROBE] });
|
|
assert.ok(frame);
|
|
const copy = serverOwnedChoiceCopy(frame);
|
|
assert.ok(copy);
|
|
assert.match(copy.prompt, /2016 年前后/);
|
|
assert.match(copy.prompt, /搬家、离乡或长期异地/);
|
|
assert.doesNotMatch(copy.prompt, /有没有明显/);
|
|
assert.equal(copy.option_a, DYNAMIC_STYLE_OPTIONS[0].label);
|
|
assert.equal(copy.option_c, DYNAMIC_STYLE_OPTIONS[2].label);
|
|
});
|
|
|
|
const D9_STALE_KEY = "varga.d9.05:00|05:06/05:07";
|
|
const CURRENT_SET_ID = "05:00-05:07:05:00,05:06,05:07";
|
|
// The probe was minted while the candidate set still held 05:05 instead of 05:06.
|
|
const STALE_SET_ID = "05:00-05:07:05:00,05:05,05:07";
|
|
const D9_STYLE_OPTIONS = [
|
|
{ sign: "天秤座", label: "和谐、美感、合作", answer_class: "yes" },
|
|
{ sign: "天蝎座", label: "深刻、占有欲、转化", answer_class: "weak_yes" },
|
|
{ sign: "射手座", label: "自由、哲学、冒险", answer_class: "no" },
|
|
{ label: "这段记不清楚", answer_class: "unsure" },
|
|
] as const;
|
|
|
|
function staleSplitDossier(schemaHash: string) {
|
|
return {
|
|
evidence: [
|
|
{ id: "e-career-a", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-04-01", occurredTo: "2020-04-01", eventKind: "career_entry" },
|
|
{ id: "e-career-b", status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2020-10-01", occurredTo: "2020-10-01", eventKind: "career_exit" },
|
|
{ id: "e-rel-end", status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-08-08", occurredTo: "2024-08-08", eventKind: "relationship_end" },
|
|
{ id: "e-rel-start", status: "confirmed", domain: "relationship", datePrecision: "month", occurredFrom: "2024-05-01", occurredTo: "2024-05-31", eventKind: "relationship_start" },
|
|
],
|
|
conversationSummary: {
|
|
activeFocus: {
|
|
id: FOCUS_ID,
|
|
questionId: `probe:${D9_STALE_KEY}`,
|
|
intent: "distinguish_candidates",
|
|
targetDomain: "relationship",
|
|
targetKind: null,
|
|
expectedAnswerSchema: {
|
|
choice: {
|
|
prompt: "2024 年前后,相处方式更接近其中一种?",
|
|
option_a: D9_STYLE_OPTIONS[0].label,
|
|
option_b: D9_STYLE_OPTIONS[1].label,
|
|
option_c: D9_STYLE_OPTIONS[2].label,
|
|
option_d: D9_STYLE_OPTIONS[3].label,
|
|
options: [
|
|
{ key: "A", label: D9_STYLE_OPTIONS[0].label, answer_class: "yes" },
|
|
{ key: "B", label: D9_STYLE_OPTIONS[1].label, answer_class: "weak_yes" },
|
|
{ key: "C", label: D9_STYLE_OPTIONS[2].label, answer_class: "no" },
|
|
{ key: "D", label: D9_STYLE_OPTIONS[3].label, answer_class: "unsure" },
|
|
],
|
|
},
|
|
probe_id: `contrast:${D9_STALE_KEY}`,
|
|
semantic_key: D9_STALE_KEY,
|
|
candidate_split_hash: schemaHash,
|
|
choice_kind: "varga_style",
|
|
},
|
|
},
|
|
declinedSkippedTopics: [],
|
|
},
|
|
latestResult: {
|
|
resultId: "66666666-6666-4666-8666-666666666666",
|
|
selectionAllowed: false,
|
|
candidates: [
|
|
{ time: "05:00", relativeSupport: 34 },
|
|
{ time: "05:06", relativeSupport: 33 },
|
|
{ time: "05:07", relativeSupport: 33 },
|
|
],
|
|
decisionReceipt: {
|
|
window_scan: {
|
|
scanned: true,
|
|
d9_candidates_differ: true,
|
|
d9_sign_names: ["天秤座", "天蝎座", "射手座"],
|
|
transitions: [
|
|
{ layer: "d9", at: "05:06", from_sign: "天秤座", to_sign: "天蝎座" },
|
|
{ layer: "d9", at: "05:07", from_sign: "天蝎座", to_sign: "射手座" },
|
|
],
|
|
},
|
|
inference_state: {
|
|
algorithm_version: "rectification-inference-v1",
|
|
candidate_set_id: CURRENT_SET_ID,
|
|
revision: 1,
|
|
phase: "discrimination",
|
|
result_status: "discriminating",
|
|
range_start: "05:00",
|
|
range_end: "05:07",
|
|
candidates: [
|
|
{ id: "05:00", time: "05:00", cluster_range: ["05:00", "05:00"], prior_score: 34, posterior_score: 34, probability: 0.34, status: "active", rank: 1, strong_conflict_count: 0 },
|
|
{ id: "05:06", time: "05:06", cluster_range: ["05:06", "05:06"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 2, strong_conflict_count: 0 },
|
|
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 33, posterior_score: 33, probability: 0.33, status: "active", rank: 3, strong_conflict_count: 0 },
|
|
],
|
|
events: [
|
|
{ id: "e-career-a", domain: "career", year: 2020, precision: "month", usage: "training" },
|
|
{ id: "e-career-b", domain: "career", year: 2020, precision: "month", usage: "training" },
|
|
{ id: "e-rel-end", domain: "relationship", year: 2024, precision: "day", usage: "training" },
|
|
{ id: "e-rel-start", domain: "relationship", year: 2024, precision: "month", usage: "holdout" },
|
|
],
|
|
answered_probes: [],
|
|
probes: [{
|
|
id: `contrast:${D9_STALE_KEY}`,
|
|
semantic_key: D9_STALE_KEY,
|
|
// minted one scoring round earlier, so the prefix names the old set
|
|
candidate_split_hash: `${STALE_SET_ID}:${D9_STALE_KEY}`,
|
|
domain: "relationship",
|
|
year: 0,
|
|
question: "亲密关系里更接近下面哪一种相处方式?",
|
|
candidate_ids: ["05:00", "05:06", "05:07"],
|
|
information_gain: 1.53,
|
|
source: "varga_contrast",
|
|
choice_kind: "varga_style",
|
|
style_options: D9_STYLE_OPTIONS,
|
|
expected_outcomes: [
|
|
{ answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] },
|
|
{ answer_class: "weak_yes", supports: ["05:06"], conflicts: ["05:00", "05:07"] },
|
|
{ answer_class: "no", supports: ["05:07"], conflicts: ["05:00", "05:06"] },
|
|
],
|
|
}],
|
|
rounds: [],
|
|
entropy: 1.5,
|
|
representative_time: "05:00",
|
|
credible_range: ["05:00", "05:07"],
|
|
},
|
|
},
|
|
},
|
|
case: { acceptedTime: null },
|
|
turns: [],
|
|
};
|
|
}
|
|
|
|
test("GET still renders the card when the probe hash carries an older candidate set prefix", () => {
|
|
const card = choiceCardFromCaseDossier(
|
|
staleSplitDossier(`${STALE_SET_ID}:${D9_STALE_KEY}`) as never,
|
|
);
|
|
assert.ok(card, "a stale candidate-set prefix must not silence the persisted card");
|
|
assert.equal(card.probe_id, `contrast:${D9_STALE_KEY}`);
|
|
assert.equal(card.question_id, `probe:${D9_STALE_KEY}`);
|
|
});
|
|
|
|
test("GET keeps hiding the card when the persisted split belongs to another probe", () => {
|
|
const card = choiceCardFromCaseDossier(
|
|
staleSplitDossier(`${CURRENT_SET_ID}:varga.d10.05:00|05:06/05:07`) as never,
|
|
);
|
|
assert.equal(card, null);
|
|
});
|