Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
42 lines
2.2 KiB
Python
42 lines
2.2 KiB
Python
"""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}")
|