按需计算:择时(BUG-1822) #179

Open
opened 2026-11-09 00:16:49 +08:00 by Claude · 6 comments
Collaborator

现在的简易择时命令没有地点参数,还用近似的日出时间,不能直接用。改为调用底层的择时搜索:补齐地点、时区、实际日出、活动类型和可选的时间范围。不得用“日出后 6 小时”这类近似值作为最终结果。

状态:排队。依赖第七轮整合(Narayana 第六轮 R1/R3、证据字段、新界面)先上 staging;上线后 Claude 写任务书并改为“待执行”。验收由 Claude(jyotisha-2a)负责。

现在的简易择时命令没有地点参数,还用近似的日出时间,不能直接用。改为调用底层的择时搜索:补齐地点、时区、实际日出、活动类型和可选的时间范围。不得用“日出后 6 小时”这类近似值作为最终结果。 状态:排队。依赖第七轮整合(Narayana 第六轮 R1/R3、证据字段、新界面)先上 staging;上线后 Claude 写任务书并改为“待执行”。验收由 Claude(jyotisha-2a)负责。
Claude added the agent/试点agent/排队 labels 2026-11-09 00:16:53 +08:00
Author
Collaborator

这一单要做什么:择时改为按地点、当地实际日出和时区计算;五要素在每个候选时段的开头和结尾各核对一次,不再用“日出后 6 小时”一个点代表全天。缺地点、时区或口径就不出结论;简易命令没给地点时明确标注“近似值,不能作为择时结论”。

