# TASK · 上游同步二修复单(网页婚恋路径拿不到大运 / 围栏失败会打断报告)— 2026-09-09 - 基线:`origin/staging` @ `04ad3325`(`TASK-upstream-sync2-20260909` 实现,已验收;`TASK-report-chart-render-20260909` 实现 `2fdcb14f` 同批在 staging)。 - 分支:`codex/upstream-sync2-fix-20260909`,worktree `.worktrees/upstream-sync2-fix-20260909`。 - 验收结论摘要(`04ad3325`):定向 Python 176 通过;quick 门 708 通过 / 1 失败(`test_shadbala_endpoint_returns_ranked_planet_strength`,验收机缺 `timezonefinder`,环境缺口);真实 `pl9-export` 时间系统表 5 + 10 行正确;api_server 11300 行(上限 11363)。前端(`2fdcb14f`):tsc 0 错、lint 0 error、`npm test` 2991 / 2949 通过 / 28 失败(无 Docker 基线同 28)/ 14 跳过;`next build --webpack` `/` Static;`globals.css` gzip 35290 → 35630(+0.96%)。 ## 1. 事故实证 ### P1 · 网页咨询路径的婚恋三层分类永远没有大运命中 在 `04ad3325` 起本地 API(`scripts/jyotish_api_server.py --port 5299`)对 `/api/consultation_workflow`(`theme=marriage`,虚构出生 1990-06-15 10:30 北京)实测: | 观察 | 值 | | --- | --- | | `thematic_report.themes.marriage.evidence` | `D1-7th-house`、`Spouse-status-yoga`、`DK-UL-Dasha timing`、`Romantic-activation` | | `Romantic-activation.details.hits` | `[]`(只有 `natal_structure: ["7H_Moon"]`) | | `Relationship-formation` / `Legal-marriage` | 不出现 | | `thematic_report.themes.marriage.timing.dasha_period` | `Saturn-Unknown` | | `chart.modules.dasha` 的键 | `current_md / remaining_years / total_years / start_date / periods / birth_balance / …`,**没有** `maha_dasha` / `antar_dasha`,也没有 `vimshottari_analysis`;`current_md` 是出生时大运(Rahu),不是当前 | | `chart.modules.dasha_sub_periods.current` | `{"mahadasha": {"lord": "Saturn", …}, "antardasha": {"lord": "Jupiter", …}}`(正确的当前 MD/AD) | 链路:`_thematic_dasha_info`(L6555)只从 `chart['dasha']` 与 `dasha['vimshottari_analysis']['current']` 取 `maha_dasha` / `antar_dasha`;咨询路径 `full_reading_used=false`、`dasha = full_modules.get('dasha')`(L5425)是 chart 模块的 `dasha`,没有 `vimshottari_analysis`。于是传进 `analyze_relationship` 的 `dasha_info` 只有 `current_md`(出生 MD),`MD_AD_KEYS = ('maha_dasha','mahadasha','antar_dasha','antardasha','bhukti')` 一个都对不上 → `event_class_split.*.hits` 恒空。 **后果**:任务书 §2 的核心目标(按 MD/AD 把心动 / 成对 / 领证分开)在网页聊天路径上是空转,只剩本命结构观察;同时 `report_orchestrator.py` L701 那句「婚姻相关宫位与 karaka 将被激活,是缔结或调整伴侣关系的关键期」仍随 `timing.dasha_period`(`Saturn-Unknown`)无条件输出——这正是 BUG-608 要拆掉的合并式「婚恋机会」,只是换了个文件。 ### P2 · 围栏构造在 try 外,任何一张分盘出 ValueError 会让整份长报告 500 `scripts/jyotish_engine.py` `_render_south_chart`(L4226):`render_south_indian_chart` 在 `try` 内,失败返回 `_图盘生成失败_`;但紧接的 `build_chart_block(...)` 在 `try` 外,`resolve_chart_id` / `_sign_name` 会 `raise ValueError('chart id missing' / 'ascendant sign missing')`。分盘 `Ascendant` 行的 `sign` 不在英文 12 星座内(例如引擎某分盘输出 `sign_idx` 而无 `sign`、或中文星座名)时,`render_pl9_markdown` 整体抛错,`/api/reports//professional-reference` 与 `pl9-export` 一起失败。22 张 golden 都是正常路径,没有覆盖这个分支。 ### P3 · `/api/health` 的 `version` 写死 `6.9.14` `scripts/jyotish_api_server.py` L3402 `'version': '6.9.14'`(L7000 另一处 `6.9.15`),`jyotish_vedic.__version__` 已是 `6.9.16`。健康检查报的 Skill 版本与实际不符(`5d35731f` 引入,非本轮回归,顺带修)。 ## 2. 根因 - P1:`_thematic_dasha_info` 是为 `/api/thematic_report` 走 full reading 时写的,前提是 `dasha` 带 `vimshottari_analysis`;咨询路径 09-06 起改为复用 chart 模块后这个前提就没了,但当时只影响一句 legacy `timing`,没人发现。BUG-608 把 `dasha_info` 接上后,这条断链第一次有了产品后果。 - P2:任务书 §5 任务 1 写「失败路径不加围栏」,执行方理解为「SVG 失败不加」,没把围栏自身的失败纳入。 - P3:版本字符串散落三处,没有从 `jyotish_vedic.__version__` 读。 ## 3. 决策记录 | 决策 | 内容 | | --- | --- | | D1 | `dasha_info` 的当前 MD/AD/PD 一律从 `chart.modules.dasha_sub_periods.current`(`_consultation_dasha_sub_periods` L1432,按请求参考日切出)取;缺失时再回退 `vimshottari_analysis`;两者都缺时**不**从 `dasha.periods` 手算——日期只许来自引擎已切好的边界。 | | D2 | `report_orchestrator.py` 婚恋主题的 `activation_description` 改为按 `event_class_split` 出句:有 `legal_marriage` MD/AD 命中才可写「缔结」;只有 `romantic_activation` 时写「感情被激活的观察期,不等于领证」;没有命中时不出「关键期」句,`timing` 保留日期但 `activation_description` 写「本期无婚恋事件类命中」。`Saturn-Unknown` 这种 AD 缺失时不得拼 `-Unknown`,改用 MD 单独显示。 | | D3 | 围栏失败只丢围栏、保留 SVG;不新增用户可见文案。 | | D4 | 三处版本字符串统一读 `jyotish_vedic.__version__`。 | 不推翻 `TASK-upstream-sync2-20260909` 任何红线。 ## 4. 硬红线 1. `scripts/jyotish_api_server.py` 净增 ≤ 15 行(当前 11300,上限 11363);`_thematic_dasha_info` 改动就地,不新增 handler。 2. `/api/consultation_workflow` 契约只增不改;`thematic_report.themes.marriage.timing` 现有键保留。 3. `relationship_analysis.py` / `relationship_event_class_evidence.py` 不改逻辑;P1 只修喂数。 4. 前端不动。 ## 5. 任务分解 ### 任务 1 · 喂正确的 MD/AD(P1) - `_thematic_dasha_info(chart, dasha)`:先读 `chart.get('modules', {}).get('dasha_sub_periods', {}).get('current')`,映射 `mahadasha.lord → maha_dasha`、`antardasha.lord → antar_dasha`、`pratyantardasha.lord → pratyantar_dasha`(若存在);再按原逻辑回退 `vimshottari_analysis`。 - 新测试 `tests/test_thematic_dasha_info_source.py`:构造 chart(含 `dasha_sub_periods.current` Saturn/Jupiter)与无 `vimshottari_analysis` 的 dasha → 返回 `maha_dasha='Saturn', antar_dasha='Jupiter'`;无 `dasha_sub_periods` 但有 `vimshottari_analysis` → 走回退;两者都无 → 不含 `maha_dasha`。 - 端到端:`tests/test_relationship_event_class_evidence.py` 加一条走 `_derived_marriage_evidence` 的用例,输入来自真实 `/api/consultation_workflow` 响应(把本轮实测的 1990-06-15 虚构盘响应存为 `tests/golden/upstream_sync2/consultation_marriage_1990_fictional.json`,只保留 `chart.modules.dasha / dasha_sub_periods / planets / ascendant` 与 `thematic_report.themes.marriage`),断言 `Romantic-activation` 或 `Relationship-formation` 至少一条 `details.hits` 非空(Saturn MD / Jupiter AD:Jupiter 为该盘 7L 或 DK 之一时命中;若该盘不命中,换一个命中的虚构出生并写明)。 验收:新测试绿;重跑本单 §1 的 curl,`hits` 非空、`timing.dasha_period` 不再含 `Unknown`。 ### 任务 2 · 婚恋 activation 句按事件类出(P1 后半) - `scripts/report_orchestrator.py` L701 附近:婚恋主题 `activation_description` 从 `event_class_split` 派生(见 D2);`dasha_period` 在 AD 缺失时只写 MD。 - 测试:`tests/test_report_orchestrator_marriage_timing.py`(或就近既有文件)三例:仅 romantic → 含「不等于领证」;legal MD/AD 命中 → 可含「缔结」;无命中 → 不含「关键期」「缔结」。 验收:`grep -n "缔结或调整伴侣关系的关键期" scripts/report_orchestrator.py` 只在 legal 分支出现。 ### 任务 3 · 围栏失败不打断报告(P2) - `_render_south_chart`:`build_chart_block` 包 `try/except Exception`,失败返回 `svg`(不带围栏),并 `logging.warning` 一次(不含地名 / 用户串)。 - `tests/test_report_chart_block.py` 加一条:`ascendant_row={'sign': '白羊'}` 时 `render_pl9_markdown` 不抛、Markdown 含 `