import assert from "node:assert/strict"; import test from "node:test"; import { buildChoiceFrame, choiceCardUserMessage, HOLDOUT_MESSAGE_PREFIX, isHoldoutVerificationQuote, lifePeriodLabel, mergeChoiceCard, parseAgentChoiceCopy, parseChoiceKeyFromUserMessage, parseRectificationChoiceCard, } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { buildMethodFollowupPlan, projectRectificationChoiceCard } from "../src/lib/rectification-agentic/v9/method-followup.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 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: "reverse_verify", }; 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("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 asks 发挥失常 instead of existence", () => { const frame = buildChoiceFrame({ method_id: "d5_education", ask_theme: "education_style", domain: "education", user_prompt_hint: "unused", }, { 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: "distinguish", }], }); assert.equal(frame.period, "2015 年前后"); assert.match(frame.why, /发挥失常/); assert.match(frame.why, /请写成/); 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 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: { 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: "reverse_verify", }], activeFocus: { 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 ?? "", /不要写 expectedAnswerSchema\.choice/); }); 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 hidden once representative time can be offered", () => { 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, activeFocus: { intent: "distinguish_candidates", targetDomain: "relocation", targetKind: "home_change", expectedAnswerSchema: { choice: SAMPLE_COPY }, }, }); 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: { 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); });