import assert from "node:assert/strict"; import test from "node:test"; import { COLLECT_FLOW_BANNED_PHRASES, anchoredFollowups, collectionQuestionPool, inviteDeclined, moreCollectHint, preciseGapNarration, } from "../src/lib/rectification-agentic/v9/collection-question-pool.ts"; const educationStart = { status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2016-09-01", occurredTo: "2016-09-30", eventKind: "education_start", summary: "上大学", } as const; const educationEnd = { status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2020-06-01", occurredTo: "2020-06-30", eventKind: "education_completion", summary: "毕业", } as const; const inviteDeclinedTopic = { target_domain: "other", status: "declined", intent: "collect_method_evidence", questionId: "collect:invite:more", target_kind: "invite_more", }; function assertNoBanned(text: string) { for (const phrase of COLLECT_FLOW_BANNED_PHRASES) { assert.equal(text.includes(phrase), false, phrase); } } test("invite stays first while it is producing, then drops after a decline", () => { const first = collectionQuestionPool([educationStart]); assert.equal(first[0]?.kind, "invite"); assert.equal(first[0]?.key, "collect:invite:more"); assert.match(first[0]?.prompt ?? "", /^还有吗?/); const still = collectionQuestionPool([educationStart, educationEnd]); assert.equal(still[0]?.kind, "invite"); assert.equal(inviteDeclined([inviteDeclinedTopic]), true); const afterDecline = collectionQuestionPool( [educationStart, educationEnd], [inviteDeclinedTopic], ); assert.notEqual(afterDecline[0]?.kind, "invite"); assert.equal(afterDecline.some((item) => item.kind === "invite"), false); }); test("education completion anchors to the graduation job question from the user year", () => { const asked = new Set(); const anchors = anchoredFollowups([educationStart, educationEnd], new Set(), asked); assert.match(anchors[0]?.prompt ?? "", /2020 年毕业后第一份工作/); assert.doesNotMatch(anchors.map((item) => item.prompt).join("\n"), /年前后/); const declinedAnchor = collectionQuestionPool( [educationStart, educationEnd], [ inviteDeclinedTopic, { target_domain: "career", status: "declined", intent: "collect_method_evidence", questionId: "collect:anchor:education_completion:2020", target_kind: "anchor:education_completion:2020", }, ], ); assert.equal( declinedAnchor.some((item) => item.key === "collect:anchor:education_completion:2020"), false, ); }); test("precise gap names the recorded events and at least two uncovered kinds", () => { const gap = preciseGapNarration([educationStart, educationEnd], [inviteDeclinedTopic]); assert.match(gap, /现在记下的是/); assert.match(gap, /2016 年 9 月上大学/); assert.match(gap, /2020 年 6 月毕业/); assert.match(gap, /都是上学的事/); assert.match(gap, /就能开始筛/); const examples = (gap.match(/第一份工作|搬到别的城市|谈恋爱|家里添丁|生病受伤/g) ?? []).length; assert.ok(examples >= 2, gap); assertNoBanned(gap); }); test("more-collect hint after delivery never uses banned collect-flow phrases", () => { const hint = moreCollectHint([educationStart, educationEnd], [inviteDeclinedTopic]); assert.match(hint, /范围还能再收一截/); assertNoBanned(hint); });