a0ce55f066
Showing a choice card is no longer treated as completion. Distinguish probes require real candidate groups, holdout stays out of scoring, and ordinary sessions can finish with a credible range instead of an exact-minute gate. Co-authored-by: Cursor <cursoragent@cursor.com>
974 lines
32 KiB
TypeScript
974 lines
32 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import test from "node:test";
|
||
|
||
import {
|
||
buildChoiceFrame,
|
||
choiceCardUserMessage,
|
||
HOLDOUT_MESSAGE_PREFIX,
|
||
isHoldoutVerificationQuote,
|
||
lifePeriodLabel,
|
||
mergeChoiceCard,
|
||
parseAgentChoiceCopy,
|
||
parseChoiceKeyFromUserMessage,
|
||
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 SAMPLE_COPY = {
|
||
prompt: "2016 年前后,有没有明显搬家、离乡或长期异地?",
|
||
option_a: "是,大概就在那段时间",
|
||
option_b: "有类似,但年份不对或不够重大",
|
||
option_c: "没有明显发生",
|
||
option_d: "不记得 / 不确定",
|
||
} 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"] },
|
||
],
|
||
};
|
||
|
||
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.equal(frame.period, "2016 年前后");
|
||
assert.match(frame.why, /年份锁定/);
|
||
assert.match(frame.why, /请写成/);
|
||
assert.doesNotMatch(frame.why, /^2016 年前后,有没有明显/);
|
||
assert.match(frame.option_a_hint, /是,大概就在那段时间/);
|
||
assert.match(frame.option_b_hint, /年份不对或不够重大/);
|
||
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] },
|
||
);
|
||
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],
|
||
});
|
||
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 },
|
||
);
|
||
const card = mergeChoiceCard(frame, {
|
||
prompt: "家人这条线还没用过,有没有一件记得大概年份的变化?",
|
||
option_a: "是,大概就在那段时间",
|
||
option_b: "有类似,但年份不对或不够重大",
|
||
option_c: "没有明显发生",
|
||
option_d: "不记得 / 不确定",
|
||
});
|
||
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",
|
||
}],
|
||
},
|
||
);
|
||
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("known-event quality probe uses the recorded year, not a discriminator split", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d5_education",
|
||
ask_theme: "education_style",
|
||
domain: "education",
|
||
user_prompt_hint: "unused",
|
||
choice_kind: "event_quality",
|
||
}, {
|
||
evidence: [{
|
||
status: "confirmed",
|
||
domain: "education",
|
||
datePrecision: "year",
|
||
occurredFrom: "2015-06-01",
|
||
occurredTo: null,
|
||
}],
|
||
probes: [{
|
||
year: 2015,
|
||
year_label: "2015 年前后",
|
||
domain: "education",
|
||
event_family: "高考或重要考试发挥明显失常、压力很大",
|
||
source: "known_event_quality",
|
||
tracks: ["vimshottari", "narayana"],
|
||
tracks_agree: true,
|
||
unique_minute_claim: false,
|
||
user_meaning: "年份锁定 2015 年前后。已有相关经历。请按事件家族改写成自然语言。不得改年份。",
|
||
role: "clarify",
|
||
phase: "event_clarification",
|
||
choice_kind: "event_quality",
|
||
}],
|
||
});
|
||
assert.equal(frame.period, "2015 年前后");
|
||
assert.equal(frame.choice_kind, "event_quality");
|
||
assert.doesNotMatch(`${frame.why}${frame.option_a_hint}${frame.option_b_hint}`, /更像哪一件/);
|
||
});
|
||
|
||
test("age-band fallback uses birth date, not another event's year", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d4_home",
|
||
ask_theme: "home_change",
|
||
domain: "relocation",
|
||
user_prompt_hint: "unused",
|
||
}, {
|
||
birthDate: "1997-08-08",
|
||
evidence: [{
|
||
status: "confirmed",
|
||
domain: "education",
|
||
datePrecision: "year",
|
||
occurredFrom: "2016-01-01",
|
||
occurredTo: null,
|
||
}],
|
||
});
|
||
assert.equal(frame.period, "2018 年前后");
|
||
assert.match(frame.option_a_hint, /是,大概就在那段时间/);
|
||
assert.doesNotMatch(frame.period, /2016/);
|
||
});
|
||
|
||
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 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",
|
||
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",
|
||
}],
|
||
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("choice card user messages expose A/B/C/D as a leading key", () => {
|
||
assert.equal(parseChoiceKeyFromUserMessage("C. 没有明显发生"), "C");
|
||
assert.equal(parseChoiceKeyFromUserMessage("D、不记得 / 不确定"), "D");
|
||
assert.equal(parseChoiceKeyFromUserMessage(`${HOLDOUT_MESSAGE_PREFIX}:B. 有类似但年份不对`), "B");
|
||
assert.equal(parseChoiceKeyFromUserMessage("没有明显发生"), null);
|
||
});
|
||
|
||
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",
|
||
},
|
||
},
|
||
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,
|
||
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: {
|
||
answered_probes: [{
|
||
probe_id: "probe:education.2016",
|
||
semantic_key: "education.2016",
|
||
answer_class: "yes",
|
||
}],
|
||
probes: [{
|
||
id: "probe:education.2016",
|
||
semantic_key: "education.2016",
|
||
information_gain: 0,
|
||
source: "known_event_quality",
|
||
expected_outcomes: [],
|
||
}],
|
||
},
|
||
},
|
||
},
|
||
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 after 没有了 when selection is allowed", () => {
|
||
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("remaining D10 style card uses type-table labels instead of existence", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d10_career",
|
||
ask_theme: "career_style",
|
||
domain: "career",
|
||
user_prompt_hint: "unused",
|
||
choice_kind: "varga_style",
|
||
style_options: [
|
||
{ label: "照顾、情感", answer_class: "yes", sign: "巨蟹座" },
|
||
{ label: "领导、表演", answer_class: "weak_yes", sign: "狮子座" },
|
||
{ label: "细致、服务", answer_class: "no", sign: "处女座" },
|
||
],
|
||
});
|
||
const copy = serverOwnedChoiceCopy(frame);
|
||
assert.ok(copy);
|
||
assert.equal(frame.choice_kind, "varga_style");
|
||
assert.match(copy.prompt, /长期工作更接近哪一类/);
|
||
assert.doesNotMatch(copy.prompt, /有没有明显/);
|
||
assert.equal(copy.option_a, "照顾、情感");
|
||
assert.equal(copy.option_b, "领导、表演");
|
||
assert.equal(copy.option_c, "细致、服务");
|
||
});
|
||
|
||
test("two-way remaining D9 puts 都不像 on C", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d9_relationship",
|
||
ask_theme: "relationship_style",
|
||
domain: "relationship",
|
||
user_prompt_hint: "unused",
|
||
choice_kind: "varga_style",
|
||
style_options: [
|
||
{ label: "情感丰富、家庭导向", answer_class: "yes", sign: "巨蟹座" },
|
||
{ label: "骄傲、戏剧性、领导欲", answer_class: "weak_yes", sign: "狮子座" },
|
||
],
|
||
});
|
||
const copy = serverOwnedChoiceCopy(frame);
|
||
assert.ok(copy);
|
||
assert.match(copy.prompt, /相处/);
|
||
assert.match(copy.option_c, /都不像/);
|
||
assert.match(copy.option_d, /不确定/);
|
||
});
|
||
|
||
test("known exam quality card keeps the recorded year and quality mapping", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d5_education",
|
||
ask_theme: "education_style",
|
||
domain: "education",
|
||
user_prompt_hint: "unused",
|
||
choice_kind: "event_quality",
|
||
}, {
|
||
probes: [{
|
||
year: 2015,
|
||
year_label: "2015 年前后",
|
||
domain: "education",
|
||
event_family: "高考或重要考试发挥明显失常、压力很大",
|
||
source: "known_event_quality",
|
||
tracks: ["vimshottari", "narayana"],
|
||
tracks_agree: true,
|
||
unique_minute_claim: false,
|
||
user_meaning: "年份锁定 2015 年前后。已有相关经历。请按事件家族改写成自然语言。不得改年份。",
|
||
role: "clarify",
|
||
phase: "event_clarification",
|
||
choice_kind: "event_quality",
|
||
}],
|
||
});
|
||
const copy = serverOwnedChoiceCopy(frame);
|
||
assert.ok(copy);
|
||
assert.equal(frame.period, "2015 年前后");
|
||
assert.equal(frame.choice_kind, "event_quality");
|
||
assert.doesNotMatch(copy.prompt, /有没有/);
|
||
});
|
||
|
||
test("GET card prompt keeps the agent's year-locked question", () => {
|
||
const fallback = {
|
||
prompt: "2018 年前后 · 认真关系进入、结束或关系观明显转变",
|
||
option_a: SAMPLE_COPY.option_a,
|
||
option_b: SAMPLE_COPY.option_b,
|
||
option_c: SAMPLE_COPY.option_c,
|
||
option_d: SAMPLE_COPY.option_d,
|
||
};
|
||
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",
|
||
}],
|
||
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("GET without agent speech shows the lock label, not a yes/no template", () => {
|
||
const fallback = {
|
||
prompt: "2018 年前后 · 认真关系进入、结束或关系观明显转变",
|
||
option_a: SAMPLE_COPY.option_a,
|
||
option_b: SAMPLE_COPY.option_b,
|
||
option_c: SAMPLE_COPY.option_c,
|
||
option_d: SAMPLE_COPY.option_d,
|
||
};
|
||
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",
|
||
}],
|
||
activeFocus: {
|
||
id: FOCUS_ID,
|
||
intent: "distinguish_candidates",
|
||
targetDomain: "relationship",
|
||
targetKind: "relationship_change",
|
||
expectedAnswerSchema: { choice: fallback, probe_id: "relationship.2018" },
|
||
},
|
||
});
|
||
assert.ok(card);
|
||
assert.equal(card.prompt, fallback.prompt);
|
||
assert.doesNotMatch(card.prompt, /有没有/);
|
||
});
|
||
|
||
test("career event_quality does not reuse the exam underperformance copy", () => {
|
||
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",
|
||
choice_kind: "event_quality",
|
||
}],
|
||
});
|
||
const copy = serverOwnedChoiceCopy(frame);
|
||
assert.ok(copy);
|
||
assert.match(copy.prompt, /2020 年前后/);
|
||
assert.match(copy.prompt, /入职、升职或职责明显加重/);
|
||
assert.doesNotMatch(copy.prompt, /高考|失常|压力很大/);
|
||
assert.doesNotMatch(copy.prompt, /有没有/);
|
||
assert.match(copy.prompt, /·/);
|
||
assert.equal(copy.option_a, "是,大概就在那段时间");
|
||
assert.doesNotMatch(copy.option_a, /失常/);
|
||
});
|
||
|
||
test("dasha existence cards lock the year and family, not a yes/no template", () => {
|
||
const frame = buildChoiceFrame({
|
||
method_id: "d4_home",
|
||
ask_theme: "home_change",
|
||
domain: "relocation",
|
||
user_prompt_hint: "unused",
|
||
choice_kind: "existence",
|
||
}, { probes: [MOVE_PROBE] });
|
||
const copy = serverOwnedChoiceCopy(frame);
|
||
assert.ok(copy);
|
||
assert.match(copy.prompt, /2016 年前后/);
|
||
assert.match(copy.prompt, /搬家、离乡或长期异地/);
|
||
assert.doesNotMatch(copy.prompt, /有没有明显/);
|
||
assert.match(copy.option_a, /是,大概就在那段时间/);
|
||
assert.match(copy.option_c, /没有明显发生/);
|
||
});
|