Files
Jyotisha/frontend/tests/rectification-board-model.test.ts
T
Jesse_Chen e07d1bada4 fix(rectification): show natal houses and lagna changes in a live right rail
The board was live data sitting under chat, so each new event looked like nothing changed. Keep candidate cards in the transcript and group 换升 by minute for the side panel.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 14:24:05 +08:00

133 lines
4.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
changedHouseNumbers,
diffRectificationBoard,
groupWindowTransitions,
rectificationBoardPeekCopy,
} from "../src/lib/rectification-board-model.ts";
import { parseRectificationCandidateResult } from "../src/lib/rectification-candidate-result.ts";
import type { WindowScanTransition } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
const CANDIDATE_ID = "88888888-8888-4888-8888-888888888881";
const SECOND_CANDIDATE_ID = "88888888-8888-4888-8888-888888888882";
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
function transition(layer: WindowScanTransition["layer"], at: string): WindowScanTransition {
return { layer, at, user_meaning: `${layer}${at} 发生变化` };
}
function houses(lagna: string, seventh = lagna) {
return Array.from({ length: 12 }, (_, index) => ({
house: index + 1,
sign: index === 6 ? seventh : lagna,
occupants: index === 0 ? ["太阳"] : [],
}));
}
function snapshot(input: Readonly<{
time: string;
lagna: string;
seventh?: string;
transitions?: readonly WindowScanTransition[];
}>) {
return parseRectificationCandidateResult({
resultId: "11111111-1111-4111-8111-111111111111",
candidates: [
{ candidateId: CANDIDATE_ID, rank: 1, time: input.time, relativeSupport: 34, tiedMinuteCount: 1 },
{ candidateId: SECOND_CANDIDATE_ID, rank: 2, time: "05:08", relativeSupport: 33, tiedMinuteCount: 1 },
{ candidateId: THIRD_CANDIDATE_ID, rank: 3, time: "05:09", relativeSupport: 33, tiedMinuteCount: 1 },
],
overallConfidence: "low",
selectionAllowed: true,
confirmationAllowed: false,
representativeTime: input.time,
selectedTime: null,
selectionKind: null,
decisionReceipt: {
house_table: {
time: input.time,
lagna: input.lagna,
houses: houses(input.lagna, input.seventh ?? input.lagna),
},
window_scan: {
scanned: true,
d9_lagna_count: 2,
d10_lagna_count: 1,
transitions: input.transitions ?? [],
},
},
});
}
test("groups 换升时刻 by clock minute and splits scoring layers from display layers", () => {
const grouped = groupWindowTransitions([
transition("d11", "04:47"),
transition("hora", "04:47"),
transition("d24", "04:47"),
transition("d10", "04:48"),
transition("pranapada", "05:14"),
transition("d9", "04:57"),
]);
assert.deepEqual(grouped.map((row) => row.at), ["04:47", "04:48", "04:57", "05:14"]);
assert.deepEqual(grouped[0], {
at: "04:47",
scoringLayers: ["d24", "d11"],
displayLayers: ["hora"],
});
assert.deepEqual(grouped[1]?.scoringLayers, ["d10"]);
assert.deepEqual(grouped[3], {
at: "05:14",
scoringLayers: [],
displayLayers: ["pranapada"],
});
});
test("marks only houses whose sign or occupants actually changed", () => {
const before = snapshot({ time: "04:45", lagna: "金牛座" })?.houseTable ?? null;
const after = snapshot({ time: "04:48", lagna: "金牛座", seventh: "天秤座" })?.houseTable ?? null;
assert.deepEqual(changedHouseNumbers(before, after), [7]);
assert.deepEqual(changedHouseNumbers(null, after), []);
});
test("announces the first board fill and later house or 换升 changes without dumping the table", () => {
const first = snapshot({
time: "04:45",
lagna: "金牛座",
transitions: [transition("d9", "04:57")],
});
const second = snapshot({
time: "04:48",
lagna: "金牛座",
seventh: "天秤座",
transitions: [transition("d9", "04:57"), transition("d10", "04:48")],
});
assert.ok(first && second);
const opened = diffRectificationBoard(null, first);
assert.equal(opened.liveMessage, "当前本命宫位已按 04:45 排出,上升金牛座。");
assert.deepEqual(opened.houseNumbers, []);
assert.equal(opened.timeChanged, false);
const updated = diffRectificationBoard(first, second);
assert.equal(updated.timeChanged, true);
assert.deepEqual(updated.houseNumbers, [7]);
assert.deepEqual(updated.transitionMinutes, ["04:48"]);
assert.match(updated.liveMessage, /本命宫位已按 04:48 重算(上升 金牛座)。/);
assert.match(updated.liveMessage, /第7宫已更新/);
assert.match(updated.liveMessage, /换升时刻已更新/);
assert.doesNotMatch(updated.liveMessage, /太阳|第1宫|第2宫|下面是本轮/);
});
test("peek copy stays a control label, not a second dump of the house table", () => {
const empty = rectificationBoardPeekCopy(null);
assert.equal(empty.title, "当前盘面");
assert.equal(empty.detail, "补充经历后会在这里更新");
const filled = rectificationBoardPeekCopy(snapshot({ time: "04:48", lagna: "金牛座" }));
assert.equal(filled.title, "当前盘面");
assert.equal(filled.detail, "04:48 · 上升金牛座");
});