Files
Jyotisha/frontend/tests/rectification-chart-tier-caveats-consult.test.ts
T
Jesse_ChenandClaude Opus 5.5 2004a6edc1 feat(reports): a line under D9/D10 chart and table sections when the adopted segment left them undetermined (BUG-1139, T3)
Applied before the longform snapshot is hashed, Chinese and English bodies.
The engine payload is unchanged; legacy and unrectified reports store the
engine text byte-for-byte. Chart cards keep their grid (the line becomes the
card caption).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-01 10:15:10 +08:00

136 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { applyUnreliableVargasToWorkflowContext, unreliableVargasNotice } from "../src/lib/consultation-birth-time-mode.ts";
import { loadReportCandidateRange } from "../src/lib/report-candidate-range.ts";
import { clearAdoptedChartTierCache } from "../src/lib/rectification-adopted-chart-tiers.ts";
import { buildSegmentSummary, type SegmentMinute } from "../src/lib/rectification-agentic/core/segment-summary.ts";
import { runConsultationWorkflow, toAgentConsultationContext, toModelOutput, type ConsultationInput } from "../src/mastra/consultation-workflow.ts";
// Real engine consultation_workflow response for a public Rodden-AA chart.
const golden = JSON.parse(readFileSync(new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url), "utf8"));
const realWorkflow = golden.charts[0].workflow;
const userId = "11111111-1111-4111-8111-111111111111";
const caseId = "22222222-2222-4222-8222-222222222222";
const resultId = "33333333-3333-4333-8333-333333333333";
// Explicitly fictional profile (not a real person).
const profile = Object.freeze({
name: "虚构测试", birth_date: "2000-06-14", reported_birth_time: "09:10:00", active_birth_time: "09:05:00",
birth_time_source: "approximate", birth_time_status: "accepted", country_code: "CN", province_code: "130000",
city_code: "130400", district_code: "130406", latitude: 36.420487, longitude: 114.209936, timezone_offset: 8,
});
// Explicitly fictional ±60 summary: a 121-minute window blocks D9/D10 by width.
function wideSummary() {
const minutes: SegmentMinute[] = Array.from({ length: 121 }, (_, offset) => ({
offset, date: "2000-06-14", time: `${String(8 + Math.floor(offset / 60)).padStart(2, "0")}:${String(offset % 60).padStart(2, "0")}`,
signs: { D1: 4, D9: Math.floor(offset / 13) % 12, D10: Math.floor(offset / 12) % 12 },
}));
return buildSegmentSummary({ minutes, candidates: [{ time: minutes[60]!.time, score: 10 }], windowMinutes: 121, scanComplete: true });
}
function accountingClient(provenance: unknown, calls: string[] = []) {
return {
rpc: async (fn: string) => {
calls.push(fn);
if (fn === "read_report_candidate_range") return { data: { start_time: "08:00", end_time: "10:00" }, error: null };
if (fn === "get_agentic_rectification_case_dossier") return { data: { case: { case_id: caseId },
latest_result: { result_id: resultId, decision_receipt: { inference_state: { segment_summary: wideSummary() } } } }, error: null };
throw new Error(`unexpected RPC ${fn}`);
},
from(table: string) {
assert.equal(table, "profiles");
calls.push("profiles");
return { select: (columns: string) => { assert.equal(columns, "active_birth_provenance"); return {
eq: (column: string, value: string) => { assert.equal(column, "id"); assert.equal(value, userId); return {
maybeSingle: async () => ({ data: { active_birth_provenance: provenance }, error: null }) }; } }; } };
},
};
}
const segmentV1 = { contract: "segment-v1", case_id: caseId, result_id: resultId };
const legacyAdoption = { contract: "candidate-v2", case_id: caseId, result_id: resultId };
test("range loader: segment-v1 adds unreliable charts; legacy returns the identical range object", async () => {
clearAdoptedChartTierCache();
const segment = await loadReportCandidateRange(accountingClient(segmentV1), { userId });
assert.deepEqual(segment, { startTime: "08:00", endTime: "10:00", unreliableVargas: ["D9", "D10"] });
const legacy = await loadReportCandidateRange(accountingClient(legacyAdoption), { userId });
assert.equal(JSON.stringify(legacy), JSON.stringify({ startTime: "08:00", endTime: "10:00" }));
// An rpc-only client (no profile access) and a failing profile read never drop the range.
const rpcOnly = await loadReportCandidateRange({ rpc: accountingClient(segmentV1).rpc }, { userId });
assert.equal(JSON.stringify(rpcOnly), JSON.stringify({ startTime: "08:00", endTime: "10:00" }));
const broken = { ...accountingClient(segmentV1), from: () => { throw new Error("boom"); } };
assert.equal(JSON.stringify(await loadReportCandidateRange(broken, { userId })), JSON.stringify({ startTime: "08:00", endTime: "10:00" }));
});
test("chat tool input: segment-v1 carries unreliable_vargas; legacy tool input is byte-identical", async () => {
clearAdoptedChartTierCache();
const prepare = (provenance: unknown) => prepareConsultationRoute({
userId, mode: "verified_chart",
loadProfile: async () => profile,
loadCandidateRange: (id) => loadReportCandidateRange(accountingClient(provenance), { userId: id }),
reserve: async () => "reserved",
});
const segment = await prepare(segmentV1);
const legacy = await prepare(legacyAdoption);
assert.deepEqual(segment.serverChart?.toolInput.unreliable_vargas, ["D9", "D10"]);
assert.equal("unreliable_vargas" in (legacy.serverChart?.toolInput ?? {}), false);
const rest = Object.fromEntries(Object.entries(segment.serverChart!.toolInput).filter(([key]) => key !== "unreliable_vargas"));
assert.equal(JSON.stringify(rest), JSON.stringify(legacy.serverChart!.toolInput));
// Byte-identical snapshot of the legacy accepted tool input (red line 1).
assert.equal(JSON.stringify(legacy.serverChart!.toolInput),
'{"year":2000,"month":6,"day":14,"hour":9,"minute":5,"city":"中国 · 河北省 · 邯郸市 · 峰峰矿区","lat":36.420487,"lon":114.209936,"tz":8,"ayanamsa":"raman","declared_accuracy":"rectified","time_source":"rectified","birth_time_accuracy":"provisional","candidate_range":{"start_time":"08:00","end_time":"10:00"}}');
});
async function runWith(input: ConsultationInput) {
let body = "";
const originalFetch = globalThis.fetch;
globalThis.fetch = async (_url, init) => {
body = typeof init?.body === "string" ? init.body : "";
return new Response(JSON.stringify(realWorkflow), { status: 200 });
};
try {
return { context: await runConsultationWorkflow(input), body: JSON.parse(body) as Record<string, unknown> };
} finally {
globalThis.fetch = originalFetch;
}
}
test("workflow: unreliable_vargas never reaches the engine; the model packet gets the policy and one sentence", async () => {
const base: ConsultationInput = {
year: 2000, month: 6, day: 14, hour: 9, minute: 5, lat: 39.9, lon: 116.4, tz: 8, ayanamsa: "raman",
city: "北京", question: "我的婚姻怎么样?", theme: "marriage", entryMode: "direct_chart",
declared_accuracy: "rectified", time_source: "rectified", birth_time_accuracy: "provisional",
candidate_range: { start_time: "08:00", end_time: "10:00" },
};
const legacy = await runWith(base);
const segment = await runWith({ ...base, unreliable_vargas: ["D9", "D10"] });
// The engine payload is identical with or without the server-owned field.
assert.equal(JSON.stringify(segment.body), JSON.stringify(legacy.body));
assert.equal("unreliable_vargas" in segment.body, false);
// Legacy context equals the untouched engine projection.
assert.equal(JSON.stringify(legacy.context), JSON.stringify(applyUnreliableVargasToWorkflowContext(legacy.context, undefined)));
const policy = segment.context.consumer_context.answer_policy as Record<string, unknown>;
assert.deepEqual(policy.unreliable_vargas, ["D9", "D10"]);
assert.ok((policy.deterministic_claims_forbidden_for as string[]).includes("D9"));
const notice = unreliableVargasNotice(["D9", "D10"]);
assert.equal(notice, "你的出生时间范围里,婚恋(D9)、事业(D10)的上升会变,这几张盘判断不了;涉及它们的部分只看本命盘和大运,不按它们下结论。");
assert.ok(String(segment.context.consumer_context.user_facing_limitation).endsWith(notice));
const legacyPacket = toModelOutput(toAgentConsultationContext(legacy.context));
const segmentPacket = toModelOutput(toAgentConsultationContext(segment.context));
assert.deepEqual((segmentPacket.evidence_contract.answer_policy as Record<string, unknown>).unreliable_vargas, ["D9", "D10"]);
assert.ok(String(segmentPacket.evidence_contract.user_facing_limitation).includes("婚恋(D9)"));
assert.equal("unreliable_vargas" in (legacyPacket.evidence_contract.answer_policy as Record<string, unknown>), false);
assert.ok(!String(legacyPacket.evidence_contract.user_facing_limitation ?? "").includes("婚恋(D9)"));
});
test("notice wording for one chart and for D1", () => {
assert.equal(unreliableVargasNotice(["D9"]), "你的出生时间范围里,婚恋(D9)的上升会变,这张盘判断不了;涉及它的部分只看本命盘和大运,不按它下结论。");
assert.match(unreliableVargasNotice(["D1", "D9"]), /不按上升和宫位下结论/);
});