Files
Jyotisha/frontend/tests/rectification-collection-question-pool.test.ts
T
Jesse_ChenandCursor 1fa994ea63
Independent Staging Quality Gate / validate (push) Successful in 13m26s
Independent Staging Quality Gate / publish (push) Successful in 9m33s
fix(rectification): keep dated occupation answers scoreable (BUG-649/650)
Occupation collect was wiping year-month into an unscored note, and idle gap copy never joined the evidence turn.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 11:49:52 +08:00

134 lines
5.0 KiB
TypeScript

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";
import { USER_COLLECT_QUESTION, USER_COLLECT_QUESTION_RETRY } from "../src/lib/rectification-agentic/user-copy.ts";
import { isOccupationCollectFocus } from "../src/lib/rectification-agentic/v9/evidence-model.ts";
import { followupFromPoolItem } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { stableFollowupQuestionId } from "../src/lib/rectification-agentic/v9/server-focus.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<string>();
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);
});
test("generic occupation collect does not ask for a year", () => {
assert.match(USER_COLLECT_QUESTION.occupation, /你平时主要做什么工作/);
assert.doesNotMatch(USER_COLLECT_QUESTION.occupation, /哪年|哪一年|年份/);
assert.doesNotMatch(USER_COLLECT_QUESTION_RETRY.occupation, /哪年|哪一年|年份/);
});
test("graduation job follow-up is a career anchor, not occupation collect", () => {
const asked = new Set<string>();
const anchors = anchoredFollowups([educationStart, educationEnd], new Set(), asked);
const job = anchors.find((item) => item.key === "collect:anchor:education_completion:2020");
assert.ok(job);
assert.equal(job?.domain, "career");
assert.match(job?.prompt ?? "", /2020 年毕业后第一份工作/);
const followup = followupFromPoolItem(job!);
assert.equal(followup.domain, "career");
const questionId = stableFollowupQuestionId(followup);
assert.equal(questionId, "collect:anchor:education_completion:2020");
assert.equal(questionId.startsWith("collect:occupation:"), false);
assert.equal(isOccupationCollectFocus({
intent: followup.intent,
targetDomain: followup.domain,
targetKind: followup.kind_hint,
questionId,
}), false);
});