import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { withSpokenPrompt } from "../src/lib/rectification-agentic/v9/spoken-prompt.ts"; import { projectCurrentQuestion } from "../src/lib/rectification-agentic/v9/turn-decision.ts"; import { TARGETED_YEAR_PROMPT, isYearStageQuestionId, pendingTargetedYearDomain, } from "../src/lib/rectification-agentic/v9/collection-question-pool.ts"; import { collectFocusSchema, isCollectFocusSchema, persistServerOwnedFocus, shouldResolveCollectFocusAfterEvidence, } from "../src/lib/rectification-agentic/v9/server-focus.ts"; import { shouldHostReaskYearEntry, userMessageHasYear } from "../src/lib/rectification-agentic/v9/year-entry-host.ts"; import { TARGETED_COLLECT_OPTION_A } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { targetedCollectYearFollowup as yearFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import { CASE_ID, FOCUS_ID, USER_ID, fakeAccounting, } from "./rectification-v9-test-support.ts"; const YEAR_Q = "collect:targeted:health_pressure:year"; const SERVER_PROMPT = TARGETED_YEAR_PROMPT; const FOLLOWUP = "2019年3月这次手术,是你本人做的吗?"; test("year-stage set-focus overlay keeps the server prompt (BUG-908)", () => { const schema = { collect: true, prompt: SERVER_PROMPT, }; const next = withSpokenPrompt(schema, FOLLOWUP, YEAR_Q); assert.equal(next.prompt, SERVER_PROMPT); assert.equal(next.spoken_prompt, FOLLOWUP); const question = projectCurrentQuestion({ id: FOCUS_ID, questionId: YEAR_Q, intent: "collect_method_evidence", targetDomain: "health_pressure", expectedAnswerSchema: next, }); assert.equal(question?.prompt, SERVER_PROMPT); assert.equal(question?.kind, "collect_spoken"); }); test("year-stage questions are typed collect, not an entry card (T0)", () => { assert.equal(isYearStageQuestionId(YEAR_Q), true); assert.equal(isYearStageQuestionId("collect:targeted:health_pressure"), false); assert.equal(TARGETED_COLLECT_OPTION_A, "有,我来说"); const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8"); assert.doesNotMatch(chat, /EventDateEntryCard|event_date_entry/); const followup = yearFollowup("health_pressure"); const schema = collectFocusSchema(followup); assert.ok(isCollectFocusSchema(schema)); assert.equal(schema?.event_date_entry, undefined); assert.equal(followup.spoken_prompt, TARGETED_YEAR_PROMPT); }); test("batch and propose-evidence share the same collect-focus resolve (BUG-909)", () => { const tools = readFileSync(new URL("../src/mastra/rectification-v9-tools.ts", import.meta.url), "utf8"); const first = tools.indexOf("await resolveActiveCollectFocusAfterEvidence("); const second = tools.indexOf("await resolveActiveCollectFocusAfterEvidence(", first + 1); assert.ok(first > 0 && second > first); assert.equal(tools.indexOf("await resolveActiveCollectFocusAfterEvidence(", second + 1), -1); }); test("dated evidence closes the year stage for that domain (BUG-909)", () => { const topics = [{ questionId: "collect:targeted:health_pressure", target_domain: "health_pressure", status: "resolved", intent: "collect_method_evidence", }]; assert.equal(pendingTargetedYearDomain(topics, []), "health_pressure"); assert.equal(pendingTargetedYearDomain(topics, [{ status: "confirmed", domain: "health", datePrecision: "month", occurredFrom: "2019-03-01", occurredTo: "2019-03-01", }]), null); const schema = collectFocusSchema(yearFollowup("health_pressure")); assert.equal(shouldResolveCollectFocusAfterEvidence({ intent: "collect_method_evidence", questionId: YEAR_Q, expectedAnswerSchema: schema ?? { collect: true, prompt: SERVER_PROMPT }, }), true); }); test("persist year followup writes a collect schema that batch can resolve (BUG-909 a)", async () => { const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: (_fn, args) => ({ focus: { id: FOCUS_ID, case_id: CASE_ID, question_id: args.p_question_id, intent: args.p_intent, target_domain: args.p_target_domain, expected_answer_schema: args.p_expected_answer_schema, status: "active", asked_at: "2026-09-16T00:00:00.000Z", }, idempotent: false, }), }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: null, followup: yearFollowup("health_pressure"), }); assert.equal(result.status, "created"); assert.equal(isCollectFocusSchema(result.focus?.expectedAnswerSchema ?? null), true); assert.equal(result.focus?.expectedAnswerSchema.prompt, TARGETED_YEAR_PROMPT); assert.equal(result.focus?.expectedAnswerSchema.event_date_entry, undefined); }); test("yearless short replies are held by the host pre-step (BUG-910)", () => { assert.equal(userMessageHasYear("是"), false); assert.equal(userMessageHasYear("不是本人"), false); assert.equal(userMessageHasYear("2019年3月做过手术"), true); assert.equal(userMessageHasYear("2020 年住院"), true); const focus = { questionId: YEAR_Q, expectedAnswerSchema: { collect: true, prompt: SERVER_PROMPT }, }; assert.equal(shouldHostReaskYearEntry(focus, "是"), SERVER_PROMPT); assert.equal(shouldHostReaskYearEntry(focus, "2019年3月做过手术"), null); const agentRun = readFileSync(new URL("../src/lib/rectification-agentic/v9/agent-run.ts", import.meta.url), "utf8"); const reliability = agentRun.indexOf("classifyDateReliabilityUtterance"); const yearHost = agentRun.indexOf("shouldHostReaskYearEntry"); const reserve = agentRun.indexOf("billing.reserve"); assert.ok(reliability > 0 && yearHost > reliability && reserve > yearHost); }); test("standalone persisted question shares the assistant inset (BUG-911)", () => { const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); assert.match( styles, /\.rectification-message-question\.is-standalone \{[\s\S]*margin-inline-start: var\(--assistant-content-inset\)/, ); assert.match( styles, /\.rectification-message-question\.is-standalone \.rectification-choice-card\.is-embedded \{[\s\S]*margin-inline-start: 0/, ); assert.doesNotMatch(styles, /\.rectification-event-entry \{/); });