55d3119c68
Keep confirmation fail-closed until the public AA set is ready, and rewrite Technique Audit from the attached VedAstro status instead of a hardcoded blocked row. Co-authored-by: Cursor <cursoragent@cursor.com>
208 lines
7.6 KiB
TypeScript
208 lines
7.6 KiB
TypeScript
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);
|
|
});
|
|
|