"""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"]}