Files
Jyotisha/frontend/tests/rectification-varga-style-weight.test.ts
T
Jesse_ChenandCursor fef54b6e42
Independent Staging Quality Gate / validate (push) Successful in 11m54s
Independent Staging Quality Gate / publish (push) Successful in 8m14s
fix(rectification): downgrade yearless personality probes to tie-breakers (BUG-629)
Ask dated dasha probes first; D9/D10 and nakshatra wait until that pool is empty, score at half weight, and never eliminate. Skill 10.0.21.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 00:26:58 +08:00

264 lines
11 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 { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import {
buildCandidateContrastPacket,
conflictProbesFromContrast,
inspectDiscriminatorProbes,
} from "../src/lib/rectification-agentic/core/candidate-contrast-packet.ts";
import { asInferenceState } from "../src/lib/rectification-agentic/core/compose-receipt.ts";
import { decisionStateFingerprint } from "../src/lib/rectification-agentic/core/decision-fingerprint.ts";
import type { AnswerClass, ConflictProbe } from "../src/lib/rectification-agentic/core/types.ts";
function twoGroupStylePacket() {
return buildCandidateContrastPacket({
candidateSetVersion: "05:00-05:04",
candidateTimes: ["05:00", "05:04"],
transitions: [
{ layer: "d9", at: "05:04", from_sign: "巨蟹座", to_sign: "狮子座" },
],
});
}
function threeGroupStylePacket() {
return buildCandidateContrastPacket({
candidateSetVersion: "05:00-05:04",
candidateTimes: ["05:00", "05:03", "05:04"],
transitions: [
{ layer: "d10", at: "05:03", from_sign: "巨蟹座", to_sign: "狮子座" },
{ layer: "d10", at: "05:04", from_sign: "狮子座", to_sign: "处女座" },
],
});
}
function styleProbeFrom(packet: ReturnType<typeof buildCandidateContrastPacket>): ConflictProbe {
const probe = conflictProbesFromContrast(packet).find((item) => item.source === "varga_contrast");
assert.ok(probe);
return probe;
}
function absSupportDelta(probe: ConflictProbe, answer: AnswerClass, time: string): number {
const scores = Object.fromEntries(probe.candidate_ids.map((id) => [id, 10]));
const applied = applyProbeOutcome(scores, probe, answer);
return Math.abs(applied.deltas[time] ?? 0);
}
test("two-group varga_style A and B move their groups by the same absolute delta", () => {
const probe = styleProbeFrom(twoGroupStylePacket());
assert.equal(probe.choice_kind, "varga_style");
const yesGroup = probe.expected_outcomes.find((row) => row.answer_class === "yes")?.supports[0];
const weakGroup = probe.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.supports[0];
assert.ok(yesGroup);
assert.ok(weakGroup);
assert.notEqual(yesGroup, weakGroup);
const yesDelta = absSupportDelta(probe, "yes", yesGroup);
const weakDelta = absSupportDelta(probe, "weak_yes", weakGroup);
// 原值: 2 and 2(与带年月题同权)
// 新值: 1 and 1(仍相等)
// 原因: BUG-629 决策 2PROBE_WEIGHT.yearless = 0.5
assert.equal(yesDelta, 1);
assert.equal(weakDelta, 1);
assert.equal(yesDelta, weakDelta);
const unsure = applyProbeOutcome(
Object.fromEntries(probe.candidate_ids.map((id) => [id, 10])),
probe,
"unsure",
);
assert.ok(Object.values(unsure.deltas).every((value) => value === 0));
});
test("three-group varga_style A/B/C each carry full peer weight", () => {
const probe = styleProbeFrom(threeGroupStylePacket());
assert.equal(probe.choice_kind, "varga_style");
const scored = (["yes", "weak_yes", "no"] as const).map((answer) => {
const support = probe.expected_outcomes.find((row) => row.answer_class === answer)?.supports[0];
assert.ok(support, answer);
return absSupportDelta(probe, answer, support);
});
// 原值: [2, 2, 2]
// 新值: [1, 1, 1]
// 原因: BUG-629 决策 2,三组性格题同样减半
assert.deepEqual(scored, [1, 1, 1]);
});
test("old ConflictProbe receipts without choice_kind keep half-weight weak_yes", () => {
const produced = styleProbeFrom(twoGroupStylePacket());
const legacy: ConflictProbe = {
id: produced.id,
semantic_key: produced.semantic_key,
candidate_split_hash: produced.candidate_split_hash,
domain: produced.domain,
year: produced.year,
question: produced.question,
candidate_ids: produced.candidate_ids,
expected_outcomes: produced.expected_outcomes,
information_gain: produced.information_gain,
source: produced.source,
};
assert.equal(legacy.choice_kind, undefined);
const state = buildInferenceState({
range_start: "05:00",
range_end: "05:04",
candidates: [
{ id: "05:00", time: "05:00", relative_support: 34 },
{ id: "05:04", time: "05:04", relative_support: 33 },
],
events: [{ id: "e-rel", domain: "relationship", year: 2024, precision: "year" }],
probes: [legacy],
});
const loaded = asInferenceState(JSON.parse(JSON.stringify(state)));
assert.ok(loaded);
const loadedProbe = loaded.probes.find((item) => item.id === produced.id);
assert.ok(loadedProbe);
assert.equal(loadedProbe.choice_kind, undefined);
const weakGroup = loadedProbe.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.supports[0];
const yesGroup = loadedProbe.expected_outcomes.find((row) => row.answer_class === "yes")?.supports[0];
assert.ok(weakGroup);
assert.ok(yesGroup);
assert.equal(absSupportDelta(loadedProbe, "weak_yes", weakGroup), 1);
assert.equal(absSupportDelta(loadedProbe, "yes", yesGroup), 2);
assert.equal(loaded.candidate_set_id, state.candidate_set_id);
assert.equal(loaded.revision, state.revision);
});
test("varga_style B-option weak_yes counts as strong conflict; existence weak_yes does not", () => {
const style = styleProbeFrom(twoGroupStylePacket());
assert.equal(style.choice_kind, "varga_style");
const styleConflicted = style.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
assert.ok(styleConflicted);
let styleScores = Object.fromEntries(style.candidate_ids.map((id) => [id, 10]));
let styleCounts: Record<string, number> = {};
let eliminated = new Set<string>();
for (let round = 0; round < 3; round += 1) {
const applied = applyProbeOutcome(styleScores, style, "weak_yes", {
strongConflictCounts: styleCounts,
eliminatedIds: eliminated,
});
styleScores = { ...applied.scores };
styleCounts = { ...applied.strong_conflict_counts };
eliminated = new Set(applied.eliminated_ids);
}
// 原值: 三次 B 选项计 3 次强冲突并淘汰
// 新值: strong_conflict_count 仍为 0,不淘汰
// 原因: BUG-629 决策 2,性格题不计淘汰
assert.equal(styleCounts[styleConflicted], 0);
assert.equal(eliminated.has(styleConflicted), false);
const existencePacket = buildCandidateContrastPacket({
candidateSetVersion: "05:00-05:04",
candidateTimes: ["05:00", "05:04"],
transitions: [{ layer: "d9", at: "05:04" }],
});
const existence = conflictProbesFromContrast(existencePacket).find((item) => item.choice_kind === "existence");
assert.ok(existence);
const existenceConflicted = existence.expected_outcomes.find((row) => row.answer_class === "weak_yes")?.conflicts[0];
assert.ok(existenceConflicted);
let existenceScores = Object.fromEntries(existence.candidate_ids.map((id) => [id, 10]));
let existenceCounts: Record<string, number> = {};
for (let round = 0; round < 3; round += 1) {
const applied = applyProbeOutcome(existenceScores, existence, "weak_yes", {
strongConflictCounts: existenceCounts,
});
existenceScores = { ...applied.scores };
existenceCounts = { ...applied.strong_conflict_counts };
assert.equal(applied.eliminated_ids.length, 0);
}
assert.equal(existenceCounts[existenceConflicted], 0);
});
test("varga_style contrast writeback keeps style_options for the next round", () => {
const probe = styleProbeFrom(twoGroupStylePacket());
assert.equal(probe.choice_kind, "varga_style");
assert.ok((probe.style_options?.length ?? 0) >= 2);
assert.ok(probe.style_options?.every((item) => item.label.trim().length > 0));
});
test("engine varga.d9/d10 without style_options scores with the render effective kind", () => {
for (const semanticKey of ["varga.d9", "varga.d10"] as const) {
const packet = {
candidateSetVersion: "05:00-05:04",
vargaDifferences: [],
probes: [{
probeId: `contrast:${semanticKey}.engine-no-signs`,
candidateSetVersion: "05:00-05:04",
question: "那几年相处更接近哪一种?",
expectedOutcomes: [
{ outcomeId: "supports_05:00", supportsCandidateIds: ["05:00"], conflictsCandidateIds: ["05:04"] },
{ outcomeId: "supports_05:04", supportsCandidateIds: ["05:04"], conflictsCandidateIds: ["05:00"] },
],
candidateSplitHash: `05:00-05:04:${semanticKey}.engine-no-signs`,
informationGain: 1.2,
sourceFeatures: [{ technique: semanticKey.slice(6).toUpperCase(), calculationResultId: null }],
domain: "relationship",
year: null,
semanticKey,
choiceKind: "varga_style" as const,
}],
};
const scored = conflictProbesFromContrast(packet);
const inspected = inspectDiscriminatorProbes(packet);
assert.equal(scored.length, 1, semanticKey);
assert.equal(inspected.selected, null, semanticKey);
assert.equal(inspected.dropped.some((item) => (
item.semantic_key === semanticKey && item.reason === "yearless_ungrounded_contrast"
)), true, semanticKey);
assert.equal(scored[0]?.choice_kind, "existence", semanticKey);
}
});
test("replaying a varga_style probe keeps candidate_set_id and a monotonic revision", () => {
const probe = styleProbeFrom(twoGroupStylePacket());
const before = buildInferenceState({
range_start: "05:00",
range_end: "05:04",
candidates: [
{ id: "05:00", time: "05:00", relative_support: 34 },
{ id: "05:04", time: "05:04", relative_support: 33 },
],
events: [{ id: "e-rel", domain: "relationship", year: 2024, precision: "year" }],
probes: [probe],
});
const after = buildInferenceState({
range_start: before.range_start,
range_end: before.range_end,
candidates: before.candidates.map((item) => ({
id: item.id,
time: item.time,
relative_support: item.prior_score,
})),
events: before.events,
probes: before.probes,
previous: before,
answered_probes: [{
probe_id: probe.id,
semantic_key: probe.semantic_key,
candidate_split_hash: probe.candidate_split_hash,
answer_class: "weak_yes",
classified_from: "choice",
}],
});
assert.equal(after.candidate_set_id, before.candidate_set_id);
assert.ok(after.revision >= before.revision);
const beforeFp = decisionStateFingerprint({
caseId: "case-style-weight",
evidenceLedgerFingerprint: "fp-a",
candidateSetId: before.candidate_set_id,
inferenceRevision: before.revision,
answeredProbeIds: before.answered_probes.map((item) => item.probe_id),
scoringPolicyVersion: "policy-v2",
});
const afterFp = decisionStateFingerprint({
caseId: "case-style-weight",
evidenceLedgerFingerprint: "fp-a",
candidateSetId: after.candidate_set_id,
inferenceRevision: after.revision,
answeredProbeIds: after.answered_probes.map((item) => item.probe_id),
scoringPolicyVersion: "policy-v2",
});
assert.notEqual(afterFp, beforeFp);
});