Engine by_time ledgers now include the full candidate set so posterior columns can look up fit and windows. Delivery turns keep up to three sentences after idle persist; the card subtracts repeated traits and folds the board chart. Co-authored-by: Cursor <cursoragent@cursor.com>
168 lines
6.1 KiB
TypeScript
168 lines
6.1 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
|
|
import {
|
|
changedHouseNumbers,
|
|
diffRectificationBoard,
|
|
groupWindowTransitions,
|
|
houseTableToNorthIndianChart,
|
|
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";
|
|
import { RectificationBoard } from "../src/components/rectification-board.tsx";
|
|
|
|
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 · 上升金牛座");
|
|
});
|
|
|
|
test("board shows a north-indian chart and folds 换升时刻 by default", () => {
|
|
const result = snapshot({
|
|
time: "04:45",
|
|
lagna: "金牛座",
|
|
transitions: [transition("d9", "04:57"), transition("hora", "04:47")],
|
|
});
|
|
assert.ok(result);
|
|
const chart = houseTableToNorthIndianChart(result.houseTable!);
|
|
assert.equal(chart.houses.length, 12);
|
|
assert.equal(chart.houses[0]?.houseNumber, 1);
|
|
assert.equal(chart.houses[0]?.sign, "金牛座");
|
|
const html = renderToStaticMarkup(createElement(RectificationBoard, {
|
|
result,
|
|
declaredTime: "04:45",
|
|
savedStatus: null,
|
|
diff: diffRectificationBoard(null, result),
|
|
compact: false,
|
|
open: true,
|
|
boardId: "board",
|
|
titleId: "board-title",
|
|
onClose: () => undefined,
|
|
}));
|
|
assert.match(html, /role="img"/);
|
|
assert.match(html, /当前本命盘,上升金牛座/);
|
|
assert.match(html, /换升时刻 · 2 个/);
|
|
assert.match(html, /<details/);
|
|
assert.doesNotMatch(html, /<details[^>]*\sopen/);
|
|
assert.match(html, /本命宫位表/);
|
|
assert.doesNotMatch(html, /<section[^>]*aria-label="换升时刻"/);
|
|
});
|