Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a78638beb | ||
|
|
c6a091cae3 | ||
|
|
fb70744cc6 | ||
|
|
e5f2a75183 | ||
|
|
1a8c97f64a | ||
|
|
1678263436 |
@@ -1,5 +1,10 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-10-08 — 星盘经度补上章动,大运日期可能前后移动 2–3 天(未上线)
|
||||
|
||||
- 行星、上升和宫头以前用含章动的视位置,减去不含章动的平岁差,全体偏一个很小的角度。现在与 Swiss Ephemeris 的恒星黄道使用同一套章动(BUG-1275)。
|
||||
- 大运日期可能因此前后移动 2–3 天。同一份生时校正输入的分数会变。算法身份从 scoring-11 升到 scoring-12。以前做好的校正记录仍能打开,只读,并可以按新算法再比一次。Skill 版本不变。
|
||||
|
||||
## 2026-10-08 — 特殊上升点在没有 PyJHora 的环境里也能算出(未上线)
|
||||
|
||||
- 生产镜像装不了 PyJHora,Bhava、Hora、Ghati、Vighati、Sree、Indu、Pranapada、Varnada 这八个点以前算不出来。现在用 Swiss Ephemeris 在本进程里算(BUG-1273)。
|
||||
|
||||
+20
-2
@@ -16999,7 +16999,7 @@
|
||||
- 相关记录:BUG-1190
|
||||
- 复发自:无
|
||||
- 修复版本:本分支 `4b9c0198`
|
||||
- 验收补记(Claude,2026-10-08):生产与 staging 镜像只装 `requirements.txt`,不含 PyJHora(AGPL,只能作外部对照)。屏蔽 `jhora` 导入后,乔布斯盘 `Hora_Lagna`、`Ghati_Lagna`、`Sree_Lagna`、`Indu_Lagna`、`Pranapada_Lagna` 全部 `blocked`(`pyjhora_unavailable`)。即 staging `2df6ee72` 上这些点不显示。sync6 验收机装了 PyJHora,没看出。修复单 `docs/tasks/TASK-special-lagnas-native-20261008.md`(原生实现,PyJHora 只作冻结对照答案)。状态应视为 regressed,待修复单关闭。
|
||||
- 验收补记(Claude,2026-10-08):生产与 staging 镜像只装 `requirements.txt`,不含 PyJHora(AGPL,只能作外部对照)。屏蔽 `jhora` 导入后,乔布斯盘 `Hora_Lagna`、`Ghati_Lagna`、`Sree_Lagna`、`Indu_Lagna`、`Pranapada_Lagna` 全部 `blocked`(`pyjhora_unavailable`)。即 staging `2df6ee72` 上这些点不显示。sync6 验收机装了 PyJHora,没看出。修复单 `docs/tasks/TASK-special-lagnas-native-20261008.md`(原生实现,PyJHora 只作冻结对照答案)。状态应视为 regressed,待修复单关闭。已由 BUG-1273 关闭(staging `795b8845`)。
|
||||
- 回归关闭(2026-10-08,BUG-1273):同一条「屏蔽 `jhora` 再算全盘」的路径上,八个点(含 Vighati、Varnada)都是 `computed`,`source` 为 `native_special_lagna`。历史修复段仍指 `4b9c0198` 的 PyJHora 调用,生产不可用不是那一版能单独解决的。这版代码尚未换成线上镜像。
|
||||
|
||||
## BUG-1269 | 导入引擎时的岁差引导盖掉调用方已选的模式
|
||||
@@ -17117,7 +17117,7 @@
|
||||
|
||||
## BUG-1273 | 生产镜像没有 PyJHora,八个特殊上升点算不出来
|
||||
|
||||
- 状态:resolved(代码已在本分支;未上线,部署以 `/api/health` 的 `deployment.gitCommit` 为准)
|
||||
- 状态:resolved(已部署 staging `795b8845`:门禁 run 3218、deploy run 3220;`/api/health` gitCommit 一致,`/login` 200,未登录 `/api/account` 401)
|
||||
- 首次发现:2026-10-08
|
||||
- 最近更新:2026-10-08
|
||||
- 影响面:全盘、报告和对话卡里的 Bhava、Hora、Ghati、Vighati、Sree、Indu、Pranapada、Varnada。生时校正打分不走这条路径
|
||||
@@ -17130,3 +17130,21 @@
|
||||
- 相关记录:BUG-1265
|
||||
- 复发自:BUG-1265
|
||||
- 修复版本:本分支 `codex/special-lagnas-native-20261008`
|
||||
- Claude 验收(2026-10-08,Linux):屏蔽 `jhora` 导入后跑 `cmd_full_reading`,乔布斯、奥巴马、虚构盘 × Lahiri / Raman 共 6 组,8 个点全部 `computed`、`source` 为 `native_special_lagna`,与 `fb8277bf` 调 PyJHora 的结果最大差 0°。Python 全量 62 条失败,与 `2df6ee72` 逐名相同;快速门通过;`tests/test_runtime_jhora_import_scan.py` 在快速门内,人为在 `native_special_lagnas.py` 加一行 `from jhora ...` 后该测试失败(变异检查)。年运资料包屏蔽 `jhora` 前后逐字节相同。
|
||||
- 待问占星师:Vighati 上升按每分钟 15° 计(与 PyJHora 一致);按字面「每个 vighati 一个星座」应为每分钟 75°。
|
||||
|
||||
## BUG-1275 | 恒星黄道经度少扣章动:所有行星偏约 0.005°,Vimshottari 日期差 2–3 天
|
||||
|
||||
- 状态:resolved(只覆盖恒星黄道经度与同一框架的上升、宫头;v5 77 例评测单独记在进度,未完成时不把红线 3 的评测格写成已关闭)
|
||||
- 首次发现 / 最近更新:2026-10-08 / 2026-10-08
|
||||
- 来源:Claude 验收完整数据版报告时,对比上游 `23be1807` 同盘导出。
|
||||
- 影响面:产品默认盘、行运、Bhava Chalit、日运月亮、Shadbala 记录的岁差、KP 宫头展示、生时校正打分。`mean` 档仍去掉章动。西方盘是热带,不适用。Hora / Ghati / Bhava 上升公式未改。
|
||||
- 现象:乔布斯公开盘(Lahiri,平均交点)月亮相对 Swiss Ephemeris `FLG_SIDEREAL` 偏约 0.005°;大运日期可因此差 2–3 天。
|
||||
- 触发条件:任何盘;偏差等于出生时刻的黄经章动 Δψ(最大约 17.2″),符号随年份变化。
|
||||
- 根因:行星取含章动的热带视位置,减去 `swe.get_ayanamsa(jd)` 的不含章动平岁差,两边口径不一致。差值与 `swe.calc_ut(jd, swe.ECL_NUT)` 的 Δψ 一致。
|
||||
- 修复:`legacy` 与产品默认路径改为 `get_ayanamsa_ex_ut` 的真岁差,与 `FLG_SIDEREAL` 同一框架。不新增第四档。`mean` 仍用 `FLG_NONUT`。宫制仍是 `sripati` / `b"R"`。同输入分数变化,算法身份升到 `rectification-v5-matrix-scoring-12`。日期窗 SQL 守卫已能接受该代数,没有新迁移。
|
||||
- 验证:`tests/test_sidereal_nutation_frame.py`。乔布斯、奥巴马、爱因斯坦公开盘与虚构盘,Lahiri / Raman / KP,平均交点与真实交点,九星 `degree_raw` 与 `FLG_SIDEREAL` 差 ≤ 1e-6°。PyJHora 4.8.7 只作离线 JSON 对照,pytest 不导入 jhora。
|
||||
- 防复发:上述回归。快速门导入扫描仍禁止运行时 `import jhora`。
|
||||
- 相关记录:2026-10-07 sync6 验收里「虚构盘大运与独立计算差约 2 天」曾被 Claude 判为岁差版本差异,实为本条。BUG-1268(子运起点)。BUG-621(旧校正记录仍只读打开)。
|
||||
- 复发自:无。
|
||||
- 修复版本:本分支 `codex/ephemeris-nutation-20261008`。未推送,未部署。
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"record_version": "reported-offset-native-candidate-date-v3",
|
||||
"frozen_at_utc": "2026-10-08T10:23:32.009270+00:00",
|
||||
"ayanamsa": "raman",
|
||||
"node_mode": "mean",
|
||||
"radii_minutes": [
|
||||
15,
|
||||
30,
|
||||
60
|
||||
],
|
||||
"offsets_minutes": [
|
||||
-30,
|
||||
-20,
|
||||
-15,
|
||||
-10,
|
||||
-8,
|
||||
-5,
|
||||
-3,
|
||||
0,
|
||||
3,
|
||||
5,
|
||||
8,
|
||||
10,
|
||||
15,
|
||||
20,
|
||||
30
|
||||
],
|
||||
"minute_step": 1,
|
||||
"dataset": "references/real_case_calibration/minute_rectification_holdout_v3.json",
|
||||
"dataset_sha256": "45aa76ab79dfc6c6404b6252083e1aa5c7e2e2b7de811fc11042a8ad2da3f0ea",
|
||||
"dataset_benchmark_id": "minute_rectification_fact_ranker_v4_holdout_v3",
|
||||
"implementation_sha256": "511158c8387658d51f6055bd7afd15730b8b25314510d48f52498ed737c824d7",
|
||||
"implementation_sha256_prefix": "511158c8387658d5",
|
||||
"historical_artifacts_manifest_sha256": "6102a26a840be207b5858b3a4c0509274468ae9071e86d308cbbac7371d6864e",
|
||||
"production_scoring_files": [
|
||||
"scripts/active_rectification_event_engine.py",
|
||||
"scripts/active_rectification_events.py",
|
||||
"scripts/ashtakavarga.py",
|
||||
"scripts/dasha_analyzer.py",
|
||||
"scripts/divisional_charts_extended.py",
|
||||
"scripts/domain_calculation_service.py",
|
||||
"scripts/functional_benefics.py",
|
||||
"scripts/jaimini.py",
|
||||
"scripts/minute_rectification_fact_ranker_v4.py",
|
||||
"scripts/minute_rectification_feature_facts_v4.py",
|
||||
"scripts/narayana_dasha.py",
|
||||
"scripts/rectification/api_service.py",
|
||||
"scripts/rectification/candidate_contrast.py",
|
||||
"scripts/rectification/candidate_window.py",
|
||||
"scripts/rectification/case_holdout.py",
|
||||
"scripts/rectification/contracts.py",
|
||||
"scripts/rectification/dasha_transition_proximity.py",
|
||||
"scripts/rectification/decision_policy.py",
|
||||
"scripts/rectification/event_probes.py",
|
||||
"scripts/rectification/refinement_packet.py",
|
||||
"scripts/rectification/scoring_service.py",
|
||||
"scripts/shadbala.py",
|
||||
"scripts/varga.py"
|
||||
],
|
||||
"production_scoring_sha256": "b72dbd61feec556c3863cb1b4496ded677c7077324a71b86e31722250a8d1528",
|
||||
"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"
|
||||
],
|
||||
"research_implementation_sha256": "12dc4049d8feeaf9e8f7df54666e7de7e8de6b45f195f52999bdbd52217908d9",
|
||||
"file_sha256": {
|
||||
"scripts/active_rectification_event_engine.py": "cdc0e68edc770a2a1810fbdb68cbfd46dcd50b267c739c496fcc078c5e26b5b4",
|
||||
"scripts/active_rectification_events.py": "03e333ccf1103caf685dfe50fe890ab1793e945037085f50ce834a4a3b232222",
|
||||
"scripts/ashtakavarga.py": "9bfc0b8a06af6122fa6c3881706e64f28b9ba58b53d98ff55957d4741d820f3f",
|
||||
"scripts/dasha_analyzer.py": "5e53b1b4d4c7414c08dd0e7716f120a63c70ec3428ec93547f64d55c580da0e1",
|
||||
"scripts/divisional_charts_extended.py": "e9820e80f14fddc86ab5495d11455d16c2d3c9502a9a242fcf9efc41186e0d11",
|
||||
"scripts/domain_calculation_service.py": "66923a2bc69daf65c965d2973c4b9234edbec5147909eaf3609b6b377ce3c7ca",
|
||||
"scripts/functional_benefics.py": "cbaac3b8c09b97f8724b5300e9a402cbf77089af6c7db8788550d40fd35ac57b",
|
||||
"scripts/jaimini.py": "6b88658da5404da9b136686d92a04be462d61a39da6e8de4255021e2ed2d1173",
|
||||
"scripts/minute_rectification_blind_eval.py": "07aa30d8bad3278669495d5fea8be937aaf6367c8af980ff4f2fe9aa84ccc6ff",
|
||||
"scripts/minute_rectification_fact_blind_eval_v4.py": "bdf460e3467208e8b87345c5beb2128f3e22de7eeffd68f72b281b3ad9affc92",
|
||||
"scripts/minute_rectification_fact_ranker_v4.py": "f1a0d8d2482cd754d2e7f253a9606b0373519435a2bf58cfc3753e2b9ae0fb76",
|
||||
"scripts/minute_rectification_feature_facts_v4.py": "3d899a2cdd4825b8e32062443d487edcd2173a4ea55a36eb9f3abe587c0faf0f",
|
||||
"scripts/minute_rectification_holdout_validator.py": "b60b1c752e58672beaee8b36eb3b2348169726cda070ec969db0f13e7ed715af",
|
||||
"scripts/narayana_dasha.py": "c7aaefd4b7b7ea98a5b94e0f7798e5d0139c7423d10ce4ebfff258a460d4456f",
|
||||
"scripts/rectification/api_service.py": "e316ced0f3796d0378a0fe1aad950ee25922ae8afd53add578ed59ca8b2f0f79",
|
||||
"scripts/rectification/candidate_contrast.py": "eee7da71c0574a08876404b78ca111153fa95fb83ace82b9fdca141d1992e273",
|
||||
"scripts/rectification/candidate_window.py": "2fbc5058b12049b15fca375cdc125b8283e19f9f3e4bedcc291e344501891bef",
|
||||
"scripts/rectification/case_holdout.py": "fe0e699b617d9e216acdfeff87af4221c1d6f4c101bf33e1dbf14e2bcd75a8b5",
|
||||
"scripts/rectification/contracts.py": "c6dc6bd230e38f9f2126c224aae9ddbd6415304c15340f8c7cd3f06f6ef3160f",
|
||||
"scripts/rectification/dasha_transition_proximity.py": "0afba494aa12899621173c3cc941c6c1d0be9cec0bdf4c4a1c9bcd004ab22de8",
|
||||
"scripts/rectification/decision_policy.py": "80e8de4680d19a052213bb821d2a9c94143a2962235aeb27f6ad81374d2e60d5",
|
||||
"scripts/rectification/event_probes.py": "9bbadc246e6b8e94bb047522c3e5645613f508b3473ef40b38a207c77fdad58c",
|
||||
"scripts/rectification/refinement_packet.py": "dbeea5aa780889785ce850f3881e8b7f67bb44594e9bc382c65b2e730884a2f4",
|
||||
"scripts/rectification/scoring_service.py": "fa056fa30d9e6ffa4b75295fc49c793e35b26ebeca64ca843d26c8abef3050f2",
|
||||
"scripts/research/cluster_width_lib.py": "1d8f2ffa7795d62069deebb2de8e16749317037d4ed3f24a2c0092676e5cfcc6",
|
||||
"scripts/research/probe_supply_after_six.py": "098a5b4398d6f8d996917d809c2fc922ad64031d3b70660f8ef7c876aba92438",
|
||||
"scripts/research/reported_offset_sweep.py": "9df602c938f87a7b540e97c6c31a263f07b4ed0ccaeb1d2fac39b0d3d27ba0ed",
|
||||
"scripts/research/sealed_holdout_rerun.py": "a7f8a1c2230e33441e335810f13cfd723f3f8ad5b03a3657cb606e29d8559f19",
|
||||
"scripts/shadbala.py": "334dc9d8e4292f0c95d40fd4c29285aa89b9e49d4f187fb9498f4bb0ad93bdfc",
|
||||
"scripts/varga.py": "c1f06967c3d4d214edadff139c24aa31e59fb628477174235abe14f799a99076"
|
||||
},
|
||||
"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_native_single_matrix",
|
||||
"evaluator_sha256": "9df602c938f87a7b540e97c6c31a263f07b4ed0ccaeb1d2fac39b0d3d27ba0ed",
|
||||
"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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"record_version": "exposed-v3-fixed-protocol-cross-midnight-rerun-v2",
|
||||
"extended_identity": {
|
||||
"historical_artifacts_manifest_sha256": "6102a26a840be207b5858b3a4c0509274468ae9071e86d308cbbac7371d6864e",
|
||||
"production_scoring_files": [
|
||||
"scripts/active_rectification_event_engine.py",
|
||||
"scripts/active_rectification_events.py",
|
||||
"scripts/ashtakavarga.py",
|
||||
"scripts/dasha_analyzer.py",
|
||||
"scripts/divisional_charts_extended.py",
|
||||
"scripts/domain_calculation_service.py",
|
||||
"scripts/functional_benefics.py",
|
||||
"scripts/jaimini.py",
|
||||
"scripts/minute_rectification_fact_ranker_v4.py",
|
||||
"scripts/minute_rectification_feature_facts_v4.py",
|
||||
"scripts/narayana_dasha.py",
|
||||
"scripts/rectification/api_service.py",
|
||||
"scripts/rectification/candidate_contrast.py",
|
||||
"scripts/rectification/candidate_window.py",
|
||||
"scripts/rectification/case_holdout.py",
|
||||
"scripts/rectification/contracts.py",
|
||||
"scripts/rectification/dasha_transition_proximity.py",
|
||||
"scripts/rectification/decision_policy.py",
|
||||
"scripts/rectification/event_probes.py",
|
||||
"scripts/rectification/refinement_packet.py",
|
||||
"scripts/rectification/scoring_service.py",
|
||||
"scripts/shadbala.py",
|
||||
"scripts/varga.py"
|
||||
],
|
||||
"production_scoring_sha256": "b72dbd61feec556c3863cb1b4496ded677c7077324a71b86e31722250a8d1528",
|
||||
"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"
|
||||
],
|
||||
"research_implementation_sha256": "12dc4049d8feeaf9e8f7df54666e7de7e8de6b45f195f52999bdbd52217908d9",
|
||||
"file_sha256": {
|
||||
"scripts/active_rectification_event_engine.py": "cdc0e68edc770a2a1810fbdb68cbfd46dcd50b267c739c496fcc078c5e26b5b4",
|
||||
"scripts/active_rectification_events.py": "03e333ccf1103caf685dfe50fe890ab1793e945037085f50ce834a4a3b232222",
|
||||
"scripts/ashtakavarga.py": "9bfc0b8a06af6122fa6c3881706e64f28b9ba58b53d98ff55957d4741d820f3f",
|
||||
"scripts/dasha_analyzer.py": "5e53b1b4d4c7414c08dd0e7716f120a63c70ec3428ec93547f64d55c580da0e1",
|
||||
"scripts/divisional_charts_extended.py": "e9820e80f14fddc86ab5495d11455d16c2d3c9502a9a242fcf9efc41186e0d11",
|
||||
"scripts/domain_calculation_service.py": "66923a2bc69daf65c965d2973c4b9234edbec5147909eaf3609b6b377ce3c7ca",
|
||||
"scripts/functional_benefics.py": "cbaac3b8c09b97f8724b5300e9a402cbf77089af6c7db8788550d40fd35ac57b",
|
||||
"scripts/jaimini.py": "6b88658da5404da9b136686d92a04be462d61a39da6e8de4255021e2ed2d1173",
|
||||
"scripts/minute_rectification_blind_eval.py": "07aa30d8bad3278669495d5fea8be937aaf6367c8af980ff4f2fe9aa84ccc6ff",
|
||||
"scripts/minute_rectification_fact_blind_eval_v4.py": "bdf460e3467208e8b87345c5beb2128f3e22de7eeffd68f72b281b3ad9affc92",
|
||||
"scripts/minute_rectification_fact_ranker_v4.py": "f1a0d8d2482cd754d2e7f253a9606b0373519435a2bf58cfc3753e2b9ae0fb76",
|
||||
"scripts/minute_rectification_feature_facts_v4.py": "3d899a2cdd4825b8e32062443d487edcd2173a4ea55a36eb9f3abe587c0faf0f",
|
||||
"scripts/minute_rectification_holdout_validator.py": "b60b1c752e58672beaee8b36eb3b2348169726cda070ec969db0f13e7ed715af",
|
||||
"scripts/narayana_dasha.py": "c7aaefd4b7b7ea98a5b94e0f7798e5d0139c7423d10ce4ebfff258a460d4456f",
|
||||
"scripts/rectification/api_service.py": "e316ced0f3796d0378a0fe1aad950ee25922ae8afd53add578ed59ca8b2f0f79",
|
||||
"scripts/rectification/candidate_contrast.py": "eee7da71c0574a08876404b78ca111153fa95fb83ace82b9fdca141d1992e273",
|
||||
"scripts/rectification/candidate_window.py": "2fbc5058b12049b15fca375cdc125b8283e19f9f3e4bedcc291e344501891bef",
|
||||
"scripts/rectification/case_holdout.py": "fe0e699b617d9e216acdfeff87af4221c1d6f4c101bf33e1dbf14e2bcd75a8b5",
|
||||
"scripts/rectification/contracts.py": "c6dc6bd230e38f9f2126c224aae9ddbd6415304c15340f8c7cd3f06f6ef3160f",
|
||||
"scripts/rectification/dasha_transition_proximity.py": "0afba494aa12899621173c3cc941c6c1d0be9cec0bdf4c4a1c9bcd004ab22de8",
|
||||
"scripts/rectification/decision_policy.py": "80e8de4680d19a052213bb821d2a9c94143a2962235aeb27f6ad81374d2e60d5",
|
||||
"scripts/rectification/event_probes.py": "9bbadc246e6b8e94bb047522c3e5645613f508b3473ef40b38a207c77fdad58c",
|
||||
"scripts/rectification/refinement_packet.py": "dbeea5aa780889785ce850f3881e8b7f67bb44594e9bc382c65b2e730884a2f4",
|
||||
"scripts/rectification/scoring_service.py": "fa056fa30d9e6ffa4b75295fc49c793e35b26ebeca64ca843d26c8abef3050f2",
|
||||
"scripts/research/cluster_width_lib.py": "1d8f2ffa7795d62069deebb2de8e16749317037d4ed3f24a2c0092676e5cfcc6",
|
||||
"scripts/research/probe_supply_after_six.py": "098a5b4398d6f8d996917d809c2fc922ad64031d3b70660f8ef7c876aba92438",
|
||||
"scripts/research/reported_offset_sweep.py": "9df602c938f87a7b540e97c6c31a263f07b4ed0ccaeb1d2fac39b0d3d27ba0ed",
|
||||
"scripts/research/sealed_holdout_rerun.py": "a7f8a1c2230e33441e335810f13cfd723f3f8ad5b03a3657cb606e29d8559f19",
|
||||
"scripts/shadbala.py": "334dc9d8e4292f0c95d40fd4c29285aa89b9e49d4f187fb9498f4bb0ad93bdfc",
|
||||
"scripts/varga.py": "c1f06967c3d4d214edadff139c24aa31e59fb628477174235abe14f799a99076"
|
||||
},
|
||||
"hash_scope": "explicit_identity_file_sets_not_a_transitive_dependency_lock"
|
||||
},
|
||||
"production_identity_scope": "context_only_shadow_scorer_does_not_call_transition_proximity",
|
||||
"frozen_at_utc": "2026-10-08T10:23:31.558176+00:00",
|
||||
"dataset_path": "references/real_case_calibration/minute_rectification_holdout_v3.json",
|
||||
"dataset_sha256": "45aa76ab79dfc6c6404b6252083e1aa5c7e2e2b7de811fc11042a8ad2da3f0ea",
|
||||
"algorithm_version": "birth-time-event-fact-ranker-v4-shadow",
|
||||
"implementation_sha256": "511158c8387658d51f6055bd7afd15730b8b25314510d48f52498ed737c824d7",
|
||||
"files": [
|
||||
"scripts/active_rectification_event_engine.py",
|
||||
"scripts/active_rectification_events.py",
|
||||
"scripts/ashtakavarga.py",
|
||||
"scripts/dasha_analyzer.py",
|
||||
"scripts/divisional_charts_extended.py",
|
||||
"scripts/domain_calculation_service.py",
|
||||
"scripts/jaimini.py",
|
||||
"scripts/minute_rectification_fact_ranker_v4.py",
|
||||
"scripts/minute_rectification_feature_facts_v4.py",
|
||||
"scripts/narayana_dasha.py",
|
||||
"scripts/shadbala.py",
|
||||
"scripts/varga.py"
|
||||
],
|
||||
"historical_frozen_sha256": "f41c298dd6cdcebe7a93e632f7191954be7987f6012ca2a34cecb6e447fbf196",
|
||||
"evaluator_sha256": "a7f8a1c2230e33441e335810f13cfd723f3f8ad5b03a3657cb606e29d8559f19",
|
||||
"ayanamsa": "raman",
|
||||
"node_mode": "mean",
|
||||
"candidate_radius_minutes": [
|
||||
10
|
||||
],
|
||||
"minute_step": 1,
|
||||
"release_metrics": {
|
||||
"top_1_rate_minimum": 0.6,
|
||||
"top_3_rate_minimum": 0.85,
|
||||
"mean_absolute_minute_error_maximum": 2.0,
|
||||
"false_confirmation_rate_maximum": 0.05,
|
||||
"correct_insufficient_evidence_rejection_rate_minimum": 0.9
|
||||
},
|
||||
"results_previously_seen": true,
|
||||
"official_valid_independent_blind": false,
|
||||
"must_not_use_for_tuning": true,
|
||||
"tie_breaker": "sha256(benchmark_id:case_id:candidate_time); published minute is never passed to the ranker",
|
||||
"metric_rank_definition": "competition_rank_1_plus_strictly_higher_scores_legacy_protocol",
|
||||
"extra_metric_rank_definition": "score_desc_then_existing_opaque_sha256_total_order"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
# PROGRESS · 恒星黄道章动(2026-10-08)
|
||||
|
||||
工作树:`G:\Ferti\Jyotisha\.worktrees\ephemeris-nutation-20261008`。分支 `codex/ephemeris-nutation-20261008`。开工 HEAD `1a8c97f6`。没有 upstream。未推送,未做 `HEAD:staging`,未改 `.gitea/workflows`,未提升 `main`,未动主检出。未核对 `/api/health`。
|
||||
|
||||
## 开工预检
|
||||
|
||||
`python scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` 退出码 1,用时 144.42 秒。JSON 状态 fail。远端可见。碎片扫描 90 秒超时,定向测试 45 秒超时。外部适配器正常。本机没有仓库 `.venv`(`.venv/bin/python` 与 `.venv/Scripts/python.exe` 均为 WinError 2)。解释器是 `C:\Users\74082\anaconda3\python.exe` 3.11.7。超时退出不算通过。没有另建镜像,没有放宽断言。
|
||||
|
||||
## T0 调用点
|
||||
|
||||
| 调用点 | 开工分类 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| `_sidereal_calculation_profile` 的 `mean` | 一致 | 保持 `FLG_NONUT` |
|
||||
| 同函数 `apparent` | 一致 | 保持真岁差 |
|
||||
| 同函数 `legacy` 的 `get_ayanamsa` | 不一致 | 改为与 `apparent` 相同的 `get_ayanamsa_ex_ut`。不新增第四档 |
|
||||
| `_calc_sidereal_planets_for_jd` | 不一致 | 真岁差,与 `FLG_SIDEREAL` 一致。这是产品默认路径 |
|
||||
| `compute_chart_data` 上升、宫头、行星 | 不一致 | 同一个真岁差。宫位仍用 `houses(..., b'A')`。速度仍取热带,避免岁差变化率改动逆行判断 |
|
||||
| 引擎约 13006 | 不一致,下一行覆盖 | 删掉死赋值 |
|
||||
| `cmd_double_transit_pac` | 不一致 | 真岁差,并用同一套 flags |
|
||||
| `_calc_transit_lon` | 不一致 | 真岁差 |
|
||||
| 政策字段约 13620、13805 | 标签 | 记录的数字改为真岁差,避免和经度框架不一致 |
|
||||
| `cmd_planetary_congregation` | 不一致 | 真岁差 |
|
||||
| 全盘约 16519、16738,`cmd_nakshatra_adv.py`,`consultation_native_layers.py` | 经共享函数,原先不一致 | 随 `_calc_sidereal_planets_for_jd` 一致 |
|
||||
| 全盘 `houses_ex(..., b"R")` | 宫头已是 `FLG_SIDEREAL`;行星和上升来自有偏差的盘 | 宫制仍是 `sripati` / `b"R"`。盘修好后相对误差消失 |
|
||||
| `cmd_bhava_chalit` 读取的 MC | 随函数返回的岁差 | 返回值改为真岁差后一致 |
|
||||
| `bhava_chalit._cusps_swe` | 不一致 | 真岁差。只影响 Placidus / Koch |
|
||||
| `domain_calculation_service.compute_chart` | 经 `compute_chart_data` | 修好后一致 |
|
||||
| `compute_transit_longitude` | 不一致 | 真岁差。`calc_ut` 的 flags 未改 |
|
||||
| `daily_guidance_service._moon_transit` | 不一致 | 真岁差 |
|
||||
| `shadbala` 宫头 | 已是 `FLG_SIDEREAL`,一致 | 不改。Dig 短弧仍是 BUG-1270,不在本轮 |
|
||||
| `shadbala` 的 `ayanamsa_degrees` | 标签是平岁差 | 改记真岁差 |
|
||||
| `western_chart_engine.py` | 不适用(热带;岁差只作显示偏移) | 不改 |
|
||||
| `kp_cusp_observation.py` | 不一致(展示,不计分) | 真岁差 |
|
||||
| `validate_yoga_accuracy.py` 上升 | 不一致(校验脚本) | 上升改为真岁差;行星本来就是 `FLG_SIDEREAL` |
|
||||
| `muhurta` 固定 23.85 | 不适用 | 不改 |
|
||||
| `solar_return.py`、`ephemeris_events.py`、`native_special_lagnas.py` | 已是 `FLG_SIDEREAL`,一致 | 不改 |
|
||||
| `calculate_hora_lagna` / `calculate_ghati_lagna` / `calculate_bhava_lagna` | 红线 | 不改 |
|
||||
| 供应商树与基准脚本里的 `get_ayanamsa` | 不适用 | 不改 |
|
||||
|
||||
Windows 上 `inspect.signature(swe.houses_ex)` 会 `ValueError`。没有去包这个签名。产品盘用热带 `houses` 减去与行星相同标志的真岁差。四盘、三种岁差下,未取整的等宫宫头与 `houses_ex` 的 `FLG_SIDEREAL` 差在 1e-6° 内。
|
||||
|
||||
## 度数差
|
||||
|
||||
相对旧公式(热带视位置减 `get_ayanamsa`),四张盘、Lahiri / Raman / KP 的最大绝对差是 0.005043°。章动范围约 −0.002279° 到 +0.005043°。回归要求的对照是 Swiss Ephemeris `FLG_SIDEREAL`,不是旧公式。`tests/test_sidereal_nutation_frame.py` 24 组(四盘 × 三种岁差 × 两种交点)九星 `degree_raw` 差 ≤ 1e-6°,退出码 0。
|
||||
|
||||
Vimshottari 月亮余额折成日(365.2425):乔布斯公开盘土星 +2.625 天;奥巴马公开盘月亮 −0.624 天;爱因斯坦公开盘水星 +1.926 天;虚构盘金星 +2.299 天。主星都没有换。
|
||||
|
||||
## 换座
|
||||
|
||||
四盘 × Lahiri / Raman / KP × 九星与上升:星座、星宿、D9、D10 都没有换。唯一分盘槽位变化是虚构盘在 Lahiri 下月亮的 D60。
|
||||
|
||||
## PyJHora
|
||||
|
||||
离线生成器 `scripts/research/freeze_sidereal_nutation_pyjhora_fixture.py` 用本机 anaconda 里的 PyJHora 4.8.7。`drik.planetary_positions` 会 `AttributeError`(`planet_list` 是 dict,没有 `index`),所以冻结的是 `drik.sidereal_longitude`。pytest 只读 `tests/fixtures/sidereal_nutation_pyjhora_487.json`,不导入 jhora。真实交点下,引擎与冻结值的最大绝对差是 0.014454°(奥巴马公开盘的水星,三种岁差相同)。差来自 PyJHora 的真位置标志,不是 1e-6° 那条红线。产品运行时不导入 jhora。
|
||||
|
||||
## 算法身份
|
||||
|
||||
同一份公开烟测请求:12:00 由 8.5289 变为 8.5336,12:01 仍是 8.5242,12:02 由 8.0833 变为 8.088。身份升到 `rectification-v5-matrix-scoring-12`。策略仍是 policy-v4。记忆化 golden 新写 v5,v1–v4 逐字节保留。v4 sha256 `8491093281fe9767defea1b46d325237f1a4c6c0d4f076b9a77f07b985b526c3`。v5 的 `source_commit` 是章动修复提交 `e5f2a75183381c4a027014c7f1e89ddcb8393bd3`。
|
||||
|
||||
日期窗合同本来就是代数 ≥ 9。数据库守卫已是 `scoring-(9|[1-9][0-9]+)`,没有新迁移。本机没有把 `npm run test:db` 当 SQL 证据(迁移符号链接)。scoring-11 历史记录在 scoring-12 下只读打开并可以再比(BUG-621)。没有加拒绝打开旧会话的门。
|
||||
|
||||
## ERR-110
|
||||
|
||||
另存,不覆盖 sync6 记录:
|
||||
|
||||
- `docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.freeze.json`
|
||||
- `docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json`
|
||||
- `docs/research/reported_offset_ephemeris_nutation_2026_10_08.freeze.json`
|
||||
|
||||
影子 20 例相对 2026-09-20 历史报告 `changed_trial_count` 为 8。指标 top-1 0.5、top-3 0.5、平均绝对分钟误差 6.65。这不是发布指标,没有调参。合同 `references/rectification_sealed_holdout.v1.json` 的当前树指针改到这份记录;根上的历史 `top_1_rate` 0.15 与 `status: not_ready` 未改。
|
||||
|
||||
报偏移:900 例,退出码 0,约 911 秒。写出 `docs/research/reported_offset_ephemeris_nutation_2026_10_08.json`(sha256 `b433a906280a5b12e4e48fd5e86292b73d21a627f490e3083d719aceea3252c9`)和已有冻结文件(sha256 `bc43921250cdcc160fb51d88ebcae0262d180e18c0ab2b14791605b09d639d39`)。相对 2026-09-20 历史报告 `changed_trial_count` 581。sync6 那次是 235。没有调参。合同当前指针改到这份记录,sync6 放进该对象里的 `previous_reported_offset_replay`。根上原来的 batch1 指针留着。sync6 文件没有覆盖。
|
||||
|
||||
## 三栏
|
||||
|
||||
- `tests/test_cli_smoke.py` 全盘第一段大运起点。原值 `1952-03-19T20:24:47`;新值 `1952-03-22T11:42:07`;原因:章动进入月亮经度后大运起点移动(BUG-1275)。
|
||||
- `tests/test_rectification_engine_memoization.py`、`tests/test_dasha_transition_proximity_cross_midnight.py`、`tests/test_rectification_relative_support.py`:当前身份 scoring-11 → scoring-12。原因:同输入分数变了(BUG-1275)。
|
||||
- `frontend/tests/rectification-dated-algorithm-generation-20261002.test.ts`:回执默认 scoring-12。另有一条 scoring-11 历史在 scoring-12 下只读打开(BUG-621)。
|
||||
- `frontend/tests/rectification-engine-version-cross-midnight.test.ts`:`LIVE_CURRENT` 改为 scoring-12。scoring-11 缓存不再当现行。
|
||||
- `frontend/tests/rectification-midnight-date-anchor.test.ts`:fixture 循环加上 scoring-12。文件由 `midnight_date_anchor_regression.py --golden` 用真实引擎写出,虚构请求。
|
||||
- `frontend/tests/rectification-confirmation-gate.test.ts` 与密封合同:当前冻结重跑路径从 sync6 改到 `ephemeris_nutation_2026_10_08`。
|
||||
- `tests/test_sealed_holdout_contract_freshness.py`:身份排除名单加上这两份新冻结路径。
|
||||
|
||||
报告、对话卡、日运、Bhava Chalit、Shadbala 的定向测试没有把旧平岁差经度钉死。上述集合通过后没有重写那些 JSON。
|
||||
|
||||
## 测试
|
||||
|
||||
| 命令 | 退出码 |
|
||||
| --- | --- |
|
||||
| `pytest tests/test_sidereal_nutation_frame.py tests/test_runtime_jhora_import_scan.py` | 0(30 通过) |
|
||||
| `pytest` 记忆化、岁差四条路径、相对支持、跨午夜、Bhava、日运、星宿、KP 宫头观察 | 0 |
|
||||
| `pytest` 大运、cli 冒烟、Shadbala 完整、分盘、报告读者、密封合同、校正日期过渡、行运、条件大运、领域计算 | 1,仅 `test_full_reading_accepts_second_and_preserves_birth_time_precision`(旧大运起点)。改断言后单测退出码 0 |
|
||||
| 前端 `tsx --test`:日期窗代数、跨午夜、午夜锚、确认门 | 0(55 通过,0 失败,0 取消) |
|
||||
| `pytest tests/test_reported_offset_research.py::test_recorded_specification_and_all_prespecified_cells tests/test_sealed_holdout_contract_freshness.py` | 0(10 通过) |
|
||||
|
||||
Python 全量没有跑完。不把失败名单写成 Linux 62 的子集。已知 Windows 失败 `tests/test_consultation_native_layers.py::test_the_new_layer_leaves_every_existing_output_unchanged` 不是发布依据,本轮没有拿它当门。`scripts/jyotish_api_server.py` 没有加方法体。`page.tsx` 的 useState / useRef 没有增长。
|
||||
|
||||
快速门与 v5 若还在跑,见文末。
|
||||
|
||||
## v5 九格
|
||||
|
||||
对照 sync6 已接受的九格(±10 / ±30 / ±60):先验头名 0.2078 / 0.1169 / 0.0909;六题回放 0.6104 / 0.5325 / 0.3117;线上区间回放 0.6623 / 0.5844 / 0.3896。
|
||||
|
||||
评测进程已结束,退出码 0,77 例。先验头名 `top1`:0.1948 / 0.1039 / 0.0779。六题回放 `replay_top1`:0.6364 / 0.5455 / 0.3766。线上区间回放 `cluster_width` 的 `top1`:0.6623 / 0.5325 / 0.3896。和已接受的九格相比,先验头名三格各下降 0.0130(1.3 个百分点);线上区间 ±30 由 0.5844 降到 0.5325(5.2 个百分点);线上区间 ±10 与 ±60 持平。六题回放三格上升(+0.0260 / +0.0130 / +0.0649)。按红线停下:不调参,不推送。JSON 在临时目录,不进仓库。
|
||||
|
||||
命令:`C:\Users\74082\anaconda3\python.exe scripts/research/holdout_v5_baseline.py --dataset v5 --out-dir D:\Users\74082\AppData\Local\Temp\nutation-v5-20261008 --json-out D:\Users\74082\AppData\Local\Temp\nutation-v5-20261008\holdout_v5_baseline.json`
|
||||
|
||||
日志:`C:\Users\74082\.grok\sessions\G%3A%5CFerti%5CJyotisha%5C.worktrees%5Cephemeris-nutation-20261008\01a11af8-5880-78f1-be55-5fe3bf46523a\terminal\call-6ae2fd7a-997e-4a5a-abff-159b4b919895-104.log`
|
||||
|
||||
代码与 scoring-12 落地后才启动。脚本的临时副本 `references/real_case_calibration/_holdout_v5_baseline_tmp_minute_rectification_holdout_v5_all.json` 不提交。
|
||||
|
||||
## 未完成
|
||||
|
||||
- v5 77 例已结束,退出码 0。先验头名三格各降 1.3 个百分点,线上区间 ±30 降 5.2 个百分点。停下,不调参,不推送。BUG-1275 的 resolved 只覆盖经度修复。
|
||||
- 报偏移重放:已结束,见 ERR-110。581/900 与历史报告不同,没有调参。
|
||||
- 快速门:同一条命令已经结束,退出码 1。pytest 段 4 failed / 1059 passed / 1 skipped,用时 667.64 秒。两条记忆化失败是门禁进程读到的还是 `PENDING_NUTATION_COMMIT`;第二笔提交把 `SOURCE_COMMIT` 写成 `e5f2a75183381c4a027014c7f1e89ddcb8393bd3` 之后,`tests/test_rectification_engine_memoization.py` 再跑为 25 通过、退出码 0。`test_consultation_native_layers.py::test_the_new_layer_leaves_every_existing_output_unchanged` 是本机已知失败,不是发布依据。`tests/test_double_transit_d9_targets.py::test_d1_and_chandra_lagna_layers_match_the_pre_fix_golden` 仍对照章动修正前的相位度数,golden 没有重生成。这一条算未完成,不把快速门写成通过。
|
||||
- `npm run test:db` 未跑。SQL 守卫没有改。
|
||||
- 记忆化 v5 的 `source_commit` 已写成 `e5f2a75183381c4a027014c7f1e89ddcb8393bd3`。
|
||||
@@ -35,3 +35,7 @@
|
||||
|
||||
- 导入扫描会走到 `jyotish_api_server` 再走到校正三引擎包。那里的 `import_module("jhora...")` 挪进 pyjhora 文件名的参考模块,包函数 `_pyjhora_d1` 仍在原处,现有测试照旧替换它。
|
||||
- 全盘年运段仍会按需导入 `annual_pyjhora_replay`(文件名含 pyjhora,扫描不进入)。导入失败时该段记 blocked,不挡八个特殊上升点。
|
||||
|
||||
## Claude 验收(2026-10-08)
|
||||
|
||||
通过。见 `docs/BUG_HISTORY.md` BUG-1273 的验收补记。已部署 staging `795b8845`。
|
||||
|
||||
@@ -157,3 +157,5 @@ reported-offset:900 例,exit 0,约 804 秒。相对 2026-09-20 历史报
|
||||
| 抽查 | 乔布斯盘 D5/D6/D8/D11、Bhava/Hora/Ghati/Sree/Indu 上升与上游 `23be1807` 一致;Yogini 顺序与结构一致,首段余额差约 0.002 年(月亮约 17 角秒,属岁差细节) |
|
||||
| v5 全表 | 执行方「六题回放 ±10 下降 2.6、线上区间 ±60 下降 1.3」复核无误(列为 ±10 / ±30 / ±60)。全表:先验 +2.6 / +2.6 / +1.3;六题回放 −2.6 / +3.9 / 0;线上区间 0 / +2.6 / −1.3。9 格 5 升 2 平 2 降 |
|
||||
| 产品决定 | 2026-10-07 接受上述评测变化;门禁补修由 Claude 直接执行(分支 `codex/sync6-gate-fix-20261007`:重新生成扫描金样的 `scan`,更新小运起点断言并写三栏,Bug 历史补记) |
|
||||
|
||||
当时「虚构盘大运与独立计算差约 2 天」误判为岁差版本差异,实为 BUG-1275。
|
||||
|
||||
@@ -202,7 +202,8 @@
|
||||
| 任务书 | 进度 | 主题 | 状态 | 落点 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `TASK-report-full-data-edition-20261007.md` | — | **报告改为完整数据版、删除阅读版**:网站阅读版中文 7.6 万字符 vs 上游 `pl9_ai_density` 97 万;移植原始数据附录 / 清理器 / 时间补充,中英文;我方报告口径(BUG-1199~1229)全部保持;旧报告仍可打开;排在 sync6 之后 | 未合入(分支 `06028759`,验收未通过,见 fix-20261008) | — |
|
||||
| `TASK-report-full-data-fix-20261008.md` | — | **完整数据版补齐**(同分支 `06028759` 继续):中文只有上游 19%(9.9 万 vs 52.6 万字);产品允许移植上游解释生成器并逐条核口径;年运表接我方字段;英文必须交付才上线;其他 7 种大运另开单 | 待领取 | — |
|
||||
| `TASK-report-full-data-fix-20261008.md` | — | **完整数据版补齐**(同分支 `06028759` 继续):中文只有上游 19%(9.9 万 vs 52.6 万字);产品允许移植上游解释生成器并逐条核口径;年运表接我方字段;英文必须交付才上线;其他 7 种大运另开单 | 验收未通过(`32f19f07`,见 fix2) | — |
|
||||
| `TASK-report-full-data-fix2-20261008.md` | — | **完整数据版第二轮**(同分支 `32f19f07` 续):英文已过;未过——全量 +1 旧断言、寿命断言、半译词与字段名被改坏、「守护第未列出宫」163 处、年运章 6/29 节、大运解释只有上游 42%(产品:补回传统段落改「可能」口吻) | 待领取 | — |
|
||||
| `TASK-report-reader-main-fix-20260925.md` | `PROGRESS-report-reader-main-fix-20260925.md` | **读者版修复**:真实年主比较、分字段绑定与未来返照;保留 Bhava Bala 和标题,隔离报告/聊天规则 | 已验收(Claude 09-25:年运表逐年填齐且不同、Bhava Bala 保留、泄漏 0) | `4d801e53`(已部署,health 核对一致) |
|
||||
| `TASK-report-reader-main-20260924.md` | `PROGRESS-report-reader-main-20260924.md` | **报告正文换上游读者版 reader_main + KP / Muntha 计算缺陷 + 投影截断(BUG-1026/1027/1028)**:正文一直是上游审计版(读者版 09-09 才出、从未同步),本仓又自加「结论等级规则」/英文段/异常原文;投影黑名单不认 PL9 词汇且截断半句、删表留说明;KP args `vars()` 拷贝为空恒失败;Muntha 漏合 `birth_asc_sign_idx` → Year Lord 恒火星。产品拍板接入读者版、旧报告不回填。 | 部分通过(Claude 09-25:读者版切换、泄漏 0 命中、KP 已修;年运 Year Lord/Muntha 全为「-」P1、投影误删 Bhava Bala P2 → 修复单) | `3c2f7bd5`(已部署) |
|
||||
| `TASK-report-reader-actions-20260924.md` | — | **报告操作收敛**:列表只留「查看报告」;阅读页删「下载原始附录」「打印」,导出改图标按钮;导出对话框 ≥860 居中、窄屏仍贴底(修订 chapter-export D9/D10)。产品变更,不开 BUG 号 | 基本通过(Claude 09-24:入口收敛、图标、≥860 居中均符合;原始附录三文件因执行方权限未删、PROGRESS/清单未写 → 并入修复单) | `f125fae0`(未部署) |
|
||||
@@ -315,7 +316,8 @@
|
||||
| `TASK-rectification-probe-supply-research-20260913.md` | — | 研究单:六题后引擎在剩余候选上再出带年月题的四种放宽规则,20 例公开 AA 数据离线量收益,有收益才立实现单 | 已合入(离线测量,不改线上) | `b063c668` |
|
||||
| `TASK-rectification-refresh-r3-r4-20260913.md` | `PROGRESS-rectification-refresh-r3-r4-20260913.md` | 刷新阶段 R3(MIN_BOUNDARY_DAYS 45→30)+ R4(pratyantar 与 D9/D10 上升 Narayana);首轮出题不变。即使放宽仍有 0 题例子,定向补事另线保留(BUG-664/665) | 已实现 `ab1ade59`,已部署;2026-09-13 验收通过(实现单由执行方自拟,无产品决策记录段;收益口径见研究文档) | `codex/rectification-refresh-r3-r4-20260913` |
|
||||
| `TASK-upstream-sync6-20261007.md` | [PROGRESS-upstream-sync6-20261007.md](PROGRESS-upstream-sync6-20261007.md) | **上游同步第六轮(`23be1807`)**:10-05 五个计算修正一行未合——D5/D6/D8/D11 映射(乔布斯盘四张分盘星座全不同)、Vimsopaka 权重、Yogini 顺序与出生余额、Panchadhikari 宫位参照、Narayana 并列、特殊上升、出生秒数、Neecha Bhanga 两条件、首段大运子运、岁差被覆盖;逐项对照原书核实;最先做 | 已验收(门禁补修 `2df6ee72`,已部署 staging) | [PROGRESS-upstream-sync6-20261007.md](PROGRESS-upstream-sync6-20261007.md) |
|
||||
| `TASK-special-lagnas-native-20261008.md` | `PROGRESS-special-lagnas-native-20261008.md` | **特殊上升点原生计算(线上缺陷)**:sync6 U6 运行时调 PyJHora,生产镜像没有 PyJHora → staging 上 Hora/Ghati/Sree/Indu/Pranapada 全 blocked;原生实现、PyJHora 只作冻结对照、快速门禁止运行时 import jhora;最先做 | 待验收 | `codex/special-lagnas-native-20261008`(未上线;部署以 `/api/health` 为准) |
|
||||
| `TASK-special-lagnas-native-20261008.md` | `PROGRESS-special-lagnas-native-20261008.md` | **特殊上升点原生计算(线上缺陷)**:sync6 U6 运行时调 PyJHora,生产镜像没有 PyJHora → staging 上 Hora/Ghati/Sree/Indu/Pranapada 全 blocked;原生实现、PyJHora 只作冻结对照、快速门禁止运行时 import jhora;最先做 | 已验收(已部署 staging `795b8845`,BUG-1273) | `codex/special-lagnas-native-20261008`(未上线;部署以 `/api/health` 为准) |
|
||||
| `TASK-ephemeris-nutation-20261008.md` | `PROGRESS-ephemeris-nutation-20261008.md` | **恒星黄道少扣章动(BUG-1275)**:行星 = 含章动热带视位置 − 平岁差,全体偏 Δψ(≤17″),大运日期差 2–3 天;改标准算法、PyJHora 冻结对照、scoring-12、v5 重跑 | 待验收 | `codex/ephemeris-nutation-20261008`(未推送,未部署) |
|
||||
| `TASK-sync6-gate-red-20261007.md` | — | **sync6 后门禁仍红**:run 3213 快速门已过,「Frontend and database tests」开跑 20 秒后日志中断、15 分钟后判失败,后续步骤 failure 0 秒;疑运行机掉线;先 Re-run,红则本机带 Docker 跑 `CI=1 npm test` 对照 `0ed45a4b` | 已完成(Re-run 通过,BUG-1271,部署 `2df6ee72`) | — |
|
||||
|
||||
| `TASK-rectification-tie-break-entry-fix-20260913.md` | `PROGRESS-rectification-tie-break-entry-fix-20260913.md` | 修复单:参考题入口只读 `window_scan` 标志位 → 两道答完后仍显示、再点弹「现在没有可答的参考题」;并按产品拍板改成点一次连出 D9+D10(BUG-666~668,Skill 10.0.26) | 已合入(无独立验收记录) | `99127601`(BUG-666~668 resolved;已部署 staging,run 2589) |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# TASK · 恒星黄道经度少扣章动:所有行星偏约 0.005°,大运日期差 2–3 天(2026-10-08)
|
||||
|
||||
- 基线:`origin/staging`(写作时 `16782634`,已部署 `795b8845`)。
|
||||
- 分支 / worktree:`codex/ephemeris-nutation-20261008` / `.worktrees/ephemeris-nutation-20261008`
|
||||
- 串行:与 `TASK-report-full-data-fix2-20261008` 可以并行开工;**谁后合入 staging,谁 rebase 后重跑 Python 全量与报告定向测试**。两单都会让报告 golden 变化。
|
||||
- BUG 编号:本单是 **BUG-1275**(已在 `docs/BUG_HISTORY.md` 记为 investigating)。
|
||||
|
||||
## 1. 事故实证
|
||||
|
||||
Claude 2026-10-08 验收报告时,用乔布斯公开盘(1955-02-24 19:15 PST,Lahiri,平均交点)对比:
|
||||
|
||||
| 来源 | 月亮恒星黄道经度 | 土星余额后第一个大运(水星)起点 |
|
||||
| --- | --- | --- |
|
||||
| 我方引擎(报告「本命盘行星原始位置」) | 344.52°(内部 344.51652) | 1958-03-19 |
|
||||
| 上游 `23be1807` 同盘导出 | 344.51° | 1958-03-22 |
|
||||
| Swiss Ephemeris `FLG_SIDEREAL`(标准做法,本机直接算) | 344.51148 | — |
|
||||
|
||||
差值 0.00504°,正好等于这一时刻的黄经章动 Δψ(`swe.calc_ut(jd, swe.ECL_NUT)` 第 3 项 = 0.0050426°)。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`scripts/jyotish_engine.py` 的 `_calc_sidereal_planets_for_jd` 以及 `_sidereal_calculation_profile` 的 `legacy` 档:行星用默认标志算**含章动的热带视位置**,再减 `swe.get_ayanamsa(jd)`,而后者是**不含章动的平岁差**。两边口径不一致,所有行星统一多出 Δψ(|Δψ| 最大约 17.2″ ≈ 0.0048°,随年份在正负之间变化)。
|
||||
|
||||
影响:
|
||||
|
||||
- 所有行星、交点的恒星黄道经度同方向偏移 Δψ;落在星座 / 星宿 / 分盘边界附近的星会换格。
|
||||
- Vimshottari 等以月亮度数起算的大运:0.005° ÷ 13.333° × 大运年数 ≈ 2–3 天。
|
||||
- 上升点若用另一条路径计算(例如带 `FLG_SIDEREAL` 的宫位),行星与上升之间会有 Δψ 的相对误差,需在 T0 核实。
|
||||
- 我方 2026-10-07 记录过「虚构盘大运与独立计算差约 2 天」,当时被误判为岁差版本差异;其实也是本条(见 `docs/tasks/PROGRESS-upstream-sync6-20261007.md` 的 Claude 验收段,本单关闭时补一句更正)。
|
||||
|
||||
同一写法的其他调用点(T0 逐一核实是否同样不一致):`jyotish_engine.py` 中 `get_ayanamsa(` / `get_ayanamsa_ut(` 共约 10 处(约第 828、841、12350、13006、13371、13583、13620、13714、13805 行附近,按符号定位);`bhava_chalit.py`、`domain_calculation_service.py`、`daily_guidance_service.py`、`shadbala.py`、`western_chart_engine.py`(西洋盘用热带黄道,可能不受影响,要核实)。
|
||||
|
||||
## 3. 决策记录(产品 2026-10-08)
|
||||
|
||||
1. 单独开一单尽快修。
|
||||
2. 按标准恒星黄道算法改:行星与岁差使用同一口径(`FLG_SIDEREAL`,或 `get_ayanamsa_ex_ut` 取与行星同标志的岁差)。PyJHora 只作对照答案,不进运行时。
|
||||
3. 修正后所有日期都会微移;生时校正按版本规则升级并重跑评测。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. 修正后,三张公开盘(乔布斯、奥巴马、爱因斯坦)与虚构盘的九星恒星黄道经度,与 Swiss Ephemeris `FLG_SIDEREAL` 的差 ≤ 1e-6°;Lahiri、Raman、KP 三种岁差都测。PyJHora 4.8.7 的对照答案离线冻结成 fixture,差值写进进度记录。
|
||||
2. `position_mode` 的 `mean` / `apparent` 两档语义不变;`legacy` 档如果就是这条错误本身,改正并在进度记录写明;不得新增第四档绕开。
|
||||
3. 生时校正:同一输入分数会变 → 升 `rectification-v5-matrix-scoring-12`;记忆化 golden 出新版本、旧版冻结;研究记录按 ERR-110 重新冻结;历史校正记录仍能只读打开(BUG-621);v5 77 例评测逐格报告,任一格下降超过 1 个百分点就停下报产品。
|
||||
4. 引擎数值一变就跑 Python 全量,失败名单 ⊆ 基线(Linux 62 条)。golden 一律用真实引擎重生成;改断言写三栏。
|
||||
5. 运行时不得导入 `jhora`(快速门的导入扫描);验收在无 PyJHora 环境跑。
|
||||
6. 不改 workflow、不提升 main、不动迁移(除非版本字面量需要,届时按 sync5 先例只改守卫一行并真跑 `test:db`)。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T0 | 盘点所有「热带位置 − 岁差」的调用点,逐个标「一致 / 不一致 / 不适用(热带西洋)」;核实上升点与宫头走哪条路径 | 进度记录里有调用点表 |
|
||||
| T1 | 修正不一致的调用点 | 红线 1 |
|
||||
| T2 | 下游:报告、对话卡、日运、Bhava Chalit、Shadbala 的 golden 重生成 | 每份 golden 变化列表(哪些星换了星座 / 星宿 / 分盘) |
|
||||
| T3 | 生时校正 | 红线 3 |
|
||||
| T4 | 记录 | BUG-1275 改为 resolved(附验证);`CHANGELOG.md`(用户可感知:大运日期可能前后移动 2–3 天);进度记录;状态板 |
|
||||
|
||||
## 6. 验收口径
|
||||
|
||||
ruff、py_compile、快速门、Python 全量失败名单 ⊆ 基线、`npm test` 失败名单与基线相同、`next build` `/` Static。部署后核对 `/api/health` 的 `deployment.gitCommit`、`/login` 200、未登录 `/api/account` 401。
|
||||
@@ -19,7 +19,7 @@
|
||||
| 缺的内容 | 约字数 | 上游从哪里来 |
|
||||
| --- | --- | --- |
|
||||
| 每段大运主运 + 子运解释(9 段) | 22 万 | `scripts/professional_parity_closure.py` 的 `build_dasha_interpretation_pack`、`build_vimshottari_dasha_report_pack` |
|
||||
| Mudda / Patyayini 年运表、Patyayini 结束日期表 | 6.5 万 | 我方年运资料包**已有** `mudda_dasha`、`patyayini_dasha`,渲染器没认出字段名 |
|
||||
| Mudda / Patyayini 年运表、Patyayini 结束日期表 | 6.5 万 | 我方年运资料包有 `mudda_dasha`(`partial_verified`),渲染器没认出字段名。**`patyayini_dasha` 在我方是 `blocked`**(有无 PyJHora 都一样,引擎不产出),Patyayini 两节写进缺口清单,不补算 |
|
||||
| Sthira、Drig、Shoola、Navamsha、Niryaana Shoola、Lagna Kendradi 等大运 | 5 万 | 另开一单(见决策 4) |
|
||||
| 生命周期年度返照、年度北印度图盘、月返照、八年总览 | 1.9 万 | 部分在我方年运资料包里,逐项核对 |
|
||||
| Sahams、Raman Sahams、Tajika 呈现 / Condition / 格局对照、Tripataki | 0.6 万 | 我方年运资料包有 `sahams`、`tajika_yogas` |
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# TASK · 完整数据版第二轮修复:年运章、大运解释深度、口径与译名(2026-10-08)
|
||||
|
||||
- 分支:继续在 `codex/report-full-data-edition-20261007`(远端 `32f19f07`)上做。
|
||||
- 前序:`TASK-report-full-data-edition-20261007`、`TASK-report-full-data-fix-20261008` 仍有效;本单只列 Claude 2026-10-08 验收未通过的项。
|
||||
- 与 `TASK-ephemeris-nutation-20261008` 可以并行;谁后合入 staging,谁 rebase 后重跑 Python 全量与报告定向测试。
|
||||
- BUG 编号:本分支已用 BUG-1272、BUG-1274;BUG-1275 已给章动单。本单从 **1276** 起,开工时再核对。
|
||||
|
||||
## 1. 验收实证(Claude,2026-10-08,Linux,屏蔽 PyJHora,乔布斯盘 Lahiri)
|
||||
|
||||
通过:英文 0 汉字、中英二级标题 35/35;大运、行星、宫主逐宫、Dosha、瑜伽解释都在;「重点」事实正确;生成 12.5 秒;`npm test` 4991 / 失败 24 与基线逐名相同;tsc 0;lint 0 error;快速门通过;`next build --webpack` 后 `/` Static。
|
||||
|
||||
未通过:
|
||||
|
||||
| # | 问题 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| A1 | Python 全量多 1 条失败 | `tests/test_year_lord_blocked_no_fallback.py::test_report_shows_the_blocked_sentence_in_both_editions` 仍要求英文保留「年主暂无法确定」与 `[source text retained:`;英文已清零后该断言失去对象。全量 63 vs 基线 62 |
|
||||
| A2 | 寿命断言(红线 2) | 中文瑜伽表「长寿格局 \| 寿命/健康 \| 强 \| … \| 长寿;健康良好」;英文「Ayur Yoga (Strong Longevity)」。旧阅读版已有,但红线 2 要求全文 |
|
||||
| A3 | 半译词(T5 未达) | 中文:「功能吉星s」「功能凶星s」「功能中性s」「角宫s」「宫条件s」;字段名被术语表改坏:`strict_功能吉星_malefic_v2`、`bphs_ch34_general_with_星座_exceptions_v2` |
|
||||
| A4 | 病句 | 「守护第未列出宫」中文出现 163 次(罗睺 / 计都不守护宫位) |
|
||||
| A5 | 年运章没有补(T2 / T7 未达) | 「年度图盘与 Tajika」本分支 6 节、约 4 千字;上游 29 节、约 11.8 万字。进度记录的覆盖度表按自己的分组计数,没有按 T7 与上游逐节比对;表中「太阳返照:已有」,中文正文无「真实太阳返照」一节 |
|
||||
| A6 | 大运解释太薄 | 结构对齐(本分支 734 小节,上游 743),但「大运解释」章本分支约 8.5 万字、上游约 20.2 万字。上游每段主运有「通常会启动的主题」「可能带来的机会」「条件良好时」「受压时」「细读方式」等段落,本分支大多只剩一句 |
|
||||
|
||||
## 2. 决策记录(产品 2026-10-08)
|
||||
|
||||
1. **大运解释把上游的传统解读段落补回来,改成「可能」口吻**:保留「这段时间可能突出的主题」「条件好时可能」「受压时可能表现为」「怎么细读」等段落;只删红线 2 的内容(疾病诊断、死亡、寿命、必然 / 一定会、名誉一定受损这类断语),改写成不确定的说法。
|
||||
2. 年运章按上游逐节补;我方资料包里没有数据的节写进缺口清单,不补算。
|
||||
3. 其余沿用前序任务书决策(英文必须交付才合入;七种大运另开单;不搬视位置 / 拟合偏移)。
|
||||
|
||||
## 3. 任务分解
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| F1 | A1:更新断言,写三栏(原值 / 新值 / 原因) | Python 全量失败名单 ⊆ 基线(Linux 62,与 `795b8845` 逐名相同) |
|
||||
| F2 | A2:寿命、长寿、Longevity、Ayur 类格局的「作用」列改为不断言寿命的说法(例如「传统上与体质和恢复力有关,仅作参考」),中英同改 | 新测试对中英全文断言:不出现「长寿」「寿命长」「短寿」「longevity」等断语(格局名本身可保留原名,作用说明不得断言) |
|
||||
| F3 | A3:术语替换只作用于自然语言文本,不进入字段名、标识符、反引号、表格中的机器键;复数 `s` 一律随英文词处理 | 正则测试:中文全文不得出现 `[一-鿿]+s\b`;字段名 / 标识符中不得出现汉字 |
|
||||
| F4 | A4:罗睺 / 计都写「不守护宫位」(英文 "rules no house");其他「未列出」按句意改写 | 中文全文「守护第未列出宫」为 0;新测试锁住 |
|
||||
| F5 | A5:年运章按上游 `23be1807` 同盘导出逐节对照。我方 `annual_tajika_pack` 有数据的节必须出现:真实太阳返照(`solar_return`)、Panchadhikari / 年主依据(`year_lord`)、年度锚点与年度行星资料(`annual_chart`)、Tajika 呈现 / Condition / Forming Pair / 力量 / 格局对照(`tajika_yogas`)、年度北印度图盘(`annual_chart`)、Varshaphala 年度结果解释与年度大运结果解释(上游模板,按决策 1 的口吻)、Mudda 表全量(目前只有约 860 字,上游约 3 万字,逐项核对少在哪) | 进度记录列一张表:上游每个 `###` 年运节 → 「已有 / 缺字段(写缺哪个字段)/ 决策号」。Patyayini(`patyayini_dasha` 为 blocked)、Raman Sahams、Tripataki、月返照、八年总览、生命周期年度返照若我方无数据,写进缺字段,不补算 |
|
||||
| F6 | A6:按决策 1 补回大运解释段落 | 乔布斯盘「大运解释」章 ≥ 上游的 80%(按字符);红线 2 全部测试仍过 |
|
||||
| F7 | 覆盖度复算 | 用同盘对上游逐个 `##` / `###` 标题比对(大运标题里的日期不同,按行星名配对)。除决策 4 的七种大运外,「缺字段」不超过 10 节;列出全部清单 |
|
||||
| F8 | 测试与门禁(**Linux 或门禁同等环境**,屏蔽 PyJHora 跑一次报告导出) | 快速门、Python 全量 ⊆ 基线、`npm test` 失败名单与基线相同、tsc 0、lint 0 error、`next build` `/` Static |
|
||||
| F9 | 记录 | BUG 新编号(1276 起)、`CHANGELOG.md`、进度记录、状态板、`docs/testing/` 清单 |
|
||||
|
||||
## 4. 让步顺序
|
||||
|
||||
F5 中的 Varshaphala / 年度大运结果解释 → F6 的 80% 降到 70%(需写明原因)。F1–F4、F7、F8 不让步。
|
||||
@@ -1071,7 +1071,7 @@ export async function runV9Diagnostics(input: {
|
||||
// Keep the receipt default aligned with Python's ALGORITHM_VERSION; persisted
|
||||
// receipts retain their original identity (this is not a Skill/open gate).
|
||||
export const v9EngineVersion = (): string =>
|
||||
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-11";
|
||||
process.env.RECTIFICATION_ENGINE_VERSION?.trim() || "rectification-v5-matrix-scoring-12";
|
||||
|
||||
export type V9RangeReading = Readonly<{
|
||||
stableThemes: readonly string[];
|
||||
|
||||
@@ -134,7 +134,7 @@ test(`dated adoption ${declaration.time} persists date/spec atomically and rejec
|
||||
if sqlerrm <> 'agentic_rectification_candidate_state_inconsistent' then raise; end if;
|
||||
end;
|
||||
end $$; select 'rejected';`);
|
||||
for (const version of ["rectification-v5-matrix-scoring-10", "rectification-v5-matrix-scoring-11"]) {
|
||||
for (const version of ["rectification-v5-matrix-scoring-10", "rectification-v5-matrix-scoring-11", "rectification-v5-matrix-scoring-12"]) {
|
||||
setVersion(version);
|
||||
assert.match(rejects(), /rejected/, `${version} must not pass as legacy`);
|
||||
}
|
||||
|
||||
+5098
File diff suppressed because it is too large
Load Diff
@@ -102,8 +102,11 @@ const diagnosticReport = JSON.parse(readFileSync(
|
||||
// 原值(7):docs/research/sealed_holdout_rerun_astrologer_rulings_batch1_2026_10_03.json
|
||||
// 新值(7):docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json
|
||||
// 原因(7):TASK-upstream-sync6-20261007 改了冻结身份内的 varga.py / divisional_charts_extended.py / narayana_dasha.py / scoring_service.py,按 ERR-110 另存新冻结重跑;影子 20 例相对 2026-09-20 历史报告逐条不变,确认门行为与阈值不变。
|
||||
// 原值(8):docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json
|
||||
// 新值(8):docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json
|
||||
// 原因(8):章动修正与 scoring-12 改了冻结身份(ERR-110,BUG-1275)。影子重跑不是发布指标,确认门行为与阈值不变。
|
||||
const currentTreeRerun = JSON.parse(readFileSync(
|
||||
new URL("../../docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json", import.meta.url),
|
||||
new URL("../../docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json", import.meta.url),
|
||||
"utf8",
|
||||
)) as {
|
||||
frozen_record: { implementation_sha256: string };
|
||||
@@ -248,9 +251,10 @@ test("sealed holdout aggregates match the v3 report and still block confirmation
|
||||
// 原值(4):…upstream_sync4_2026_10_02.json / 新值(4):…upstream_sync5_2026_10_02.json / 原因(4):sync5 重新冻结(同上)。
|
||||
// 原值(5):…upstream_sync5_2026_10_02.json / 新值(5):…astrologer_rulings_batch1_2026_10_03.json / 原因(5):TASK-astrologer-rulings-batch1 重新冻结(同上)。
|
||||
// 原值(6):…astrologer_rulings_batch1_2026_10_03.json / 新值(6):…upstream_sync6_2026_10_07.json / 原因(6):sync6 的 D5/D11 与 scoring-11 重新冻结(ERR-110,BUG-1260)。
|
||||
// 原值(7):…upstream_sync6_2026_10_07.json / 新值(7):…ephemeris_nutation_2026_10_08.json / 原因(7):章动修正与 scoring-12 重新冻结(ERR-110,BUG-1275)。
|
||||
assert.equal(
|
||||
productHoldout.current_tree_scorer.source_report,
|
||||
"docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json",
|
||||
"docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json",
|
||||
);
|
||||
assert.equal(currentTreeRerun.official_valid_independent_blind, false);
|
||||
assert.equal(productHoldout.current_tree_scorer.matches_metrics_scorer, false);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { dossierFixture } from "./rectification-v9-test-support.ts";
|
||||
|
||||
test("dated contract is a generation threshold, not one literal", () => {
|
||||
assert.equal(FIRST_DATED_SCORING_GENERATION, 9);
|
||||
for (const version of ["rectification-v5-matrix-scoring-9", "rectification-v5-matrix-scoring-10", "rectification-v5-matrix-scoring-11", "rectification-v5-matrix-scoring-100"]) {
|
||||
for (const version of ["rectification-v5-matrix-scoring-9", "rectification-v5-matrix-scoring-10", "rectification-v5-matrix-scoring-11", "rectification-v5-matrix-scoring-12", "rectification-v5-matrix-scoring-100"]) {
|
||||
assert.equal(isDatedScoringAlgorithmVersion(version), true, version);
|
||||
}
|
||||
for (const version of ["rectification-v5-matrix-scoring-8", "rectification-v5-matrix-scoring-1", "rectification-v5-matrix-scoring-09",
|
||||
@@ -22,14 +22,14 @@ test("dated contract is a generation threshold, not one literal", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("receipt default and Python identity agree on scoring-11", (t) => {
|
||||
test("receipt default and Python identity agree on scoring-12", (t) => {
|
||||
const saved = process.env.RECTIFICATION_ENGINE_VERSION;
|
||||
delete process.env.RECTIFICATION_ENGINE_VERSION;
|
||||
t.after(() => { if (saved === undefined) delete process.env.RECTIFICATION_ENGINE_VERSION; else process.env.RECTIFICATION_ENGINE_VERSION = saved; });
|
||||
// 原值: rectification-v5-matrix-scoring-10;新值: ...-scoring-11;原因: D5/D11 改变同输入分数(BUG-1260)。
|
||||
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-11");
|
||||
// 原值: rectification-v5-matrix-scoring-11;新值: ...-scoring-12;原因: 章动修正改变同输入分数(BUG-1275)。
|
||||
assert.equal(v9EngineVersion(), "rectification-v5-matrix-scoring-12");
|
||||
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
|
||||
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"$/m);
|
||||
assert.match(python, /^ALGORITHM_VERSION = "rectification-v5-matrix-scoring-12"$/m);
|
||||
});
|
||||
|
||||
test("no source file compares the algorithm identity with a scoring-N literal", () => {
|
||||
@@ -85,5 +85,20 @@ test("a scoring-10 history opens read-only under scoring-11 and offers a recompa
|
||||
const policyVersion = historical.decision_policy_version;
|
||||
const underEleven = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-11", policyVersion });
|
||||
assert.deepEqual([underEleven.read_only, underEleven.can_recompare, underEleven.algorithm_version], [true, true, "rectification-v5-matrix-scoring-10"]);
|
||||
const underTwelve = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-12", policyVersion });
|
||||
assert.deepEqual([underTwelve.read_only, underTwelve.can_recompare, underTwelve.algorithm_version], [true, true, "rectification-v5-matrix-scoring-10"]);
|
||||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||||
});
|
||||
|
||||
test("a scoring-11 history opens read-only under scoring-12 and offers a recompare (BUG-621)", () => {
|
||||
const historical = JSON.parse(readFileSync(new URL("./fixtures/rectification-midnight-date-anchor.delivery.scoring-11.native.json", import.meta.url), "utf8")).response;
|
||||
assert.equal(historical.algorithm_version, "rectification-v5-matrix-scoring-11");
|
||||
const latest = { ...historical, candidates: historical.candidate_decisions };
|
||||
const dossier = parseV9CaseDossier(dossierFixture({ stage: "minute", latestResult: latest }));
|
||||
assert.ok(dossier);
|
||||
assert.equal(dossier.latestResult?.algorithmVersion, "rectification-v5-matrix-scoring-11");
|
||||
const policyVersion = historical.decision_policy_version;
|
||||
const underTwelve = resultIdentityView(dossier, { algorithmVersion: "rectification-v5-matrix-scoring-12", policyVersion });
|
||||
assert.deepEqual([underTwelve.read_only, underTwelve.can_recompare, underTwelve.algorithm_version], [true, true, "rectification-v5-matrix-scoring-11"]);
|
||||
assert.ok(dossier.latestResult!.candidates.every((candidate) => typeof candidate.candidate_date === "string"));
|
||||
});
|
||||
|
||||
@@ -31,21 +31,25 @@ const golden = JSON.parse(readFileSync(new URL(
|
||||
// Historical golden identity stays fixed; it must not be relabeled as the live engine.
|
||||
const CURRENT = "rectification-v5-matrix-scoring-8";
|
||||
const PREVIOUS = "rectification-v5-matrix-scoring-7";
|
||||
// 原值: LIVE_CURRENT = "rectification-v5-matrix-scoring-10";新值: "...-scoring-11";
|
||||
// scoring-10 dated golden 留作历史,当前版本另有真实引擎生成的 scoring-11 golden。
|
||||
// 原因: D5/D11 改变同输入分数(BUG-1260),按 BUG-1181 升算法身份。
|
||||
const LIVE_CURRENT = "rectification-v5-matrix-scoring-11";
|
||||
// 原值: LIVE_CURRENT = "rectification-v5-matrix-scoring-11";新值: "...-scoring-12";
|
||||
// scoring-11 dated golden 留作历史,当前版本另有真实引擎生成的 scoring-12 golden。
|
||||
// 原因: 章动修正改变同输入分数(BUG-1275),按 BUG-1181 升算法身份。
|
||||
const LIVE_CURRENT = "rectification-v5-matrix-scoring-12";
|
||||
const DATED_HISTORICAL = "rectification-v5-matrix-scoring-9";
|
||||
const SCORING_10 = "rectification-v5-matrix-scoring-10";
|
||||
const SCORING_11 = "rectification-v5-matrix-scoring-11";
|
||||
const liveGolden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-midnight-date-anchor.delivery.native.json", import.meta.url,
|
||||
), "utf8"));
|
||||
const scoring10Golden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-10.native.json", import.meta.url,
|
||||
), "utf8"));
|
||||
const currentGolden = JSON.parse(readFileSync(new URL(
|
||||
const scoring11Golden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-11.native.json", import.meta.url,
|
||||
), "utf8"));
|
||||
const currentGolden = JSON.parse(readFileSync(new URL(
|
||||
"./fixtures/rectification-midnight-date-anchor.delivery.scoring-12.native.json", import.meta.url,
|
||||
), "utf8"));
|
||||
const envKeys = [
|
||||
"RECTIFICATION_ENGINE_VERSION", "RECTIFICATION_ALGORITHM_VERSION",
|
||||
"RECTIFICATION_DECISION_POLICY_VERSION",
|
||||
@@ -72,9 +76,10 @@ test("receipt default matches the cross-midnight native scoring version without
|
||||
assert.equal(golden.score.algorithm_version, CURRENT);
|
||||
assert.equal(golden.versions.algorithm_version, CURRENT);
|
||||
const python = readFileSync(new URL("../../scripts/rectification/scoring_service.py", import.meta.url), "utf8");
|
||||
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"/);
|
||||
assert.match(python, /ALGORITHM_VERSION = "rectification-v5-matrix-scoring-12"/);
|
||||
assert.equal(liveGolden.response.algorithm_version, DATED_HISTORICAL);
|
||||
assert.equal(scoring10Golden.response.algorithm_version, SCORING_10);
|
||||
assert.equal(scoring11Golden.response.algorithm_version, SCORING_11);
|
||||
assert.equal(currentGolden.response.algorithm_version, LIVE_CURRENT);
|
||||
const fingerprints = { evidenceLedgerFingerprint: "e".repeat(64), candidateRangeFingerprint: "c".repeat(64) };
|
||||
const live = {
|
||||
@@ -83,9 +88,10 @@ test("receipt default matches the cross-midnight native scoring version without
|
||||
};
|
||||
const historical = { ...fingerprints, algorithmVersion: CURRENT, policyVersion: golden.versions.decision_policy_version };
|
||||
assert.equal(cachedEngineScoreIsReusable(historical, fingerprints, live), false);
|
||||
// BUG-1181 / BUG-1260: scoring-9 and scoring-10 caches are not current under scoring-11.
|
||||
// BUG-1181 / BUG-1260 / BUG-1275: scoring-9, scoring-10 and scoring-11 caches are not current under scoring-12.
|
||||
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: DATED_HISTORICAL }, fingerprints, live), false);
|
||||
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: SCORING_10 }, fingerprints, live), false);
|
||||
assert.equal(cachedEngineScoreIsReusable({ ...historical, algorithmVersion: SCORING_11 }, fingerprints, live), false);
|
||||
// 原值: 只把 algorithmVersion 改成 LIVE_CURRENT 即可复用(当时 golden 的策略与旧 fixture 相同)
|
||||
// 新值: 复用还要带上当前 golden 的 policyVersion
|
||||
// 原因: scoring-11 回执的策略是 policy-v4,旧跨午夜 fixture 的策略不是这一版(BUG-1260)
|
||||
|
||||
@@ -98,8 +98,8 @@ for (const option of ["A", "B", "C", "skip_probe"] as const) test(`D1 initial no
|
||||
});
|
||||
|
||||
// BUG-1181: scoring-9 and later goldens must parse as dated.
|
||||
// 原值: native + scoring-10;新值: 再加 scoring-11;原因: D5/D11 升到 scoring-11(BUG-1260),旧文件仍要按日期窗打开。
|
||||
for (const fixture of ["rectification-midnight-date-anchor.delivery.native.json", "rectification-midnight-date-anchor.delivery.scoring-10.native.json", "rectification-midnight-date-anchor.delivery.scoring-11.native.json"]) test(`native golden crosses engine parsing, inference and dated delivery without reanchoring (${fixture})`, async () => {
|
||||
// 原值: native + scoring-10 + scoring-11;新值: 再加 scoring-12;原因: 章动修正升到 scoring-12(BUG-1275),旧文件仍要按日期窗打开。
|
||||
for (const fixture of ["rectification-midnight-date-anchor.delivery.native.json", "rectification-midnight-date-anchor.delivery.scoring-10.native.json", "rectification-midnight-date-anchor.delivery.scoring-11.native.json", "rectification-midnight-date-anchor.delivery.scoring-12.native.json"]) test(`native golden crosses engine parsing, inference and dated delivery without reanchoring (${fixture})`, async () => {
|
||||
const golden = JSON.parse(readFileSync(new URL(`./fixtures/${fixture}`, import.meta.url), "utf8"));
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () => new Response(JSON.stringify(golden.response), { status: 200 })) as typeof fetch;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"dataset_path": "references/real_case_calibration/minute_rectification_holdout_v3.json",
|
||||
"report_path": "references/real_case_calibration/minute_rectification_holdout_v3_report.json",
|
||||
"source_audit_status": "corrected_known_date_errors",
|
||||
"evaluated_on": "2026-10-07",
|
||||
"evaluated_on": "2026-10-08",
|
||||
"evaluated_on_scope": "latest_fixed_protocol_rerun_not_historical_runtime_metrics",
|
||||
"historical_source_audit_status": "invalidated_after_replay",
|
||||
"report_status": "invalidated_after_source_audit",
|
||||
@@ -24,7 +24,7 @@
|
||||
"evaluated_on": "2026-07-21"
|
||||
},
|
||||
"current_tree_scorer": {
|
||||
"implementation_sha256": "b1b5f5e4faa34033880113d481796544eb0bb62ce78aa917b0c8c172759c5dcf",
|
||||
"implementation_sha256": "511158c8387658d51f6055bd7afd15730b8b25314510d48f52498ed737c824d7",
|
||||
"extended_identity": {
|
||||
"historical_artifacts_manifest_sha256": "6102a26a840be207b5858b3a4c0509274468ae9071e86d308cbbac7371d6864e",
|
||||
"production_scoring_files": [
|
||||
@@ -52,7 +52,7 @@
|
||||
"scripts/shadbala.py",
|
||||
"scripts/varga.py"
|
||||
],
|
||||
"production_scoring_sha256": "98ea64425d33095b33412f3a6373b2ad68b97eae94d49f85f52bb54f3c7b73ad",
|
||||
"production_scoring_sha256": "b72dbd61feec556c3863cb1b4496ded677c7077324a71b86e31722250a8d1528",
|
||||
"research_files": [
|
||||
"scripts/research/reported_offset_sweep.py",
|
||||
"scripts/research/cluster_width_lib.py",
|
||||
@@ -62,14 +62,14 @@
|
||||
"scripts/minute_rectification_fact_blind_eval_v4.py",
|
||||
"scripts/minute_rectification_holdout_validator.py"
|
||||
],
|
||||
"research_implementation_sha256": "173fd3e37a70a4e30717d1015cf6e4346820d544a0f3be24137165c810c8d4d8",
|
||||
"research_implementation_sha256": "12dc4049d8feeaf9e8f7df54666e7de7e8de6b45f195f52999bdbd52217908d9",
|
||||
"file_sha256": {
|
||||
"scripts/active_rectification_event_engine.py": "cdc0e68edc770a2a1810fbdb68cbfd46dcd50b267c739c496fcc078c5e26b5b4",
|
||||
"scripts/active_rectification_events.py": "03e333ccf1103caf685dfe50fe890ab1793e945037085f50ce834a4a3b232222",
|
||||
"scripts/ashtakavarga.py": "9bfc0b8a06af6122fa6c3881706e64f28b9ba58b53d98ff55957d4741d820f3f",
|
||||
"scripts/dasha_analyzer.py": "5e53b1b4d4c7414c08dd0e7716f120a63c70ec3428ec93547f64d55c580da0e1",
|
||||
"scripts/divisional_charts_extended.py": "e9820e80f14fddc86ab5495d11455d16c2d3c9502a9a242fcf9efc41186e0d11",
|
||||
"scripts/domain_calculation_service.py": "9ee955874eea806c7b56f4246d90009c02ee8318b8f3dfe1d7132b1c0b4262e1",
|
||||
"scripts/domain_calculation_service.py": "66923a2bc69daf65c965d2973c4b9234edbec5147909eaf3609b6b377ce3c7ca",
|
||||
"scripts/functional_benefics.py": "cbaac3b8c09b97f8724b5300e9a402cbf77089af6c7db8788550d40fd35ac57b",
|
||||
"scripts/jaimini.py": "6b88658da5404da9b136686d92a04be462d61a39da6e8de4255021e2ed2d1173",
|
||||
"scripts/minute_rectification_blind_eval.py": "07aa30d8bad3278669495d5fea8be937aaf6367c8af980ff4f2fe9aa84ccc6ff",
|
||||
@@ -87,12 +87,12 @@
|
||||
"scripts/rectification/decision_policy.py": "80e8de4680d19a052213bb821d2a9c94143a2962235aeb27f6ad81374d2e60d5",
|
||||
"scripts/rectification/event_probes.py": "9bbadc246e6b8e94bb047522c3e5645613f508b3473ef40b38a207c77fdad58c",
|
||||
"scripts/rectification/refinement_packet.py": "dbeea5aa780889785ce850f3881e8b7f67bb44594e9bc382c65b2e730884a2f4",
|
||||
"scripts/rectification/scoring_service.py": "45d777d2d762a73c8b9193ba269572e6ab06cdee43afbfb22bce225e40a4a2dd",
|
||||
"scripts/rectification/scoring_service.py": "fa056fa30d9e6ffa4b75295fc49c793e35b26ebeca64ca843d26c8abef3050f2",
|
||||
"scripts/research/cluster_width_lib.py": "1d8f2ffa7795d62069deebb2de8e16749317037d4ed3f24a2c0092676e5cfcc6",
|
||||
"scripts/research/probe_supply_after_six.py": "098a5b4398d6f8d996917d809c2fc922ad64031d3b70660f8ef7c876aba92438",
|
||||
"scripts/research/reported_offset_sweep.py": "6cc36e3a621bd23332951af53a091b1131fa350f6db1d52cb9fa52cc413c09f4",
|
||||
"scripts/research/sealed_holdout_rerun.py": "e5ee47a1fba92f9cd5d4e63fd52c6104fc6680fe7da1beec3d0db2b4cfcb9abc",
|
||||
"scripts/shadbala.py": "e949ff1cc925ccbc9a25b68981091d1d621b80a5b3839a3b3ade655d082424d6",
|
||||
"scripts/research/reported_offset_sweep.py": "9df602c938f87a7b540e97c6c31a263f07b4ed0ccaeb1d2fac39b0d3d27ba0ed",
|
||||
"scripts/research/sealed_holdout_rerun.py": "a7f8a1c2230e33441e335810f13cfd723f3f8ad5b03a3657cb606e29d8559f19",
|
||||
"scripts/shadbala.py": "334dc9d8e4292f0c95d40fd4c29285aa89b9e49d4f187fb9498f4bb0ad93bdfc",
|
||||
"scripts/varga.py": "c1f06967c3d4d214edadff139c24aa31e59fb628477174235abe14f799a99076"
|
||||
},
|
||||
"hash_scope": "explicit_identity_file_sets_not_a_transitive_dependency_lock"
|
||||
@@ -100,23 +100,23 @@
|
||||
"matches_metrics_scorer": false,
|
||||
"official_eval_implementation_hash_matches": false,
|
||||
"official_eval_trial_count": 0,
|
||||
"source_report": "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json",
|
||||
"source_report": "docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json",
|
||||
"fixed_protocol_rerun_trial_count": 20,
|
||||
"fixed_protocol_rerun_hash_matches": true,
|
||||
"metrics": {
|
||||
"top_1_rate": 0.45,
|
||||
"top_1_rate": 0.5,
|
||||
"top_3_rate": 0.5,
|
||||
"mean_absolute_minute_error": 6.45,
|
||||
"mean_absolute_minute_error": 6.65,
|
||||
"false_confirmation_rate": 0.0,
|
||||
"correct_insufficient_evidence_rejection_rate": 1.0,
|
||||
"confirmation_coverage_rate": 0.0
|
||||
}
|
||||
},
|
||||
"current_tree_fixed_protocol_rerun": {
|
||||
"evaluated_on": "2026-10-07",
|
||||
"report_path": "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json",
|
||||
"freeze_record_path": "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.freeze.json",
|
||||
"implementation_sha256": "b1b5f5e4faa34033880113d481796544eb0bb62ce78aa917b0c8c172759c5dcf",
|
||||
"evaluated_on": "2026-10-08",
|
||||
"report_path": "docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.json",
|
||||
"freeze_record_path": "docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.freeze.json",
|
||||
"implementation_sha256": "511158c8387658d51f6055bd7afd15730b8b25314510d48f52498ed737c824d7",
|
||||
"extended_identity": {
|
||||
"historical_artifacts_manifest_sha256": "6102a26a840be207b5858b3a4c0509274468ae9071e86d308cbbac7371d6864e",
|
||||
"production_scoring_files": [
|
||||
@@ -144,7 +144,7 @@
|
||||
"scripts/shadbala.py",
|
||||
"scripts/varga.py"
|
||||
],
|
||||
"production_scoring_sha256": "98ea64425d33095b33412f3a6373b2ad68b97eae94d49f85f52bb54f3c7b73ad",
|
||||
"production_scoring_sha256": "b72dbd61feec556c3863cb1b4496ded677c7077324a71b86e31722250a8d1528",
|
||||
"research_files": [
|
||||
"scripts/research/reported_offset_sweep.py",
|
||||
"scripts/research/cluster_width_lib.py",
|
||||
@@ -154,14 +154,14 @@
|
||||
"scripts/minute_rectification_fact_blind_eval_v4.py",
|
||||
"scripts/minute_rectification_holdout_validator.py"
|
||||
],
|
||||
"research_implementation_sha256": "173fd3e37a70a4e30717d1015cf6e4346820d544a0f3be24137165c810c8d4d8",
|
||||
"research_implementation_sha256": "12dc4049d8feeaf9e8f7df54666e7de7e8de6b45f195f52999bdbd52217908d9",
|
||||
"file_sha256": {
|
||||
"scripts/active_rectification_event_engine.py": "cdc0e68edc770a2a1810fbdb68cbfd46dcd50b267c739c496fcc078c5e26b5b4",
|
||||
"scripts/active_rectification_events.py": "03e333ccf1103caf685dfe50fe890ab1793e945037085f50ce834a4a3b232222",
|
||||
"scripts/ashtakavarga.py": "9bfc0b8a06af6122fa6c3881706e64f28b9ba58b53d98ff55957d4741d820f3f",
|
||||
"scripts/dasha_analyzer.py": "5e53b1b4d4c7414c08dd0e7716f120a63c70ec3428ec93547f64d55c580da0e1",
|
||||
"scripts/divisional_charts_extended.py": "e9820e80f14fddc86ab5495d11455d16c2d3c9502a9a242fcf9efc41186e0d11",
|
||||
"scripts/domain_calculation_service.py": "9ee955874eea806c7b56f4246d90009c02ee8318b8f3dfe1d7132b1c0b4262e1",
|
||||
"scripts/domain_calculation_service.py": "66923a2bc69daf65c965d2973c4b9234edbec5147909eaf3609b6b377ce3c7ca",
|
||||
"scripts/functional_benefics.py": "cbaac3b8c09b97f8724b5300e9a402cbf77089af6c7db8788550d40fd35ac57b",
|
||||
"scripts/jaimini.py": "6b88658da5404da9b136686d92a04be462d61a39da6e8de4255021e2ed2d1173",
|
||||
"scripts/minute_rectification_blind_eval.py": "07aa30d8bad3278669495d5fea8be937aaf6367c8af980ff4f2fe9aa84ccc6ff",
|
||||
@@ -179,12 +179,12 @@
|
||||
"scripts/rectification/decision_policy.py": "80e8de4680d19a052213bb821d2a9c94143a2962235aeb27f6ad81374d2e60d5",
|
||||
"scripts/rectification/event_probes.py": "9bbadc246e6b8e94bb047522c3e5645613f508b3473ef40b38a207c77fdad58c",
|
||||
"scripts/rectification/refinement_packet.py": "dbeea5aa780889785ce850f3881e8b7f67bb44594e9bc382c65b2e730884a2f4",
|
||||
"scripts/rectification/scoring_service.py": "45d777d2d762a73c8b9193ba269572e6ab06cdee43afbfb22bce225e40a4a2dd",
|
||||
"scripts/rectification/scoring_service.py": "fa056fa30d9e6ffa4b75295fc49c793e35b26ebeca64ca843d26c8abef3050f2",
|
||||
"scripts/research/cluster_width_lib.py": "1d8f2ffa7795d62069deebb2de8e16749317037d4ed3f24a2c0092676e5cfcc6",
|
||||
"scripts/research/probe_supply_after_six.py": "098a5b4398d6f8d996917d809c2fc922ad64031d3b70660f8ef7c876aba92438",
|
||||
"scripts/research/reported_offset_sweep.py": "6cc36e3a621bd23332951af53a091b1131fa350f6db1d52cb9fa52cc413c09f4",
|
||||
"scripts/research/sealed_holdout_rerun.py": "e5ee47a1fba92f9cd5d4e63fd52c6104fc6680fe7da1beec3d0db2b4cfcb9abc",
|
||||
"scripts/shadbala.py": "e949ff1cc925ccbc9a25b68981091d1d621b80a5b3839a3b3ade655d082424d6",
|
||||
"scripts/research/reported_offset_sweep.py": "9df602c938f87a7b540e97c6c31a263f07b4ed0ccaeb1d2fac39b0d3d27ba0ed",
|
||||
"scripts/research/sealed_holdout_rerun.py": "a7f8a1c2230e33441e335810f13cfd723f3f8ad5b03a3657cb606e29d8559f19",
|
||||
"scripts/shadbala.py": "334dc9d8e4292f0c95d40fd4c29285aa89b9e49d4f187fb9498f4bb0ad93bdfc",
|
||||
"scripts/varga.py": "c1f06967c3d4d214edadff139c24aa31e59fb628477174235abe14f799a99076"
|
||||
},
|
||||
"hash_scope": "explicit_identity_file_sets_not_a_transitive_dependency_lock"
|
||||
@@ -201,11 +201,16 @@
|
||||
"verified_minute_claim_allowed": false,
|
||||
"metric_gates_passed": false,
|
||||
"events_per_case": 3,
|
||||
"boundary": "Three-event low-information protocol, not a mathematical accuracy lower bound and not representative of real sessions. Historical v3/v4 exposure cannot be undone by freezing today's scorer. No tuning or release claims.",
|
||||
"report_sha256": "c92192274cdbf9a2c09b86ff90ec0786707a105e2404cb30216402f6a5c2865b",
|
||||
"freeze_record_sha256": "c93fe5299644ee99e22b8c80ba149a422ae10b06b8b1a65e9c4469df387c5a2d",
|
||||
"replay_started_at_utc": "2026-10-07T09:51:45.563938+00:00",
|
||||
"replay_finished_at_utc": "2026-10-07T09:51:49.611695+00:00"
|
||||
"boundary": "Previously exposed v3 shadow fixed-protocol rerun after the 2026-10-08 nutation fix (ERR-110, BUG-1275). Sidereal longitudes now include nutation and the scoring identity is scoring-12. Versus the 2026-09-20 historical report, changed_trial_count is 8. Not a release metric. Prior sync6 report and freeze preserved byte-for-byte.",
|
||||
"report_sha256": "9dec7743b12b5c25534096553a227809d285c1e4e32f6b10a58d80887a264e85",
|
||||
"freeze_record_sha256": "ed6c87e252a2a4d3212f7fb32d292623c0ddb48653e0550e7e5496441af274ef",
|
||||
"replay_started_at_utc": "2026-10-08T10:23:40.470851+00:00",
|
||||
"replay_finished_at_utc": "2026-10-08T10:23:44.334924+00:00",
|
||||
"previous_fixed_protocol_rerun": {
|
||||
"report_path": "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json",
|
||||
"freeze_record_path": "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.freeze.json",
|
||||
"superseded_reason": "Ephemeris nutation (ERR-110, BUG-1275). Prior sync6 report and freeze preserved byte-for-byte."
|
||||
}
|
||||
},
|
||||
"previous_fixed_protocol_rerun": {
|
||||
"evaluated_on": "2026-10-03",
|
||||
@@ -325,16 +330,26 @@
|
||||
"verified_minute_claim_allowed": false
|
||||
},
|
||||
"current_tree_reported_offset_replay": {
|
||||
"report_path": "docs/research/reported_offset_upstream_sync6_2026_10_07.json",
|
||||
"report_sha256": "1b9c9c18b6820a2beca8d7ddef20b6fa0778c5d43dd987c0c214e2f9ff00660f",
|
||||
"freeze_record_path": "docs/research/reported_offset_upstream_sync6_2026_10_07.freeze.json",
|
||||
"freeze_record_sha256": "65c7ab537b3fecc6d3e175bb2ab702b5630fe41bcff40bd551917cc32977b366",
|
||||
"report_path": "docs/research/reported_offset_ephemeris_nutation_2026_10_08.json",
|
||||
"report_sha256": "b433a906280a5b12e4e48fd5e86292b73d21a627f490e3083d719aceea3252c9",
|
||||
"freeze_record_path": "docs/research/reported_offset_ephemeris_nutation_2026_10_08.freeze.json",
|
||||
"freeze_record_sha256": "bc43921250cdcc160fb51d88ebcae0262d180e18c0ab2b14791605b09d639d39",
|
||||
"scope": "reported_offset_sensitivity_not_product_accuracy",
|
||||
"trial_count": 900,
|
||||
"official_valid_independent_blind": false,
|
||||
"official_blind_trial_count": 0,
|
||||
"must_not_use_for_tuning": true,
|
||||
"must_not_claim_as_release_metrics": true
|
||||
"must_not_claim_as_release_metrics": true,
|
||||
"replay_started_at_utc": "2026-10-08T10:23:40.483622+00:00",
|
||||
"replay_finished_at_utc": "2026-10-08T10:38:50.744210+00:00",
|
||||
"boundary": "Previously exposed reported-offset sensitivity after the 2026-10-08 nutation fix (ERR-110, BUG-1275). Versus the 2026-09-20 historical report, changed_trial_count is 581 of 900. Not a release metric. No tuning. Prior sync6 report and freeze preserved byte-for-byte.",
|
||||
"previous_reported_offset_replay": {
|
||||
"report_path": "docs/research/reported_offset_upstream_sync6_2026_10_07.json",
|
||||
"report_sha256": "1b9c9c18b6820a2beca8d7ddef20b6fa0778c5d43dd987c0c214e2f9ff00660f",
|
||||
"freeze_record_path": "docs/research/reported_offset_upstream_sync6_2026_10_07.freeze.json",
|
||||
"freeze_record_sha256": "65c7ab537b3fecc6d3e175bb2ab702b5630fe41bcff40bd551917cc32977b366",
|
||||
"superseded_reason": "Ephemeris nutation (ERR-110, BUG-1275). Prior sync6 report and freeze preserved byte-for-byte. Versus the 2026-09-20 historical report, changed_trial_count is 581 of 900. Not used for tuning."
|
||||
}
|
||||
},
|
||||
"previous_reported_offset_replay": {
|
||||
"report_path": "docs/research/reported_offset_astrologer_rulings_batch1_2026_10_03.json",
|
||||
|
||||
@@ -132,3 +132,20 @@ def temporary_ayanamsa(name: Any = None, swe_module: Any = None) -> Iterator[str
|
||||
def sidereal_flags(swe_module: Any, ayanamsa_name: Any = None) -> int:
|
||||
apply_ayanamsa(ayanamsa_name, swe_module)
|
||||
return getattr(swe_module, 'FLG_SWIEPH', 2) | getattr(swe_module, 'FLG_SIDEREAL', 65536)
|
||||
|
||||
|
||||
def sidereal_ayanamsa_degrees(jd_ut: float, swe_module: Any = None, flags: int | None = None) -> float:
|
||||
"""Ayanamsa that FLG_SIDEREAL subtracts for these flags.
|
||||
|
||||
``get_ayanamsa`` and ``get_ayanamsa_ut`` omit nutation. Apparent tropical
|
||||
positions include it, so that subtraction shifts every longitude by Δψ.
|
||||
"""
|
||||
module = swe_module or swe
|
||||
if module is None:
|
||||
raise RuntimeError("swisseph is not available")
|
||||
if flags is None:
|
||||
flags = getattr(module, "FLG_SWIEPH", 2)
|
||||
raw = module.get_ayanamsa_ex_ut(float(jd_ut), int(flags))
|
||||
if isinstance(raw, (tuple, list)):
|
||||
return float(raw[1])
|
||||
return float(raw)
|
||||
|
||||
@@ -219,7 +219,8 @@ class BhavaChalitCalculator:
|
||||
hsys = self._SWE_HSYS.get(house_system, b'P')
|
||||
cusps_swe, ascmc = self._swe.houses(jd, lat, lon, hsys)
|
||||
|
||||
ayanamsa = self._swe.get_ayanamsa(jd)
|
||||
from ayanamsa_utils import sidereal_ayanamsa_degrees
|
||||
ayanamsa = sidereal_ayanamsa_degrees(jd, self._swe)
|
||||
result = []
|
||||
for i in range(12):
|
||||
result.append(_norm(cusps_swe[i] - ayanamsa))
|
||||
|
||||
@@ -13,11 +13,11 @@ if str(SCRIPT_DIR) not in sys.path:
|
||||
|
||||
try:
|
||||
import swisseph as swe
|
||||
from ayanamsa_utils import apply_ayanamsa, normalize_ayanamsa_name
|
||||
from ayanamsa_utils import apply_ayanamsa, normalize_ayanamsa_name, sidereal_ayanamsa_degrees
|
||||
from domain_calculation_service import compute_chart, compute_vimshottari_timeline
|
||||
except ModuleNotFoundError:
|
||||
import swisseph as swe
|
||||
from scripts.ayanamsa_utils import apply_ayanamsa, normalize_ayanamsa_name
|
||||
from scripts.ayanamsa_utils import apply_ayanamsa, normalize_ayanamsa_name, sidereal_ayanamsa_degrees
|
||||
from scripts.domain_calculation_service import compute_chart, compute_vimshottari_timeline
|
||||
|
||||
SIGNS = [
|
||||
@@ -65,7 +65,7 @@ def _moon_transit(reference_date: str, tz: float, ayanamsa: str) -> dict[str, An
|
||||
local_dt = datetime.strptime(reference_date[:10], "%Y-%m-%d").replace(hour=12)
|
||||
apply_ayanamsa(normalize_ayanamsa_name(ayanamsa), swe)
|
||||
jd = swe.julday(local_dt.year, local_dt.month, local_dt.day, 12.0 - float(tz))
|
||||
ayanamsa_value = swe.get_ayanamsa(jd)
|
||||
ayanamsa_value = sidereal_ayanamsa_degrees(jd, swe)
|
||||
position, _flags = swe.calc_ut(jd, swe.MOON)
|
||||
longitude = (position[0] - ayanamsa_value) % 360
|
||||
sign_index = int(longitude // 30)
|
||||
|
||||
@@ -18,6 +18,7 @@ from ayanamsa_utils import (
|
||||
apply_ayanamsa,
|
||||
is_supported_ayanamsa_name,
|
||||
normalize_ayanamsa_name,
|
||||
sidereal_ayanamsa_degrees,
|
||||
supported_ayanamsa_names,
|
||||
)
|
||||
from dasha_analyzer import build_dasha_timeline, lon_to_nakshatra
|
||||
@@ -300,7 +301,7 @@ def compute_transit_longitude(
|
||||
local_dt.day,
|
||||
12.0 - float(tz),
|
||||
)
|
||||
ayanamsa_value = swe.get_ayanamsa(jd)
|
||||
ayanamsa_value = sidereal_ayanamsa_degrees(jd, swe)
|
||||
position, flags = swe.calc_ut(jd, _PLANET_IDS[planet])
|
||||
longitude = (position[0] - ayanamsa_value) % 360
|
||||
return {
|
||||
|
||||
+18
-20
@@ -97,6 +97,7 @@ from ayanamsa_utils import (
|
||||
ayanamsa_already_applied,
|
||||
ayanamsa_display_name,
|
||||
current_ayanamsa_name,
|
||||
sidereal_ayanamsa_degrees,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
@@ -816,16 +817,17 @@ def _normalize_position_mode(position_mode):
|
||||
|
||||
|
||||
def _sidereal_calculation_profile(jd, position_mode):
|
||||
"""Return matching Swiss Ephemeris flags and ayanamsa for one frame."""
|
||||
"""Return matching Swiss Ephemeris flags and ayanamsa for one frame.
|
||||
|
||||
``legacy`` used to take ``get_ayanamsa`` (no nutation) against apparent
|
||||
positions. That was the Δψ shift. It now uses the same true ayanamsa as
|
||||
``apparent``. ``mean`` still drops nutation on both sides. No fourth mode.
|
||||
"""
|
||||
mode = _normalize_position_mode(position_mode)
|
||||
ephemeris_flags = getattr(swe, 'FLG_SWIEPH', 2)
|
||||
if mode == 'mean':
|
||||
ephemeris_flags |= getattr(swe, 'FLG_NONUT', 64)
|
||||
ayanamsa = swe.get_ayanamsa_ex_ut(jd, ephemeris_flags)[1]
|
||||
elif mode == 'apparent':
|
||||
ayanamsa = swe.get_ayanamsa_ex_ut(jd, ephemeris_flags)[1]
|
||||
else:
|
||||
ayanamsa = swe.get_ayanamsa(jd)
|
||||
ayanamsa = sidereal_ayanamsa_degrees(jd, swe, ephemeris_flags)
|
||||
return mode, ephemeris_flags, ayanamsa
|
||||
|
||||
|
||||
@@ -838,8 +840,7 @@ def _calc_sidereal_planets_for_jd(jd, node_mode='mean', include_ketu=True, ayana
|
||||
return {}, None
|
||||
if ayanamsa_name:
|
||||
_apply_ayanamsa(ayanamsa_name)
|
||||
ayanamsa = swe.get_ayanamsa(jd)
|
||||
calc_flags = getattr(swe, 'FLG_SWIEPH', 2)
|
||||
_mode, calc_flags, ayanamsa = _sidereal_calculation_profile(jd, 'legacy')
|
||||
data = {}
|
||||
for pname, pid in _planet_map_for_node_mode(node_mode).items():
|
||||
pos, ret = swe.calc_ut(jd, pid, calc_flags | getattr(swe, 'FLG_SPEED', 256))
|
||||
@@ -12347,7 +12348,7 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='
|
||||
second = int(second or 0)
|
||||
hour_decimal = _birth_hour_decimal(hour, minute, second) - tz
|
||||
jd = swe.julday(year, month, day, hour_decimal)
|
||||
ayanamsa = swe.get_ayanamsa(jd)
|
||||
_mode, calc_flags, ayanamsa = _sidereal_calculation_profile(jd, 'legacy')
|
||||
|
||||
node_mode = (node_mode or 'mean').lower()
|
||||
if node_mode not in ('mean', 'true'):
|
||||
@@ -12384,7 +12385,6 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='
|
||||
"lord": SIGN_LORDS[SIGNS[si]]}
|
||||
|
||||
planets_swe = {**BASE_PLANETS_SWE, 'Rahu': node_pid}
|
||||
calc_flags = getattr(swe, 'FLG_SWIEPH', 2)
|
||||
for pname, pid in planets_swe.items():
|
||||
try:
|
||||
pos, _ret_flags = swe.calc_ut(jd, pid, calc_flags | getattr(swe, 'FLG_SPEED', 256))
|
||||
@@ -13003,8 +13003,6 @@ def cmd_transit(args):
|
||||
except Exception as e:
|
||||
return {"error": f"日期计算失败: {e}"}
|
||||
|
||||
ayanamsa = swe.get_ayanamsa(jd_mid)
|
||||
|
||||
# --- 计算行星位置 ---
|
||||
node_mode = getattr(args, 'node_mode', 'mean')
|
||||
transit_data, ayanamsa = _calc_sidereal_planets_for_jd(
|
||||
@@ -13368,12 +13366,12 @@ def cmd_double_transit_pac(args):
|
||||
transit_year, transit_month, transit_day = map(int, args.date.split('-'))
|
||||
transit_hour = 12.0 - args.tz # 正午 UT
|
||||
transit_jd = swe.julday(transit_year, transit_month, transit_day, transit_hour)
|
||||
transit_ayanamsa = swe.get_ayanamsa(transit_jd)
|
||||
_mode, transit_flags, transit_ayanamsa = _sidereal_calculation_profile(transit_jd, 'legacy')
|
||||
|
||||
transit_planets = {}
|
||||
for pname, pid in PLANETS_SWE.items():
|
||||
try:
|
||||
pos, _ = swe.calc_ut(transit_jd, pid)
|
||||
pos, _ = swe.calc_ut(transit_jd, pid, transit_flags)
|
||||
lon_p = (pos[0] - transit_ayanamsa) % 360
|
||||
transit_planets[pname] = {'lon': lon_p, 'sign': SIGNS[int(lon_p / 30)]}
|
||||
if pname == 'Rahu':
|
||||
@@ -13579,8 +13577,8 @@ def _calc_transit_lon(jd, planet_name):
|
||||
pid = pid_map.get(planet_name)
|
||||
if pid is None:
|
||||
return None
|
||||
pos, _ = swe.calc_ut(jd, pid)
|
||||
aya = swe.get_ayanamsa_ut(jd)
|
||||
_mode, flags, aya = _sidereal_calculation_profile(jd, 'legacy')
|
||||
pos, _ = swe.calc_ut(jd, pid, flags)
|
||||
return (pos[0] - aya) % 360
|
||||
|
||||
|
||||
@@ -13617,7 +13615,7 @@ def cmd_transit_ll7l(args):
|
||||
'transit_ayanamsa_policy': _transit_ayanamsa_mixed_review_policy(
|
||||
'transit-ll7l',
|
||||
requested_ayanamsa=getattr(args, 'ayanamsa', None),
|
||||
transit_ayanamsa=swe.get_ayanamsa_ut(transit_jd),
|
||||
transit_ayanamsa=_sidereal_calculation_profile(transit_jd, 'legacy')[2],
|
||||
),
|
||||
'lagna_lord': ll_name,
|
||||
'seventh_lord': seven_lord,
|
||||
@@ -13711,7 +13709,7 @@ def cmd_planetary_congregation(args):
|
||||
if args.transit_date:
|
||||
t_year, t_month, t_day = map(int, args.transit_date.split('-'))
|
||||
transit_jd = swe.julday(t_year, t_month, t_day, 12.0 - args.tz)
|
||||
transit_aya = swe.get_ayanamsa(transit_jd)
|
||||
_mode, transit_flags, transit_aya = _sidereal_calculation_profile(transit_jd, 'legacy')
|
||||
result['transit_ayanamsa_policy'] = _transit_ayanamsa_mixed_review_policy(
|
||||
'planetary-congregation',
|
||||
requested_ayanamsa=getattr(args, 'ayanamsa', None),
|
||||
@@ -13720,7 +13718,7 @@ def cmd_planetary_congregation(args):
|
||||
result['transit'] = {str(h): [] for h in range(1, 13)}
|
||||
for pname, pid in PLANETS_SWE.items():
|
||||
try:
|
||||
pos, _ = swe.calc_ut(transit_jd, pid)
|
||||
pos, _ = swe.calc_ut(transit_jd, pid, transit_flags)
|
||||
lon_p = (pos[0] - transit_aya) % 360
|
||||
si = int(lon_p / 30)
|
||||
house = ((si - asc_idx) % 12) + 1
|
||||
@@ -13802,7 +13800,7 @@ def cmd_vivah_saham(args):
|
||||
result['transit_ayanamsa_policy'] = _transit_ayanamsa_mixed_review_policy(
|
||||
'vivah-saham',
|
||||
requested_ayanamsa=getattr(args, 'ayanamsa', None),
|
||||
transit_ayanamsa=swe.get_ayanamsa_ut(transit_jd),
|
||||
transit_ayanamsa=_sidereal_calculation_profile(transit_jd, 'legacy')[2],
|
||||
)
|
||||
|
||||
result['transit_activation'] = {'jupiter': [], 'saturn': [], 'double_activation': False}
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME, apply_ayanamsa, current_ayanamsa_name
|
||||
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME, apply_ayanamsa, current_ayanamsa_name, sidereal_ayanamsa_degrees
|
||||
from kp_system import KP_LORDS, get_kp_lords
|
||||
from scripts.domain_calculation_service import swiss_ephemeris_lock
|
||||
|
||||
@@ -59,7 +59,7 @@ def observe_kp_cusps(jd: float | None, lat: float | None, lon: float | None) ->
|
||||
try:
|
||||
apply_ayanamsa("krishnamurti", swe)
|
||||
cusps_raw, _ascmc = swe.houses(float(jd), float(lat), float(lon), b"P")
|
||||
ayanamsa = float(swe.get_ayanamsa(float(jd)))
|
||||
ayanamsa = sidereal_ayanamsa_degrees(float(jd), swe)
|
||||
houses: dict[str, dict[str, Any]] = {}
|
||||
indices: dict[str, int] = {}
|
||||
for house in range(1, 13):
|
||||
|
||||
@@ -21,7 +21,7 @@ from scripts.rectification.case_holdout import holdout_event_ids
|
||||
# scoring-11 (2026-10-07, BUG-1260): D5/D11 sign mappings change education and
|
||||
# finance varga points for the same input. Cached scoring-10 results must not
|
||||
# be reused as current. Policy and the dated window contract stay as they are.
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"
|
||||
ALGORITHM_VERSION = "rectification-v5-matrix-scoring-12"
|
||||
INPUT_CONTRACT_VERSION = "rectification-calculation-spec-v5"
|
||||
PRECISION_WEIGHTS = {
|
||||
"day": 1.0,
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Freeze PyJHora 4.8.7 sidereal longitudes. Offline only.
|
||||
|
||||
Run from the repo root with a Python that has PyJHora 4.8.7:
|
||||
|
||||
python scripts/research/freeze_sidereal_nutation_pyjhora_fixture.py
|
||||
|
||||
Tests read the JSON and do not import this module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
from contextlib import redirect_stdout
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path[:0] = [str(ROOT), str(ROOT / "scripts")]
|
||||
|
||||
import swisseph as swe # noqa: E402
|
||||
from jhora import const # noqa: E402
|
||||
from jhora.panchanga import drik # noqa: E402
|
||||
from jyotish_engine import compute_chart_data # noqa: E402
|
||||
|
||||
OUT = ROOT / "tests" / "fixtures" / "sidereal_nutation_pyjhora_487.json"
|
||||
MODES = {"lahiri": "LAHIRI", "raman": "RAMAN", "kp": "KP"}
|
||||
BODIES = (
|
||||
("Sun", const._SUN), ("Moon", const._MOON), ("Mars", const._MARS),
|
||||
("Mercury", const._MERCURY), ("Jupiter", const._JUPITER), ("Venus", const._VENUS),
|
||||
("Saturn", const._SATURN), ("Rahu", const._RAHU),
|
||||
)
|
||||
|
||||
|
||||
def _births() -> dict[str, dict]:
|
||||
public = json.loads((ROOT / "references" / "public_oracle_cases.json").read_text(encoding="utf-8"))
|
||||
births = {
|
||||
case["id"]: case["birth"]
|
||||
for case in public["cases"]
|
||||
if case["id"] in {"steve_jobs_1955_aa", "barack_obama_1961_aa", "einstein_1879_aa"}
|
||||
}
|
||||
special = json.loads((ROOT / "tests" / "fixtures" / "special_lagnas_pyjhora_487.json").read_text(encoding="utf-8"))
|
||||
births["fictional_reader_main"] = special["cases"]["fictional_reader_main"]["birth"]
|
||||
return births
|
||||
|
||||
|
||||
def _longitudes(birth: dict, mode: str) -> dict[str, float]:
|
||||
"""Same UTC conversion as drik.planetary_positions, via sidereal_longitude.
|
||||
|
||||
PyJHora 4.8.7's planetary_positions calls dict.index on planet_list and
|
||||
raises AttributeError, so the wrapper cannot be the frozen source.
|
||||
"""
|
||||
local_hour = birth["hour"] + birth["minute"] / 60.0 + birth.get("second", 0) / 3600.0
|
||||
jd_local = swe.julday(birth["year"], birth["month"], birth["day"], local_hour)
|
||||
jd_ut = jd_local - float(birth["tz"]) / 24.0
|
||||
with redirect_stdout(io.StringIO()):
|
||||
drik.set_ayanamsa_mode(mode)
|
||||
longitudes = {
|
||||
name: float(drik.sidereal_longitude(jd_ut, planet)) % 360.0
|
||||
for name, planet in BODIES
|
||||
}
|
||||
longitudes["Ketu"] = float(drik.ketu(longitudes["Rahu"])) % 360.0
|
||||
return longitudes
|
||||
|
||||
|
||||
def _gap(left: float, right: float) -> float:
|
||||
return abs(((left - right + 180.0) % 360.0) - 180.0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cases = {}
|
||||
worst = 0.0
|
||||
for case_id, birth in _births().items():
|
||||
by_mode = {}
|
||||
for name, mode in MODES.items():
|
||||
frozen = _longitudes(birth, mode)
|
||||
chart, *_rest = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], node_mode="true",
|
||||
second=birth.get("second", 0), ayanamsa_name=name,
|
||||
)
|
||||
for planet, longitude in frozen.items():
|
||||
worst = max(worst, _gap(chart["planets"][planet]["degree_raw"], longitude))
|
||||
by_mode[name] = frozen
|
||||
cases[case_id] = {"ayanamsa": by_mode}
|
||||
payload = {
|
||||
"pyjhora_version": importlib.metadata.version("PyJHora"),
|
||||
"generator": "python scripts/research/freeze_sidereal_nutation_pyjhora_fixture.py",
|
||||
"generated_on": "2026-10-08",
|
||||
"node_mode": "true",
|
||||
"max_abs_difference_degrees": worst,
|
||||
"note": "Frozen from drik.sidereal_longitude. PyJHora 4.8.7 planetary_positions raises AttributeError because planet_list is a dict. Rahu body is const._RAHU (Swiss 11). Differences are recorded, not a second runtime.",
|
||||
"cases": cases,
|
||||
}
|
||||
OUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", newline="\n")
|
||||
print(OUT.relative_to(ROOT).as_posix())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -30,8 +30,8 @@ from scripts.research.sealed_holdout_rerun import (
|
||||
OFFSETS = (-30, -20, -15, -10, -8, -5, -3, 0, 3, 5, 8, 10, 15, 20, 30)
|
||||
RADII = (15, 30, 60)
|
||||
MINUTE_STEP = 1
|
||||
FREEZE = ROOT / "docs/research/reported_offset_upstream_sync6_2026_10_07.freeze.json"
|
||||
REPORT = ROOT / "docs/research/reported_offset_upstream_sync6_2026_10_07.json"
|
||||
FREEZE = ROOT / "docs/research/reported_offset_ephemeris_nutation_2026_10_08.freeze.json"
|
||||
REPORT = ROOT / "docs/research/reported_offset_ephemeris_nutation_2026_10_08.json"
|
||||
LEGACY_REPORT = ROOT / "docs/research/reported_offset_2026_09_20.json"
|
||||
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ 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_upstream_sync6_2026_10_07.freeze.json"
|
||||
REPORT = ROOT / "docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07.json"
|
||||
FREEZE = ROOT / "docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.freeze.json"
|
||||
REPORT = ROOT / "docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08.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 = [
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ _script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if _script_dir not in sys.path:
|
||||
sys.path.insert(0, _script_dir)
|
||||
from varga import varga_map, SIGNS as VARGA_SIGNS, SIGN_LORDS as VARGA_SIGN_LORDS
|
||||
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME, apply_ayanamsa, normalize_ayanamsa_name
|
||||
from ayanamsa_utils import DEFAULT_AYANAMSA_NAME, apply_ayanamsa, normalize_ayanamsa_name, sidereal_ayanamsa_degrees
|
||||
|
||||
# ============================================================================
|
||||
# 常量
|
||||
@@ -173,7 +173,7 @@ def build_shadbala_context(jd_ut: float, lat: float, lon: float, ayanamsa: str =
|
||||
"jd_ut": float(jd_ut), "jd_local": local_jd,
|
||||
"lat": float(lat), "lon": float(lon), "timezone": float(timezone),
|
||||
"year": int(year), "month": int(month), "day": int(day), "local_hour": float(local_hour),
|
||||
"ayanamsa": key, "ayanamsa_degrees": float(swe.get_ayanamsa_ut(float(jd_ut))),
|
||||
"ayanamsa": key, "ayanamsa_degrees": sidereal_ayanamsa_degrees(float(jd_ut), swe),
|
||||
"sunrise_hour": sunrise, "sunset_hour": sunset, "previous_sunset_hour": previous_sunset,
|
||||
"house_midpoints": [float(value) % 360 for value in cusps],
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import traceback
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, Set
|
||||
|
||||
from ayanamsa_utils import sidereal_flags
|
||||
from ayanamsa_utils import sidereal_ayanamsa_degrees, sidereal_flags
|
||||
|
||||
# ============================================================
|
||||
# 路径设置
|
||||
@@ -103,7 +103,7 @@ def skill_compute_chart(year, month, day, hour, minute, lat, lon, tz,
|
||||
|
||||
# Ayanamsa
|
||||
flags = sidereal_flags(swe, 'lahiri')
|
||||
ayanamsa = swe.get_ayanamsa_ut(jd)
|
||||
ayanamsa = sidereal_ayanamsa_degrees(jd, swe)
|
||||
|
||||
# Ascendant
|
||||
asc_info = swe.houses(jd, lat, lon, b'A') # 'A' = equal houses (Vedic)
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"pyjhora_version": "4.8.7",
|
||||
"generator": "python scripts/research/freeze_sidereal_nutation_pyjhora_fixture.py",
|
||||
"generated_on": "2026-10-08",
|
||||
"node_mode": "true",
|
||||
"max_abs_difference_degrees": 0.014454176402182384,
|
||||
"note": "Frozen from drik.sidereal_longitude. PyJHora 4.8.7 planetary_positions raises AttributeError because planet_list is a dict. Rahu body is const._RAHU (Swiss 11). Differences are recorded, not a second runtime.",
|
||||
"cases": {
|
||||
"einstein_1879_aa": {
|
||||
"ayanamsa": {
|
||||
"lahiri": {
|
||||
"Sun": 331.33023005511546,
|
||||
"Moon": 232.22620394496533,
|
||||
"Mars": 274.74079089624144,
|
||||
"Mercury": 340.96616653121913,
|
||||
"Jupiter": 305.3154892687192,
|
||||
"Venus": 354.8104387336731,
|
||||
"Saturn": 342.02191209074596,
|
||||
"Rahu": 280.5518346721791,
|
||||
"Ketu": 100.5518346721791
|
||||
},
|
||||
"raman": {
|
||||
"Sun": 332.77653156215774,
|
||||
"Moon": 233.67250545200758,
|
||||
"Mars": 276.1870924032838,
|
||||
"Mercury": 342.4124680382614,
|
||||
"Jupiter": 306.76179077576154,
|
||||
"Venus": 356.2567402407154,
|
||||
"Saturn": 343.4682135977883,
|
||||
"Rahu": 281.99813617922143,
|
||||
"Ketu": 101.99813617922143
|
||||
},
|
||||
"kp": {
|
||||
"Sun": 331.42708256215775,
|
||||
"Moon": 232.3230564520076,
|
||||
"Mars": 274.83764340328383,
|
||||
"Mercury": 341.0630190382614,
|
||||
"Jupiter": 305.41234177576155,
|
||||
"Venus": 354.9072912407154,
|
||||
"Saturn": 342.1187645977883,
|
||||
"Rahu": 280.64868717922144,
|
||||
"Ketu": 100.64868717922144
|
||||
}
|
||||
}
|
||||
},
|
||||
"steve_jobs_1955_aa": {
|
||||
"ayanamsa": {
|
||||
"lahiri": {
|
||||
"Sun": 312.51819523181405,
|
||||
"Moon": 344.51168181221095,
|
||||
"Mars": 5.862334338243065,
|
||||
"Mercury": 291.12589362817465,
|
||||
"Jupiter": 87.27055150216493,
|
||||
"Venus": 267.94211928370015,
|
||||
"Saturn": 207.92738390514677,
|
||||
"Rahu": 250.17570529839801,
|
||||
"Ketu": 70.17570529839804
|
||||
},
|
||||
"raman": {
|
||||
"Sun": 313.96449660578025,
|
||||
"Moon": 345.9579831861772,
|
||||
"Mars": 7.308635712209287,
|
||||
"Mercury": 292.57219500214086,
|
||||
"Jupiter": 88.71685287613116,
|
||||
"Venus": 269.3884206576664,
|
||||
"Saturn": 209.373685279113,
|
||||
"Rahu": 251.62200667236425,
|
||||
"Ketu": 71.62200667236425
|
||||
},
|
||||
"kp": {
|
||||
"Sun": 312.61504760578026,
|
||||
"Moon": 344.6085341861772,
|
||||
"Mars": 5.959186712209296,
|
||||
"Mercury": 291.22274600214087,
|
||||
"Jupiter": 87.36740387613116,
|
||||
"Venus": 268.03897165766637,
|
||||
"Saturn": 208.02423627911304,
|
||||
"Rahu": 250.27255767236423,
|
||||
"Ketu": 70.2725576723642
|
||||
}
|
||||
}
|
||||
},
|
||||
"barack_obama_1961_aa": {
|
||||
"ayanamsa": {
|
||||
"lahiri": {
|
||||
"Sun": 109.23518945852025,
|
||||
"Moon": 40.0394553657099,
|
||||
"Mars": 149.26640544096526,
|
||||
"Mercury": 99.02772319705292,
|
||||
"Jupiter": 277.53729754967225,
|
||||
"Venus": 68.4779756797172,
|
||||
"Saturn": 272.0088308190876,
|
||||
"Rahu": 123.98114859107521,
|
||||
"Ketu": 303.9811485910752
|
||||
},
|
||||
"raman": {
|
||||
"Sun": 110.68149082295898,
|
||||
"Moon": 41.48575673014863,
|
||||
"Mars": 150.712706805404,
|
||||
"Mercury": 100.47402456149166,
|
||||
"Jupiter": 278.983598914111,
|
||||
"Venus": 69.92427704415594,
|
||||
"Saturn": 273.4551321835263,
|
||||
"Rahu": 125.42744995551394,
|
||||
"Ketu": 305.4274499555139
|
||||
},
|
||||
"kp": {
|
||||
"Sun": 109.332041822959,
|
||||
"Moon": 40.136307730148644,
|
||||
"Mars": 149.36325780540403,
|
||||
"Mercury": 99.12457556149167,
|
||||
"Jupiter": 277.634149914111,
|
||||
"Venus": 68.57482804415595,
|
||||
"Saturn": 272.1056831835263,
|
||||
"Rahu": 124.07800095551394,
|
||||
"Ketu": 304.07800095551397
|
||||
}
|
||||
}
|
||||
},
|
||||
"fictional_reader_main": {
|
||||
"ayanamsa": {
|
||||
"lahiri": {
|
||||
"Sun": 352.9959273272877,
|
||||
"Moon": 260.49801426025005,
|
||||
"Mars": 68.40348564673326,
|
||||
"Mercury": 4.987951885744678,
|
||||
"Jupiter": 99.89939212201921,
|
||||
"Venus": 29.231572538839952,
|
||||
"Saturn": 281.8223083068704,
|
||||
"Rahu": 270.37602435184266,
|
||||
"Ketu": 90.37602435184266
|
||||
},
|
||||
"raman": {
|
||||
"Sun": 354.44222865141984,
|
||||
"Moon": 261.9443155843822,
|
||||
"Mars": 69.84978697086541,
|
||||
"Mercury": 6.434253209876828,
|
||||
"Jupiter": 101.34569344615136,
|
||||
"Venus": 30.677873862972103,
|
||||
"Saturn": 283.2686096310025,
|
||||
"Rahu": 271.8223256759748,
|
||||
"Ketu": 91.82232567597481
|
||||
},
|
||||
"kp": {
|
||||
"Sun": 353.0927796514199,
|
||||
"Moon": 260.59486658438215,
|
||||
"Mars": 68.5003379708654,
|
||||
"Mercury": 5.084804209876837,
|
||||
"Jupiter": 99.99624444615137,
|
||||
"Venus": 29.328424862972106,
|
||||
"Saturn": 281.9191606310025,
|
||||
"Rahu": 270.47287667597476,
|
||||
"Ketu": 90.47287667597476
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -240,7 +240,8 @@ def test_full_reading_accepts_second_and_preserves_birth_time_precision() -> Non
|
||||
assert result["birth_info"]["second"] == 20
|
||||
assert result["modules"]["chart"]["birth_info"]["time"] == "19:45:20"
|
||||
assert result["modules"]["dasha"]["birth_time"] == "19:45:20"
|
||||
assert result["modules"]["dasha"]["timeline"][0]["start_datetime"].startswith("1952-03-19T20:24:47")
|
||||
# 原值: 1952-03-19T20:24:47;新值: 1952-03-22T11:42:07;原因: 恒星黄道补上章动后月亮位置移动约一个章动,第一段大运起点跟着移(BUG-1275)。
|
||||
assert result["modules"]["dasha"]["timeline"][0]["start_datetime"].startswith("1952-03-22T11:42:07")
|
||||
|
||||
|
||||
def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None:
|
||||
|
||||
@@ -31,9 +31,9 @@ def test_fixed_scoring_identity_is_exposed_without_changing_input_contract():
|
||||
ALGORITHM_VERSION, INPUT_CONTRACT_VERSION, calculation_spec, sha256,
|
||||
)
|
||||
|
||||
# 原值: "rectification-v5-matrix-scoring-10";新值: "...-scoring-11";原因: D5/D11 映射改变学业与财务分盘得分
|
||||
# (BUG-1260),同输入分数变了,按 BUG-1181 升算法身份;输入合同 v5 与日期窗合同不变。
|
||||
assert engine_scoring_versions()["algorithm_version"] == ALGORITHM_VERSION == "rectification-v5-matrix-scoring-11"
|
||||
# 原值: "rectification-v5-matrix-scoring-11";新值: "...-scoring-12";原因: 恒星黄道补上章动后同输入分数变了
|
||||
# (BUG-1275),按 BUG-1181 升算法身份;输入合同 v5 与日期窗合同不变。
|
||||
assert engine_scoring_versions()["algorithm_version"] == ALGORITHM_VERSION == "rectification-v5-matrix-scoring-12"
|
||||
assert INPUT_CONTRACT_VERSION == "rectification-calculation-spec-v5"
|
||||
request = {"birth_date": "2000-03-01", "start_time": "00:00", "end_time": "00:02",
|
||||
"lat": 0.0, "lon": 0.0, "tz": 0.0, "events": []}
|
||||
|
||||
@@ -30,11 +30,17 @@ rectification-candidate-policy-v3 -> v4, event_quality / domain_diversity
|
||||
minimum 3/2 -> 4/3, and the representative candidate UUID (its namespace
|
||||
carries the policy version). v2 is frozen by sha256 below.
|
||||
|
||||
Current golden tests/golden/rectification_engine_memoization_v4.json
|
||||
Golden tests/golden/rectification_engine_memoization_v4.json
|
||||
(2026-10-07, BUG-1260) was written by ``write_golden`` after the D5/D11
|
||||
mappings changed education and finance varga points for this same request.
|
||||
The algorithm is rectification-v5-matrix-scoring-11. Policy stays v4.
|
||||
v3 stays byte-for-byte and is pinned below.
|
||||
v3 stays byte-for-byte and is pinned below. v4 is now frozen the same way.
|
||||
|
||||
Current golden tests/golden/rectification_engine_memoization_v5.json
|
||||
(2026-10-08, BUG-1275) was written by ``write_golden`` after sidereal
|
||||
longitudes included nutation. The same request moves (12:00 8.5289 -> 8.5336).
|
||||
The algorithm is rectification-v5-matrix-scoring-12. Policy stays v4.
|
||||
v4 stays byte-for-byte and is pinned below.
|
||||
|
||||
Do not compare that payload with a whole-structure ``==``. Cross-machine
|
||||
libm / pyswisseph rounding already drifted ``margin_percent`` by 1.1e-3
|
||||
@@ -74,7 +80,10 @@ import scripts.rectification.scoring_service as scoring_service
|
||||
import shadbala
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v4.json"
|
||||
GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v5.json"
|
||||
V4_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v4.json"
|
||||
V4_GOLDEN_SHA256 = "8491093281fe9767defea1b46d325237f1a4c6c0d4f076b9a77f07b985b526c3"
|
||||
V4_SOURCE_COMMIT = "db68e90816d462b0c17b4f84a0d19df83010fbce"
|
||||
V3_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v3.json"
|
||||
V3_GOLDEN_SHA256 = "7064f30bea3a7bcd8058fdcb6b52484a96a09767c0c289e0d71f3e2368fc8d45"
|
||||
V3_SOURCE_COMMIT = "7fb165ecf66ba1726f2fc74ffa2353fadc65ba39"
|
||||
@@ -83,15 +92,14 @@ PREVIOUS_GOLDEN_SHA256 = "abb15efae4d86387d930cd463b82a22fe95ccd1779e85ca6cc4cdf
|
||||
PREVIOUS_SOURCE_COMMIT = "57782aea8ae28f5dd165a08d221ec27dbef391f8"
|
||||
HISTORICAL_GOLDEN_PATH = ROOT / "tests" / "golden" / "rectification_engine_memoization_v1.json"
|
||||
HISTORICAL_GOLDEN_SHA256 = "6266e448d7dad204b264cbe8f9bcf4c36e02286794764a94edb9e2ce4d0e3015"
|
||||
# 原值: rectification-v5-matrix-scoring-10;新值: ...-scoring-11;原因: D5/D11 改变同输入分数(BUG-1260)。
|
||||
CURRENT_ALGORITHM_VERSION = "rectification-v5-matrix-scoring-11"
|
||||
# 原值: rectification-v5-matrix-scoring-11;新值: ...-scoring-12;原因: 章动修正改变同输入分数(BUG-1275)。
|
||||
CURRENT_ALGORITHM_VERSION = "rectification-v5-matrix-scoring-12"
|
||||
FROZEN_TODAY = date(2026, 9, 16)
|
||||
TIMING_KEYS = frozenset({"column_compare_ms"})
|
||||
HISTORICAL_SOURCE_COMMIT = "a8d29d1b6cc37ff865ddec6c8bccdf9aa889ee53"
|
||||
CURRENT_POLICY_VERSION = "rectification-candidate-policy-v4"
|
||||
# D5/D11 mapping commit that moves these scores (BUG-1260). The v4 file is
|
||||
# written later on this branch; v3 keeps its own source commit below.
|
||||
SOURCE_COMMIT = "db68e90816d462b0c17b4f84a0d19df83010fbce"
|
||||
# Nutation fix commit that moves these scores (BUG-1275). v4 keeps V4_SOURCE_COMMIT.
|
||||
SOURCE_COMMIT = "e5f2a75183381c4a027014c7f1e89ddcb8393bd3"
|
||||
CACHE_LAYER_KEYS = (
|
||||
"ashtakavarga_result",
|
||||
"shadbala_result",
|
||||
@@ -275,7 +283,7 @@ def _contexts_with_layer_cache_cleared(contexts: list[dict[str, Any]]) -> list[d
|
||||
|
||||
|
||||
def _assert_dated_payload_against_golden(actual: dict[str, Any], expected: dict[str, Any]) -> None:
|
||||
"""Compare with the scoring-11 / policy-v4 golden; identity is spelled out, not trusted."""
|
||||
"""Compare with the scoring-12 / policy-v4 golden; identity is spelled out, not trusted."""
|
||||
metadata = {
|
||||
"candidate_window_contract": "dated-v1",
|
||||
"candidate_intervals": [{"start_at": "1990-01-01T12:00", "end_at": "1990-01-01T12:02"}],
|
||||
@@ -348,9 +356,23 @@ def test_scoring_10_golden_is_frozen_and_scores_moved() -> None:
|
||||
raw = V3_GOLDEN_PATH.read_bytes()
|
||||
assert hashlib.sha256(raw).hexdigest() == V3_GOLDEN_SHA256
|
||||
previous = json.loads(raw)
|
||||
current = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
scoring11 = json.loads(V4_GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
assert previous["source_commit"] == V3_SOURCE_COMMIT
|
||||
assert previous["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-10"
|
||||
assert scoring11["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-11"
|
||||
assert scoring11["source_commit"] == V4_SOURCE_COMMIT
|
||||
with pytest.raises(AssertionError):
|
||||
_assert_memoization_payloads(scoring11, previous)
|
||||
|
||||
|
||||
def test_scoring_11_golden_is_frozen_and_scores_moved() -> None:
|
||||
"""BUG-1275: v4 stays byte-identical; nutation moved this request, hence scoring-12."""
|
||||
raw = V4_GOLDEN_PATH.read_bytes()
|
||||
assert hashlib.sha256(raw).hexdigest() == V4_GOLDEN_SHA256
|
||||
previous = json.loads(raw)
|
||||
current = json.loads(GOLDEN_PATH.read_text(encoding="utf-8"))
|
||||
assert previous["source_commit"] == V4_SOURCE_COMMIT
|
||||
assert previous["candidate_feature_snapshot"]["algorithm_version"] == "rectification-v5-matrix-scoring-11"
|
||||
assert current["candidate_feature_snapshot"]["algorithm_version"] == CURRENT_ALGORITHM_VERSION
|
||||
assert current["source_commit"] == SOURCE_COMMIT
|
||||
with pytest.raises(AssertionError):
|
||||
|
||||
@@ -21,8 +21,8 @@ class RelativeSupportScaleTest(unittest.TestCase):
|
||||
# 原值: policy-v3;新值: policy-v4;原因: R1 区间门槛 4 件 3 域(BUG-1193),相对支持口径不变。
|
||||
self.assertEqual(POLICY_VERSION, "rectification-candidate-policy-v4")
|
||||
# Explicit date windows: scoring identity -8 -> -9, not a prior/policy change.
|
||||
# 原值: -10;新值: -11;原因: D5/D11 改变同输入分数(BUG-1260),策略 v4 与相对支持口径不变。
|
||||
self.assertEqual(ALGORITHM_VERSION, "rectification-v5-matrix-scoring-11")
|
||||
# 原值: -11;新值: -12;原因: 章动修正改变同输入分数(BUG-1275),策略 v4 与相对支持口径不变。
|
||||
self.assertEqual(ALGORITHM_VERSION, "rectification-v5-matrix-scoring-12")
|
||||
self.assertEqual(RELATIVE_SUPPORT_MODE, "proportional")
|
||||
|
||||
def test_offset_top_two_lead_is_at_least_proportional(self) -> None:
|
||||
|
||||
@@ -38,6 +38,11 @@ IDENTITY_FORBIDDEN_PREFIXES = (
|
||||
# 原因: ERR-110,D5/D11 与 scoring-11 改了冻结文件,另存新记录,旧记录留在清单外
|
||||
"docs/research/sealed_holdout_rerun_upstream_sync6_2026_10_07",
|
||||
"docs/research/reported_offset_upstream_sync6_2026_10_07",
|
||||
# 原值: 排除到 upstream_sync6_2026_10_07
|
||||
# 新值: 再排除 ephemeris_nutation_2026_10_08 两份冻结路径
|
||||
# 原因: ERR-110,章动修正与 scoring-12 改了冻结身份,另存新记录,旧记录留在清单外(BUG-1275)
|
||||
"docs/research/sealed_holdout_rerun_ephemeris_nutation_2026_10_08",
|
||||
"docs/research/reported_offset_ephemeris_nutation_2026_10_08",
|
||||
"docs/research/history/",
|
||||
"artifacts/",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"""BUG-1275: sidereal longitudes use the same nutation as FLG_SIDEREAL.
|
||||
|
||||
PyJHora 4.8.7 answers are frozen in tests/fixtures/sidereal_nutation_pyjhora_487.json.
|
||||
This file does not import jhora. The offline generator is
|
||||
scripts/research/freeze_sidereal_nutation_pyjhora_fixture.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import swisseph as swe
|
||||
|
||||
from scripts.ayanamsa_utils import apply_ayanamsa
|
||||
from scripts.jyotish_engine import (
|
||||
POSITION_MODES,
|
||||
_calc_sidereal_planets_for_jd,
|
||||
_sidereal_calculation_profile,
|
||||
compute_chart_data,
|
||||
)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PUBLIC_CASES = ROOT / "references" / "public_oracle_cases.json"
|
||||
SPECIAL_FIXTURE = ROOT / "tests" / "fixtures" / "special_lagnas_pyjhora_487.json"
|
||||
PYJHORA_FIXTURE = ROOT / "tests" / "fixtures" / "sidereal_nutation_pyjhora_487.json"
|
||||
CASE_IDS = (
|
||||
"steve_jobs_1955_aa",
|
||||
"barack_obama_1961_aa",
|
||||
"einstein_1879_aa",
|
||||
"fictional_reader_main",
|
||||
)
|
||||
AYANAMSAS = ("lahiri", "raman", "kp")
|
||||
PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
|
||||
LIMIT = 1e-6
|
||||
|
||||
|
||||
def _births() -> dict[str, dict]:
|
||||
public = json.loads(PUBLIC_CASES.read_text(encoding="utf-8"))
|
||||
births = {case["id"]: case["birth"] for case in public["cases"]}
|
||||
special = json.loads(SPECIAL_FIXTURE.read_text(encoding="utf-8"))
|
||||
births["fictional_reader_main"] = special["cases"]["fictional_reader_main"]["birth"]
|
||||
return births
|
||||
|
||||
|
||||
def _jd(birth: dict) -> float:
|
||||
hour = birth["hour"] + birth["minute"] / 60.0 + birth.get("second", 0) / 3600.0 - birth["tz"]
|
||||
return swe.julday(birth["year"], birth["month"], birth["day"], hour)
|
||||
|
||||
|
||||
def _delta(left: float, right: float) -> float:
|
||||
return ((left - right + 180.0) % 360.0) - 180.0
|
||||
|
||||
|
||||
def _reference_longitudes(jd: float, ayanamsa: str, node_mode: str) -> dict[str, float]:
|
||||
apply_ayanamsa(ayanamsa)
|
||||
node = swe.TRUE_NODE if node_mode == "true" else swe.MEAN_NODE
|
||||
flags = swe.FLG_SWIEPH | swe.FLG_SPEED | swe.FLG_SIDEREAL
|
||||
bodies = {
|
||||
"Sun": swe.SUN, "Moon": swe.MOON, "Mars": swe.MARS, "Mercury": swe.MERCURY,
|
||||
"Jupiter": swe.JUPITER, "Venus": swe.VENUS, "Saturn": swe.SATURN, "Rahu": node,
|
||||
}
|
||||
longitudes = {name: swe.calc_ut(jd, pid, flags)[0][0] % 360.0 for name, pid in bodies.items()}
|
||||
longitudes["Ketu"] = (longitudes["Rahu"] + 180.0) % 360.0
|
||||
return longitudes
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case_id", CASE_IDS)
|
||||
@pytest.mark.parametrize("ayanamsa", AYANAMSAS)
|
||||
@pytest.mark.parametrize("node_mode", ("mean", "true"))
|
||||
def test_nine_planets_match_swiss_sidereal_flag(case_id, ayanamsa, node_mode):
|
||||
birth = _births()[case_id]
|
||||
jd = _jd(birth)
|
||||
expected = _reference_longitudes(jd, ayanamsa, node_mode)
|
||||
chart, _asc_idx, returned_jd, ayanamsa_value = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], node_mode=node_mode, second=birth.get("second", 0),
|
||||
ayanamsa_name=ayanamsa,
|
||||
)
|
||||
assert returned_jd == pytest.approx(jd, abs=1e-6)
|
||||
for name in PLANETS:
|
||||
actual = chart["planets"][name]["degree_raw"]
|
||||
assert abs(_delta(actual, expected[name])) <= LIMIT, name
|
||||
transit, _ = _calc_sidereal_planets_for_jd(jd, node_mode=node_mode, ayanamsa_name=ayanamsa)
|
||||
for name in PLANETS:
|
||||
assert abs(_delta(transit[name]["degree_raw"], expected[name])) <= LIMIT, name
|
||||
|
||||
cusps, _ascmc = swe.houses(jd, birth["lat"], birth["lon"], b"A")
|
||||
sidereal_cusps, sidereal_ascmc = swe.houses_ex(
|
||||
jd, birth["lat"], birth["lon"], b"A", swe.FLG_SWIEPH | swe.FLG_SIDEREAL,
|
||||
)
|
||||
for index in range(12):
|
||||
assert abs(_delta((cusps[index] - ayanamsa_value) % 360.0, sidereal_cusps[index] % 360.0)) <= LIMIT
|
||||
assert abs(_delta(chart["ascendant"]["degree_raw"], sidereal_cusps[0] % 360.0)) <= 5.1e-5
|
||||
_mode, flags, _aya = _sidereal_calculation_profile(jd, "legacy")
|
||||
_mc_cusps, mc_ascmc = swe.houses_ex(jd, birth["lat"], birth["lon"], b"R", flags | swe.FLG_SIDEREAL)
|
||||
assert abs(_delta(sidereal_ascmc[1] % 360.0, mc_ascmc[1] % 360.0)) <= LIMIT
|
||||
|
||||
|
||||
def test_legacy_matches_apparent_and_mean_still_drops_nutation():
|
||||
assert POSITION_MODES == ("legacy", "mean", "apparent")
|
||||
birth = _births()["steve_jobs_1955_aa"]
|
||||
jd = _jd(birth)
|
||||
apply_ayanamsa("lahiri")
|
||||
legacy = _sidereal_calculation_profile(jd, "legacy")
|
||||
apparent = _sidereal_calculation_profile(jd, "apparent")
|
||||
mean = _sidereal_calculation_profile(jd, "mean")
|
||||
assert legacy[0] == "legacy"
|
||||
assert legacy[1] == apparent[1]
|
||||
assert legacy[2] == pytest.approx(apparent[2], abs=0.0)
|
||||
assert mean[1] & swe.FLG_NONUT
|
||||
nutation = swe.calc_ut(jd, swe.ECL_NUT)[0][2]
|
||||
assert abs((legacy[2] - mean[2]) - nutation) <= 1e-6
|
||||
assert abs((legacy[2] - swe.get_ayanamsa(jd)) - nutation) <= 1e-4
|
||||
|
||||
|
||||
def test_frozen_pyjhora_difference_stays_inside_the_recorded_bound():
|
||||
payload = json.loads(PYJHORA_FIXTURE.read_text(encoding="utf-8"))
|
||||
assert payload["pyjhora_version"] == "4.8.7"
|
||||
assert payload["node_mode"] == "true"
|
||||
births = _births()
|
||||
worst = 0.0
|
||||
for case_id in CASE_IDS:
|
||||
birth = births[case_id]
|
||||
for ayanamsa in AYANAMSAS:
|
||||
chart, *_ = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], node_mode="true",
|
||||
second=birth.get("second", 0), ayanamsa_name=ayanamsa,
|
||||
)
|
||||
expected = payload["cases"][case_id]["ayanamsa"][ayanamsa]
|
||||
for name in PLANETS:
|
||||
gap = abs(_delta(chart["planets"][name]["degree_raw"], expected[name]))
|
||||
worst = max(worst, gap)
|
||||
assert gap <= payload["max_abs_difference_degrees"] + 1e-9, (case_id, ayanamsa, name)
|
||||
assert worst <= payload["max_abs_difference_degrees"] + 1e-9
|
||||
assert worst > 0.0
|
||||
Reference in New Issue
Block a user