Files
Jyotisha/frontend/tests/rectification-targeted-collect-cards-20260913.test.ts
T
jesse-ux a266b6b727
Independent Staging Quality Gate / validate (push) Failing after 10m16s
Independent Staging Quality Gate / publish (push) Skipped
fix(rectification): invite another dated event instead of saying the pool is closed (BUG-689)
When the seven collect lines are closed, the range card still appears, but the copy asks for one more exact-day event of any kind instead of saying the questions are finished. A new dated event after delivery stales the snapshot so the session does not stay delivered.
2026-09-14 22:56:01 +08:00

234 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import test from "node:test";
import { applyProbeOutcome } from "../src/lib/rectification-agentic/core/apply-probe-outcome.ts";
import type { ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts";
import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
import {
SPLIT_ENDPOINT_PHRASE,
pendingTargetedYearDomain,
targetedCollectExhausted,
targetedCollectPool,
} from "../src/lib/rectification-agentic/v9/collection-question-pool.ts";
import { rangeDeliveryForSnapshot } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import {
buildMethodFollowupPlan,
targetedCollectFollowup,
tieBreakPersonalityAvailable,
tieBreakPersonalityFollowup,
} from "../src/lib/rectification-agentic/v9/method-followup.ts";
const TIMES = ["04:48", "04:53", "05:06", "05:07"] as const;
const LAYERS = ["d9", "d10", "d4", "d12", "d2", "d11"];
const EVIDENCE = [
{
status: "confirmed",
domain: "education",
datePrecision: "month",
occurredFrom: "2016-09-01",
occurredTo: "2016-09-30",
eventKind: "education_start",
summary: "上大学",
},
{
status: "confirmed",
domain: "career",
datePrecision: "month",
occurredFrom: "2020-04-01",
occurredTo: null,
eventKind: "career_entry",
summary: "入职实习",
},
] as const;
function declined(domain: string) {
return {
target_domain: domain,
status: "declined" as const,
intent: "collect_method_evidence",
questionId: `collect:targeted:${domain}`,
target_kind: `targeted:${domain}`,
};
}
test("A resolves existence then asks the year; B/C close only that line", () => {
const first = targetedCollectFollowup(LAYERS, EVIDENCE, [], ["04:48", "05:07"], 4);
assert.equal(first?.collection_key, "collect:targeted:relationship");
assert.equal(first?.choice_frame?.scoring, false);
assert.equal(first?.choice_frame?.prompt, "结过婚或订过婚吗?");
assert.match(first?.spoken_prompt ?? "", /现在还剩 04:48–05:07 里 4 个候选/);
assert.doesNotMatch(first?.spoken_prompt ?? "", SPLIT_ENDPOINT_PHRASE);
const afterYes = targetedCollectFollowup(
LAYERS,
EVIDENCE,
[{ ...declined("relationship"), status: "resolved", questionId: "collect:targeted:relationship" }],
["04:48", "05:07"],
4,
);
assert.equal(pendingTargetedYearDomain([{
...declined("relationship"),
status: "resolved",
}], EVIDENCE), "relationship");
assert.equal(afterYes?.collection_key, "collect:targeted:relationship:year");
assert.equal(afterYes?.choice_frame, null);
assert.equal(afterYes?.spoken_prompt, "大概哪年几月?");
const afterNo = targetedCollectPool(
LAYERS,
EVIDENCE,
[declined("relationship")],
["04:48", "05:07"],
4,
);
assert.equal(afterNo[0]?.domain, "relocation");
assert.equal(targetedCollectExhausted(LAYERS, EVIDENCE, [
declined("relationship"),
declined("relocation"),
declined("family"),
declined("finance"),
], ["04:48", "05:07"], 4), true);
});
test("personality D9/D10 stay out of the scoring pool until the range-card opt-in", () => {
const ledger = [
...EVIDENCE,
{
status: "confirmed",
domain: "relationship",
datePrecision: "month",
occurredFrom: "2024-05-01",
occurredTo: null,
eventKind: "relationship_start",
summary: "确定关系",
},
{
status: "confirmed",
domain: "family",
datePrecision: "month",
occurredFrom: "2016-05-01",
occurredTo: null,
eventKind: "family_event",
summary: "家里添丁",
},
];
const d9 = {
probeId: "contrast:varga.d9.巨蟹座/狮子座",
candidateSetVersion: "04:48-05:07",
question: "亲密关系里更接近下面哪一种相处方式?",
expectedOutcomes: [
{ outcomeId: "yes", supportsCandidateIds: ["04:53"], conflictsCandidateIds: ["05:06"] },
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:06"], conflictsCandidateIds: ["04:53"] },
],
candidateSplitHash: "varga.d9.巨蟹座/狮子座",
informationGain: 1.4,
sourceFeatures: [{ technique: "D9", calculationResultId: null }],
domain: "relationship",
year: null,
semanticKey: "varga.d9.巨蟹座/狮子座",
choiceKind: "varga_style" as const,
styleOptions: [
{ label: "相处里更在意照顾对方的感受", answerClass: "yes" as const, sign: "巨蟹座" },
{ label: "习惯带头,也不排斥站到台前", answerClass: "weak_yes" as const, sign: "狮子座" },
],
};
const planInput = {
evidence: ledger,
declinedTopics: ["relocation", "finance"].map(declined),
sessionOutcome: "discriminate_candidates" as const,
candidatesSeparated: false,
remainingLayers: LAYERS,
remainingSplitTimes: ["04:48", "05:07"] as const,
remainingCandidateCount: 4,
topCandidateTimes: [...TIMES],
contrastPacket: {
candidateSetVersion: "04:48-05:07",
vargaDifferences: [],
probes: [d9],
},
};
const auto = buildMethodFollowupPlan(planInput);
assert.notEqual(auto.next_followup?.choice_kind, "varga_style");
const opted = tieBreakPersonalityFollowup(planInput);
assert.equal(opted?.choice_kind, "varga_style");
assert.match(opted?.semantic_key ?? "", /varga\.d9/);
assert.equal(opted?.tie_break_round, true);
const d10: ConflictProbe = {
id: "probe:varga.d10.狮子座/处女座",
semantic_key: "varga.d10.狮子座/处女座",
candidate_split_hash: "04:48-05:07:varga.d10",
domain: "career",
year: 0,
question: "平时做事,你更接近下面哪一种?",
candidate_ids: [...TIMES],
expected_outcomes: [
{ answer_class: "yes", supports: ["04:53"], conflicts: ["05:06"] },
{ answer_class: "weak_yes", supports: ["05:06"], conflicts: ["04:53"] },
{ answer_class: "no", supports: [], conflicts: [] },
{ answer_class: "unsure", supports: [], conflicts: [] },
],
information_gain: 0.9,
source: "varga_contrast",
choice_kind: "varga_style",
style_options: [
{ label: "相处里更在意照顾对方的感受", answer_class: "yes", sign: "巨蟹座" },
{ label: "习惯带头,也不排斥站到台前", answer_class: "weak_yes", sign: "狮子座" },
],
};
const scored = applyProbeOutcome(
{ "04:48": 10, "04:53": 12, "05:06": 11, "05:07": 10 },
d10,
"yes",
);
assert.equal(scored.kind, "tie_break");
assert.equal(scored.deltas["04:53"], 1);
assert.equal(scored.eliminated_ids.length, 0);
const delivery = rangeDeliveryForSnapshot({
candidates: TIMES.map((time, index) => ({
candidateId: `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`,
time,
})),
representativeTime: "04:53",
credibleRange: ["04:48", "05:07"],
evidence: ledger,
declinedTopics: ["relocation", "finance"].map(declined),
window_scan: {
scanned: true,
d9_lagna_count: 2,
d10_lagna_count: 2,
d9_candidates_differ: true,
d10_candidates_differ: true,
d4_candidates_differ: true,
d12_candidates_differ: true,
d2_candidates_differ: true,
d11_candidates_differ: true,
},
tieBreakAvailable: tieBreakPersonalityAvailable(planInput),
});
// 原值: 拒答后收口「能问的都问完了」
// 新值: 仍不列出已关闭的线;收口改为不限领域的补充邀请
// 原因: BUG-687 不得列出已关闭的线;BUG-689 采集线关闭不等于校正结束
assert.match(delivery.narrow_hint ?? "", /不限领域/);
assert.doesNotMatch(delivery.narrow_hint ?? "", /问完了/);
assert.doesNotMatch(delivery.narrow_hint ?? "", /搬家|收入/);
assert.doesNotMatch(delivery.narrow_hint ?? "", SPLIT_ENDPOINT_PHRASE);
assert.equal(delivery.tie_break_available, true);
const incompleteScan = rangeDeliveryForSnapshot({
candidates: TIMES.map((time, index) => ({
candidateId: `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`,
time,
})),
representativeTime: "04:53",
credibleRange: ["04:48", "05:07"],
window_scan: {
d9_candidates_differ: true,
d10_candidates_differ: false,
},
});
// 原值: true(window_scan.d9_candidates_differ 为真就显示入口)
// 新值: false(没有未问的 D9/D10 varga_style 时不显示)
// 原因: BUG-666 入口与 tie-break 路由共用「还有未答参考题」判定
assert.equal(incompleteScan.tie_break_available, false);
assert.equal(RECTIFICATION_USER_COPY.rangeDeliveryTieBreakEntry, "再答两道参考题微调排序");
});