test(rectification): compare same-day scores against legacy path in process

Replace machine-specific float hashes with strict score and matrix byte comparisons. Keep quick bridge coverage and document duplicate collection. Verify Windows/Linux float behavior and in-memory candidate-date reversal; record the separate-machine acceptance gap and BUG-984 end-to-end blocker.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-20 14:38:29 +08:00
co-authored by Claude Code
parent 777bd53d7e
commit 25232ce4ce
10 changed files with 195 additions and 18 deletions
@@ -1,7 +1,6 @@
"""Candidate-local dasha dates; public AA replay and synthetic cache boundaries."""
from __future__ import annotations
import hashlib
import json
from datetime import date, datetime, timedelta
@@ -100,20 +99,39 @@ def test_legacy_context_without_candidate_at_retains_request_date(monkeypatch):
assert calls == ["2000-01-01", "2000-01-01"]
@pytest.mark.parametrize("ordinal,score_sha256", [
(1, "2aabdda6bb56baf6a9d0b119964ee1ab8023fede9ea8703f6d265207c490bec2"),
(2, "6585d895aeb79e26257b1002696c16b5e252f9a702f2a5702f929fd80486abb9"),
(3, "246296903d915e4886229530fa554428d7f65fb686f70cc369711347ebaf2610"),
])
def test_same_day_public_aa_scores_keep_pre_fix_bytes(ordinal, score_sha256):
# Golden hashes captured from the unmodified production path, 121 minutes each.
# Only ordinal and score bytes are retained; no birth data or coordinates.
@pytest.mark.parametrize("ordinal", [1, 2, 3])
def test_same_day_public_aa_scores_keep_pre_fix_bytes(monkeypatch, ordinal):
from scripts.rectification import scoring_service
# BUG-985 / BUG-733: equivalence is same-process, not a cross-machine float golden.
request, moments = shifted_window(_cases()[ordinal - 1], 0, 60)
assert len({moment.date() for moment in moments}) == 1
built = build_event_contribution_matrix(request)
assert {moment.date().isoformat() for moment in moments} == {request["birth_date"]}
contexts = compute_candidate_static_contexts(request)
assert [context["candidate_at"] for context in contexts] == moments
built = build_event_contribution_matrix(request, static_contexts=contexts)
scores = [row["score"] for row in score_from_matrix(request, built)]
assert len(scores) == 121
assert hashlib.sha256(_canonical(scores)).hexdigest() == score_sha256
legacy_calls = []
def legacy_merge(matrix, events, static_contexts, birth_date, **kwargs):
# Strip only at the helper boundary; the main event engine needs candidate_at.
assert static_contexts is contexts
legacy_contexts = [
{key: value for key, value in context.items() if key != "candidate_at"}
for context in static_contexts
]
legacy_calls.append(len(legacy_contexts))
return proximity.merge_transition_proximity(
matrix, events, legacy_contexts, birth_date, **kwargs,
)
with monkeypatch.context() as legacy:
legacy.setattr(scoring_service, "merge_transition_proximity", legacy_merge)
legacy_built = build_event_contribution_matrix(request, static_contexts=contexts)
legacy_scores = [row["score"] for row in score_from_matrix(request, legacy_built)]
assert legacy_calls == [121]
assert len(scores) == len(legacy_scores) == 121
assert _canonical(scores) == _canonical(legacy_scores)
assert _canonical(built["matrix"]) == _canonical(legacy_built["matrix"])
def test_real_cross_midnight_all_candidates_match_independent_dated_calculation(monkeypatch):
@@ -1,4 +1,9 @@
"""Collect candidate-date regressions through quick's test_rectification_*.py glob."""
"""Collect candidate-date regressions through quick's test_rectification_*.py glob.
The re-export deliberately keeps all nine cases inside the quick gate. If both
this bridge and the source file are selected (e.g. the full suite), pytest runs
them twice: eighteen collected items are nine independent cases, not eighteen.
"""
from tests.test_dasha_transition_proximity_cross_midnight import ( # noqa: F401
test_every_candidate_uses_own_date_and_caches_do_not_cross_dates,