import assert from "node:assert/strict"; import test from "node:test"; import { buildChoiceFrame, choiceCardUserMessage, HOLDOUT_MESSAGE_PREFIX, isHoldoutVerificationQuote, lifePeriodLabel, mergeChoiceCard, parseAgentChoiceCopy, parseRectificationChoiceCard, } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { buildMethodFollowupPlan, projectRectificationChoiceCard } from "../src/lib/rectification-agentic/v9/method-followup.ts"; const SAMPLE_COPY = { prompt: "2016 年前后,更像哪一件?", option_a: "那时开始一段认真交往", option_b: "那时工作突然压上来", option_c: "那几年感情和工作都没什么明显变化", option_d: "不记得那几年发生过什么", } as const; test("choice frames reverse-infer competing relationship vs career when D9 and D10 both differ", () => { const frame = buildChoiceFrame( { method_id: "d9_relationship", ask_theme: "relationship_style", domain: "relationship", user_prompt_hint: "unused", }, { observations: [ { layer: "d9", candidates_differ: true, ask_theme: "relationship_style" }, { layer: "d10", candidates_differ: true, ask_theme: "career_style" }, ], evidence: [{ status: "confirmed", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null, }], }, ); assert.equal(frame.period, "2016 年前后"); assert.match(frame.option_a_hint, /认真关系/); assert.match(frame.option_b_hint, /职责/); assert.equal(/外貌|疤痕|胎记/.test(frame.option_a_hint + frame.option_b_hint), false); assert.equal(frame.scoring, true); assert.equal(mergeChoiceCard(frame, null), null); }); test("the visible card uses Agent copy for A/B/C/D, not the server hint", () => { const frame = buildChoiceFrame({ method_id: "d9_relationship", ask_theme: "relationship_style", domain: "relationship", user_prompt_hint: "unused", }, { evidence: [{ status: "confirmed", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null, }], }); 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?.options[2]?.label === frame.neither_label, false); assert.equal(card?.options[3]?.label === frame.unsure_label, false); 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); }); test("life period collapses a single dated year and skips unknown dates", () => { assert.equal(lifePeriodLabel([ { status: "confirmed", datePrecision: "unknown", occurredFrom: null, occurredTo: null }, ]), "按还在比的两套盘"); assert.equal(lifePeriodLabel([ { status: "confirmed", datePrecision: "year", occurredFrom: "2018-01-01", occurredTo: null }, { status: "confirmed", datePrecision: "year", occurredFrom: "2020-06-01", occurredTo: null }, ]), "2018–2020 年这段里"); }); 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 appears only after the Agent writes choice copy onto the focus", () => { const withoutCopy = projectRectificationChoiceCard({ evidence: [] }); assert.equal(withoutCopy, null); const withCopy = projectRectificationChoiceCard({ evidence: [], activeFocus: { intent: "collect_method_evidence", targetDomain: null, targetKind: null, expectedAnswerSchema: { choice: SAMPLE_COPY }, }, }); assert.equal(withCopy?.prompt, SAMPLE_COPY.prompt); assert.equal(withCopy?.options[2]?.label, SAMPLE_COPY.option_c); assert.ok(parseRectificationChoiceCard(withCopy)); }); test("opening follow-up ships a discriminator frame, not a canned user card", () => { const plan = buildMethodFollowupPlan({ evidence: [] }); assert.equal(plan.next_followup?.method_id, "dasha_events"); assert.ok(plan.next_followup?.choice_frame); assert.equal(plan.next_followup?.choice_frame.choice_mode, "A/B/C/D"); assert.doesNotMatch(plan.next_followup?.user_prompt_hint ?? "", /请用 A\/B\/C\/D 回答/); assert.match(plan.next_followup?.user_prompt_hint ?? "", /expectedAnswerSchema\.choice/); });