Files
Jyotisha/frontend/tests/rectification-tie-break-entry-20260913.test.ts
T
jesse-ux 6aabbe386f
Independent Staging Quality Gate / validate (push) Successful in 10m1s
Independent Staging Quality Gate / publish (push) Successful in 36m54s
feat(rectification): 删掉年月录入卡,年月阶段打字回答且不被追问覆盖
2026-09-16 23:22:32 +08:00

204 lines
8.7 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 { readFileSync } from "node:fs";
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 { isTieBreakRoundSchema } from "../src/lib/rectification-agentic/v9/choice-card.ts";
import { rangeDeliveryForSnapshot } from "../src/lib/rectification-agentic/v9/divergence-panel.ts";
import {
buildMethodFollowupPlan,
tieBreakPersonalityAvailable,
tieBreakPersonalityFollowup,
} from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { expectedAnswerSchemaFor, stableFollowupQuestionId } from "../src/lib/rectification-agentic/v9/server-focus.ts";
const TIMES = ["04:48", "04:53", "05:06", "05:07"] as const;
const LEDGER = [
{
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: "入职实习",
},
{
status: "confirmed",
domain: "relationship",
datePrecision: "month",
occurredFrom: "2024-05-01",
occurredTo: null,
eventKind: "relationship_start",
summary: "确定关系",
},
] as const;
function contrastProbe(layer: "d9" | "d10") {
const signs = layer === "d9" ? ["巨蟹座", "狮子座"] as const : ["狮子座", "处女座"];
return {
probeId: `contrast:varga.${layer}.${signs[0]}/${signs[1]}`,
candidateSetVersion: "04:48-05:07",
question: layer === "d9"
? "亲密关系里更接近下面哪一种相处方式?"
: "平时做事,你更接近下面哪一种?",
expectedOutcomes: [
{ outcomeId: "yes", supportsCandidateIds: ["04:53"], conflictsCandidateIds: ["05:06"] },
{ outcomeId: "weak_yes", supportsCandidateIds: ["05:06"], conflictsCandidateIds: ["04:53"] },
],
candidateSplitHash: `varga.${layer}.${signs[0]}/${signs[1]}`,
informationGain: layer === "d9" ? 1.4 : 0.9,
sourceFeatures: [{ technique: layer.toUpperCase(), calculationResultId: null }],
domain: layer === "d9" ? "relationship" : "career",
year: null,
semanticKey: `varga.${layer}.${signs[0]}/${signs[1]}`,
choiceKind: "varga_style" as const,
styleOptions: [
{ label: "相处里更在意照顾对方的感受", answerClass: "yes" as const, sign: signs[0] },
{ label: "习惯带头,也不排斥站到台前", answerClass: "weak_yes" as const, sign: signs[1] },
],
};
}
const D9 = contrastProbe("d9");
const D10 = contrastProbe("d10");
function planInput(askedProbeKeys: readonly string[] = []) {
return {
evidence: LEDGER,
declinedTopics: [],
sessionOutcome: "discriminate_candidates" as const,
candidatesSeparated: false,
remainingLayers: ["d9", "d10"],
remainingSplitTimes: ["04:48", "05:07"] as const,
remainingCandidateCount: 4,
topCandidateTimes: [...TIMES],
askedProbeKeys,
contrastPacket: {
candidateSetVersion: "04:48-05:07",
vargaDifferences: [],
probes: [D9, D10],
},
};
}
test("GET availability matches unasked D9/D10 varga_style, not window_scan", () => {
const open = planInput();
// 原值: 主 followup 无 choice_kind
// 新值: 未覆盖领域会先出定向存在性题
// 原因: D5 七条线都会问到;性格题仍是卡下可选入口
const followup = buildMethodFollowupPlan(open).next_followup;
if (followup?.intent === "collect_method_evidence") {
assert.equal(followup.choice_kind, "existence");
}
assert.equal(tieBreakPersonalityAvailable(open), true);
assert.match(tieBreakPersonalityFollowup(open)?.semantic_key ?? "", /varga\.d9/);
assert.equal(rangeDeliveryForSnapshot({
window_scan: { d9_candidates_differ: true, d10_candidates_differ: true },
tieBreakAvailable: tieBreakPersonalityAvailable(open),
}).tie_break_available, true);
const afterD9 = planInput([D9.semanticKey]);
const second = tieBreakPersonalityFollowup(afterD9);
assert.match(second?.semantic_key ?? "", /varga\.d10/);
assert.equal(second?.tie_break_round, true);
assert.equal(tieBreakPersonalityAvailable(afterD9), true);
const bothAsked = planInput([D9.semanticKey, D10.semanticKey]);
assert.equal(tieBreakPersonalityFollowup(bothAsked), null);
assert.equal(tieBreakPersonalityAvailable(bothAsked), false);
assert.equal(rangeDeliveryForSnapshot({
window_scan: { d9_candidates_differ: true, d10_candidates_differ: true },
tieBreakAvailable: tieBreakPersonalityAvailable(bothAsked),
}).tie_break_available, false);
assert.equal(tieBreakPersonalityAvailable({ ...open, accepted: true }), false);
});
test("one click stamps a round schema; scoring stays tie_break without elimination", () => {
const followup = tieBreakPersonalityFollowup(planInput());
assert.ok(followup?.choice_frame);
const schema = expectedAnswerSchemaFor(
followup.choice_frame,
stableFollowupQuestionId(followup),
null,
followup,
);
assert.equal(schema?.tie_break_round, true);
assert.equal(isTieBreakRoundSchema(schema), true);
assert.equal(isTieBreakRoundSchema({ choice_kind: "varga_style" }), false);
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);
});
test("click path does not surface 409 copy; round continue is opt-in only", () => {
const chat = readFileSync(new URL("../src/components/rectification-agentic-chat.tsx", import.meta.url), "utf8");
const answer = readFileSync(new URL("../src/lib/rectification-agentic/v9/answer-choice.ts", import.meta.url), "utf8");
const panel = readFileSync(new URL("../src/lib/rectification-agentic/v9/divergence-panel.ts", import.meta.url), "utf8");
const pool = readFileSync(new URL("../src/lib/rectification-agentic/v9/collection-question-pool.ts", import.meta.url), "utf8");
const route = readFileSync(
new URL("../src/app/api/rectification/cases/[caseId]/tie-break/route.ts", import.meta.url),
"utf8",
);
// 原值: 客户端 requestTieBreak 把 tie_break_unavailable 当静默刷新,不 setError
// 新值: 卡上入口已删,客户端不再有这条 click path409 口径留在 API
// 原因: BUG-706
assert.doesNotMatch(chat, /requestTieBreak/);
assert.doesNotMatch(chat, /tie_break_unavailable/);
assert.match(route, /tie_break_unavailable/);
assert.match(answer, /continueTieBreakRound\?: boolean/);
assert.match(answer, /continueTieBreakRound === true/);
assert.match(answer, /isTieBreakRoundSchema/);
assert.doesNotMatch(panel, /d9_candidates_differ[\s\S]{0,80}tieBreakAvailable/);
assert.doesNotMatch(pool, /export function isTargetedCollectDeclined/);
assert.doesNotMatch(pool, /export function isTargetedCollectYearFollowup/);
});
test("Skill 10.0.26 lists the fourth targeted-collect skip option", () => {
const skill = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url), "utf8");
const strategy = readFileSync(new URL("../../skills/jyotish-birth-time-rectification/references/conversation-strategy.md", import.meta.url), "utf8");
// 原值: /^version: 10\.0\.28$/ / 新值: /^version: 10\.0\.29$/ / 原因: 年月阶段改口述并禁止追问本人
assert.match(skill, /^version: 10\.0\.29$/m);
assert.match(skill, /已拒绝(没有发生过 \/ 这类事都没有过)的目标不得换词重问;跳过的按服务器计划最多重问一次/);
assert.match(strategy, /跳过的线按服务器计划最多换一种问法再问一次/);
});