Files
Jyotisha/docs/tasks/TASK-dasha-unify-20261013.md
T
Jesse_ChenandClaude Opus 5.5 2994588ad4 docs(tasks): fold product decisions into audit briefs (BUG-1344..1346)
- dasha-unify: fix Ashtottari birth balance (BUG-1344)
- rect-scoring-perf: server-side candidate cap 241 (BUG-1345)
- node-runtime-resources: enable user-side session cookie cache, 300 s (BUG-1346)
- consult-prep-parallel: classifier/reserve concurrency declined
- answer-effort-ab: model key supplied by product at acceptance
- drop stricter-than-test line-count red lines (AGENTS.md section 6)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-14 00:41:02 +08:00

28 KiB
Raw Blame History

TASK · 大运实现收口:/api/dasha 改走引擎同一套实现,删占位注册表(issue #待定)

  • 日期:2026-10-13
  • 来源:2026-10-13 全仓屎山与性能排查 ·「大运多套实现」条目(scripts/*dasha*.py 28 个、两套条件大运、_birth_lord_and_balance 多份、Vimshottari 年数表多处硬编码)
  • 执行方:Grok(Windows + Docker),分支 codex/dasha-unify-20261013,只推这个分支
  • 验收:Claude(Linux)
  • BUG 编号:BUG-1341、BUG-1344(开工时核对 docs/BUG_HISTORY.md 最大号;写作时最大号 BUG-1317;号段由主会话统一分配)(另:TASK-upstream-sync7-20261014 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
  • 开工前置:api-chart-subprocess-gate 已合入 origin/staging(它改 jyotish_api_server.py 与排盘路径)。与 python-hygiene(BUG-1340)同为第二批,两单都改 scripts/jyotish_api_server.py 与 scripts/jyotish_engine.py:本单在 python-hygiene 合入后开工,若主会话决定并行,则本单只碰第 6 节列出的函数,合入时由后合的一方 rebase。
  • 主会话补充的串行链(2026-10-14):引擎/API 文件链按此顺序逐单合入后开工(upstream-sync7 也改 jyotish_engine.py / jyotish_api_server.py,若在途,后合一方 rebase):api-disk-cache-retention + report-reference-perf → api-chart-subprocess-gate → pl9-render-split → python-hygiene → dasha-unify → api-server-service-extract。本单的前置以这条链为准(与上一行不一致时以本行为准)。
  • 后继:api-server-service-extract 依赖本单合入。

1. 基线

  • 基线 commit:origin/staging 77057a819(写作时);开工以前置单合入后的 origin/staging 为准。
  • 实测脚本:compare.py(写作时放在起草方 scratch,下文给出可照抄的要点)。虚构盘:1990-01-01 12:00,新德里 28.6139 / 77.2090,时区 +5.5,Lahiri,平均交点。月亮 306.4653°(第 23 宿 Dhanishta 第 4 足),上升 343.9226°(双鱼),太阳 256.8599°。
  • 本机参照:PyJHora 4.8.7(jhora.horoscope.dhasa.graha.* / raasi.narayana),drik.set_ayanamsa_mode("LAHIRI")。PyJHora 只在研究 / 测试中作对照,不进运行时(许可证边界见 AGENTS §9 与 tests/test_runtime_jhora_import_scan.py)。

2. 事故实证(77057a819)

2.1 同一张盘,两条入口给出两套大运

引擎全量解读(scripts/jyotish_engine.py::cmd_full_reading 约第 16860 行起,按字符串表 __import__ 独立模块)与接口 POST /api/dasha(scripts/jyotish_api_server.py::JyotishAPIHandler._compute_dasha_system → scripts/extended_dashas.py::calc_any_dasha)对比,前 3 段主运(主星 / 起始日):

体系 /api/dasha(extended_dashas) 引擎独立模块 PyJHora 4.8.7
Panchottari Ketu 1990-01-01、Venus 2002-01-01、Sun 2014-01-01(Vimshottari 的九星,各 12 年) Jupiter 至 1990-04-10、Sun 1990-04-10、Mercury 2002-04-10、Saturn 2015-04-11 Jupiter(1972-04-10 起)、Sun 1990-04-10、Mercury 2002-04-10、Saturn 2015-04-11
Satabdika Ketu 1990-01-01、Venus 2001-01-01、Sun 2012-01-02(各 11 年) Venus 余额、Mercury 1990-02-25、Jupiter 2000-02-26、Mars 2020-02-26 Venus(1980 起)、Mercury 1990-02-25、Jupiter 2000-02-26、Mars 2020-02-26
Chaturshitisama(注册键 chaturaaseeti) Ketu 1990-01-01、Venus 1999-01-01、Sun 2008-01-02(各 9 年) Moon 余额、Mars 1990-03-08、Mercury 2002-03-08、Jupiter 2014-03-08 Moon(1978 起)、Mars 1990-03-08、Mercury 2002-03-08、Jupiter 2014-03-08
Shastihayani Moon 1990-01-01、Mars 1998-01-01、Mercury 2008-01-02 Saturn 余额、Rahu 1990-01-12、Jupiter 1996-01-13、Sun 2006-01-12 Saturn(1984 起)、Rahu 1990-02-03、Jupiter 1996-02-04、Sun 2006-02-03
Narayana Pisces 3 年、Capricorn 6、Aquarius 9((idx%3+1)*3 编出来的年数) narayana_dasha.py(旧算法,已标注):Pisces 3、Aries 7、Taurus 8 Pisces 9、Cancer 5、Scorpio 8

结论:

  • Panchottari / Satabdika / Chaturshitisama:引擎独立模块与 PyJHora 逐日一致(主运起始日完全相同);extended_dashas 完全对不上。
  • Shastihayani:引擎模块与 PyJHora 主星顺序一致,起点差约 22 天,原因是模块按文献用 28 宿(含 Abhijit)切分(scripts/shastihayani_dasha.py 模块说明与 source_locator 写明出处)。这是有出处的口径选择,本单不改。
  • Narayana:三套各不相同。引擎用的 narayana_dasha.py 是已知旧算法(BUG-1215 已加「旧算法」标注;BUG-1218 的 Rath 版只并列展示);extended_dashas 是第三套、比旧算法还假。选哪套为准属于占星裁定,已有 BUG-1215 / 1218 在推进,本单不裁定,只要求 /api/dasha 与引擎、咨询给出同一份(含旧算法标注)。

2.2 extended_dashas 是占位实现,不是另一种口径

scripts/extended_dashas.py(408 行)登记 35 个键。逐键用两组完全不同的输入(月宿 3 vs 17、上升 0 vs 7、太阳星座 2 vs 9 …)各算一次:

现象 键数
两组输入输出完全相同(不看盘) 28 / 35(含无实现返回空表的 vimshottari、ashtottari、chara)
输出就是「Vimshottari 九星 × 每段 4 年」同一张表(calc_generic_dasha 或 calc_any_dasha 的兜底) 18
总年数与登记年数不符 例:panchottari 登记 105 实出 108;kalachakra 94 实出 144;shasti_hayani 60 实出 84;dwadashottari 112 实出 36
  • 文件自己写了:scripts/panchottari_dasha.py 模块说明「The legacy extended_dashas helper is deliberately not used: it is a generic placeholder」。
  • _compute_dasha_system 对这些结果回 precision: 'calculator'(只要注册函数名不是 calc_generic_dasha),把占位结果标成真算。
  • 已有记录:BUG-1262 防复发写「extended_dashas.py 的另一套 Yogini 仍按月宿对 8 取余且不裁余额,全盘报告不走它」;BUG-1284(mitigated)写「'shoola' 注册项……接口层仍能按这个键调用……本单不得改 jyotish_api_server.py 的方法体,所以注册项还在」。两条都把问题留给了今天这一单。

谁在用:

  • POST /api/dasha 非 Vimshottari 键:只有引擎内网接口与 /api/technique_example(技法浏览器示例,默认 payload 是 Vimshottari)。前端 frontend/src 只调 /api/dasha/chara;/api/daily-starlanguage 已改走 /api/daily_guidance(BUG-265 记录里「并行取 /api/dasha」已过时)。没有用户可见入口,所以是 P2 级缺陷,但它是对外公开的接口合同。
  • /api/chart(_compute_chart_sync)返回体里的 available_dashas(35 条元数据)与 dasha_count 来自 extended_dashas.DASHA_REGISTRY;frontend/tests/fixtures/chara-zero-year-1985-08-03-beijing.golden.json 与 tests/golden/consultation_contract_keypaths_v1.json 含此字段。
  • tests/run_all.py(release 门第 84 行会跑)有 extended_dashas / conditional_dashas 导入与「至少 30 / 35 个」断言。

2.3 conditional_dashas.py 只被测试用

scripts/conditional_dashas.py(193 行,calc_dwisaptati_dasha / calc_shattrimsa_dasha / calc_dwadashottari_dasha 等 7 个函数)全仓只有 tests/run_all.py 4 处导入;生产用的是 dwisaptatisama_dasha.py / shattrimshatsama_dasha.py / dwadashottari_dasha.py。

2.4 _birth_lord_and_balance 8 份:6 份同一算法换参数

文件 起算宿 结构
panchottari_dasha.py Anuradha(16) 同一算法:offset=(宿-起算宿)%27,lord=SEQ[offset%len],余额=年数×(1-宿内比例)
satabdika_dasha.py Revati(26) 同上
chaturshitisama_dasha.py Swati(14) 同上
dwisaptatisama_dasha.py Mula(18) 同上
shattrimshatsama_dasha.py Sravana(21) 同上
shodashottari_dasha.py Pushya(7) 同上
shastihayani_dasha.py 28 宿分组 不同算法(关键字参数,组内多宿)
dwadashottari_dasha.py — 不同算法(返回 4 元组)

排查报告「8 份互不相同」修正为:6 份是同一算法的参数化副本,2 份是真不同。

2.5 Vimshottari 年数表硬编码 15 处(排查报告写 7 处)

{'Ketu': 7, 'Venus': 20, …, 'Mercury': 17} 字面量:jyotish_engine.py、dasha_analyzer.py、dasha_calculator.py、dasha_calculator_enhanced.py、vimshottari_subperiod_timeline.py、conditional_dashas.py、event_prediction_model.py、tajika.py(2)、prashna.py、validate.py、nakshatra_dasha.py、kp_system.py、divisional_charts_extended.py、research/offline_research_20260926_lib.py。复现:grep -rnE "Rahu['\"]?\s*:\s*18" --include=*.py scripts mcp_server.py jyotish_vedic。

其中 dasha_analyzer.py、divisional_charts_extended.py、narayana_dasha.py 在校正冻结身份里(references/real_case_calibration/minute_rectification_holdout_v3.json frozen_scoring.files;tests/test_sealed_holdout_contract_freshness.py 与 tests/test_rectification_validation_integrity_gate.py 按字节哈希核对,ERR-110 / ERR-114)。改一个字节就要按研究协议重新冻结。

2.6 附带发现:全量解读的 Ashtottari 没有出生余额(产品 10-14 决定:修,见 T6,BUG-1344)

同一虚构盘:

来源 前两段主运
ashtottari_dasha.calculate_ashtottari_dasha(全量解读 modules.ashtottari_dasha) Jupiter 1990-01-01 起满 19 年、Rahu 2009-01-01
PyJHora get_ashtottari_dhasa_bhukthi Jupiter 1983-10-07 起(出生时已走约 6.2 年)、Rahu 2002-10-07
nakshatra_dasha.calc_ashtottari_dasha(全量解读 modules.nakshatra_dasha 内) 起运星 Mars(与前两者都不同)

原因(代码可见):ashtottari_dasha.py 只有在拿到 moon_longitude 且适用规则族是「Rahu 从命主」时才算出生余额;cmd_full_reading 传的是 birth_info_for_alt_dasha(没有 moon_longitude),所以第一段从出生满额起算,后续每段都差约 6 年。改它会改报告里 Ashtottari 表的全部日期。主会话补充核实(77057a819):即便拿到 moon_longitude,_build_major_periods(start_lord_idx, birth_dt) 仍从出生时刻按满额排第一段,_calc_candidate_balance_and_birth_chain 的结果只作附加字段、不平移时间线;默认规则族(月宿 + 月相)路径根本不算余额;「当前大运」也按「第一段从出生开始」推算。产品 10-14 决定修复(T6)。

3. 根因

  • extended_dashas.py(「冲刺全球第一 · Dasha 总数 10 → 18 → 35」)是早期为凑覆盖数写的占位;之后逐个体系补了有出处的独立模块(2026-09 起),引擎全量解读改走独立模块,但 HTTP 接口方法体受「不得改 jyotish_api_server.py」限制一直没换(BUG-1284 原文)。不是回归,是迁移只做了一半。
  • 条件大运每加一个就复制一份 _birth_lord_and_balance;年数表各文件自带。

4. 决策记录

  • 2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行。
  • 权威实现 = 引擎全量解读当前调用的独立模块(第 6 节 T2 映射表)。依据是代码与对照数据,不是占星判断:Panchottari / Satabdika / Chaturshitisama 与 PyJHora 逐日一致,extended_dashas 不读盘。
  • Narayana 权威 = narayana_dasha.narayana_dasha_full_report + narayana_legacy_label.label_legacy_narayana(与咨询 _attach_local_consultation_layers 同一调用)。不在本单裁定旧算法 vs Rath 版(BUG-1215 / 1218 红线:Rath 版只并列展示,替换由产品在 T5 试算后决定)。
  • Shastihayani 28 宿口径维持。
  • 没有独立实现的注册键(tara、sudasa、mandooka、lagnamsaka、yogardha、kendradi、tithi_ashtottari、tithi_yogini、patyayini、paryaaya、rashmi、naisargika、aayu、karaka、panchasvara、sudarsana 等,开工重核):/api/dasha 改为返回 400 dasha system not implemented,不再编周期。依据:用户偏好「多余入口宁可删除也不修」;这些键没有任何用户可见调用方。
  • /api/chart 的 available_dashas / dasha_count 本单保持逐字节不变(元数据搬到新注册表,内容不改),避免牵动排盘 golden 与排盘缓存;与 /api/dasha 能力不一致的问题列入「后续阶段」,需拍板是否裁剪。
  • conditional_dashas.py、extended_dashas.py 删除。tests/run_all.py(release 门)里对应断言按三栏改。
  • 冻结身份内文件(dasha_analyzer.py、divisional_charts_extended.py、narayana_dasha.py、domain_calculation_service.py 等)本单一个字节不改;它们的年数表留到下次有理由重新冻结时一起收。
  • 2.6 Ashtottari 出生余额:产品负责人 2026-10-14 决定修复(T6,BUG-1344)。这是本单唯一允许改变引擎全量解读输出的项:只许 modules.ashtottari_dasha(及 T6 列出的同源 Ashtottari 输出)变化,其余逐字节不变。适用性规则(哪些盘适用 Ashtottari、两种规则族的判定)不改,只修「已判定适用时的时间线」。
  • 推翻的既有约束:BUG-1284「本单不得改 jyotish_api_server.py 的方法体」只是那一单的范围限制,不是长期红线;本单授权改 _compute_dasha_system / _fallback_dasha_periods / _compute_chart_sync 里读注册表的三行;行数按增长合同粗护栏。

5. 硬红线

  1. 引擎全量解读输出逐字节不变(modules.*_dasha 全部;唯一例外是 T6 的 Ashtottari 时间线)。本单只改接口路径与去重;若 T3 / T4 去重让任何数值或字段顺序变化,即为搬错,改代码不改断言。
  2. 冻结身份两份清单内的文件一个字节不改(开工打印清单,第 9 节)。
  3. 不改 narayana_dasha.py、narayana_rath.py、narayana_legacy_label.py;不改 BUG-1215 / 1218 的展示与打分口径。
  4. scripts/jyotish_api_server.py:行数只按 tests/test_api_server_growth_contract.py 的粗护栏(AGENTS.md §6:不另加比测试更严的行数红线,也不得压行凑数),类方法数与 JyotishAPIHandler.__new__ 伪造点不得增长;类方法数(删 _fallback_dasha_periods 后应下降 1,合同测试基线同步下调并写三栏);JyotishAPIHandler.__new__ 计数不得增长。
  5. /api/chart 响应逐字节不变(available_dashas 内容与顺序不变)。
  6. 新注册模块不得 import jyotish_api_server;不得在运行时 import jhora(PyJHora 只在测试里作对照,且测试需在缺 PyJHora 时 skip,镜像不装它——见记忆「验收要屏蔽 PyJHora 模拟生产」)。
  7. 不改 .gitea/workflows/**、不动表结构与迁移、不升级依赖。
  8. 改既有断言写「原值 / 新值 / 原因」三栏;golden 只能来自真实引擎输出。
  9. 不碰 skills/*/versions/**。

6. 任务分解

T1 · 实测对照固化为测试(必做,先做)

  • 新测试 tests/test_dasha_api_engine_parity.py:
    • 用第 1 节虚构盘,对 Panchottari、Satabdika、Chaturshitisama、Shastihayani、Dwisaptatisama、Shattrimshatsama、Shodashottari、Dwadashottari、Yogini、Ashtottari、Kalachakra、Narayana:/api/dasha(handler 方法直调)返回的主运列表 == 引擎全量解读同一模块的主运列表(主星、起止日期、年数逐项相等)。本条在基线上应当红(证明 BUG-1341),修完转绿;把基线红的输出贴进进度记录。
    • PyJHora 对照(pytest.importorskip("jhora")):Panchottari / Satabdika / Chaturshitisama 主运起始日与 PyJHora 逐日相等——锁住「为什么以独立模块为准」。golden 由真实 PyJHora 与真实引擎输出生成,fixtureProvenance 写命令。
  • 验收:Grok 在基线上跑一次(应红)、修后跑一次(应绿),两次输出都进进度记录。

T2 · /api/dasha 改走权威实现,删 extended_dashas.py(必做)

改什么:

  • 新建 scripts/dasha_registry.py(不 import jyotish_api_server):
    • DASHA_REGISTRY_METADATA:从 extended_dashas.DASHA_REGISTRY 原样搬(35 个键、顺序、name / years / type 不变),只供 /api/chart 的 available_dashas。

    • IMPLEMENTED:键 → 权威函数映射(写作时,开工重核):

      注册键 权威实现
      vimshottari 不变:domain_calculation_service.compute_vimshottari_timeline(handler 现有分支)
      yogini yogini_dasha.calculate_yogini_dasha
      ashtottari ashtottari_dasha.calculate_ashtottari_dasha(与全量解读同参,含 2.6 的问题,原样)
      kalachakra kalachakra_dasha.calculate_kalachakra_dasha
      panchottari / satabdika / chaturaaseeti / shasti_hayani / shodasottari / dwadashottari / dwisaptati / shattrimsa 对应独立模块的 calculate_*,参数与 cmd_full_reading 的 conditional_birth_info / extra 相同
      narayana narayana_dasha_full_report + label_legacy_narayana(与咨询同参)
      shoola 不接(BUG-1284:报告走 rashi_dashas_native 的 shoola_forward_md_direct_ad_v1;若开工确认 rashi_dashas_native.calculate_rashi_dashas 的 shoola 段可直接复用则接它,否则 400)
      chara 400,消息指向 /api/dasha/chara
      其余 400 dasha system not implemented
    • 一个构造入参的函数:从 planets(含 lon)与 ascendant(含 lon 或 sign_idx)算出 conditional_birth_info 所需字段(上升经度、月亮经度、太阳经度、D9 上升星座、十宫主所在星座、罗睺星座、命主所在星座、白天出生、月宿、月足、太阴半月等)。这部分逻辑优先从 cmd_full_reading 抽成共享函数、两边共用,保证「同盘同参」;若抽取会改 jyotish_engine.py 输出(硬红线 1),则在新模块里实现并用 T1 测试锁住等价。

  • _compute_dasha_system 非 Vimshottari 分支改为调用 dasha_registry.compute(key, ...);precision 字段:权威实现回 'engine_module',删掉 'generic' / 'calculator' 两个旧值(对外合同变化,写进 CHANGELOG 与 BUG 记录)。
  • 删 JyotishAPIHandler._fallback_dasha_periods;_compute_chart_sync 改读 dasha_registry.DASHA_REGISTRY_METADATA。
  • 删除 scripts/extended_dashas.py。tests/test_rashi_dashas_native.py 里「不导入 extended_dashas」断言保留(自然成立)。
  • 不改:/api/dasha/chara;/api/dasha Vimshottari 分支;引擎全量解读任何调用。

验收:

  • T1 测试绿。
  • /api/chart 虚构盘响应基线 vs 本分支逐字节一致(同进程无法同时加载两版,用两个工作树各跑一次 handler._compute_chart_sync 落盘 JSON 后 cmp;排盘缓存先清空 scratch/local/api_chart_cache,VEDASTRO_ENABLE_NETWORK=0,PYTHONHASHSEED=0)。
  • 新测试:未实现键返回 400;chara 400 且消息含 /api/dasha/chara;响应不再出现 precision: 'generic'。
  • tests/test_api_server_security.py、tests/test_api_server_growth_contract.py(方法数基线 218 → 实测值,三栏)、tests/test_rashi_dashas_native.py、tests/test_narayana_legacy_label.py 绿。

T3 · 删 conditional_dashas.py,改 tests/run_all.py(必做)

  • 开工先 grep -rn "conditional_dashas" --include=*.py --include=*.ts --include=*.json . 复核只剩 tests/run_all.py;references/**/*.json 若有路径记录(写作时 references/oracle/full_technique_invocation_matrix_2026_07_22.json 等提到 extended_dashas),不改历史证据 JSON,在进度记录列出。
  • tests/run_all.py:t18、t19 与第 129–130、219–233、402–409、624–630 行附近断言改为对应独立模块或注册表(三栏)。这个文件由 release 门执行(.gitea/workflows/release-quality-gate.yml 第 84 行),必须本地跑通:python tests/run_all.py。

T4 · 去重:条件大运余额函数与 Vimshottari 年数表(必做,限非冻结文件)

  • 新建 scripts/dasha_common.py:
    • nakshatra_seeded_birth_balance(moon_longitude, *, anchor_index, sequence) -> (lord, balance_years, nakshatra_index),算式与 6 份副本逐运算相同(运算顺序不变,保证浮点逐位相等)。
    • VIMSHOTTARI_YEARS(插入顺序 Ketu、Venus、Sun、Moon、Mars、Rahu、Jupiter、Saturn、Mercury)与 VIMSHOTTARI_ORDER。
  • 第 2.4 节 6 个模块的 _birth_lord_and_balance 改为一行调用(保留函数名,测试可能按名调用);shastihayani、dwadashottari 不动。
  • 第 2.5 节非冻结文件里的字面量年数表改为引用常量;每处先断言 list(旧表.items()) == list(VIMSHOTTARI_YEARS.items()),顺序或数值不同的不换,在进度记录说明。冻结的 dasha_analyzer.py、divisional_charts_extended.py 与 research/ 下的研究脚本不动。
  • 验收:
    • 新测试:6 个模块各用 2,000 个随机月亮经度(固定种子)比对新旧函数 ==(旧函数在测试里以源码副本形式保留作对照,或用基线工作树导出的 golden)。
    • 新测试(只许降):scripts/ 根目录非冻结文件里 Vimshottari 年数字面量计数 == 本单收尾值(冻结文件列白名单)。
    • 全量解读逐字节不变:基线工作树与本分支各跑一次虚构盘 python scripts/jyotish_engine.py full-reading …(参数见 --help),PYTHONHASHSEED=0,去掉墙钟字段后 cmp。

T6 · Ashtottari 出生余额(BUG-1344,必做,产品 10-14 决定)

  • 改 scripts/ashtottari_dasha.py::calculate_ashtottari_dasha:已判定适用时,按月亮在起运星所辖星宿组内已走过的比例算出生余额,第一段主运起点 = 出生时刻 − 已走过部分,后续各段与次级周期、current 推算都随之平移。两种规则族都要算余额(默认规则族现在完全不算)。
  • scripts/jyotish_engine.py::cmd_full_reading 调用 calculate_ashtottari_dasha 时补传 moon_longitude(conditional_birth_info 已有该值,照它传;不得改 birth_info_for_alt_dasha 本身,那会牵动 Yogini / Kalachakra)。scripts/orchestrator_bridge.py 等其余调用方开工 grep 全,同样补传。
  • 余额算法以 PyJHora get_ashtottari_dhasa_bhukthi 的语义为对照(只对照结果,不复制其代码,注意许可证边界,AGENTS.md §B1)。
  • 全量解读里第二套 nakshatra_dasha.calc_ashtottari_dasha(起运星 Mars,与前两者都不同):开工查清它在报告 / 咨询 / 前端哪里被展示。若用户可见,改为直接复用 ashtottari_dasha.py 的结果;若无人读取,只在 PROGRESS 记录,留后续阶段。
  • 验收:
    • 新测试:至少 3 张盘(第 1 节虚构盘 + 2 张公开名人盘,覆盖两种规则族各至少 1 张;PyJHora 缺失时 skip 对照部分,但保留与固化 golden 的比对)主运起止日期与 PyJHora 相差 ≤ 1 天,起运星一致;出生时刻落在第一段内;current 与按日期查表一致。
    • golden:受影响的报告 / 全量解读 golden 只能用真实引擎重新采集,PROGRESS 列出每个变化文件与变化只限 Ashtottari 段(其余段 diff 为空)。
    • Claude 验收跑全量 pytest 与基线逐名比对(引擎数值改动,见验收分工)。

T5 · 记录(必做)

  • docs/BUG_HISTORY.md 新增 BUG-1341:/api/dasha 非 Vimshottari 体系是占位实现(不读盘、年数自编、标成 calculator);修复为走引擎同一套模块;关联 BUG-1262、BUG-1284(把 1284 状态从 mitigated 更新为 resolved 并注明本单)、BUG-1215 / 1218(Narayana 未裁定)。另新增 BUG-1344:全量解读 Ashtottari 无出生余额、时间线整体偏移(修复 T6,状态按验收证据写)。
  • docs/tasks/PROGRESS-dasha-unify-20261013.md:T1 基线红 / 修后绿输出、映射表最终版、/api/chart 与全量解读两次逐字节对照、T4 每处替换清单。
  • CHANGELOG.md:两条(/api/dasha 合同变化:未实现体系 400、precision 取值变化;报告里 Ashtottari 大运日期修正为含出生余额)。Skill 版本不 bump。
  • frontend/DESIGN.md:不改。

7. 让步顺序

时间不够按此顺序砍:T4 年数表部分 → T4 余额函数部分。T1、T2、T3、T5、T6 不可砍(T2 是 BUG-1341 本体,T6 是产品 10-14 决定)。

8. 验收命令分工

谁 命令
Grok 自跑 python -m pytest tests/test_dasha_api_engine_parity.py tests/test_api_server_security.py tests/test_api_server_growth_contract.py tests/test_rashi_dashas_native.py tests/test_narayana_legacy_label.py tests/test_panchottari_dasha.py tests/test_satabdika_dasha.py tests/test_chaturshitisama_dasha.py tests/test_shastihayani_dasha.py tests/test_dwisaptatisama_dasha.py tests/test_shattrimshatsama_dasha.py tests/test_shodashottari_dasha.py tests/test_dwadashottari_dasha.py tests/test_full_reading_conditional_dashas.py tests/test_rectification_validation_integrity_gate.py tests/test_sealed_holdout_contract_freshness.py -q;python tests/run_all.py;两次逐字节对照。快速门、全量 pytest 不跑,以 Claude 为准
Claude 验收 上述 + 快速门 + 全量 pytest 与基线逐名比对(引擎相关改动,必须全量)+ 屏蔽 PyJHora 再跑一遍 T1 以外的测试(模拟生产镜像无 PyJHora)+ 前端 chart-view-zero-year 等用到 chara golden 的单文件测试(证明 /api/chart 不变)
不需要 test:db

9. 开工前置命令

git fetch origin --prune
git log --oneline origin/staging | grep -i "api-chart-subprocess-gate\|python-hygiene"   # 前置已合入
git worktree add -b codex/dasha-unify-20261013 .worktrees/dasha-unify-20261013 origin/staging
cd .worktrees/dasha-unify-20261013
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3
python - <<'EOF'
import json, sys; sys.path[:0] = ["scripts/research", "scripts", "."]
from sealed_holdout_rerun import PRODUCTION_FILES, RESEARCH_FILES
print(PRODUCTION_FILES, RESEARCH_FILES)
print(json.load(open("references/real_case_calibration/minute_rectification_holdout_v3.json"))["frozen_scoring"]["files"])
EOF
grep -rn "extended_dashas\|conditional_dashas\|_fallback_dasha_periods\|available_dashas\|precision.*generic" tests frontend/tests scripts mcp_server.py
grep -rnE "Rahu['\"]?\s*:\s*18" --include=*.py scripts mcp_server.py jyotish_vedic
wc -l scripts/jyotish_api_server.py
python -m pytest tests -q -p no:cacheprovider > /tmp/baseline-full.txt 2>&1; tail -3 /tmp/baseline-full.txt

对照脚本要点(写作时用过,可照抄):swisseph 设 SIDM_LAHIRI,julday(1990,1,1,12-5.5) 取月亮 / 太阳 / 平均罗睺 / Placidus 上升;独立模块入参加 standard_table_profile="pl9_reports_all_dasha_tables_v1"(与报告一致,强制出表)、navamsa_ascendant_sign_index、tenth_lord_sign_index;PyJHora 用 drik.Place("f",28.6139,77.2090,5.5)、get_dhasa_bhukthi(dob, tob, place, dhasa_level_index=1)。

10. BUG 编号起点

BUG-1341(写作时最大号 BUG-1317)。开工核对,被占用则顺延并说明。

11. 交付物清单

  • 代码:新 scripts/dasha_registry.py、新 scripts/dasha_common.py;scripts/jyotish_api_server.py(_compute_dasha_system、_compute_chart_sync 一行、删 _fallback_dasha_periods);6 个条件大运模块;第 2.5 节非冻结年数表所在文件;删 scripts/extended_dashas.py、scripts/conditional_dashas.py;(可能)scripts/jyotish_engine.py 抽共享入参函数
  • 测试:tests/test_dasha_api_engine_parity.py、去重等价测试、年数表计数测试;tests/run_all.py、tests/test_api_server_growth_contract.py 三栏改动
  • 文档:docs/tasks/PROGRESS-dasha-unify-20261013.md、docs/BUG_HISTORY.md(BUG-1341,更新 BUG-1284)、CHANGELOG.md
  • 不改:frontend/**、frontend/DESIGN.md、冻结身份文件、Narayana 三个文件

后续阶段(本单不做)

  1. available_dashas 裁剪到真实可算的体系(改 /api/chart 合同与 golden,需拍板)。
  2. 冻结身份内 dasha_analyzer.py / divisional_charts_extended.py 的年数表,在下次因其他理由重新冻结时一起收。
  3. Narayana 旧算法 vs Rath 版(BUG-1215 / 1218,等产品看 T5 试算)。
  4. dasha_calculator.py、dasha_calculator_enhanced.py、nakshatra_dasha.calc_ashtottari_dasha 等其余重复实现的归并。