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,90 @@
|
||||
"""Native golden verification plus explicitly synthetic order/gap arithmetic fixtures."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.active_rectification_event_engine import compute_candidate_static_contexts
|
||||
from scripts.rectification.refinement_packet import cluster_scan, lagna_contrast, window_scan
|
||||
|
||||
|
||||
def test_native_lagna_golden_has_explicit_date_labels_and_segments():
|
||||
fixture = json.loads((Path(__file__).resolve().parents[1]
|
||||
/ "frontend/tests/fixtures/rectification-dated-lagna.native.json").read_text(encoding="utf-8"))
|
||||
request = fixture["request"]
|
||||
actual = lagna_contrast({"static_contexts": compute_candidate_static_contexts(request),
|
||||
"candidate_intervals": request["candidate_intervals"]})
|
||||
assert actual == fixture["lagna_contrast"]
|
||||
assert "2000-02-29 23:58" in actual["user_meaning"]
|
||||
assert [row["segment_index"] for row in actual["intervals"]] == [0, 1]
|
||||
|
||||
|
||||
def test_native_scan_golden_matches_real_static_contexts():
|
||||
fixture = json.loads((Path(__file__).resolve().parents[1]
|
||||
/ "frontend/tests/fixtures/rectification-dated-transitions.native.json").read_text(encoding="utf-8"))
|
||||
request = fixture["request"]
|
||||
contexts = compute_candidate_static_contexts(request)
|
||||
assert window_scan({"static_contexts": contexts,
|
||||
"candidate_intervals": request["candidate_intervals"]}) == fixture["scan"]
|
||||
|
||||
|
||||
def context(time, date, index, offset, segment, sign):
|
||||
return {"time": time, "candidate_date": date, "window_index": index,
|
||||
"window_offset_minutes": offset, "segment_index": segment,
|
||||
"feature": {"time": time, "varga_ascendants": {"D9": sign}}}
|
||||
|
||||
|
||||
def test_midnight_transitions_follow_candidate_order_not_clock_order():
|
||||
rows = [context("23:58", "2000-06-14", 0, 0, 0, 0),
|
||||
context("23:59", "2000-06-14", 1, 1, 0, 1),
|
||||
context("00:00", "2000-06-15", 2, 2, 0, 2)]
|
||||
transitions = [row for row in window_scan({"static_contexts": rows, "candidate_intervals": [{"start_at": "2000-06-14T23:58", "end_at": "2000-06-15T00:00"}]})["transitions"] if row["layer"] == "d9"]
|
||||
changes = [row for row in transitions if not row.get("segment_start")]
|
||||
assert [row["at"] for row in changes] == ["23:59", "00:00"]
|
||||
assert [row["window_index"] for row in transitions] == [0, 1, 2]
|
||||
assert transitions[0]["segment_start"] is True
|
||||
assert transitions[-1]["candidate_date"] == "2000-06-15"
|
||||
|
||||
|
||||
def test_disjoint_segments_have_independent_initial_states_not_gap_transitions():
|
||||
rows = [context("00:00", "2000-06-15", 0, 0, 0, 0),
|
||||
context("00:01", "2000-06-15", 1, 1, 0, 1),
|
||||
context("23:00", "2000-06-15", 2, 1380, 1, 5),
|
||||
context("23:01", "2000-06-15", 3, 1381, 1, 6)]
|
||||
transitions = [row for row in window_scan({"static_contexts": rows, "candidate_intervals": [
|
||||
{"start_at": "2000-06-15T00:00", "end_at": "2000-06-15T00:01"},
|
||||
{"start_at": "2000-06-15T23:00", "end_at": "2000-06-15T23:01"},
|
||||
]})["transitions"] if row["layer"] == "d9"]
|
||||
assert [row["at"] for row in transitions if not row.get("segment_start")] == ["00:01", "23:01"]
|
||||
starts = [row for row in transitions if row.get("segment_start")]
|
||||
assert [row["segment_index"] for row in starts] == [0, 1]
|
||||
assert starts[1]["from_sign"] == starts[1]["to_sign"]
|
||||
assert starts[0]["segment_end_at"] == "2000-06-15T00:01"
|
||||
assert starts[1]["segment_start_at"] == "2000-06-15T23:00"
|
||||
|
||||
|
||||
def test_same_day_dated_changes_preserve_legacy_scan_output():
|
||||
rows = [context(f"10:0{i}", "2000-06-15", i, i, 0, i) for i in range(4)]
|
||||
legacy = window_scan({"static_contexts": rows})
|
||||
dated = window_scan({"static_contexts": rows, "candidate_intervals": [{"start_at": "2000-06-15T10:00", "end_at": "2000-06-15T10:03"}]})
|
||||
dated["transitions"] = [{key: value for key, value in row.items()
|
||||
if key not in {"candidate_date", "window_index", "window_offset_minutes", "segment_index", "segment_start_at", "segment_end_at"}}
|
||||
for row in dated["transitions"] if not row.get("segment_start")]
|
||||
assert dated == legacy
|
||||
|
||||
|
||||
def test_cluster_scan_uses_midnight_order_and_never_fills_segment_gaps():
|
||||
rows = [context("23:58", "2000-06-14", 0, 0, 0, 0),
|
||||
context("23:59", "2000-06-14", 1, 1, 0, 1),
|
||||
context("00:00", "2000-06-15", 2, 2, 0, 2),
|
||||
context("00:01", "2000-06-15", 3, 3, 0, 3)]
|
||||
built = {"static_contexts": rows, "candidate_intervals": [{"start_at": "2000-06-14T23:58", "end_at": "2000-06-15T00:01"}]}
|
||||
result = cluster_scan(built, ["23:59", "00:00"], "23:59", 2)
|
||||
assert result["d9_lagna_count"] == 2
|
||||
assert {row["at"] for row in result["transitions"]} == {"23:59", "00:00"}
|
||||
|
||||
|
||||
def test_legacy_transition_shape_has_no_reconstructed_dates():
|
||||
rows = [{"feature": {"time": time, "varga_ascendants": {"D9": sign}}}
|
||||
for time, sign in [("10:00", 0), ("10:01", 1), ("10:02", 2)]]
|
||||
transitions = window_scan({"static_contexts": rows})["transitions"]
|
||||
assert [row["at"] for row in transitions] == ["10:01", "10:02"]
|
||||
assert all("candidate_date" not in row and "segment_start" not in row for row in transitions)
|
||||
Reference in New Issue
Block a user