From 4ed2e5562778145baff5db46bf451c7597411d2a Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Thu, 1 Oct 2026 10:55:45 +0800 Subject: [PATCH] research(rectification): positive control for the angle-timing pipeline; Claude acceptance (BUG-1141) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8 --- docs/BUG_HISTORY.md | 1 + ...fication-angle-timing-research-20261001.md | 12 ++++++ .../research/angle_timing_positive_control.py | 41 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 scripts/research/angle_timing_positive_control.py diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index d7ded71d..553ee806 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -15327,6 +15327,7 @@ - 修复:不改代码;产品口径(盘型 + 可信度、不承诺分钟)不变。 - 验证:`scripts/research/angle_timing_research.py`,结果 `docs/research/angle_timing_{a1,jitter,subsets}_2026_10_01.json` 两次复跑逐字节一致;`tests/test_angle_timing_research.py` 11 passed;生产代码零改动。 - 防复发:再提「换某种时间技法就能定到分钟」前,先按本研究的乱序 / 年龄保持双对照在 v5 上预先登记检验;不得在本轮结果上事后挑选容许度、角点或领域映射。 +- Claude 验收正对照(2026-10-01):`scripts/research/angle_timing_positive_control.py` 每例埋 3 个「行运土星距真实上升 <0.15°」的日精度事件,用同一 `ranks_for`:真值分钟平均排位 0.077(77/77 例 <0.15);同日期整年挪动 0.502;真实事件 0.514。流程能检出真信号,真实事件无信号的结论成立。 - 相关记录:BUG-1091、BUG-1105、BUG-1115~1117。 - 复发自:无 - 修复版本:研究分支 `codex/rectification-angle-timing-research-20261001`。 diff --git a/docs/tasks/PROGRESS-rectification-angle-timing-research-20261001.md b/docs/tasks/PROGRESS-rectification-angle-timing-research-20261001.md index 14e6ad2c..c48b36af 100644 --- a/docs/tasks/PROGRESS-rectification-angle-timing-research-20261001.md +++ b/docs/tasks/PROGRESS-rectification-angle-timing-research-20261001.md @@ -41,3 +41,15 @@ A1 中太阳弧 1° / 2° 真实日期反而更差,执行中先推测为乱序 - 领域 → 角点映射、只用相合、主限向运、Naibod 弧未测(需新的预先登记)。 - 开放集非盲测;事件精度到月 / 日的只占 40%。 + +## Claude 独立验收(2026-10-01) + +执行方未做正对照(「没信号」也可能是流程写错)。补做 `scripts/research/angle_timing_positive_control.py`(TR、1°、±30): + +| 事件日期 | 真值平均排位(0 最好,0.5 随机) | 排位 <0.15 的例数比例 | +| --- | --- | --- | +| 埋入:行运土星距真实上升 <0.15° 的 3 个日期 | 0.077 | 1.00 | +| 同一日期整年挪 ±2~3 年 | 0.502 | 0.04 | +| 真实事件 | 0.514 | 0.10 | + +结论:流程能检出真信号;三类角度技法在真实事件上无信号成立,BUG-1141 `closed_by_design`。范围:只 `scripts/research/`、`tests/`、`docs/`;生产代码零改动。 diff --git a/scripts/research/angle_timing_positive_control.py b/scripts/research/angle_timing_positive_control.py new file mode 100644 index 00000000..09ddf84c --- /dev/null +++ b/scripts/research/angle_timing_positive_control.py @@ -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}")