fix(engine): pair double-transit D1+D9 targets by identity, not name digits (BUG-1061)

Every D9 target name contains the "9" of "D9", so the digit comparison never
paired D1 N宫 with D9_N宫 and, for house 9, paired D1 9宫 with any D9 planet
target. Targets now carry structured metadata (layer/kind/house/planet); the
cross-layer loop pairs only the same house or targets that both point at the
D1 house-N lord. Target names and D1/D9/CL layer results are unchanged
(D1/CL golden regenerated byte-identical); 48 samples: cross entries 31 -> 38.
Evidence-card golden regenerated with the capture script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 19:08:07 +08:00
co-authored by Claude Opus 5.5
parent 037cd1aa1d
commit fb543d3849
8 changed files with 370 additions and 17 deletions
+27 -9
View File
@@ -14305,24 +14305,42 @@
- 对报告的影响:报告里「双重过运」一节的 D9 行会变(目标命中与否、D1+D9 跨层条目数);报告摘要句本轮 48 个样本中无一变化。`frontend/tests/fixtures/report-density-fictional-reader.json` 是 09-23 的冻结快照、没有采集脚本、测试不拿它与现引擎比对,本轮未重生成;按现引擎它的「双重触发条目=2」会变成 4。
- 部署注意:API 的 chart 缓存(`scratch/local/api_chart_cache`,15 分钟 TTL,键不含代码版本)在部署后最多 15 分钟内可能仍返回旧的 D9 结果。
- 防复发:接入原生函数时逐个核对目标名与实际参与计算的值是否一致;D9 目标集中在一个可单测的函数里。采集数据卡 golden 用 `JYOTISH_API_CHART_CACHE_TTL_SECONDS=0 PYTHONHASHSEED=0`:chart 缓存按排序后的键存盘,热缓存会改变字典与列表顺序(值不变),09-27 的 golden 正是冷热混合顺序,已写进采集脚本说明。
- 相关记录:BUG-1061(同函数跨层 D1+D9 判定按数字拼接比对,本轮未修)
- 相关记录:BUG-1061(同函数跨层 D1+D9 判定按数字拼接比对,本轮未修;09-27 已另行修复)
- 复发自:无
- 修复版本:`f6fa367f`,随 `7a06aa00` 部署 staging(run 2976)。
## BUG-1061 | 双重过运跨层(D1+D9)判定按目标名里的数字比对,D9 前缀的「9」会误配也会漏配
- 状态:investigating(BUG-1060 审计时发现,本轮不修);产品 2026-09-27 同意修复,排在性别选填单之后
- 首次发现 / 最近更新:2026-09-27 / 2026-09-27
- 影响面:`scripts/jyotish_engine.py` `cmd_double_transit_pac` 「跨层 Double Transit」段;报告与数据卡的 D1+D9 结论。
- 状态:resolved(待部署;本地回归与门禁集通过,分支 `codex/bug-1061-cross-layer-pairing-20260927` 未推送)
- 首次发现 / 最近更新:2026-09-27 / 2026-09-27(BUG-1060 审计时发现;产品 2026-09-27 同意修复)
- 影响面:`scripts/jyotish_engine.py` `cmd_double_transit_pac` 「跨层 Double Transit」段;报告「双重过运」一节(全读固定 `house = 7`)与普通对话数据卡的双重过运结论(`scripts/consultation_native_layers.py` 跑 1–12 宫)。D1 / D9 / CL 各层命中结果与层内 overlap 条目不受影响。
- 现象:跨层配对用 `''.join(c for c in name if c.isdigit())` 比较 D1 与 D9 目标名。D9 目标名都带「D9」,数字串总含「9」:`D9_7宫(...)` → 「97」,永远配不上 D1 的「7宫」(本意的同宫号跨层配对从不发生);而 `house = 9` 时 D1 的「9宫(...)」→「9」会与任何 `{星}_D9(...)` / `D9_{星}(宫主)` 配上。实证:数据卡 golden 里一张公开盘 9 宫有一条 `Jupiter(D1)9宫(Cancer) + Saturn(D9)Mars_D9(Capricorn)`,这里的 `Mars_D9` 是上升主的 D9 星座目标,与 D1 第 9 宫不是同一目标,配上只因两边数字都是「9」。
- 触发条件:任意调用都漏配同宫号;`house = 9` 时误配。
- 根因:按目标名字符串抽数字判断「同一目标」,未排除层前缀。已核对代码与 golden 一条实例;未评估改法对报告结论数量的影响。
- 修复:未修。改法需产品决定(例如按目标类型 + 宫号结构化比对),会改变报告和数据卡的跨层条目。
- 验证:—
- 防复发:跨层配对不要解析展示用的名字。
- 根因:按展示用的目标名字符串抽数字判断「同一目标」,未排除层前缀。修复前实际生效的只有第二条规则(两边目标名都含 D1 第 N 宫主的星名,子串匹配),数字规则只产生过 9 宫误配。
- 修复:新增 `_double_transit_target_meta`(给每个 D1 / D9 目标结构化元数据:`layer`、`kind`、`house`、`planet`;名字与目标表逐字相同,`{星}_D9` 两键重合时同名多条)与 `_double_transit_target_identities`(把元数据映射成身份集合);跨层两段循环改为「D1 与 D9 目标身份有交集才配对」,不再解析名字。目标名(字典键)、D1 / D9 / CL 各层结果、条目格式、摘要文案都不变。
- 配对规则(逐对决定;依据 `references/marriage-timing-comprehensive-techniques.md` KN Rao「木星 / 土星关联事件宫 / 宫主 / 宫主 D9 星座」与 `strict-workflow-router.md` Double Transit 行;跨层条目回答「木星在一层、土星在另一层是否同时激活第 N 宫这件事的同一个征象点」):
| D1 目标(kind) | D9 目标(kind) | 决定 | 理由 |
| --- | --- | --- | --- |
| `N宫`(house) | `D9_N宫`(house) | **配** | 同一宫在两层;原数字规则的本意,修复前从不发生 |
| `N宫` | 任何星体目标 | 不配 | 宫位与星体不是同一目标;修复前 `house = 9` 时误配 |
| `{宫主}(宫主)`(house_lord) | `{宫主}_D9`(lord_d9_sign) | 配(保持) | KN Rao「宫主 / 宫主 D9 星座」,同一颗星 |
| `{宫主}(宫主)` | `D9_{星}(宫主)`(d9_house_lord) | 仅当这颗星就是 D1 第 N 宫主时配(保持) | 同一颗星才是同一征象点;另一颗星担任的 D9 第 N 宫主不在 KN Rao 三点里。试算过「两层第 N 宫主互配」:4 组输入 × 12 宫跨层条目 31 → 59、摘要句变 14 处,属扩大技法口径,未采纳 |
| `{上升主}(LL)` / `{对宫主}(对宫主)`(lagna_lord / opposite_lord) | 指向 D1 第 N 宫主的 D9 目标 | 仅当这颗星就是 D1 第 N 宫主时配(保持) | 与修复前子串规则等价 |
| `{上升主}(LL)` | `{上升主}_D9`(lagna_lord_d9_sign) | 上升主不是第 N 宫主时不配(保持) | 与第 N 宫无关;试算过同星互配(连同上一行的两层宫主互配一起):同一对 `Saturn(LL) + Saturn_D9` 会在 12 个宫的调用里重复出现,跨层条目 31 → 89、摘要句变 24 处,未采纳 |
身份集合只有两种:`('house', N)` 与 `('event_house_lord', N)`(目标的星 = D1 第 N 宫主)。
- 前后对照(3 张公开 AA 盘 + 报告 golden 的虚构输入,各 12 宫,`PYTHONHASHSEED=0`、chart 缓存 TTL 0):跨层条目 31 → 38(奥巴马 10 → 10、伊丽莎白·泰勒 9 → 10、乔布斯 6 → 9、虚构 6 → 9);变化只有两类:新增 8 条 D1 第 N 宫 ↔ D9 第 N 宫(泰勒 6/12 宫、乔布斯 2/6/7 宫、虚构 1/2/8 宫),删除 1 条 9 宫误配(泰勒 9 宫);所有宫主类条目 48/48 不变。`d1`、`d9`、`cl`、层内条目、`stats` 48/48 不变;摘要句 7/48 变化(6 处「❌ 无」→「⚠️ 跨层间接」,泰勒 9 宫反向)。
- 验证:
- 新增 `tests/test_double_transit_cross_layer_pairing.py`(8 条,已进快速门 `CORE_PYTEST_TARGETS`),三张公开 AA 盘真实引擎运行:元数据名与命令目标名逐字一致;身份规则单测;乔布斯 7 宫 `Saturn(D1)7宫(Pisces) + Jupiter(D9)D9_7宫(Cancer)` 出现;泰勒 9 宫两个命中仍在但不再配对;每条跨层条目都是同宫或同为 D1 第 N 宫主;凡同宫两层都命中必成条目(公开盘恰为 5 处);修复前的宫主类配对全部保留。在基线引擎上跑:3 failed / 4 errors(新函数不存在)/ 1 passed(快速门登记)。
- D1 / CL golden `tests/fixtures/double_transit_d1_cl_golden.json` 按其采集命令重生成,逐字节不变。
- 数据卡 golden `frontend/tests/fixtures/consult-evidence-card-golden.json` 用采集脚本(`JYOTISH_API_CHART_CACHE_TTL_SECONDS=0 PYTHONHASHSEED=0`)重生成,两次逐字节相同;与原文件相比只有双重过运子树变化:乔布斯结论 6 → 9(新增 2/6/7 宫同宫条目,`house_7_summary` 由「❌ 无」变「⚠️ 跨层间接」),泰勒 9 → 10(删 9 宫误配、加 6/12 宫),奥巴马不变;family / annual / timing 三路相同。
- 对报告 / 数据卡的影响:报告只跑 7 宫,所以只会多出「D1 7 宫 + D9 7 宫」这一类条目(公开盘中乔布斯在参考日命中),9 宫误配从未进报告;报告 golden 虚构输入 7 宫条目数不变(4)。`frontend/tests/fixtures/report-density-fictional-reader.json` 仍是 09-23 冻结快照,未重生成。数据卡按 12 宫汇总,结论条数与「有双重过运的宫」会按上面的对照变化(上限 24 条未触及)。
- 部署注意:chart 缓存(15 分钟 TTL,键不含代码版本)部署后最多 15 分钟内可能仍返回旧跨层条目。
- 防复发:跨层配对不解析展示用名字;目标的语义放在结构化元数据里,新增目标必须同时登记 kind,`test_meta_names_are_the_command_target_names` 会拦住名字与元数据不一致。
- 相关记录:BUG-1060
- 复发自:无
- 修复版本:待定
- 修复版本:待提交(分支 `codex/bug-1061-cross-layer-pairing-20260927`)
## BUG-1062 | 服务角色读不到「采用日期」三列:账户保存与本人报告 worker 在自托管 PostgreSQL 上会 42501