research(rectification): positive control for the angle-timing pipeline; Claude acceptance (BUG-1141)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3533a79c17
commit
4ed2e55627
@@ -0,0 +1,41 @@
|
||||
"""Positive control for the angle-timing A1 pipeline (Claude acceptance 2026-10-01).
|
||||
|
||||
Plants three day-precision events per case on dates when transit Saturn sits within
|
||||
0.15 deg of the TRUE natal ascendant, then ranks the truth minute with the same
|
||||
ranks_for() used by A1. A working pipeline must rank it near 0; moving the same
|
||||
dates by whole years must return it to chance (~0.5).
|
||||
|
||||
PYTHONHASHSEED=0 python3 scripts/research/angle_timing_positive_control.py
|
||||
"""
|
||||
import sys, statistics, random
|
||||
from datetime import date, timedelta
|
||||
sys.path.insert(0, ".")
|
||||
import swisseph as swe
|
||||
from scripts.research import angle_timing_lib as at
|
||||
from scripts.research.angle_timing_research import ranks_for
|
||||
cases = at.load_cases()
|
||||
res = {"real": [], "planted": [], "planted_shuffled_years": []}
|
||||
rng = random.Random(7)
|
||||
for case in cases:
|
||||
ch = at.CaseChart(case)
|
||||
asc_truth = float(ch.asc[at.RADIUS_MAX])
|
||||
# find Saturn conjunction dates with the TRUE natal ascendant (tropical, both sides)
|
||||
d = ch.local.date() + timedelta(days=400); hits = []
|
||||
prev = None
|
||||
while d < date(2026, 9, 1) and len(hits) < 3:
|
||||
jd = swe.julday(d.year, d.month, d.day, 12.0)
|
||||
s = float(at.aspect_sep(__import__("numpy").array([asc_truth]), at.planet_lon(jd, swe.SATURN))[0])
|
||||
if s < 0.15 and (not hits or (d - hits[-1]).days > 200): hits.append(d)
|
||||
d += timedelta(days=2)
|
||||
if not hits:
|
||||
continue
|
||||
planted = [{"id": f"p{i}", "domain": "career", "precision": "day", "date": h.isoformat()} for i, h in enumerate(hits)]
|
||||
# same dates moved by random whole years (keeps precision, breaks the alignment)
|
||||
moved = [{**e, "date": (date.fromisoformat(e["date"]) + timedelta(days=365 * rng.choice([-3, -2, 2, 3]))).isoformat()} for e in planted]
|
||||
moved = [e for e in moved if ch.local.date() < date.fromisoformat(e["date"]) < date(2026, 9, 1)]
|
||||
for key, evs in (("planted", planted), ("planted_shuffled_years", moved)):
|
||||
r = ranks_for(ch, evs)
|
||||
res[key].append(r["TR"]["1.0"]["30"])
|
||||
res["real"].append(ranks_for(ch, None)["TR"]["1.0"]["30"])
|
||||
for k, v in res.items():
|
||||
print(f"{k:24s} n={len(v):2d} mean truth rank (TR, 1°, ±30) = {statistics.mean(v):.3f} share with rank<0.15: {sum(1 for x in v if x < 0.15)/len(v):.2f}")
|
||||
Reference in New Issue
Block a user