fix(ondemand): beyond_next_return compares on one time basis (#175 round 2, BUG-1818)
打回:Mudda 原始段是返照地点的本地时刻,覆盖区间终点却拿 utc 去比,差一个时区偏移。 - 比较两边都改用 coverage.end.local(没有 local 时退回 utc + 返照地点偏移) - 新增 4 条测试(北京 +08 与纽约 −05 各 ±3 小时);旧代码上 2 条失败 - 未改 scripts/solar_return.py 或任何冻结文件;既有 16 条未动 Tests: 本文件 20 条,连同 transit_window/transit_date/contract/api growth 共 70 条全过。 Co-Authored-By: Grok (agent loop)
This commit is contained in:
co-authored by
Grok (agent loop)
parent
4651a7a486
commit
b8a56c48ed
@@ -24560,4 +24560,5 @@
|
||||
- 验证:tests/test_ondemand_solar_return_20261109.py(16 条)与 transit_date / transit_window / contract / api growth 合计 66 条全过;合成盘两地返照时刻差 0 秒、上升不同;覆盖终点与下一年返照时刻差 0.0 秒;10 段 Mudda 里只有最后一段 beyond_next_return。
|
||||
- 防复发:上述测试;合同 docstring 的注册步骤已含本工具。
|
||||
- 关联记录:占星师 2026-11-07 Q9、2026-11-08 第五轮第 9 条与第 2 条;#173(共用骨架)、#174(前一个工具)、BUG-1216 / BUG-1722(三层年运)
|
||||
- 验收退回(2026-11-09,FIX-ondemand-solar-return-1):越界标记时区基准不一致——Mudda 原始段是返照地点本地时刻,覆盖区间终点却用了 utc,差一个时区偏移(东八区把下一次回归后 8 小时内的段误标越界,西五区漏标)。已改为两边都用 coverage.end.local(没有 local 时退回 utc + 返照地点偏移);新增 4 条真实/西半球各 ±3 小时的测试,旧代码上 2 条失败。
|
||||
- 修复版本:分支 codex/ondemand-solar-return-20261109
|
||||
|
||||
@@ -51,3 +51,29 @@
|
||||
2. **`tz` 与 `timezone_id` 的「之一」语义**:`current_location` 缺 `tz` 但给了 `timezone_id` 时**不算缺项**(合同原文就是「tz 或 timezone_id 之一」),所以测试里那条「缺时区」同时去掉 `timezone_id`。
|
||||
3. **`annual_houses` / `muntha` / `year_lord` 原样搬运**,只改名不改内容;`planets` 按任务书精简成 `{longitude, sign, house}` 三个字段(其余字段仍在 `sr_chart` 的结果里,本工具不重复搬)。
|
||||
4. 没有遇到需要拍板的取舍。
|
||||
|
||||
|
||||
## 第 1 次打回(FIX-ondemand-solar-return-1,2026-11-09)
|
||||
|
||||
**问题**:`beyond_next_return` 两边不是同一时间基准。`mudda_raw[*].end` 是返照地点的**本地时刻**
|
||||
(读代码确认:`scripts/solar_return.py` 把 `annual_start=sr['solar_return']['dt_local']`、
|
||||
`coverage_end=next_return_local(...)` 交给 `tajika_mudda.annual_mudda_dasha`,
|
||||
`tajika_mudda.TIME_BASIS` 明写 `naive local wall time in the UTC offset of the solar-return instant`),
|
||||
而 `coverage.end.utc` 是 UTC,`_parse_moment` 去掉 `+00:00` 后直接比 —— 差一个时区偏移。
|
||||
|
||||
**改法**:比较两边都改用本地时刻——`coverage.end.local`;只有结果里没有 `local` 时才退回
|
||||
`utc + 返照地点偏移`,仍在同一基准上比较。没有改 `scripts/solar_return.py` 或任何冻结文件。
|
||||
|
||||
**新增 4 条测试**(monkeypatch 引擎:真实结果照算,只把最后一段终点挪到 `coverage.end.local ± 3 小时`):
|
||||
|
||||
| 返照地点 | 终点 | 期望 `beyond_next_return` | 旧代码实际 |
|
||||
| --- | --- | --- | --- |
|
||||
| 北京(+08) | 覆盖终点后 3 小时 | `true` | `true`(碰巧对) |
|
||||
| 北京(+08) | 覆盖终点前 3 小时 | `false` | **`true`(错)** |
|
||||
| 纽约(−05) | 覆盖终点后 3 小时 | `true` | **`false`(错)** |
|
||||
| 纽约(−05) | 覆盖终点前 3 小时 | `false` | `false`(碰巧对) |
|
||||
|
||||
旧代码上这四条里 **2 条失败**(正是差 8 小时与差 5 小时的那两侧),改后 4 条全过;
|
||||
既有 16 条没有改、仍然通过。
|
||||
|
||||
**自测**:`pytest tests/test_ondemand_solar_return_20261109.py`(20 条)与本单其他四个测试文件合计 **70 过 / 0 败**。
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
@@ -250,7 +250,15 @@ def run(body: Dict[str, Any]) -> Dict[str, Any]:
|
||||
elif abs(float(dasha_year['days']) - NOMINAL_YEAR_DAYS) > 1e-9:
|
||||
gaps.append({'field': 'nominal_year', 'reason': 'dasha_year_days_not_365_25'})
|
||||
|
||||
coverage_end = _parse_moment((coverage_interval.get('end') or {}).get('utc'))
|
||||
# 原值:用 coverage.end 的 utc 去比(mudda 段是返照地点的本地时刻,两边差一个时区偏移)
|
||||
# 新值:两边都用本地时刻(coverage.end.local 与 raw_periods 同一地点、同一时区)
|
||||
# 原因:验收退回(BUG-1818):beyond_next_return 的时区基准必须一致
|
||||
coverage_end = _parse_moment((coverage_interval.get('end') or {}).get('local'))
|
||||
if coverage_end is None:
|
||||
# 没有 local 时才退回 utc + 返照地点偏移,仍在同一基准上比较。
|
||||
coverage_end_utc = _parse_moment((coverage_interval.get('end') or {}).get('utc'))
|
||||
if coverage_end_utc is not None:
|
||||
coverage_end = coverage_end_utc + timedelta(hours=float(profile['tz']))
|
||||
raw_periods: List[Dict[str, Any]] = []
|
||||
for item in (mudda.get('raw_periods') or []):
|
||||
if not isinstance(item, dict):
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -24,6 +24,8 @@ from tests.neutral_subject import ( # noqa: E402
|
||||
)
|
||||
|
||||
LONDON = {'lat': 51.5074, 'lon': -0.1278, 'timezone_id': 'Europe/London'}
|
||||
# 西五区:与 UTC 差 5 小时,用来看越界标记有没有拿本地时刻比 UTC。
|
||||
NEW_YORK = {'lat': 40.7128, 'lon': -74.0060, 'timezone_id': 'America/New_York'}
|
||||
|
||||
BASE = {
|
||||
'tool': 'solar_return',
|
||||
@@ -209,3 +211,40 @@ def test_registry_dispatches_to_the_new_tool():
|
||||
envelope = ondemand_calc_api(_body())
|
||||
assert envelope['tool'] == 'solar_return'
|
||||
assert envelope['schema'] == 'jyotish.ondemand_calc.v1'
|
||||
|
||||
|
||||
def _patch_engine_with_last_period(monkeypatch, shift_hours: float) -> None:
|
||||
"""monkeypatch 引擎:真实结果照算,只替换 mudda 的原始段与覆盖区间终点。"""
|
||||
real = solar_return._solar_return_module()
|
||||
real_report = real.solar_return_full_report
|
||||
|
||||
def patched(*args, **kwargs):
|
||||
result = real_report(*args, **kwargs)
|
||||
coverage_end_local = ((result.get('annual_interval') or {}).get('solar_return_interval') or {}).get('end', {}).get('local')
|
||||
base = datetime.strptime(str(coverage_end_local)[:19], '%Y-%m-%dT%H:%M:%S')
|
||||
mudda = dict(result.get('mudda_dasha') or {})
|
||||
mudda['raw_periods'] = [{
|
||||
'order': 1, 'lord': 'Rahu', 'start': '2026-01-01 00:00:00',
|
||||
'end': (base + timedelta(hours=shift_hours)).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}]
|
||||
mudda['periods'] = []
|
||||
result = dict(result, mudda_dasha=mudda)
|
||||
return result
|
||||
|
||||
monkeypatch.setattr(real, 'solar_return_full_report', patched)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('shift_hours,beyond', [(3.0, True), (-3.0, False)])
|
||||
def test_beyond_next_return_uses_the_same_local_time_basis_in_the_east(monkeypatch, shift_hours, beyond):
|
||||
_patch_engine_with_last_period(monkeypatch, shift_hours)
|
||||
envelope = ondemand_calc_api(_body()) # 北京,+08
|
||||
assert envelope['calculation_status'] in {'executed', 'partial'}
|
||||
assert envelope['raw']['mudda_raw'][0]['beyond_next_return'] is beyond
|
||||
|
||||
|
||||
@pytest.mark.parametrize('shift_hours,beyond', [(3.0, True), (-3.0, False)])
|
||||
def test_beyond_next_return_uses_the_same_local_time_basis_in_the_west(monkeypatch, shift_hours, beyond):
|
||||
_patch_engine_with_last_period(monkeypatch, shift_hours)
|
||||
envelope = ondemand_calc_api(_body(return_location={'mode': 'current_location', **NEW_YORK}))
|
||||
assert envelope['calculation_status'] in {'executed', 'partial'}
|
||||
assert envelope['raw']['mudda_raw'][0]['beyond_next_return'] is beyond
|
||||
|
||||
Reference in New Issue
Block a user