fix(rectification): use candidate dates for cross-midnight dasha scoring
Add date-isolated caches and regression coverage, align scoring identity, and freeze full research reruns while preserving historical artifacts. Record unresolved cache/receipt identity and end-to-end acceptance gaps for branch review only. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ bounded auxiliary signal, never larger than one day-level event body.
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from typing import Any
|
||||
|
||||
from scripts.rectification.event_probes import _narayana_start_dates, _vim_start_dates
|
||||
@@ -152,11 +152,15 @@ def merge_transition_proximity(
|
||||
moon = (context.get("planet_longitudes") or {}).get("Moon")
|
||||
if not isinstance(moon, (int, float)):
|
||||
continue
|
||||
vim_key = (birth_date, round(float(moon), 6), lo, hi)
|
||||
# Native contexts retain the local date even when the window crosses midnight.
|
||||
# Legacy time-only contexts still use the request's date.
|
||||
candidate_at = context.get("candidate_at")
|
||||
candidate_date = candidate_at.date().isoformat() if isinstance(candidate_at, datetime) else birth_date
|
||||
vim_key = (candidate_date, round(float(moon), 6), lo, hi)
|
||||
if vim_key not in vim_cache:
|
||||
vim_cache[vim_key] = _vim_start_dates(birth_date, float(moon), lo, hi)
|
||||
vim_cache[vim_key] = _vim_start_dates(candidate_date, float(moon), lo, hi)
|
||||
pd_cache[vim_key] = _vim_start_dates(
|
||||
birth_date,
|
||||
candidate_date,
|
||||
float(moon),
|
||||
lo,
|
||||
hi,
|
||||
@@ -165,7 +169,7 @@ def merge_transition_proximity(
|
||||
planets = context.get("planet_longitudes") or {}
|
||||
asc = context.get("ascendant_index")
|
||||
narayana_key = (
|
||||
birth_date,
|
||||
candidate_date,
|
||||
int(asc) if isinstance(asc, int) else None,
|
||||
lo,
|
||||
hi,
|
||||
@@ -173,7 +177,7 @@ def merge_transition_proximity(
|
||||
)
|
||||
if narayana_key not in narayana_cache:
|
||||
narayana_cache[narayana_key] = (
|
||||
_narayana_start_dates(int(asc), planets, birth_date, lo, hi)
|
||||
_narayana_start_dates(int(asc), planets, candidate_date, lo, hi)
|
||||
if isinstance(asc, int) and isinstance(planets, dict)
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from scripts.rectification.dasha_transition_proximity import merge_transition_pr
|
||||
from scripts.rectification.contracts import LifeEvent, RectificationRequest, is_scoreable_event
|
||||
from scripts.rectification.case_holdout import holdout_event_ids
|
||||
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-7"
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-8"
|
||||
INPUT_CONTRACT_VERSION = "rectification-calculation-spec-v4"
|
||||
PRECISION_WEIGHTS = {
|
||||
"day": 1.0,
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -22,26 +22,17 @@ from scripts.rectification.candidate_contrast import select_signature_representa
|
||||
from scripts.rectification.scoring_service import build_event_contribution_matrix, score_from_matrix
|
||||
from scripts.research.cluster_width_lib import SEPARATION_LEAD, still_valid_public
|
||||
from scripts.research.probe_supply_after_six import request_from_case
|
||||
from scripts.research.sealed_holdout_rerun import DATASET, file_sha256, opaque_order
|
||||
from scripts.research.sealed_holdout_rerun import (
|
||||
DATASET, file_sha256, historical_comparison, implementation_identity,
|
||||
opaque_order, verify_frozen_record,
|
||||
)
|
||||
|
||||
OFFSETS = (-30, -20, -15, -10, -8, -5, -3, 0, 3, 5, 8, 10, 15, 20, 30)
|
||||
RADII = (15, 30, 60)
|
||||
MINUTE_STEP = 1
|
||||
PRODUCTION_FILES = [
|
||||
"scripts/rectification/scoring_service.py",
|
||||
"scripts/rectification/dasha_transition_proximity.py",
|
||||
"scripts/rectification/candidate_contrast.py",
|
||||
"scripts/rectification/case_holdout.py",
|
||||
"scripts/rectification/contracts.py",
|
||||
"scripts/rectification/event_probes.py",
|
||||
]
|
||||
RESEARCH_FILES = [
|
||||
"scripts/research/reported_offset_sweep.py",
|
||||
"scripts/research/cluster_width_lib.py",
|
||||
"scripts/research/probe_supply_after_six.py",
|
||||
"scripts/research/sealed_holdout_rerun.py",
|
||||
"scripts/minute_rectification_blind_eval.py",
|
||||
]
|
||||
FREEZE = ROOT / "docs/research/reported_offset_cross_midnight_2026_09_20.final.freeze.json"
|
||||
REPORT = ROOT / "docs/research/reported_offset_cross_midnight_2026_09_20.json"
|
||||
LEGACY_REPORT = ROOT / "docs/research/reported_offset_2026_09_20.json"
|
||||
|
||||
|
||||
def shifted_window(case: dict[str, Any], offset: int, radius: int) -> tuple[dict[str, Any], list[datetime]]:
|
||||
@@ -67,21 +58,13 @@ def shifted_window(case: dict[str, Any], offset: int, radius: int) -> tuple[dict
|
||||
|
||||
|
||||
def score_window(request: dict[str, Any], contexts: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Keep auxiliary transition anchors on each candidate's actual date.
|
||||
"""Use the repaired native single matrix, with candidate dates preserved.
|
||||
|
||||
The production matrix's transition-proximity helper accepts one birth date
|
||||
per call, unlike the static chart layer which reads candidate_at. Grouping
|
||||
is only an offline adapter; it does not change the production scorer.
|
||||
The historical grouped adapter is archived, not a second active scoring
|
||||
path. This is a native scoring replay, still not a production Q&A replay.
|
||||
"""
|
||||
by_date: dict[str, list[dict[str, Any]]] = {}
|
||||
for context in contexts:
|
||||
by_date.setdefault(context["candidate_at"].date().isoformat(), []).append(context)
|
||||
by_time = {}
|
||||
for candidate_date, group in by_date.items():
|
||||
dated_request = {**request, "birth_date": candidate_date}
|
||||
built = build_event_contribution_matrix(dated_request, static_contexts=group)
|
||||
by_time.update({row["time"]: row for row in score_from_matrix(dated_request, built)})
|
||||
return [by_time[context["candidate_at"].strftime("%H:%M")] for context in contexts]
|
||||
built = build_event_contribution_matrix(request, static_contexts=contexts)
|
||||
return score_from_matrix(request, built)
|
||||
|
||||
|
||||
def delivery_moments(public: list[dict[str, Any]], candidates: list[datetime]) -> list[datetime]:
|
||||
@@ -135,11 +118,39 @@ def summarize(trials: list[dict[str, Any]], radii: tuple[int, ...], offsets: tup
|
||||
return result
|
||||
|
||||
|
||||
def run(dataset: Path = DATASET, radii: tuple[int, ...] = RADII, offsets: tuple[int, ...] = OFFSETS) -> dict[str, Any]:
|
||||
def freeze_record(dataset: Path = DATASET, radii: tuple[int, ...] = RADII, offsets: tuple[int, ...] = OFFSETS) -> dict[str, Any]:
|
||||
manifest = json.loads(dataset.read_text(encoding="utf-8"))
|
||||
legacy_hash = implementation_sha256(manifest["frozen_scoring"]["files"])
|
||||
return {
|
||||
"record_version": "reported-offset-native-candidate-date-v3",
|
||||
"frozen_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||
"ayanamsa": AYANAMSA, "node_mode": NODE_MODE,
|
||||
"radii_minutes": list(radii), "offsets_minutes": list(offsets), "minute_step": MINUTE_STEP,
|
||||
"dataset": dataset.relative_to(ROOT).as_posix(), "dataset_sha256": file_sha256(dataset),
|
||||
"dataset_benchmark_id": manifest["benchmark_id"],
|
||||
"implementation_sha256": legacy_hash,
|
||||
"implementation_sha256_prefix": legacy_hash[:16],
|
||||
**implementation_identity(dataset),
|
||||
"scorer": "native_event_contribution_matrix_not_shadow_fact_ranker",
|
||||
"delivery": "initial_signature_clusters_peak_gap_lt_8_envelope_no_answers_no_elimination",
|
||||
"rank": "score_desc_then_sha256(benchmark_id:case_id:candidate_time)",
|
||||
"grid": "every_minute_inclusive_not_production_two_minute_sampling",
|
||||
"cross_midnight": "date_aware_candidates_distance_and_native_single_matrix",
|
||||
"evaluator_sha256": file_sha256(Path(__file__)),
|
||||
"replay_revision": "native_candidate_date_v3",
|
||||
"supersedes": "candidate_date_grouped_v2_identity_and_path_not_assumed_numerically_wrong",
|
||||
"is_blind_evaluation": False, "truth_hidden_from_ranker": True,
|
||||
"official_valid_independent_blind": False, "official_blind_trial_count": 0,
|
||||
"results_previously_seen": True, "must_not_use_for_tuning": True,
|
||||
}
|
||||
|
||||
|
||||
def run(dataset: Path = DATASET, radii: tuple[int, ...] = RADII, offsets: tuple[int, ...] = OFFSETS,
|
||||
freeze_path: Path = FREEZE) -> dict[str, Any]:
|
||||
frozen = json.loads(freeze_path.read_text(encoding="utf-8"))
|
||||
verify_frozen_record(frozen, freeze_record(dataset, radii, offsets))
|
||||
replay_started_at = datetime.now(timezone.utc).isoformat()
|
||||
manifest = json.loads(dataset.read_text(encoding="utf-8"))
|
||||
frozen_files = manifest["frozen_scoring"]["files"]
|
||||
scoring_files = sorted(set(frozen_files + PRODUCTION_FILES))
|
||||
starting_hash = implementation_sha256(scoring_files)
|
||||
validation = validate(dataset)
|
||||
invalid = validation["invalid_cases"]
|
||||
trials = []
|
||||
@@ -164,32 +175,17 @@ def run(dataset: Path = DATASET, radii: tuple[int, ...] = RADII, offsets: tuple[
|
||||
"case_ordinal": index, "offset_minutes": offset, "radius_minutes": radius,
|
||||
**reveal_metrics(rows, candidates, delivery, truth, manifest["benchmark_id"], case["case_id"]),
|
||||
})
|
||||
if implementation_sha256(scoring_files) != starting_hash:
|
||||
raise ValueError("scorer_changed_during_sweep")
|
||||
verify_frozen_record(frozen, freeze_record(dataset, radii, offsets))
|
||||
return {
|
||||
"scope": "reported_offset_sensitivity_not_product_accuracy",
|
||||
"specification": {
|
||||
"ayanamsa": AYANAMSA, "node_mode": NODE_MODE,
|
||||
"radii_minutes": list(radii), "offsets_minutes": list(offsets), "minute_step": MINUTE_STEP,
|
||||
"dataset": dataset.relative_to(ROOT).as_posix(), "dataset_sha256": file_sha256(dataset),
|
||||
"dataset_benchmark_id": manifest["benchmark_id"],
|
||||
"implementation_sha256": implementation_sha256(frozen_files),
|
||||
"implementation_sha256_prefix": implementation_sha256(frozen_files)[:16],
|
||||
"production_scoring_files": scoring_files, "production_scoring_sha256": starting_hash,
|
||||
"research_files": RESEARCH_FILES,
|
||||
"research_implementation_sha256": implementation_sha256(RESEARCH_FILES),
|
||||
"hash_scope": "explicit_identity_file_sets_not_a_transitive_dependency_lock",
|
||||
"scorer": "native_event_contribution_matrix_not_shadow_fact_ranker",
|
||||
"delivery": "initial_signature_clusters_peak_gap_lt_8_envelope_no_answers_no_elimination",
|
||||
"rank": "score_desc_then_sha256(benchmark_id:case_id:candidate_time)",
|
||||
"grid": "every_minute_inclusive_not_production_two_minute_sampling",
|
||||
"cross_midnight": "date_aware_candidates_distance_and_matrix_grouped_by_candidate_date",
|
||||
"evaluator_sha256": file_sha256(Path(__file__)),
|
||||
"replay_revision": "candidate_date_grouped_v2",
|
||||
"supersedes": "initial_sweep_invalidated_cross_midnight_transition_anchor",
|
||||
"is_blind_evaluation": False, "truth_hidden_from_ranker": True,
|
||||
"results_previously_seen": True, "must_not_use_for_tuning": True,
|
||||
},
|
||||
"replay_started_at_utc": replay_started_at,
|
||||
"replay_finished_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||
"specification": frozen,
|
||||
"frozen_record": frozen,
|
||||
"freeze_record_path": freeze_path.relative_to(ROOT).as_posix(),
|
||||
"implementation_hash_matches_at_replay": True,
|
||||
"dataset_hash_matches_at_replay": True,
|
||||
"historical_comparison": historical_comparison(LEGACY_REPORT, trials, ("case_ordinal", "radius_minutes", "offset_minutes")),
|
||||
"excluded_cases": invalid, "case_count": validation["valid_public_aa_cases"],
|
||||
"trial_count": len(trials), "trials": trials,
|
||||
"summary": summarize(trials, radii, offsets),
|
||||
@@ -207,5 +203,13 @@ def run(dataset: Path = DATASET, radii: tuple[int, ...] = RADII, offsets: tuple[
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--json", action="store_true")
|
||||
parser.add_argument("--freeze", action="store_true", help="Exclusively create the record before replay")
|
||||
parser.add_argument("--freeze-path", type=Path, default=FREEZE)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(run(), ensure_ascii=False, indent=2))
|
||||
if args.freeze:
|
||||
with args.freeze_path.open("x", encoding="utf-8", newline="\n") as handle:
|
||||
json.dump(freeze_record(), handle, ensure_ascii=False, indent=2)
|
||||
handle.write("\n")
|
||||
print(args.freeze_path.relative_to(ROOT).as_posix())
|
||||
else:
|
||||
print(json.dumps(run(freeze_path=args.freeze_path), ensure_ascii=False, indent=2))
|
||||
|
||||
@@ -27,8 +27,27 @@ from scripts.minute_rectification_feature_facts_v4 import build_feature_fact_row
|
||||
from scripts.minute_rectification_holdout_validator import validate
|
||||
|
||||
DATASET = ROOT / "references/real_case_calibration/minute_rectification_holdout_v3.json"
|
||||
FREEZE = ROOT / "docs/research/sealed_holdout_rerun_2026_09_20.freeze.json"
|
||||
REPORT = ROOT / "docs/research/sealed_holdout_rerun_2026_09_20.json"
|
||||
FREEZE = ROOT / "docs/research/sealed_holdout_rerun_cross_midnight_2026_09_20.final.freeze.json"
|
||||
REPORT = ROOT / "docs/research/sealed_holdout_rerun_cross_midnight_2026_09_20.json"
|
||||
LEGACY_REPORT = ROOT / "docs/research/sealed_holdout_rerun_2026_09_20.json"
|
||||
ARCHIVE = ROOT / "docs/research/history/rectification_pre_cross_midnight_2026_09_20"
|
||||
PRODUCTION_FILES = [
|
||||
"scripts/rectification/scoring_service.py",
|
||||
"scripts/rectification/dasha_transition_proximity.py",
|
||||
"scripts/rectification/candidate_contrast.py",
|
||||
"scripts/rectification/case_holdout.py",
|
||||
"scripts/rectification/contracts.py",
|
||||
"scripts/rectification/event_probes.py",
|
||||
]
|
||||
RESEARCH_FILES = [
|
||||
"scripts/research/reported_offset_sweep.py",
|
||||
"scripts/research/cluster_width_lib.py",
|
||||
"scripts/research/probe_supply_after_six.py",
|
||||
"scripts/research/sealed_holdout_rerun.py",
|
||||
"scripts/minute_rectification_blind_eval.py",
|
||||
"scripts/minute_rectification_fact_blind_eval_v4.py",
|
||||
"scripts/minute_rectification_holdout_validator.py",
|
||||
]
|
||||
|
||||
|
||||
def file_sha256(path: Path) -> str:
|
||||
@@ -43,11 +62,70 @@ def opaque_order(benchmark_id: str, case_id: str, rows: list[dict[str, Any]]) ->
|
||||
))
|
||||
|
||||
|
||||
def implementation_identity(dataset: Path = DATASET) -> dict[str, Any]:
|
||||
"""Bind legacy scorer, changed production files and both replay adapters.
|
||||
|
||||
The production identity is contextual for the shadow rerun, not a claim
|
||||
that the shadow scorer executes transition proximity. These are explicit
|
||||
audited file sets, not a transitive dependency or environment lock.
|
||||
"""
|
||||
legacy_files = json.loads(dataset.read_text(encoding="utf-8"))["frozen_scoring"]["files"]
|
||||
production_files = sorted(set(legacy_files + PRODUCTION_FILES))
|
||||
return {
|
||||
"historical_artifacts_manifest_sha256": file_sha256(ARCHIVE / "manifest.json"),
|
||||
"production_scoring_files": production_files,
|
||||
"production_scoring_sha256": implementation_sha256(production_files),
|
||||
"research_files": RESEARCH_FILES,
|
||||
"research_implementation_sha256": implementation_sha256(RESEARCH_FILES),
|
||||
"file_sha256": {path: file_sha256(ROOT / path) for path in sorted(set(production_files + RESEARCH_FILES))},
|
||||
"hash_scope": "explicit_identity_file_sets_not_a_transitive_dependency_lock",
|
||||
}
|
||||
|
||||
|
||||
def verify_frozen_record(frozen: dict[str, Any], actual: dict[str, Any]) -> None:
|
||||
for key in actual:
|
||||
if key != "frozen_at_utc" and actual[key] != frozen.get(key):
|
||||
raise ValueError(f"frozen_record_mismatch:{key}")
|
||||
if set(frozen) != set(actual):
|
||||
raise ValueError("frozen_record_mismatch:keys")
|
||||
|
||||
|
||||
def historical_comparison(report_path: Path, trials: list[dict[str, Any]], keys: tuple[str, ...]) -> dict[str, Any]:
|
||||
"""Keep every before/after row, including unchanged rows and failures."""
|
||||
history_path = ARCHIVE / report_path.relative_to(ROOT)
|
||||
archive_manifest = json.loads((ARCHIVE / "manifest.json").read_text(encoding="utf-8"))
|
||||
entry = next(item for item in archive_manifest["files"] if item["source_path"] == report_path.relative_to(ROOT).as_posix())
|
||||
if file_sha256(history_path) != entry["sha256"]:
|
||||
raise ValueError("historical_report_bytes_changed")
|
||||
previous = json.loads(history_path.read_text(encoding="utf-8"))
|
||||
identity = lambda row: tuple(row[key] for key in keys)
|
||||
old = {identity(row): row for row in previous["trials"]}
|
||||
new = {identity(row): row for row in trials}
|
||||
if len(old) != len(previous["trials"]) or len(new) != len(trials) or old.keys() != new.keys():
|
||||
raise ValueError("historical_comparison_trial_identity_mismatch")
|
||||
comparisons = [{
|
||||
**{key: row[key] for key in keys},
|
||||
"before": old[identity(row)], "after": row,
|
||||
"changed_fields": sorted(key for key in set(row) | set(old[identity(row)])
|
||||
if row.get(key) != old[identity(row)].get(key)),
|
||||
} for row in trials]
|
||||
return {
|
||||
"source_report": history_path.relative_to(ROOT).as_posix(),
|
||||
"source_report_sha256": file_sha256(history_path),
|
||||
"reason": "BUG-981 new production identity and native single-matrix sweep; prior grouped sweep/shadow metrics not presumed incorrect",
|
||||
"trial_count": len(comparisons),
|
||||
"changed_trial_count": sum(bool(row["changed_fields"]) for row in comparisons),
|
||||
"trials": comparisons,
|
||||
}
|
||||
|
||||
|
||||
def freeze_record(dataset: Path = DATASET) -> dict[str, Any]:
|
||||
manifest = json.loads(dataset.read_text(encoding="utf-8"))
|
||||
files = manifest["frozen_scoring"]["files"]
|
||||
return {
|
||||
"record_version": "exposed-v3-fixed-protocol-rerun-v1",
|
||||
"record_version": "exposed-v3-fixed-protocol-cross-midnight-rerun-v2",
|
||||
"extended_identity": implementation_identity(dataset),
|
||||
"production_identity_scope": "context_only_shadow_scorer_does_not_call_transition_proximity",
|
||||
"frozen_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||
"dataset_path": dataset.relative_to(ROOT).as_posix(),
|
||||
"dataset_sha256": file_sha256(dataset),
|
||||
@@ -73,9 +151,8 @@ def freeze_record(dataset: Path = DATASET) -> dict[str, Any]:
|
||||
def run(freeze_path: Path = FREEZE, dataset: Path = DATASET) -> dict[str, Any]:
|
||||
frozen = json.loads(freeze_path.read_text(encoding="utf-8"))
|
||||
actual = freeze_record(dataset)
|
||||
for key in actual:
|
||||
if key != "frozen_at_utc" and actual[key] != frozen.get(key):
|
||||
raise ValueError(f"frozen_record_mismatch:{key}")
|
||||
verify_frozen_record(frozen, actual)
|
||||
replay_started_at = datetime.now(timezone.utc).isoformat()
|
||||
manifest = json.loads(dataset.read_text(encoding="utf-8"))
|
||||
validation = validate(dataset)
|
||||
invalid = validation["invalid_cases"]
|
||||
@@ -110,10 +187,14 @@ def run(freeze_path: Path = FREEZE, dataset: Path = DATASET) -> dict[str, Any]:
|
||||
"full_trial_reasons": result["reasons"],
|
||||
"sparse_trial_reasons": sparse_result["reasons"],
|
||||
})
|
||||
verify_frozen_record(frozen, freeze_record(dataset))
|
||||
aggregate = summarize_trials(trials, manifest["release_metrics"])
|
||||
count = len(trials)
|
||||
return {
|
||||
"scope": "fixed_protocol_previously_exposed_v3_rerun",
|
||||
"replay_started_at_utc": replay_started_at,
|
||||
"replay_finished_at_utc": datetime.now(timezone.utc).isoformat(),
|
||||
"historical_comparison": historical_comparison(LEGACY_REPORT, trials, ("case_ordinal",)),
|
||||
"evaluated_on": datetime.now(timezone.utc).date().isoformat(),
|
||||
"frozen_record": frozen,
|
||||
"implementation_hash_matches_at_replay": True,
|
||||
|
||||
Reference in New Issue
Block a user