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 = {}) { 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.equal(frame, null); }); test("yearless family cards do not attach a scoring frame", () => { const frame = buildChoiceFrame({ method_id: "relatives", ask_theme: "family_event", domain: "family", user_prompt_hint: "unused", choice_kind: "existence", }, { 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.equal(frame, null); }); 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", }, { 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.equal(frame, null); }); 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 = {}) => 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); });