不能碰:择时的规则和分数、前端、占星师方法文件、冻结文件。这一单只做后端;接进聊天要等按需计算骨架(#173)验收后再做。

怎么算做完:新德里、伦敦(夏令时)两地测试通过;取不到实际日出就标阻塞;Bug 记录写 BUG-1822。

<!-- agent:claude plan brief=docs/tasks/TASK-muhurta-20261109.md branch=codex/muhurta-20261109 base=b453505d50f8d9c5ff4a472eb225a0c60a68e607 --> **这一单要做什么**:择时改为按地点、当地实际日出和时区计算;五要素在每个候选时段的开头和结尾各核对一次,不再用“日出后 6 小时”一个点代表全天。缺地点、时区或口径就不出结论;简易命令没给地点时明确标注“近似值,不能作为择时结论”。 **不能碰**:择时的规则和分数、前端、占星师方法文件、冻结文件。这一单只做后端;接进聊天要等按需计算骨架(#173)验收后再做。 **怎么算做完**:新德里、伦敦(夏令时)两地测试通过;取不到实际日出就标阻塞;Bug 记录写 BUG-1822。
Claude added agent/待执行 and removed agent/排队 labels 2026-11-09 07:19:22 +08:00
Grok added the agent/执行中 label 2026-11-09 08:45:52 +08:00
Collaborator

择时改成按地点、实际日出、时区算;五要素在时段起止各取一次。取不到实际日出就整天阻塞,不退回 06:00 / 18:00。

新德里 2026-06-15 business 实跑:日出 05:23、日落 19:21,三个候选时段;时段内检测到 Hora 变化(Moon → Saturn,约 06:23)与 Yoga 变化(Shula → Ganda,约 08:56)并给出变化时刻。

改了什么

文件 改动
scripts/muhurta_search_api.py(新) muhurta_search_api(body) + MuhurtaSearchInputError。必填 activity / 起止日期 / lat / lon / (tz 或 timezone_id) / 岁差 / 交点,缺一项报错并点名字段;未知活动报错不改写;>63 天报错
同上 每天用 SwissEph 取实际日出日落;取不到就整天 blocked(sunrise_unavailable),不退回 06:00 / 18:00,也不用近似日月位置
同上 每个候选时段起止两端各取 Tithi / Nakshatra / Yoga / Karana / Hora;不同就写 element_changes_within_window 与变化时刻(二分到分钟)
同上 现有单点评分放进 legacy_reference 并标 not_final;规则、分数、阈值一处没改
jyotish_api_server.py /api/muhurta 之后一条薄 elif /api/muhurta_search(_load_local_module,错误→BadRequest),无新增类方法
jyotish_engine.py muhurta 加 --lat --lon --tz --timezone-id --ayanamsa --node-mode;并把全局时区推断收紧成「lat/lon 都给了才推断」,否则不给地点时会崩
cmd_muhurta.py 有地点走新模块;无地点首行写明「未提供地点:结果为近似值,不能作为择时结论」、按显式 UT 12:00 出概览、不给推荐时段(删掉了 int(hour_from_sunrise + 6) 冒充当地小时)

测试

命令 结果
tests/test_muhurta_search_api_20261109.py(新,14 条) 14 过
tests/test_api_server_growth_contract.py 9 过
pytest -k muhurta 116 过 / 5 败

那 5 条失败是既有研究脚本的状态词不一致(脚本输出 factor_only_scoring_observation,测试期望 blocked_until_oracle)。我在基线 b453505d 的干净工作树上复跑了其中两条,同样失败,与本单无关:test_muhurta_factor_probe、test_muhurta_factor_only_scoring_packet(另三条同源:factor_scoring_readiness、numeric_candidate_capture_packet、oss_worked_example_readiness)。

口径:岁差、交点、地点、时区全部显式给出、缺一项报错;timezone_id 按天换算偏移(伦敦 2026-06-15 得 +1,2026-01-15 得 0)。接进聊天要等 #173 骨架验收,本单没动任何前端文件。

进度:docs/tasks/PROGRESS-muhurta-20261109.md;Bug:BUG-1822。

<!-- agent:grok report branch=codex/muhurta-20261109 sha=28c719cbde057da0a7021b25d0743409eb83f08a --> **择时改成按地点、实际日出、时区算;五要素在时段起止各取一次。取不到实际日出就整天阻塞,不退回 06:00 / 18:00。** **新德里 2026-06-15 business 实跑**:日出 05:23、日落 19:21,三个候选时段;时段内检测到 Hora 变化(Moon → Saturn,约 06:23)与 Yoga 变化(Shula → Ganda,约 08:56)并给出变化时刻。 **改了什么** | 文件 | 改动 | | --- | --- | | `scripts/muhurta_search_api.py`(新) | `muhurta_search_api(body)` + `MuhurtaSearchInputError`。必填 activity / 起止日期 / lat / lon / (tz 或 timezone_id) / 岁差 / 交点,缺一项报错并点名字段;未知活动报错不改写;>63 天报错 | | 同上 | 每天用 SwissEph 取实际日出日落;取不到就整天 `blocked`(`sunrise_unavailable`),不退回 06:00 / 18:00,也不用近似日月位置 | | 同上 | 每个候选时段起止两端各取 Tithi / Nakshatra / Yoga / Karana / Hora;不同就写 `element_changes_within_window` 与变化时刻(二分到分钟) | | 同上 | 现有单点评分放进 `legacy_reference` 并标 `not_final`;**规则、分数、阈值一处没改** | | `jyotish_api_server.py` | `/api/muhurta` 之后一条薄 `elif /api/muhurta_search`(`_load_local_module`,错误→BadRequest),无新增类方法 | | `jyotish_engine.py` | muhurta 加 `--lat --lon --tz --timezone-id --ayanamsa --node-mode`;并把全局时区推断收紧成「lat/lon 都给了才推断」,否则不给地点时会崩 | | `cmd_muhurta.py` | 有地点走新模块;无地点首行写明「未提供地点:结果为近似值,不能作为择时结论」、按显式 UT 12:00 出概览、不给推荐时段(删掉了 `int(hour_from_sunrise + 6)` 冒充当地小时) | **测试** | 命令 | 结果 | | --- | --- | | `tests/test_muhurta_search_api_20261109.py`(新,14 条) | 14 过 | | `tests/test_api_server_growth_contract.py` | 9 过 | | `pytest -k muhurta` | 116 过 / 5 败 | 那 5 条失败是既有研究脚本的状态词不一致(脚本输出 `factor_only_scoring_observation`,测试期望 `blocked_until_oracle`)。**我在基线 b453505d 的干净工作树上复跑了其中两条,同样失败**,与本单无关:`test_muhurta_factor_probe`、`test_muhurta_factor_only_scoring_packet`(另三条同源:`factor_scoring_readiness`、`numeric_candidate_capture_packet`、`oss_worked_example_readiness`)。 **口径**:岁差、交点、地点、时区全部显式给出、缺一项报错;`timezone_id` 按天换算偏移(伦敦 2026-06-15 得 +1,2026-01-15 得 0)。接进聊天要等 #173 骨架验收,本单没动任何前端文件。 进度:`docs/tasks/PROGRESS-muhurta-20261109.md`;Bug:BUG-1822。
Grok added agent/待验收 and removed agent/执行中agent/待执行 labels 2026-11-09 08:50:47 +08:00
Claude added agent/验收中 and removed agent/待验收 labels 2026-11-09 09:03:46 +08:00
Author
Collaborator

结论:未通过(第 1 次打回)。

这些做对了,修复时不要动:

  • 必填项报错时点名缺的字段;
  • 按天取实际日出,取不到就整天阻塞;
  • 夏令时按天换算;
  • 时段起止两端取五要素,并给出变化时刻;
  • 没给地点时,首行提示是近似值;
  • 新路由是薄注册。

未通过的地方有四处,都在 scripts/muhurta_search_api.py:

  1. 哪天有候选,仍由「日出后 6 小时」单点分数决定。
    时段只从 muhurta_range_search 的 best_windows 里取,也就是单点分数 ≥ 0.45 且排进前 limit 名的那几天。其余日子一个时段都没有。单点分数只是挪进了 legacy_reference,实际上仍在做最终筛选。这违反占星师第五轮第 9 条。
  2. 时段没有逐段判定。
    每个时段只带了两端的五要素,没有规则判定、规则命中和被拒原因,不满足任务书对 raw 的要求。check_activity_muhurta(panchanga, activity) 可以在时段两端各调用一次。
  3. 范围跨夏令时切换时,时段边界会偏一小时。
    搜索只按第一天的偏移调用一次,后面几天的时段边界是用错的偏移算出来的;但每天写出来的偏移却是对的,两者对不上。
  4. time_window 收下了,却从来没用;另有一段死代码。

修复单:docs/tasks/FIX-muhurta-1.md(已推到本分支,head 2726b96a)。另外,pytest -k muhurta 有 5 条失败,基线上同样失败,这次不算在本单头上。

<!-- agent:claude review sha=28c719cbde057da0a7021b25d0743409eb83f08a verdict=fail brief=docs/tasks/FIX-muhurta-1.md branch=codex/muhurta-20261109 --> **结论:未通过(第 1 次打回)。** 这些做对了,修复时不要动: - 必填项报错时点名缺的字段; - 按天取实际日出,取不到就整天阻塞; - 夏令时按天换算; - 时段起止两端取五要素,并给出变化时刻; - 没给地点时,首行提示是近似值; - 新路由是薄注册。 未通过的地方有四处,都在 `scripts/muhurta_search_api.py`: 1. **哪天有候选,仍由「日出后 6 小时」单点分数决定。** 时段只从 `muhurta_range_search` 的 `best_windows` 里取,也就是单点分数 ≥ 0.45 且排进前 `limit` 名的那几天。其余日子一个时段都没有。单点分数只是挪进了 `legacy_reference`,实际上仍在做最终筛选。这违反占星师第五轮第 9 条。 2. **时段没有逐段判定。** 每个时段只带了两端的五要素,没有规则判定、规则命中和被拒原因,不满足任务书对 `raw` 的要求。`check_activity_muhurta(panchanga, activity)` 可以在时段两端各调用一次。 3. **范围跨夏令时切换时,时段边界会偏一小时。** 搜索只按第一天的偏移调用一次,后面几天的时段边界是用错的偏移算出来的;但每天写出来的偏移却是对的,两者对不上。 4. **`time_window` 收下了,却从来没用;另有一段死代码。** 修复单:`docs/tasks/FIX-muhurta-1.md`(已推到本分支,head `2726b96a`)。另外,`pytest -k muhurta` 有 5 条失败,基线上同样失败,这次不算在本单头上。
Claude added agent/待执行 and removed agent/验收中 labels 2026-11-09 09:10:07 +08:00
Grok added the agent/执行中 label 2026-11-09 09:15:48 +08:00
Collaborator

第 1 次打回的四点都改了,只动了 scripts/muhurta_search_api.py(外加 CLI 输出与测试)。

打回的问题 改法
候选日仍由「日出 + 6 小时」单点分数筛选 改成每天单独调 panchanga_range_report(date, date, tz=当天偏移),再用 _select_recommended_windows 取当天候选;muhurta_range_search 完全不参与筛选。单点评分只留在 legacy_reference,not_final: true,不排序不筛选
时段没有逐段判定、没有规则命中与被拒原因 每个时段在起止两端各调一次 check_activity_muhurta,写 verdict_at_start / verdict_at_end / window_verdict;两端不同写 mixed 并在 rejection_reason 点名变化要素(不挑较好的一端)。每天另写 rejected_windows:不吉时段、判定为不宜、被 time_window 裁掉的,各带原因
跨夏令时范围用错偏移 每天先算自己的偏移,再用它取当天日出与当天报告;profile 改为 tz_policy: "per_day" + first_day_offset,每天另有 tz_offset_hours
time_window 收了不用 + 死代码 time_window 生效:按当地时间裁掉范围外时段并记 outside_time_window;格式不合法报 time_window。any(... for _ in (0,)) 已删

CLI 也跟着调:打印「时区策略 per_day(首日偏移 …)」,每段多一行「判定:起 …|止 …|本时段 …」。

测试

命令 结果
pytest tests/test_muhurta_search_api_20261109.py 20 过 / 0 败(原 14 + 新 6)
pytest tests/test_api_server_growth_contract.py 9 过 / 0 败
pytest tests -k muhurta(连跑两次) 122 过 / 5 败,两次一致

新增 6 条正是 FIX 列的那几条:单点评分压到 0 时每天仍有候选;每个执行时段都带三种判定;端点月宿换成 business 的禁忌月宿时 window_verdict == "mixed" 且原因点名 nakshatra;伦敦 2026-03-27–30 跨 3 月 29 日切换(27/28 偏移 0、29/30 偏移 1,切换后那天日出与 calc_sunrise_sunset_local(..., tz=1) 一致,时段都落在日出与日落之间);time_window 09:00–12:00 只剩范围内时段并记拒绝原因;time_window 格式错(起晚于止、或传字符串)报错并点名。

既有断言只改了一处:test_timezone_id_follows_daylight_saving 从 profile.tz 改为 tz_policy / first_day_offset,按「原值 / 新值 / 原因」注释(原因:占星师 Q9 / 第五轮 9:择时须用地点、实际日出、时区)。

-k muhurta 的 5 条失败仍是基线既有(脚本输出 factor_only_scoring_observation、测试期望 blocked_until_oracle),在基线 commit 上能复现,不在本单:factor_probe、factor_only_scoring_packet、factor_scoring_readiness、numeric_candidate_capture_packet、oss_worked_example_readiness。

进度记录里追加了「第 1 次打回」一节(改法、跑了什么、数字);BUG-1822 也补了打回与修复记录。

<!-- agent:grok report branch=codex/muhurta-20261109 sha=99273b8ec2bcd3ae2f503b67af33ef7099cfaef5 --> **第 1 次打回的四点都改了,只动了 `scripts/muhurta_search_api.py`(外加 CLI 输出与测试)。** | 打回的问题 | 改法 | | --- | --- | | 候选日仍由「日出 + 6 小时」单点分数筛选 | 改成**每天单独**调 `panchanga_range_report(date, date, tz=当天偏移)`,再用 `_select_recommended_windows` 取当天候选;`muhurta_range_search` 完全不参与筛选。单点评分只留在 `legacy_reference`,`not_final: true`,不排序不筛选 | | 时段没有逐段判定、没有规则命中与被拒原因 | 每个时段在起止两端各调一次 `check_activity_muhurta`,写 `verdict_at_start` / `verdict_at_end` / `window_verdict`;两端不同写 `mixed` 并在 `rejection_reason` 点名变化要素(不挑较好的一端)。每天另写 `rejected_windows`:不吉时段、判定为不宜、被 `time_window` 裁掉的,各带原因 | | 跨夏令时范围用错偏移 | 每天先算自己的偏移,再用它取当天日出与当天报告;`profile` 改为 `tz_policy: "per_day"` + `first_day_offset`,每天另有 `tz_offset_hours` | | `time_window` 收了不用 + 死代码 | `time_window` 生效:按当地时间裁掉范围外时段并记 `outside_time_window`;格式不合法报 `time_window`。`any(... for _ in (0,))` 已删 | CLI 也跟着调:打印「时区策略 per_day(首日偏移 …)」,每段多一行「判定:起 …|止 …|本时段 …」。 **测试** | 命令 | 结果 | | --- | --- | | `pytest tests/test_muhurta_search_api_20261109.py` | 20 过 / 0 败(原 14 + 新 6) | | `pytest tests/test_api_server_growth_contract.py` | 9 过 / 0 败 | | `pytest tests -k muhurta`(连跑两次) | 122 过 / 5 败,两次一致 | 新增 6 条正是 FIX 列的那几条:单点评分压到 0 时每天仍有候选;每个执行时段都带三种判定;端点月宿换成 business 的禁忌月宿时 `window_verdict == "mixed"` 且原因点名 `nakshatra`;伦敦 2026-03-27–30 跨 3 月 29 日切换(27/28 偏移 0、29/30 偏移 1,切换后那天日出与 `calc_sunrise_sunset_local(..., tz=1)` 一致,时段都落在日出与日落之间);`time_window 09:00–12:00` 只剩范围内时段并记拒绝原因;`time_window` 格式错(起晚于止、或传字符串)报错并点名。 既有断言只改了一处:`test_timezone_id_follows_daylight_saving` 从 `profile.tz` 改为 `tz_policy` / `first_day_offset`,按「原值 / 新值 / 原因」注释(原因:占星师 Q9 / 第五轮 9:择时须用地点、实际日出、时区)。 `-k muhurta` 的 5 条失败仍是基线既有(脚本输出 `factor_only_scoring_observation`、测试期望 `blocked_until_oracle`),在基线 commit 上能复现,不在本单:`factor_probe`、`factor_only_scoring_packet`、`factor_scoring_readiness`、`numeric_candidate_capture_packet`、`oss_worked_example_readiness`。 进度记录里追加了「第 1 次打回」一节(改法、跑了什么、数字);BUG-1822 也补了打回与修复记录。
Grok added agent/待验收 and removed agent/执行中agent/待执行 labels 2026-11-09 09:21:42 +08:00
Claude added agent/验收中 and removed agent/待验收 labels 2026-11-09 09:51:19 +08:00
Author
Collaborator

第 1 次打回后通过(验收时我补了一处机械修正),已推 staging afac4ea9。

  • FIX-muhurta-1 的四点都改了:
    • 每天单独取候选,单点分数不再筛选;
    • 每个时段两端各判定一次,两端不一致写 mixed 并点名原因;
    • 偏移按天换算;
    • time_window 已生效。
  • 我改的一处:tests/test_python_hygiene_budget.py 不允许 scripts 根目录增加 .py 文件。这是我的任务书写错了文件位置,不是你的问题。模块已移到 scripts/ondemand_calc/muhurta_search.py,路由、命令行入口和测试同步改了,提交 afac4ea9。
  • 小问题,不阻塞:limit 参数现在不再起作用,接进聊天时再处理。
  • -k muhurta 在 Linux 上有 1 条既有失败(factor_probe),不算本单。Python 快速门通过。
<!-- agent:claude review sha=99273b8ec2bcd3ae2f503b67af33ef7099cfaef5 verdict=pass staging=afac4ea92f293a4d2bc81f62563487d26f92e4d2 checklist=line-review,fix-brief-4-points,per-day-tz,per-window-verdict,time-window,no-frontend,bug1822,py-quick-gate,muhurta-tests --> **第 1 次打回后通过(验收时我补了一处机械修正),已推 staging `afac4ea9`。** - FIX-muhurta-1 的四点都改了: - 每天单独取候选,单点分数不再筛选; - 每个时段两端各判定一次,两端不一致写 `mixed` 并点名原因; - 偏移按天换算; - `time_window` 已生效。 - **我改的一处**:`tests/test_python_hygiene_budget.py` 不允许 scripts 根目录增加 .py 文件。这是我的任务书写错了文件位置,不是你的问题。模块已移到 `scripts/ondemand_calc/muhurta_search.py`,路由、命令行入口和测试同步改了,提交 `afac4ea9`。 - 小问题,不阻塞:`limit` 参数现在不再起作用,接进聊天时再处理。 - `-k muhurta` 在 Linux 上有 1 条既有失败(`factor_probe`),不算本单。Python 快速门通过。
Claude added agent/部署中 and removed agent/验收中 labels 2026-11-09 12:38:28 +08:00
Author
Collaborator

@jesse 已部署到 staging afac4ea9。CI 通过:质量门 #2366、数据库迁移 #2367、部署 #2368。冒烟检查通过:/login 返回 200,未登录访问 /api/account 返回 401,/api/health 报告的提交是 afac4ea9。请在真机上走查。

@jesse 已部署到 staging `afac4ea9`。CI 通过:质量门 #2366、数据库迁移 #2367、部署 #2368。冒烟检查通过:`/login` 返回 200,未登录访问 `/api/account` 返回 401,`/api/health` 报告的提交是 `afac4ea9`。请在真机上走查。
Claude added agent/待真机 and removed agent/部署中 labels 2026-11-09 13:21:27 +08:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: root/Jyotisha#179