Files
Jyotisha/frontend/tests/rectification-targeted-collect-cards-20260913.test.ts
T
Jesse_ChenandCursor 9912760104
Independent Staging Quality Gate / validate (push) Successful in 13m8s
Independent Staging Quality Gate / publish (push) Canceled after 9m59s
fix(rectification): keep range-card tie-break entry honest after D9/D10 answers (BUG-666~668)
Hide the button once both personality cards are answered, persist the second card in the same opt-in round, and freeze Skill 10.0.26.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 22:45:31 +08:00

229 lines
8.1 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),
});
assert.match(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, "再答两道参考题微调排序");
});