Files
Jyotisha/frontend/tests/consultation-birth-accuracy.test.ts
T
Jesse_Chen 56daf6be49 fix(consult): pass birth-time accuracy instead of treating every chart as unrectified
Hospital records and adopted rectification times were still fed to the model as not_auto_rectified because the chart request omitted declared_accuracy/time_source and mastra hardcoded the boundary. Map profile truth into the engine request, keep rectified for accepted/confirmed active times only, and leave window/general guards unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 01:21:40 +08:00

288 lines
11 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { prepareConsultationRoute } from "../src/lib/consultation-route-service.ts";
import {
createBirthTimeModeOutputGuard,
shouldRunBirthChartWorkflow,
shouldRunDeclaredWindowWorkflow,
UNVERIFIED_BIRTH_TIME_NOTICE,
applyBirthTimeModeToWorkflowContext,
unverifiedBirthTimeNotice,
} from "../src/lib/consultation-birth-time-mode.ts";
import {
consultationInputSchema,
runConsultationWorkflow,
toAgentConsultationContext,
} from "../src/mastra/consultation-workflow.ts";
const profile = Object.freeze({
name: "岳辰",
birth_date: "1997-08-08",
reported_birth_time: "05:30:00",
active_birth_time: "05:18:00",
birth_time_source: "approximate",
birth_time_status: "candidate",
country_code: "CN",
province_code: "130000",
city_code: "130400",
district_code: "130406",
latitude: 36.420487,
longitude: 114.209936,
timezone_offset: 8,
});
const RANGE_INTERACTION = /按时间范围|candidate windows|not_auto_rectified/;
function agentContext(rectification: Record<string, unknown>) {
return toAgentConsultationContext({
success: true,
question: "事业如何",
chart: { modules: {} },
routing: { primary_theme: "career" },
consumer_context: {
route: "career",
core_status: "ready",
available_layers: ["D1", "D9", "D10"],
missing_route_layers: [],
hard_blockers: [],
answer_policy: {
can_answer_direction: true,
can_answer_precise_timing: true,
},
},
rectification,
});
}
async function chartRoute(
mode: "verified_chart" | "unverified_birth_time",
overrides: Record<string, unknown>,
) {
return prepareConsultationRoute({
userId: "user-1",
mode,
loadProfile: async () => ({ ...profile, ...overrides }),
reserve: async () => "reserved",
});
}
test("verified_chart users send rectified accuracy and do not consume a not_auto_rectified boundary", async () => {
const prepared = await chartRoute("verified_chart", {
birth_time_status: "accepted",
birth_time_source: "approximate",
});
const toolInput = prepared.serverChart?.toolInput;
assert.equal(toolInput?.declared_accuracy, "rectified");
const parsed = consultationInputSchema.parse({
...toolInput,
question: "事业如何",
theme: "career",
entryMode: "direct_chart",
});
assert.equal(parsed.declared_accuracy, "rectified");
let requestBody = "";
const originalFetch = globalThis.fetch;
globalThis.fetch = async (_input, init) => {
requestBody = typeof init?.body === "string" ? init.body : "";
return new Response(JSON.stringify({
success: true,
chart: {},
routing: {},
consumer_context: {
route: "career",
core_status: "ready",
available_layers: [],
missing_route_layers: [],
hard_blockers: [],
answer_policy: { can_answer_direction: true, can_answer_precise_timing: true },
},
}), { status: 200 });
};
try {
await runConsultationWorkflow(parsed);
} finally {
globalThis.fetch = originalFetch;
}
assert.equal(JSON.parse(requestBody).declared_accuracy, "rectified");
const context = agentContext({
effective_accuracy: "rectified",
summary: { headline: "出生时间风险较低,可进入完整解盘" },
enabled_vargas: { D1: "enabled", D9: "enabled", D10: "enabled" },
lagna_boundary: { is_sensitive: false },
});
assert.notEqual(context.rectification.boundary, "not_auto_rectified");
assert.doesNotMatch(JSON.stringify(context), RANGE_INTERACTION);
});
test("hospital_record users send minute/hospital and never receive rectified", async () => {
const prepared = await chartRoute("unverified_birth_time", {
birth_time_source: "hospital_record",
birth_time_status: "reported",
active_birth_time: "05:18:00",
});
const toolInput = prepared.serverChart?.toolInput;
assert.equal(toolInput?.declared_accuracy, "minute");
assert.equal(toolInput?.time_source, "hospital");
assert.notEqual(toolInput?.declared_accuracy, "rectified");
const mapper = await import("../src/lib/consultation-route-service.ts");
assert.equal(typeof mapper.declaredBirthAccuracyFromProfile, "function");
assert.deepEqual(mapper.declaredBirthAccuracyFromProfile({
birthTimeStatus: "reported",
birthTimeSource: "hospital_record",
hasActiveBirthTime: true,
}), { declaredAccuracy: "minute", timeSource: "hospital" });
});
test("family_exact and approximate keep the documented downgrade without range-interaction copy", async () => {
const family = await chartRoute("unverified_birth_time", {
birth_time_source: "family_exact",
birth_time_status: "reported",
active_birth_time: null,
});
assert.equal(family.serverChart?.toolInput.declared_accuracy, "minute");
assert.equal(family.serverChart?.toolInput.time_source, "family_clear");
assert.notEqual(family.serverChart?.toolInput.declared_accuracy, "rectified");
const approximate = await chartRoute("unverified_birth_time", {
birth_time_source: "approximate",
birth_time_status: "reported",
active_birth_time: null,
});
assert.equal(approximate.serverChart?.toolInput.declared_accuracy, "15min");
assert.notEqual(approximate.serverChart?.toolInput.declared_accuracy, "rectified");
const familyContext = agentContext({
effective_accuracy: "5min",
summary: { headline: "按填报时间排盘,分钟敏感结论已标注精度" },
enabled_vargas: { D1: "enabled", D9: "enabled", D10: "enabled" },
lagna_boundary: { is_sensitive: false },
});
assert.notEqual(familyContext.rectification.boundary, "not_auto_rectified");
assert.doesNotMatch(JSON.stringify(familyContext), RANGE_INTERACTION);
const approximateContext = agentContext({
effective_accuracy: "15min",
summary: { headline: "按填报时间排盘,分钟敏感结论已标注精度" },
enabled_vargas: { D1: "enabled", D9: "enabled", D10: "enabled_with_warning" },
lagna_boundary: { is_sensitive: false },
});
assert.notEqual(approximateContext.rectification.boundary, "not_auto_rectified");
assert.doesNotMatch(JSON.stringify(approximateContext), RANGE_INTERACTION);
});
test("rectified accuracy is reserved for accepted or confirmed profiles with an active birth time", async () => {
const mapper = await import("../src/lib/consultation-route-service.ts");
const map = mapper.declaredBirthAccuracyFromProfile;
assert.equal(typeof map, "function");
assert.deepEqual(map({
birthTimeStatus: "accepted",
birthTimeSource: "approximate",
hasActiveBirthTime: true,
}), { declaredAccuracy: "rectified", timeSource: "rectified" });
assert.deepEqual(map({
birthTimeStatus: "confirmed",
birthTimeSource: "hospital_record",
hasActiveBirthTime: true,
}), { declaredAccuracy: "rectified", timeSource: "rectified" });
assert.notEqual(map({
birthTimeStatus: "accepted",
birthTimeSource: "hospital_record",
hasActiveBirthTime: false,
}).declaredAccuracy, "rectified");
assert.notEqual(map({
birthTimeStatus: "candidate",
birthTimeSource: "hospital_record",
hasActiveBirthTime: true,
}).declaredAccuracy, "rectified");
assert.notEqual(map({
birthTimeStatus: "reported",
birthTimeSource: "family_exact",
hasActiveBirthTime: true,
}).declaredAccuracy, "rectified");
});
test("declared_birth_window and general_no_birth_time never enter the natal accuracy mapper", async () => {
const windowed = await prepareConsultationRoute({
userId: "user-1",
mode: "declared_birth_window",
loadProfile: async () => ({
...profile,
reported_birth_time: null,
active_birth_time: null,
birth_time_source: "period_only",
birth_time_period: "evening",
birth_time_status: "reported",
}),
reserve: async () => "reserved",
});
assert.equal(windowed.consultationMode, "declared_birth_window");
assert.equal(windowed.serverChart, null);
assert.equal("declared_accuracy" in (windowed.declaredWindow?.toolInput ?? {}), false);
assert.equal("time_source" in (windowed.declaredWindow?.toolInput ?? {}), false);
assert.equal(shouldRunBirthChartWorkflow("declared_birth_window"), false);
assert.equal(shouldRunDeclaredWindowWorkflow("declared_birth_window"), true);
assert.equal(shouldRunBirthChartWorkflow("general_no_birth_time"), false);
assert.equal(shouldRunDeclaredWindowWorkflow("general_no_birth_time"), false);
});
test("unverified notices grade by source without changing the output guards", () => {
assert.equal(unverifiedBirthTimeNotice("hospital_record"), "使用你填报的出生时间(医院记录)排盘");
assert.equal(unverifiedBirthTimeNotice("family_exact"), "使用你填报的出生时间排盘;分钟敏感结论的置信度已降低。");
assert.equal(unverifiedBirthTimeNotice("approximate"), UNVERIFIED_BIRTH_TIME_NOTICE);
assert.equal(unverifiedBirthTimeNotice("unknown"), UNVERIFIED_BIRTH_TIME_NOTICE);
const guarded = applyBirthTimeModeToWorkflowContext({
success: true,
consumer_context: {
core_status: "ready",
answer_policy: { can_answer_direction: true, can_answer_precise_timing: true },
},
}, "unverified_birth_time", { birthTimeSource: "hospital_record" });
assert.equal(
(guarded.consumer_context as { birth_time_notice?: string }).birth_time_notice,
"使用你填报的出生时间(医院记录)排盘",
);
assert.equal(guarded.consumer_context.answer_policy.can_answer_precise_timing, true);
});
test("output guards and window/general instruction seams stay byte-stable", () => {
const modeSource = readFileSync(new URL("../src/lib/consultation-birth-time-mode.ts", import.meta.url), "utf8");
const routeSource = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
assert.match(modeSource, /if \(mode === "general_no_birth_time"\) return guardGeneralNoBirthTimeOutput\(text\)/);
assert.match(
modeSource,
/if \(mode === "declared_birth_window" \|\| !canAnswerPreciseTiming\) \{[\s\S]*return guardPreciseTimingOutput\(text\);/,
);
assert.equal(UNVERIFIED_BIRTH_TIME_NOTICE.includes("未校正填报时间"), true);
const general = createBirthTimeModeOutputGuard("general_no_birth_time", false)(
"D9 在印度占星中通常用于观察婚姻与法则层面的成熟。\n你的上升是巨蟹座。",
);
assert.match(general, /D9 在印度占星中通常用于观察婚姻与法则层面的成熟/);
assert.doesNotMatch(general, /你的上升是巨蟹座/);
const windowed = createBirthTimeModeOutputGuard("declared_birth_window", false)(
"Rahu 大运为 2013年11月21日 至 2031年11月22日。",
);
assert.match(windowed, /具体时间已省略/);
assert.match(routeSource, /function generalNoMinuteInstruction\(hasPublicDaily: boolean\) \{/);
assert.match(routeSource, /function declaredWindowInstruction\(\) \{/);
assert.match(
routeSource,
/当前没有具体出生分钟。不得计算或推断个人星盘,也不得补 00:00、时段中点或任何候选分钟。/,
);
assert.match(
routeSource,
/当前是声明出生窗口咨询,没有单一出生分钟。如需个人结构结论,必须调用 run-jyotish-window-consultation。/,
);
});