import assert from "node:assert/strict"; import test from "node:test"; import { isRecommendedRectificationCandidate, parseRectificationCandidateResult, } from "../src/lib/rectification-candidate-result.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"; const camelCaseSnapshot = { resultId: "11111111-1111-4111-8111-111111111111", candidates: [ { candidateId: CANDIDATE_ID, rank: 1, time: "05:07", 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: null, selectedTime: null, selectionKind: null, }; test("parses the camelCase Candidate Snapshot returned by the Case API", () => { const result = parseRectificationCandidateResult(camelCaseSnapshot); assert.ok(result); assert.equal(result.resultId, camelCaseSnapshot.resultId); assert.equal(result.candidates[0]?.time, "05:07"); assert.equal(result.candidates[0]?.candidateId, CANDIDATE_ID); assert.equal(result.candidates[0]?.relativeSupport, 34); assert.equal(result.selectionAllowed, true); assert.equal(result.confirmationAllowed, false); }); test("low-confidence near ties never receive a recommendation badge", () => { const result = parseRectificationCandidateResult(camelCaseSnapshot); assert.ok(result); assert.equal(isRecommendedRectificationCandidate(result, result.candidates[0]!), false); }); test("only an unselected representative receives a recommendation badge", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, overallConfidence: "medium", confirmationAllowed: false, representativeTime: "05:07", }); assert.ok(result); assert.equal(isRecommendedRectificationCandidate(result, result.candidates[0]!), true); assert.equal(isRecommendedRectificationCandidate(result, result.candidates[1]!), false); const adopted = { ...result, selectedTime: "05:07" }; assert.equal(isRecommendedRectificationCandidate(adopted, adopted.candidates[0]!), false); }); test("fails closed instead of treating an HH:MM display value as a candidate id", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, candidates: [{ ...camelCaseSnapshot.candidates[0], candidateId: "05:07", }], }); assert.equal(result, null); }); test("reads the representative house table from the decision receipt", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, representativeTime: "05:07", decisionReceipt: { house_table: { time: "05:07", lagna: "金牛座", houses: Array.from({ length: 12 }, (_, index) => ({ house: index + 1, sign: "金牛座", occupants: index === 0 ? ["太阳"] : [], })), }, }, }); assert.equal(result?.houseTable?.time, "05:07"); assert.equal(result?.houseTable?.lagna, "金牛座"); assert.equal(result?.houseTable?.houses[0]?.occupants[0], "太阳"); }); test("drops a house table that still carries longitudes", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, house_table: { time: "05:07", lagna: "金牛座", houses: Array.from({ length: 12 }, (_, index) => ({ house: index + 1, sign: "金牛座", occupants: index === 0 ? ["太阳 123.4 lon"] : [], })), }, }); assert.equal(result?.houseTable, null); }); test("reads dasha ledger and change minutes without scores or D9 labels", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, decisionReceipt: { window_scan: { scanned: true, d9_lagna_count: 2, d10_lagna_count: 1, transitions: [{ layer: "d9", at: "05:14", user_meaning: "白羊座在 05:14 换成天蝎", }], }, event_dasha_ledger: [{ summary: "入职", match: "strong", match_label: "强相关", user_meaning: "入职:强相关 12.5 points", }], dasha_agreement: { status: "conflict", vimshottari_top: "05:07", narayana_top: "05:08", user_meaning: "主限更偏向 05:07,分盘大运更偏向 05:08。冲突时不能按更高把握收口。", }, precision_stage: { current: "d9_refine", can_stop: true, user_meaning: "关系盘仍会换升。", unique_minute_claim: false, }, }, }); assert.equal(result?.windowTransitions[0]?.user_meaning, "D9 在 05:14 发生变化"); assert.equal(result?.eventDashaLedger[0]?.match_label, "强相关"); assert.equal(result?.eventDashaLedger[0]?.user_meaning, "入职:强相关"); assert.equal(result?.dashaAgreement?.status, "conflict"); assert.equal(result?.precisionStage?.current, "d9_refine"); assert.doesNotMatch(JSON.stringify(result?.windowTransitions), /白羊|天蝎|points/); assert.doesNotMatch(result?.eventDashaLedger[0]?.user_meaning ?? "", /points/); }); function twelveHouses(sign: string, occupant?: string) { return Array.from({ length: 12 }, (_, index) => ({ house: index + 1, sign, occupants: index === 0 && occupant ? [occupant] : [], })); } test("adopted minute recasts the house table and keeps technique audit collapsed-safe", () => { const result = parseRectificationCandidateResult({ ...camelCaseSnapshot, representativeTime: "05:07", selectedTime: "05:08", decisionReceipt: { house_table: { time: "05:07", lagna: "金牛座", houses: twelveHouses("金牛座", "太阳") }, house_tables_by_time: { "05:07": { time: "05:07", lagna: "金牛座", houses: twelveHouses("金牛座", "太阳") }, "05:08": { time: "05:08", lagna: "双子座", houses: twelveHouses("双子座", "月亮") }, }, natal_recast: { time: "05:07", lagna: "金牛座", user_meaning: "本命宫位已按 05:07 重算(上升 金牛座)。下面是本轮实际执行的技法,不能当作唯一分钟确认。", unique_minute_claim: false, confirmation_allowed: false, }, technique_audit_table: [ { technique: "D1 本命盘", status: "executed", note: "本轮已按该分钟重算本命宫位。" }, { technique: "KP 宫头", status: "blocked", note: "KP 宫头本轮未计算。" }, { technique: "唯一分钟确认", status: "blocked", note: "采用不等于确认唯一分钟。" }, ], precision_stage: { current: "d4_refine", can_stop: true, user_meaning: "居所盘仍会换升。", unique_minute_claim: false, }, }, }); assert.equal(result?.houseTable?.time, "05:08"); assert.equal(result?.houseTable?.lagna, "双子座"); assert.equal(result?.houseTablesByTime["05:08"]?.lagna, "双子座"); assert.equal(result?.natalRecast?.time, "05:08"); assert.equal(result?.natalRecast?.lagna, "双子座"); assert.equal(result?.natalRecast?.unique_minute_claim, false); assert.equal(result?.techniqueAudit.length, 3); assert.equal(result?.techniqueAudit[2]?.status, "blocked"); assert.equal(result?.precisionStage?.current, "d4_refine"); assert.equal(result?.confirmationGate.confirmation_allowed, false); assert.equal(result?.confirmationGate.blockers.some((item) => item.id === "public_aa_holdout" && item.status === "not_ready"), true); });