fix(rectification): scan chart boundaries in the user's ayanamsa (BUG-1307, BUG-1309)

This commit is contained in:
jesse-ux
2026-10-12 14:41:13 +08:00
parent b9a2866a22
commit ce4e98036d
12 changed files with 252 additions and 25 deletions
@@ -8,7 +8,8 @@ import test from "node:test";
import { buildOpeningBrief } from "../src/lib/rectification-agentic/v9/agent-run-messages.ts";
import { orderDiscriminatorsForNearestBoundary } from "../src/lib/rectification-agentic/v9/method-followup.ts";
import { parseV9CaseDossier } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import { openingFactsFromBoundaryPayload, resolveOpeningBoundaryFacts } from "../src/lib/rectification-agentic/opening-boundary.ts";
import { fetchOpeningBoundary, openingFactsFromBoundaryPayload, resolveOpeningBoundaryFacts } from "../src/lib/rectification-agentic/opening-boundary.ts";
import { scanCaseSegments } from "../src/lib/rectification-agentic/v9/segment-scan.ts";
import {
nearestBoundaryFromTransitions,
nearestBoundaryProbesEnabled,
@@ -61,6 +62,55 @@ test("near, under a minute, stable, and only the birth chart", () => {
assert.equal(isAcceptableOpeningBody(`${near}再补一句。`, RANGE), false);
});
test("d1 change is named in the same sentence, and no d1 change keeps today's sentence", () => {
const changed = openingFactsFromBoundaryPayload({
found: true,
distance_minutes: 4,
layers: [{ layer: "d1" }, { layer: "d12" }, { layer: "d10" }],
}, false);
assert.equal(changed.d1Changed, true);
assert.deepEqual(changed.topics, ["工作", "父母"]);
assert.equal(openingBoundarySentence(changed), "你填的时间离最近会换的地方差 4 分钟,这里连本命上升都换,两边不一样的是工作、父母。");
const onlyBirth = openingFactsFromBoundaryPayload({
found: true,
distance_minutes: 0,
layers: [{ layer: "d1" }],
}, true);
assert.equal(onlyBirth.d1Changed, true);
assert.deepEqual(onlyBirth.topics, []);
assert.equal(onlyBirth.askFirst, false);
assert.equal(openingBoundarySentence(onlyBirth), "你填的时间离最近会换的地方不到 1 分钟,这里连本命上升都换。");
assert.equal(
openingBoundarySentence({ kind: "near", distanceMinutes: 2, topics: [], d1Changed: true, askFirst: true }),
"你填的时间离最近会换的地方差 2 分钟,这里连本命上升都换。",
);
const asked = openingBoundarySentence({ kind: "near", distanceMinutes: 2, topics: ["婚恋"], d1Changed: true, askFirst: true });
assert.equal(asked, "你填的时间离最近会换的地方差 2 分钟,这里连本命上升都换,两边不一样的是婚恋,接下来先问这几件。");
const unchanged = openingFactsFromBoundaryPayload({
found: true,
distance_minutes: 4,
layers: [{ layer: "d12" }, { layer: "d10" }],
}, false);
assert.equal(unchanged.d1Changed, false);
assert.equal(openingBoundarySentence(unchanged), "你填的时间离最近会换的地方差 4 分钟,两边不一样的是工作、父母。");
assert.equal(
openingBoundarySentence({ kind: "near", distanceMinutes: 0, topics: ["婚恋"] }),
"你填的时间离最近会换的地方不到 1 分钟,两边不一样的是婚恋。",
);
assert.equal(openingBoundarySentence({ kind: "stable" }), "你填的时间附近这些盘都不换,先从经历开始。");
assert.equal(openingBoundarySentence({ kind: "period_only" }), null);
assert.equal(openingBoundarySentence({ kind: "unavailable" }), null);
const askedFacts = { kind: "near" as const, distanceMinutes: 2, topics: ["婚恋"], d1Changed: true, askFirst: true };
for (const facts of [changed, onlyBirth, askedFacts, unchanged]) {
const text = openingSpokenBody(RANGE, null, facts);
assertPlain(text);
assert.equal(isAcceptableOpeningBody(text, RANGE, null, facts), true);
assert.equal(text.includes("D1"), false);
assert.equal(text.split(/(?<=。)/).filter(Boolean).length, 3);
}
assert.match(openingSpokenBody(RANGE, null, changed), /这里连本命上升都换,两边不一样的是工作、父母。/);
});
test("topics come from the layer allowlist, not the payload string", () => {
assert.deepEqual(topicsForLayers(["d12", "d10", "d1", "kp1", "nope"]), ["工作", "父母"]);
const facts = openingFactsFromBoundaryPayload({
@@ -136,3 +186,51 @@ test("period_only and a missing clock do not scan", async () => {
assert.match(withSentence, /原样放在这两句之间/);
assert.match(brief, /正文不用大运/);
});
test("opening and segment scans send the snapshot ayanamsa", async (t) => {
const bodies: Array<Record<string, unknown>> = [];
t.mock.method(globalThis, "fetch", async (_endpoint: unknown, init?: RequestInit) => {
const body = JSON.parse(String(init?.body)) as {
start_at: string;
end_at: string;
ayanamsa?: string;
boundary_charts?: unknown;
};
bodies.push(body);
const start = Date.parse(`${body.start_at}:00Z`);
const end = Date.parse(`${body.end_at}:00Z`);
const width = (end - start) / 60000 + 1;
const minutes = Array.from({ length: width }, (_, offset) => {
const stamp = new Date(start + offset * 60000).toISOString().slice(0, 16);
return { offset, date: stamp.slice(0, 10), time: stamp.slice(11), signs: { D1: 1, D9: 2, D10: 3 } };
});
return Response.json({
start_at: body.start_at,
end_at: body.end_at,
ayanamsa: body.ayanamsa,
node_mode: "mean",
step_minutes: 1,
window_minutes: width,
minutes,
nearest_boundary: { found: false, reason: "no_transition" },
});
});
const base = {
birth_date: "2000-06-14",
latitude: 40,
longitude: -74,
timezone_offset: -4,
reported_birth_time: "09:10",
};
const range = { start_time: "09:00", end_time: "09:06" };
await fetchOpeningBoundary({ snapshot: { ...base, ayanamsa: "lahiri" }, range, recordedTime: "09:10" });
await scanCaseSegments({ snapshot: { ...base, ayanamsa: "lahiri" }, range, targets: ["D1", "D9", "D10"] });
await fetchOpeningBoundary({ snapshot: base, range, recordedTime: "09:10" });
await scanCaseSegments({ snapshot: base, range, targets: ["D1", "D9", "D10"] });
assert.equal(bodies.length, 4);
assert.deepEqual(bodies.map((body) => body.ayanamsa), ["lahiri", "lahiri", "raman", "raman"]);
assert.ok(Array.isArray(bodies[0]?.boundary_charts));
assert.equal(bodies[1]?.boundary_charts, undefined);
assert.equal(bodies[0]?.node_mode, undefined);
assert.equal(bodies[1]?.node_mode, undefined);
});