1042 lines
34 KiB
TypeScript
1042 lines
34 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
buildChoiceFrame,
|
|
choiceCardUserMessage,
|
|
HOLDOUT_MESSAGE_PREFIX,
|
|
isHoldoutVerificationQuote,
|
|
lifePeriodLabel,
|
|
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 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("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"), "那段时间");
|
|
});
|
|
|
|
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("choice frame rejects incomplete, duplicate-class, illegal, and duplicate-label style options", () => {
|
|
const build = (style_options: DiscriminatingEventProbe["style_options"]) => buildChoiceFrame({
|
|
method_id: "d4_home",
|
|
ask_theme: "home_change",
|
|
domain: "relocation",
|
|
user_prompt_hint: "unused",
|
|
}, { probes: [{ ...MOVE_PROBE, style_options }] });
|
|
|
|
assert.equal(build(DYNAMIC_STYLE_OPTIONS.slice(0, 3)), null);
|
|
assert.equal(build([
|
|
...DYNAMIC_STYLE_OPTIONS,
|
|
{ label: "另一种明确发生", answer_class: "yes" },
|
|
]), null);
|
|
assert.equal(build([
|
|
{ ...DYNAMIC_STYLE_OPTIONS[0], label: "外貌更接近第一种" },
|
|
...DYNAMIC_STYLE_OPTIONS.slice(1),
|
|
]), null);
|
|
assert.equal(build([
|
|
DYNAMIC_STYLE_OPTIONS[0],
|
|
{ ...DYNAMIC_STYLE_OPTIONS[1], label: DYNAMIC_STYLE_OPTIONS[0].label },
|
|
DYNAMIC_STYLE_OPTIONS[2],
|
|
DYNAMIC_STYLE_OPTIONS[3],
|
|
]), null);
|
|
});
|
|
|
|
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 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 preserve order and carry their own answer class", () => {
|
|
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", ...styleOptions[0] },
|
|
{ key: "B", ...styleOptions[1] },
|
|
{ key: "C", ...styleOptions[2] },
|
|
{ key: "D", ...styleOptions[3] },
|
|
]);
|
|
});
|
|
|
|
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.doesNotMatch(copy.prompt, /有没有/);
|
|
assert.match(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);
|
|
});
|