Cache work that depends only on the candidate minute, drop unread response fields, and reject windows above 241 candidates before any chart is built. Eleven canonical scenario hashes match 77057a819. Sealed and reported-offset replays match the 2026-10-08 records (changed_trial_count 8 and 581) and are stored as scoring_perf_2026_10_13.
Co-Authored-By: Grok (agent loop)
362 lines
14 KiB
Python
362 lines
14 KiB
Python
"""Output-identical speedups for rectification scoring (BUG-1323, 1324, 1325, 1345)."""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import inspect
|
|
import time
|
|
from datetime import date, datetime, timedelta
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from scripts.active_rectification_event_engine import (
|
|
_active_narayana,
|
|
build_candidate_static_context,
|
|
compute_event_candidate_rows,
|
|
)
|
|
from scripts.rectification.api_service import (
|
|
MAX_CANDIDATES_PER_REQUEST,
|
|
TooManyCandidates,
|
|
_candidate_count,
|
|
_reject_if_too_many_candidates,
|
|
block_scan,
|
|
diagnostics,
|
|
score_candidates,
|
|
)
|
|
from scripts.rectification.contracts import normalize_rectification_request
|
|
from scripts.rectification.dasha_transition_proximity import merge_transition_proximity
|
|
from scripts.rectification.event_probes import guided_collect_windows
|
|
from scripts.rectification.scoring_service import build_event_contribution_matrix, sha256
|
|
import scripts.active_rectification_event_engine as event_engine
|
|
from scripts.rectification import dasha_transition_proximity as proximity
|
|
from scripts.rectification import event_probes
|
|
from scripts.rectification import scoring_service
|
|
from tests._baseline_rect_scoring_20261013 import (
|
|
guided_collect_windows as baseline_guided_collect_windows,
|
|
merge_transition_proximity as baseline_merge_transition_proximity,
|
|
)
|
|
|
|
FROZEN_TODAY = date(2026, 9, 16)
|
|
EVENT_SPECS = (
|
|
("education", "education_start", "2008-01-01", "2008-12-31", "year"),
|
|
("career", "career_entry", "2012-06-01", "2012-06-30", "month"),
|
|
("relocation", "relocation", "2014-03-01", "2014-03-31", "month"),
|
|
("relationship", "relationship_commitment", "2016-10-01", "2016-10-31", "month"),
|
|
("career", "promotion", "2018-01-01", "2018-12-31", "year"),
|
|
("finance", "asset_change", "2019-05-01", "2019-05-31", "month"),
|
|
("family", "family_event", "2020-08-01", "2020-08-31", "month"),
|
|
("health", "self_health_event", "2022-01-01", "2022-12-31", "year"),
|
|
)
|
|
|
|
|
|
class _FrozenDate(date):
|
|
@classmethod
|
|
def today(cls) -> date:
|
|
return FROZEN_TODAY
|
|
|
|
|
|
def _events() -> list[dict]:
|
|
rows = []
|
|
for index, (domain, kind, start, end, precision) in enumerate(EVENT_SPECS, start=1):
|
|
rows.append({
|
|
"id": f"00000000-0000-4000-8000-{index:012d}",
|
|
"domain": domain,
|
|
"event_kind": kind,
|
|
"date_start": start,
|
|
"date_end": end,
|
|
"precision": precision,
|
|
"summary": "虚构",
|
|
})
|
|
return rows
|
|
|
|
|
|
def _request(start: str, end: str, **extra) -> dict:
|
|
body = {
|
|
"birth_date": "1990-01-01",
|
|
"start_time": start,
|
|
"end_time": end,
|
|
"lat": 39.9,
|
|
"lon": 116.4,
|
|
"tz": 8,
|
|
"events": _events(),
|
|
**extra,
|
|
}
|
|
return normalize_rectification_request(body, today=FROZEN_TODAY)
|
|
|
|
|
|
def _count(monkeypatch, owner, name: str) -> list[int]:
|
|
original = getattr(owner, name)
|
|
counter = [0]
|
|
|
|
def wrapped(*args, **kwargs):
|
|
counter[0] += 1
|
|
return original(*args, **kwargs)
|
|
|
|
monkeypatch.setattr(owner, name, wrapped)
|
|
return counter
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def wide_case():
|
|
request = _request("11:00", "13:00")
|
|
captured: dict = {}
|
|
original = scoring_service.merge_transition_proximity
|
|
|
|
def grab(matrix, events, contexts, birth_date, *, public_technique_layers):
|
|
captured["matrix"] = copy.deepcopy(matrix)
|
|
captured["events"] = list(events)
|
|
captured["contexts"] = contexts
|
|
captured["birth_date"] = birth_date
|
|
captured["layers"] = public_technique_layers
|
|
return original(
|
|
matrix, events, contexts, birth_date,
|
|
public_technique_layers=public_technique_layers,
|
|
)
|
|
|
|
with patch.object(event_probes, "date", _FrozenDate), patch.object(
|
|
scoring_service, "merge_transition_proximity", grab,
|
|
):
|
|
built = build_event_contribution_matrix(request)
|
|
return request, built, captured
|
|
|
|
|
|
def test_guided_collect_windows_matches_baseline_and_is_linear(wide_case, monkeypatch) -> None:
|
|
request, built, _captured = wide_case
|
|
times = list(built["candidate_times"])
|
|
assert len(times) == 121
|
|
variants = [
|
|
(request, times),
|
|
(request, times[::2]),
|
|
(request, times[:3]),
|
|
({**request, "declined_domains": ["relationship"]}, times),
|
|
]
|
|
with patch.object(event_probes, "date", _FrozenDate):
|
|
for variant_request, variant_times in variants:
|
|
assert guided_collect_windows(
|
|
variant_request, built, candidate_times=variant_times, today=FROZEN_TODAY,
|
|
) == baseline_guided_collect_windows(
|
|
variant_request, built, candidate_times=variant_times, today=FROZEN_TODAY,
|
|
)
|
|
short_built = {
|
|
**built,
|
|
"static_contexts": list(built["static_contexts"])[:21],
|
|
"candidate_times": times[:21],
|
|
}
|
|
full_calls = _count(monkeypatch, event_probes, "_context_time")
|
|
guided_collect_windows(request, built, candidate_times=times, today=FROZEN_TODAY)
|
|
full = full_calls[0]
|
|
short_calls = _count(monkeypatch, event_probes, "_context_time")
|
|
guided_collect_windows(request, short_built, candidate_times=times[:21], today=FROZEN_TODAY)
|
|
short = short_calls[0]
|
|
assert full <= 16 * 121
|
|
assert short <= 16 * 21
|
|
assert full / short <= (121 / 21) * 1.2
|
|
|
|
|
|
def test_merge_transition_proximity_matches_baseline(wide_case, monkeypatch) -> None:
|
|
_request_wide, _built, captured = wide_case
|
|
vim_calls = _count(monkeypatch, proximity, "_vim_start_dates")
|
|
nara_calls = _count(monkeypatch, proximity, "_narayana_start_dates")
|
|
fresh = copy.deepcopy(captured["matrix"])
|
|
base = copy.deepcopy(captured["matrix"])
|
|
merge_transition_proximity(
|
|
fresh, captured["events"], captured["contexts"], captured["birth_date"],
|
|
public_technique_layers=captured["layers"],
|
|
)
|
|
baseline_merge_transition_proximity(
|
|
base, captured["events"], captured["contexts"], captured["birth_date"],
|
|
public_technique_layers=captured["layers"],
|
|
)
|
|
assert fresh == base
|
|
assert vim_calls[0] <= 2 * len(captured["contexts"])
|
|
assert nara_calls[0] <= len(captured["contexts"])
|
|
|
|
midnight = _request("23:20", "00:40")
|
|
midnight_captured: dict = {}
|
|
original = scoring_service.merge_transition_proximity
|
|
|
|
def grab(matrix, events, contexts, birth_date, *, public_technique_layers):
|
|
midnight_captured["matrix"] = copy.deepcopy(matrix)
|
|
midnight_captured["events"] = list(events)
|
|
midnight_captured["contexts"] = contexts
|
|
midnight_captured["birth_date"] = birth_date
|
|
midnight_captured["layers"] = public_technique_layers
|
|
return original(matrix, events, contexts, birth_date, public_technique_layers=public_technique_layers)
|
|
|
|
with patch.object(event_probes, "date", _FrozenDate), patch.object(
|
|
scoring_service, "merge_transition_proximity", grab,
|
|
):
|
|
build_event_contribution_matrix(midnight)
|
|
fresh_night = copy.deepcopy(midnight_captured["matrix"])
|
|
base_night = copy.deepcopy(midnight_captured["matrix"])
|
|
merge_transition_proximity(
|
|
fresh_night, midnight_captured["events"], midnight_captured["contexts"], midnight_captured["birth_date"],
|
|
public_technique_layers=midnight_captured["layers"],
|
|
)
|
|
baseline_merge_transition_proximity(
|
|
base_night, midnight_captured["events"], midnight_captured["contexts"], midnight_captured["birth_date"],
|
|
public_technique_layers=midnight_captured["layers"],
|
|
)
|
|
assert fresh_night == base_night
|
|
|
|
|
|
def test_static_context_caches_match_fallback_and_stay_out_of_features(wide_case, monkeypatch) -> None:
|
|
request, built, _captured = wide_case
|
|
contexts = list(built["static_contexts"])
|
|
assert len(contexts) == 121
|
|
for context in contexts:
|
|
assert "functional_profile" not in context["feature"]
|
|
assert "narayana_antardasha_by_md" not in context["feature"]
|
|
assert context.get("functional_profile")
|
|
assert context.get("narayana_antardasha_by_md")
|
|
features = [context["feature"] for context in contexts]
|
|
cleared = []
|
|
for context in contexts:
|
|
clone = dict(context)
|
|
clone["functional_profile"] = None
|
|
clone["narayana_antardasha_by_md"] = None
|
|
cleared.append(clone)
|
|
engine_request = {
|
|
"birth_date": "1990-01-01",
|
|
"start_time": "11:00",
|
|
"end_time": "13:00",
|
|
"lat": 39.9,
|
|
"lon": 116.4,
|
|
"tz": 8,
|
|
"events": [{
|
|
"id": "00000000-0000-4000-8000-000000000002",
|
|
"domain": "career",
|
|
"event_kind": "career_entry",
|
|
"date": "2012-06-15",
|
|
"precision": "day",
|
|
"summary": "虚构",
|
|
}],
|
|
}
|
|
assert compute_event_candidate_rows(engine_request, static_contexts=contexts) == compute_event_candidate_rows(
|
|
engine_request, static_contexts=cleared,
|
|
)
|
|
assert sha256(features) == sha256([dict(feature) for feature in features])
|
|
|
|
functional_calls = _count(
|
|
monkeypatch, event_engine.functional_benefics, "derive_functional_benefic_malefic",
|
|
)
|
|
narayana_calls = [0]
|
|
original = event_engine.narayana_dasha.get_current_narayana_dasha
|
|
|
|
def wrapped(*args, **kwargs):
|
|
caller = inspect.stack()[1].filename.replace("\\", "/")
|
|
names = [frame.function for frame in inspect.stack()]
|
|
if caller.endswith("/active_rectification_event_engine.py") and "_candidate_row" in names:
|
|
narayana_calls[0] += 1
|
|
return original(*args, **kwargs)
|
|
|
|
monkeypatch.setattr(event_engine.narayana_dasha, "get_current_narayana_dasha", wrapped)
|
|
with patch.object(event_probes, "date", _FrozenDate):
|
|
scored = score_candidates(request, include_internal=True)
|
|
functional_during_score = functional_calls[0]
|
|
compact = score_candidates(request)
|
|
assert functional_during_score == 121
|
|
# Probe path (_score_year) is out of scope and still calls get_current.
|
|
# The scoring loop (_candidate_row) must not.
|
|
assert narayana_calls[0] == 0
|
|
assert "event_contribution_matrix" in scored
|
|
assert "candidate_decision_receipt" in scored
|
|
assert "event_contribution_matrix" not in compact
|
|
assert "candidate_decision_receipt" not in compact
|
|
|
|
def without_timing(value):
|
|
if isinstance(value, dict):
|
|
return {
|
|
key: without_timing(item)
|
|
for key, item in value.items()
|
|
if key not in {"column_compare_ms", "event_contribution_matrix", "candidate_decision_receipt"}
|
|
}
|
|
if isinstance(value, list):
|
|
return [without_timing(item) for item in value]
|
|
return value
|
|
|
|
assert without_timing(compact) == without_timing(scored)
|
|
assert len(scored["candidate_feature_snapshot"]["features"]) == 121
|
|
assert scored["candidate_feature_snapshot"]["feature_hash"] == compact["candidate_feature_snapshot"]["feature_hash"]
|
|
|
|
|
|
def test_cached_narayana_matches_live_lookup_across_ages(wide_case) -> None:
|
|
_request, built, _captured = wide_case
|
|
contexts = list(built["static_contexts"])[:3]
|
|
assert len(contexts) == 3
|
|
for context in contexts:
|
|
periods = context["narayana_periods"]
|
|
table = context["narayana_antardasha_by_md"]
|
|
birth = context["candidate_at"]
|
|
planets = context["planet_longitudes"]
|
|
asc = context["ascendant_index"]
|
|
|
|
def at_age(age: float) -> datetime:
|
|
return birth + timedelta(seconds=age * 365.2425 * 86400)
|
|
|
|
ages = [index / 100 for index in range(0, 12001)]
|
|
total = sum(period["years"] for period in periods)
|
|
cycle_start = min(float(period.get("start_age", 0)) for period in periods)
|
|
edges = [cycle_start, cycle_start + total]
|
|
edges.extend(float(period["start_age"]) for period in periods)
|
|
edges.extend(float(period["end_age"]) for period in periods)
|
|
for edge in edges:
|
|
for delta in (-1e-9, 0.0, 1e-9):
|
|
if edge + delta >= 0:
|
|
ages.append(edge + delta)
|
|
for age in ages:
|
|
event_at = at_age(age)
|
|
cached = _active_narayana(asc, planets, birth, event_at, periods, table)
|
|
live = _active_narayana(asc, planets, birth, event_at, periods, None)
|
|
assert cached == live
|
|
|
|
|
|
def test_candidate_cap_rejects_before_charts_and_allows_product_windows() -> None:
|
|
assert _candidate_count(_request("00:00", "03:59")) == 240
|
|
assert _candidate_count(_request("00:00", "04:00")) == 241
|
|
assert _candidate_count(_request("00:00", "04:01")) == 242
|
|
_reject_if_too_many_candidates(_request("00:00", "03:59"))
|
|
_reject_if_too_many_candidates(_request("00:00", "04:00"))
|
|
with pytest.raises(TooManyCandidates) as rejected:
|
|
_reject_if_too_many_candidates(_request("00:00", "04:01"))
|
|
assert rejected.value.code == "too_many_candidates"
|
|
assert rejected.value.limit == MAX_CANDIDATES_PER_REQUEST == 241
|
|
assert "count=242" in str(rejected.value)
|
|
|
|
full_day = _request("00:00", "23:59")
|
|
assert _candidate_count(full_day) == 1440
|
|
started = time.perf_counter()
|
|
with pytest.raises(TooManyCandidates) as full:
|
|
score_candidates(full_day)
|
|
assert time.perf_counter() - started < 0.05
|
|
assert "count=1440" in str(full.value)
|
|
with pytest.raises(TooManyCandidates):
|
|
diagnostics(full_day)
|
|
|
|
step_10 = _request("00:00", "23:59", minute_step=10)
|
|
assert _candidate_count(step_10) == 144
|
|
_reject_if_too_many_candidates(step_10)
|
|
step_2 = _request("00:00", "03:00", minute_step=2)
|
|
step_5 = _request("00:00", "06:00", minute_step=5)
|
|
assert _candidate_count(step_2) <= 91
|
|
assert _candidate_count(step_5) <= 121
|
|
_reject_if_too_many_candidates(step_2)
|
|
_reject_if_too_many_candidates(step_5)
|
|
with patch(
|
|
"scripts.rectification.api_service.score_candidates",
|
|
return_value={"candidate_scores": [], "decision_receipt": {}},
|
|
) as scored:
|
|
block_scan(step_10)
|
|
block_scan(step_2)
|
|
block_scan(step_5)
|
|
assert scored.call_count == 3
|
|
|
|
|
|
def test_one_candidate_context_exposes_top_level_caches() -> None:
|
|
request = _request("12:00", "12:00")
|
|
context = build_candidate_static_context(request, datetime(1990, 1, 1, 12, 0))
|
|
assert "functional_profile" not in context["feature"]
|
|
assert "narayana_antardasha_by_md" not in context["feature"]
|
|
assert context["functional_profile"]["functional_benefics"] or context["functional_profile"]["functional_malefics"]
|
|
assert set(context["narayana_antardasha_by_md"]) == {period["sign_idx"] for period in context["narayana_periods"]}
|