import assert from "node:assert/strict"; import test from "node:test"; import { COLLECT_KIND_ORDER, GUIDED_ANY_DOMAIN, GUIDED_NARROW_HINT, firstOpenCollectKind, guidedAnyExamples, TARGETED_EXISTENCE_PROMPT, TARGETED_EXISTENCE_PROMPT_RETRY, guidedCollectExhausted, guidedRetryPool, guidedWindowPool, guidedWindowPrompt, parseYearEntryQuestionId, targetedCollectPool, rangeNarrowHint, remainingGuidedLine, type CollectionEvidence, type GuidedCollectWindow, } from "../src/lib/rectification-agentic/v9/collection-question-pool.ts"; import { targetedCollectFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import { TARGETED_COLLECT_OPTION_B, GUIDED_WINDOW_OPTION_B, } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts"; import { eventDateYearRange, formatEventDateEntryMessage, resolveEntryDomain, } from "../src/components/event-date-entry-card.tsx"; const WINDOWS: readonly GuidedCollectWindow[] = [ { year: 2018, month_lo: 3, month_hi: 6, domain: "career", split: { left: 2, right: 3 } }, { year: 2021, month_lo: 8, month_hi: 8, domain: "relationship", split: { left: 3, right: 2 } }, ]; test("guided pool order is windows, skip retry, then uncovered domains", () => { const evidence: CollectionEvidence[] = [ { status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2014-09-01", occurredTo: "2014-09-01" }, ]; const skipped = [ { questionId: "collect:targeted:family", target_domain: "family", status: "skipped", intent: "collect_method_evidence", target_kind: "targeted:family", }, ]; const windows = guidedWindowPool(WINDOWS, evidence, skipped); const retry = guidedRetryPool(evidence, skipped); const open = targetedCollectPool(["d9", "d10"], evidence, skipped); assert.equal(windows[0]?.guidedSource, "window"); assert.equal(windows[0]?.domain, "career"); assert.equal(retry[0]?.guidedSource, "retry"); assert.equal(retry[0]?.domain, "family"); assert.ok(open.some((item) => item.domain === "relationship")); assert.equal(open.some((item) => item.domain === "family"), false); const followup = targetedCollectFollowup(["d9"], evidence, skipped, ["04:51", "05:11"], 5, ["04:51", "05:11"], WINDOWS); assert.equal(followup?.collection_key, windows[0]?.key); }); test("A on a window yields a year-entry card, B/C close only that window", () => { const evidence: CollectionEvidence[] = []; const afterYes = [ { questionId: "collect:guided:window:2018:3:6:career", target_domain: "career", status: "resolved", intent: "collect_method_evidence", target_kind: "guided:window:career", }, ]; const year = parseYearEntryQuestionId("collect:guided:window:2018:3:6:career:year"); assert.equal(year?.domain, "career"); assert.equal(year?.year, 2018); const pending = targetedCollectFollowup([], evidence, afterYes, null, 5, ["04:51", "05:11"], WINDOWS); assert.equal(pending?.collection_key?.endsWith(":year"), true); const afterNo = [ { questionId: "collect:guided:window:2018:3:6:career", target_domain: "career", status: "declined", intent: "collect_method_evidence", target_kind: "guided:window:career", }, ]; const next = targetedCollectFollowup(["d10"], evidence, afterNo, null, 5, null, WINDOWS); assert.notEqual(next?.collection_key, "collect:guided:window:2018:3:6:career"); assert.ok(targetedCollectPool(["d10"], evidence, afterNo).some((item) => item.domain === "career")); }); test("skipped once retries, declined does not, second skip closes", () => { const evidence: CollectionEvidence[] = []; const skipped = [{ questionId: "collect:targeted:relationship", target_domain: "relationship", status: "skipped", intent: "collect_method_evidence", target_kind: "targeted:relationship", }]; const retry = guidedRetryPool(evidence, skipped); assert.equal(retry[0]?.domain, "relationship"); assert.match(retry[0]?.prompt ?? "", /认真关系/); const declined = [{ questionId: "collect:targeted:relationship", target_domain: "relationship", status: "declined", intent: "collect_method_evidence", target_kind: "targeted:relationship", }]; assert.equal(guidedRetryPool(evidence, declined).length, 0); assert.equal(targetedCollectPool(["d9"], evidence, declined).some((item) => item.domain === "relationship"), false); const skippedTwice = [ ...skipped, { questionId: "collect:targeted:relationship:retry", target_domain: "relationship", status: "skipped", intent: "collect_method_evidence", target_kind: "targeted:relationship:retry", retry: true, }, ]; assert.equal(guidedRetryPool(evidence, skippedTwice).length, 0); assert.equal(targetedCollectPool(["d9"], evidence, skippedTwice).some((item) => item.domain === "relationship"), false); }); test("existence prompts cover the whole domain for every collect kind", () => { for (const domain of COLLECT_KIND_ORDER) { assert.match(TARGETED_EXISTENCE_PROMPT[domain], /哪一年都算/); assert.ok(TARGETED_EXISTENCE_PROMPT_RETRY[domain].length > 8); } assert.equal(TARGETED_COLLECT_OPTION_B, "这类事都没有过"); assert.equal(GUIDED_WINDOW_OPTION_B, "这段没有"); assert.match(RECTIFICATION_USER_COPY.collectDeclinedAck, /没有发生过/); assert.match(RECTIFICATION_USER_COPY.collectSkippedAck, /换个问法再问一次/); }); test("a layer that cannot split candidates still leaves that domain in the open pool", () => { const evidence: CollectionEvidence[] = []; const open = targetedCollectPool([], evidence, []); assert.deepEqual(open.map((item) => item.domain), [...COLLECT_KIND_ORDER]); }); test("time-point no does not close a domain", () => { const evidence: CollectionEvidence[] = []; const probeNo = [{ questionId: "distinguish:relationship.2018.05.dasha_boundary", target_domain: "relationship", status: "declined", source: "event_probe", }]; assert.ok(targetedCollectPool(["d9"], evidence, probeNo).some((item) => item.domain === "relationship")); }); test("unmet-gate hint has no free-text invite", () => { const declined = COLLECT_KIND_ORDER.map((domain) => ({ questionId: `collect:targeted:${domain}`, target_domain: domain, status: "declined", intent: "collect_method_evidence", target_kind: `targeted:${domain}`, })); const copy = rangeNarrowHint( ["d9", "d10", "d4", "d5", "d7", "d2", "d30"], [], declined, ["04:51", "05:11"], 5, ["04:51", "05:11"], ); assert.match(copy, /再对照几件经历会更准/); assert.doesNotMatch(copy, /你要是还记得确切哪一天/); assert.doesNotMatch(copy, /不限领域/); assert.ok(copy.includes(GUIDED_NARROW_HINT)); const line = remainingGuidedLine(["04:51", "05:11"], 5, ["04:51", "05:11"]); assert.equal(line, "现在还剩 04:51–05:11 里 5 个候选,再对照几件经历会更准"); }); test("window prompt uses oral phrases, not server labels", () => { const prompt = guidedWindowPrompt({ year: 2018, month_lo: 3, month_hi: 6, domain: "career", }); assert.equal(prompt, "2018 年 3 到 6 月之间,有没有入职、换工作或职责变重?"); assert.doesNotMatch(prompt, /event_family|career_entry/); }); test("date entry submit walks the dated evidence path as year-month text", () => { // 原值: "2018 年 3 月,开始认真关系、分手或结婚" // 新值: "2018 年 3 月,感情方面有一件事" // 原因: F6/BUG-750——提交文本不得是选项列表,账本 summary 不得含「或」 assert.equal( formatEventDateEntryMessage({ domain: "relationship", year: 2018, month: 3, day: null }), "2018 年 3 月,感情方面有一件事", ); assert.doesNotMatch( formatEventDateEntryMessage({ domain: "relationship", year: 2018, month: 3, day: 12 }), /或/, ); assert.equal( formatEventDateEntryMessage({ domain: "family", year: 2019, month: 1, day: 5 }), "2019 年 1 月 5 日,家人方面有一件事", ); }); test("open windows ask without a domain and list open-domain orals", () => { const open = guidedWindowPrompt({ year: 2018, month_lo: 3, month_hi: 6, domain: GUIDED_ANY_DOMAIN, }, ["relocation", "relationship", "family", "finance"]); assert.equal( open, "2018 年 3 到 6 月之间,有没有什么事,比如搬家或开始长期住外地、开始认真关系、分手或结婚、添丁、长辈住院或做过手术?", ); assert.equal(guidedAnyExamples(["relocation", "relationship", "family", "finance"]).length, 3); assert.equal( guidedWindowPrompt({ year: 2018, month_lo: 3, month_hi: 3, domain: GUIDED_ANY_DOMAIN }, []), "2018 年 3 月前后,有没有什么事?", ); }); test("an open window keeps its question id and is asked once for any domain", () => { const pool = guidedWindowPool( [{ year: 2018, month_lo: 3, month_hi: 6, domain: GUIDED_ANY_DOMAIN, split: { left: 2, right: 3 } }], [], [], ); assert.equal(pool.length, 1); assert.equal(pool[0]?.domain, GUIDED_ANY_DOMAIN); assert.equal(pool[0]?.key, "collect:guided:window:2018:3:6:any"); // The same window answered under a domain label is the same window. const asked = guidedWindowPool( [{ year: 2018, month_lo: 3, month_hi: 6, domain: GUIDED_ANY_DOMAIN, split: { left: 2, right: 3 } }], [], [{ questionId: "collect:guided:window:2018:3:6:career", status: "resolved" }], ); assert.equal(asked.length, 0); }); test("the entry card opens on the first still-open domain for an open window", () => { assert.equal(resolveEntryDomain(GUIDED_ANY_DOMAIN, ["finance", "family"]), "finance"); assert.equal(resolveEntryDomain("relationship", ["finance", "family"]), "relationship"); assert.equal( firstOpenCollectKind( [{ status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2014-09-01", occurredTo: "2014-09-01" }], [{ target_domain: "career", status: "declined" }], ), "relocation", ); }); test("date picker year range is birth year through this year", () => { assert.deepEqual(eventDateYearRange(1961, 2026), { minYear: 1961, maxYear: 2026 }); assert.deepEqual(eventDateYearRange(null, 2026), { minYear: 1946, maxYear: 2026 }); }); test("guided collect is not exhausted while a window remains", () => { assert.equal(guidedCollectExhausted([], [], [], WINDOWS, ["04:51", "05:11"], 5), false); assert.equal(guidedCollectExhausted(["d9", "d10", "d4", "d5", "d7", "d2", "d30"], [ { status: "confirmed", domain: "education", datePrecision: "month", occurredFrom: "2014-09-01", occurredTo: "2014-09-01" }, { status: "confirmed", domain: "career", datePrecision: "month", occurredFrom: "2018-07-01", occurredTo: "2018-07-01" }, { status: "confirmed", domain: "relocation", datePrecision: "month", occurredFrom: "2016-08-01", occurredTo: "2016-08-01" }, { status: "confirmed", domain: "relationship", datePrecision: "month", occurredFrom: "2021-08-01", occurredTo: "2021-08-01" }, { status: "confirmed", domain: "family", datePrecision: "month", occurredFrom: "2019-01-01", occurredTo: "2019-01-01" }, { status: "confirmed", domain: "finance", datePrecision: "month", occurredFrom: "2020-03-01", occurredTo: "2020-03-01" }, { status: "confirmed", domain: "health_pressure", datePrecision: "month", occurredFrom: "2022-04-01", occurredTo: "2022-04-01" }, ], [], [], ["04:51", "05:11"], 5), true); });