Files
Jyotisha/frontend/tests/rectification-tie-break-entry-20260913.test.ts
T
jesse-ux cfb41daf3d
Independent Staging Quality Gate / validate (push) Failing after 6m28s
Independent Staging Quality Gate / publish (push) Skipped
feat(rectification): 出卡加精度门槛,补经历改成系统点名
宽度超过 10 分钟或头名并列时不再出交付卡,改为按大运边界逐条问、
用类型芯片和年/月选择器录入。跳过的线换问法再问一次;答「这类事
都没有过」的不再问。用户说「没有了」仍立刻给目前范围。Skill 10.0.27。

BUG-740~743
2026-09-16 18:35:27 +08:00

203 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 { 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");
assert.match(skill, /^version: 10\.0\.27$/m);
assert.match(skill, /已拒绝(没有发生过 \/ 这类事都没有过)的目标不得换词重问;跳过的按服务器计划最多重问一次/);
assert.match(strategy, /跳过的线按服务器计划最多换一种问法再问一次/);
});