fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock order. Cluster width, delivery, adoption, and reports keep the actual civil date; adopted date is stored separately from the reported birth_date. Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation thresholds, and Skill version are unchanged. Isolated Linux final-3 gates passed; four pre-existing Python failures remain. This is not a production release.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { clusterEquivalentCandidates } from "../src/lib/rectification-agentic/core/cluster-candidates.ts";
|
||||
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
|
||||
import { remainingSplitLayers } from "../src/lib/rectification-agentic/v9/collection-question-pool.ts";
|
||||
import { lagnaContrastIntervalLabel, parseLagnaContrast } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
|
||||
|
||||
test("native lagna absolute endpoints survive parsing and board formatting", () => {
|
||||
const fixture = JSON.parse(readFileSync(new URL("./fixtures/rectification-dated-lagna.native.json", import.meta.url), "utf8"));
|
||||
const parsed = parseLagnaContrast(fixture.lagna_contrast)!;
|
||||
assert.ok(parsed);
|
||||
assert.deepEqual(parsed.intervals.map((row) => [row.start_at, row.end_at, row.segment_index]),
|
||||
fixture.lagna_contrast.intervals.map((row: Record<string, unknown>) => [row.start_at, row.end_at, row.segment_index]));
|
||||
assert.match(lagnaContrastIntervalLabel(parsed.intervals[0]!), /2000-02-29 23:58.*2000-03-01 00:02/);
|
||||
assert.match(parsed.user_meaning, /2000-02-29 23:58/);
|
||||
const source = readFileSync(new URL("../src/components/rectification-board.tsx", import.meta.url), "utf8");
|
||||
assert.match(source, /\{lagnaContrastIntervalLabel\(interval\)\}/);
|
||||
});
|
||||
|
||||
// Synthetic arithmetic cases, not engine-response goldens.
|
||||
const candidate = (time: string, index: number, offset: number, segment: number) => ({
|
||||
id: time, time, score: 1, relative_support: 1, candidate_date: time.startsWith("23") ? "2000-06-14" : "2000-06-15",
|
||||
window_index: index, window_offset_minutes: offset, segment_index: segment,
|
||||
});
|
||||
|
||||
test("dated equivalent clustering respects segment boundaries and midnight adjacency", () => {
|
||||
const disjoint = [candidate("10:00", 0, 0, 0), candidate("10:02", 1, 2, 1)];
|
||||
assert.equal(clusterEquivalentCandidates(disjoint).length, 2);
|
||||
const midnight = [candidate("23:59", 0, 0, 0), candidate("00:00", 1, 1, 0)];
|
||||
assert.deepEqual(clusterEquivalentCandidates(midnight).map((row) => row.member_ids), [["23:59", "00:00"]]);
|
||||
assert.equal(clusterEquivalentCandidates(midnight, ["00:00"]).length, 2);
|
||||
assert.equal(clusterEquivalentCandidates([candidate("10:00", 0, 0, 0), candidate("10:05", 1, 5, 0)]).length, 2);
|
||||
const state = buildInferenceState({ range_start: "10:00", range_end: "10:02", candidates: disjoint, events: [], probes: [] });
|
||||
assert.ok(state.candidates.every((row) => row.status === "active"));
|
||||
assert.deepEqual(state.candidates.map((row) => row.posterior_score), [1, 1]);
|
||||
});
|
||||
|
||||
test("dated and legacy ordinary same-day equivalent clusters match", () => {
|
||||
const dated = [candidate("10:00", 0, 0, 0), candidate("10:01", 1, 1, 0)];
|
||||
assert.deepEqual(clusterEquivalentCandidates(dated), clusterEquivalentCandidates(dated.map(({ id, time, score }) => ({ id, time, score }))));
|
||||
});
|
||||
|
||||
const bound = { candidate_date: "2000-06-15", segment_index: 0, segment_start_at: "2000-06-15T10:00", segment_end_at: "2000-06-15T10:10" };
|
||||
const transitions = [
|
||||
{ ...bound, layer: "d9", at: "10:00", window_index: 0, window_offset_minutes: 0, segment_start: true, from_sign: "白羊", to_sign: "白羊" },
|
||||
{ ...bound, layer: "d9", at: "10:10", window_index: 10, window_offset_minutes: 10, from_sign: "白羊", to_sign: "金牛" },
|
||||
{ ...bound, layer: "d10", at: "10:00", window_index: 0, window_offset_minutes: 0, segment_start: true, from_sign: "白羊", to_sign: "白羊" },
|
||||
{ ...bound, layer: "d10", at: "10:01", window_index: 1, window_offset_minutes: 1, from_sign: "白羊", to_sign: "金牛" },
|
||||
];
|
||||
|
||||
test("active subset excludes eliminated D9 split without resurrecting full-window flags", () => {
|
||||
assert.deepEqual(remainingSplitLayers({ transitions, activeTimes: ["10:00", "10:01"], scanFlags: { d9_candidates_differ: true } }), ["d10"]);
|
||||
assert.deepEqual(remainingSplitLayers({ transitions, activeTimes: ["10:02", "10:03"], scanFlags: { d9_candidates_differ: true, d10_candidates_differ: true } }), []);
|
||||
assert.deepEqual(remainingSplitLayers({ transitions: transitions.filter((row) => !("segment_start" in row && row.segment_start)).map(({ layer, at }) => ({ layer, at })), activeTimes: ["10:00", "10:01"] }), ["d10"]);
|
||||
});
|
||||
|
||||
test("separate segment starts only justify collection when surviving signs differ", () => {
|
||||
const starts = [transitions[0]!, { ...transitions[0]!, at: "12:00", segment_start_at: "2000-06-15T12:00",
|
||||
segment_end_at: "2000-06-15T12:01", segment_index: 1, window_index: 11, window_offset_minutes: 120 }];
|
||||
assert.deepEqual(remainingSplitLayers({ transitions: starts, activeTimes: ["10:00", "12:00"] }), []);
|
||||
assert.deepEqual(remainingSplitLayers({ transitions: [starts[0]!, { ...starts[1]!, from_sign: "金牛", to_sign: "金牛" }], activeTimes: ["10:00", "12:00"] }), ["d9"]);
|
||||
});
|
||||
|
||||
test("dated lagna parser keeps absolute endpoints and rejects partial or impossible dates", () => {
|
||||
const interval = { start: "23:59", end: "00:00", lagna: "白羊", lords: {}, start_at: "2000-06-14T23:59", end_at: "2000-06-15T00:00", segment_index: 0 };
|
||||
const second = { ...interval, start: "00:01", end: "00:02", start_at: "2000-06-15T00:01", end_at: "2000-06-15T00:02" };
|
||||
const parsed = parseLagnaContrast({ intervals: [interval, second] });
|
||||
assert.equal((parsed?.intervals[0] as unknown as Record<string, unknown>)?.start_at, interval.start_at);
|
||||
assert.match(parsed!.user_meaning, /2000-06-14 23:59/);
|
||||
for (const patch of [{ start_at: "2000-02-30T23:59" }, { end_at: undefined }, { segment_index: -1 }, { start_at: "2000-06-14T23:58" }]) {
|
||||
assert.equal(parseLagnaContrast({ intervals: [{ ...interval, ...patch }, second] }), null);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user