import assert from "node:assert/strict"; import test from "node:test"; import { parseAgentChoiceCopy } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { validateSpokenPrompt, withSpokenPrompt } from "../src/lib/rectification-agentic/v9/spoken-prompt.ts"; import { MACHINE_VOICE_LEXICON } from "../src/lib/rectification-agentic/v9/agent-voice-lexicon.ts"; import type { MethodFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts"; function followup(overrides: Partial = {}): MethodFollowup { return { method_id: "d9_relationship", intent: "distinguish_candidates", ask_theme: "relationship_style", domain: "relationship", kind_hint: "relationship_change", user_prompt_hint: "probe", must_not_label: false, choice_frame: { question_id: "d9:relationship:2023", method_id: "d9_relationship", period: "2023 年前后", prompt: "2023 年前后,你有没有一段认真开始或结束的关系?", varga: "D9", why: "区分", option_a_hint: "明确发生且时间吻合", option_b_hint: "发生过但时间偏了", neither_label: "没有这回事", unsure_label: "记不清", option_a_answer_class: "yes", option_b_answer_class: "weak_yes", option_c_answer_class: "no", option_d_answer_class: "unsure", choice_mode: "A/B/C/D", stop_label: "先这样,先看当前范围", stop_message: "先这样", scoring: true, why_user: "", answer_impact: { A: "", B: "", C: "", D: "" }, }, source: "event_probe", probe_year: 2023, ...overrides, }; } test("spokenPrompt rejects length, option literals, unique-minute claims, and machine voice", () => { assert.equal(validateSpokenPrompt({ spokenPrompt: "太短了" }).ok, false); const optionLiteral = validateSpokenPrompt({ spokenPrompt: "2023 年这段感情,选项怎么选你还记得吗?", }); assert.equal(optionLiteral.ok, false); assert.equal(optionLiteral.ok ? null : optionLiteral.reason, "option_literal"); const uniqueMinute = validateSpokenPrompt({ spokenPrompt: "这能定下唯一的出生分钟吗,你还记得医院的钟点?", }); assert.equal(uniqueMinute.ok, false); assert.equal(uniqueMinute.ok ? null : uniqueMinute.reason, "unique_minute"); assert.equal(validateSpokenPrompt({ spokenPrompt: MACHINE_VOICE_LEXICON[3], }).ok, false); }); test("spokenPrompt rejects a different year or domain than the server probe", () => { const probe = followup(); const yearMismatch = validateSpokenPrompt({ spokenPrompt: "2018 年前后,你有没有一段认真开始或结束的关系?", followup: probe, targetDomain: "relationship", questionId: "d9_relationship:relationship:2023", }); assert.equal(yearMismatch.ok ? null : yearMismatch.reason, "year_mismatch"); const yearMissing = validateSpokenPrompt({ spokenPrompt: "你有没有一段认真开始或结束的关系?", followup: probe, targetDomain: "relationship", questionId: "d9_relationship:relationship:2023", }); assert.equal(yearMissing.ok ? null : yearMissing.reason, "year_missing"); const domainMismatch = validateSpokenPrompt({ spokenPrompt: "2023 年前后,你有没有换过工作?", followup: probe, targetDomain: "career", questionId: "d9_relationship:relationship:2023", }); assert.equal(domainMismatch.ok ? null : domainMismatch.reason, "domain_mismatch"); assert.equal(validateSpokenPrompt({ spokenPrompt: "工作记下了。2023 年前后,有没有一段认真开始或结束的关系?", followup: probe, targetDomain: "relationship", questionId: "d9_relationship:relationship:2023", }).ok, true); }); test("collect spokenPrompt must name the server domain; distinguish and OOS stay year-only", () => { const educationCollect: MethodFollowup = { method_id: "d5_education", intent: "collect_method_evidence", ask_theme: "education_style", domain: "education", kind_hint: "education_milestone", user_prompt_hint: "collect", must_not_label: false, choice_frame: null, source: "method_coverage", }; const missing = validateSpokenPrompt({ spokenPrompt: "除了工作,还有哪件事记得年份?", followup: educationCollect, targetDomain: "education", }); assert.equal(missing.ok, false); assert.equal(missing.ok ? null : missing.reason, "domain_missing"); assert.equal(validateSpokenPrompt({ spokenPrompt: "上学那会儿,哪年升学或大考还记得吗?", followup: educationCollect, targetDomain: "education", }).ok, true); const oos: MethodFollowup = { ...educationCollect, intent: "out_of_sample_check", source: "oos_blind", domain: "education", }; assert.equal(validateSpokenPrompt({ spokenPrompt: "除了工作,还有哪件事记得年份?", followup: oos, targetDomain: "education", }).ok, true); }); test("spokenPrompt requires each year in a period when probe_year is empty", () => { const ranged = followup({ probe_year: undefined, choice_frame: { ...followup().choice_frame!, period: "2014–2016 年前后", }, }); const questionId = "d9:relationship:2023"; const missing = validateSpokenPrompt({ spokenPrompt: "你有没有升学或换过学习环境?", followup: ranged, questionId, }); assert.equal(missing.ok ? null : missing.reason, "year_missing"); assert.equal(validateSpokenPrompt({ spokenPrompt: "2014 到 2016 年前后,有没有升学或换过学习环境?", followup: ranged, questionId, }).ok, true); const collect = followup({ choice_frame: null, probe_year: 2023 }); assert.equal(validateSpokenPrompt({ spokenPrompt: "你还记得哪年入的职吗?", followup: collect, targetDomain: "relationship", }).ok, true); }); test("withSpokenPrompt keeps a parseable choice prompt when spoken copy includes a clock time", () => { const schema = { choice: { prompt: "2016 年前后,升学结果或学习环境有没有明显变化?", option_a: "明确发生且时间吻合", option_b: "发生过但程度较弱", option_c: "明确没有发生", option_d: "这段记不清楚", 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" }, ], }, }; const next = withSpokenPrompt( schema, "已按 05:06 采用。现在核对:2016 年前后升学或学习环境有没有明显变化?", ); const copy = parseAgentChoiceCopy(next); assert.equal(copy?.options.length, 4); assert.equal(copy?.prompt, "2016 年前后,升学结果或学习环境有没有明显变化?"); assert.equal(next.prompt, "已按 05:06 采用。现在核对:2016 年前后升学或学习环境有没有明显变化?"); });