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 · 上升金牛座"); });