From b3ef2d8158f32ecc63a5bf6ac9ce34eceb3c3ce0 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sun, 18 Oct 2026 18:08:12 +0800 Subject: [PATCH] docs(tasks): marriage match p1 fix brief (issue #79) Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3 --- .../TASK-marriage-match-p1-fix-20261018.md | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 docs/tasks/TASK-marriage-match-p1-fix-20261018.md diff --git a/docs/tasks/TASK-marriage-match-p1-fix-20261018.md b/docs/tasks/TASK-marriage-match-p1-fix-20261018.md new file mode 100644 index 00000000..39908385 --- /dev/null +++ b/docs/tasks/TASK-marriage-match-p1-fix-20261018.md @@ -0,0 +1,57 @@ +# TASK · 婚恋合盘第一期 · 修复单(issue #79,第 1 次打回) + +- 日期:2026-10-18 +- 被验提交:`cf063834ca9f9f62dc8559913dddb5ccb8578999`(分支 `codex/marriage-match-p1-20261017`) +- 原任务书:`docs/tasks/TASK-marriage-match-p1-20261017.md` 第六版(下称「原单」) +- 执行方:Grok,同一分支追加提交;`origin/staging` 前进时用 `git merge origin/staging`,不 rebase(合并时 `CHANGELOG.md`、`docs/tasks/README.md` 会有纯文档冲突,按两边都保留处理) +- BUG 编号:沿用 BUG-1370~1374,不新开号 + +## 验收结论(Claude,Linux) + +引擎与上游搬运部分**通过**;存储层(T4/T5)与原单不符,打回。 + +| 项 | 结论 | 证据 | +| --- | --- | --- | +| T1 上游原样搬入 | 通过 | `scripts/marriage_match.py` 与 `docs/research/upstream_marriage_match_bde5d615/marriage_match.py.txt` 逐字相同;`skills/jyotish-full-synastry-reading/` 全部文件与副本(去 `.txt`)逐字相同;上游测试只改了调用那一处并写了三栏 | +| T1 生产依赖 | 通过 | 屏蔽 `jhora` 后两位公开名人的证据包 `delivery_readiness = ready_for_narrative`,截取结果与 `frontend/tests/fixtures/synastry-full-dossier-truncated.json` 相同 | +| T2 接口 | 通过 | 主文件只加 14 行薄注册;证据包接口进了 `HEAVY_COMPUTE_PATHS`,按两人出生资料 + 提交号做磁盘缓存 | +| T3 退役 | 通过 | `scripts/synastry.py` 已删;CLI 改为 `marriage-match`;`calc_kuja_dosha` / `match_kuja_dosha` 标为旧版 | +| **T4/T5 存储** | **未通过** | 见 F1、F2 | +| 全量 pytest / 前端 | 见 issue 评论里的数字 | — | + +## F1 · 存了整份证据包,而且存了两份(必做) + +`frontend/src/lib/marriage-match-service.ts::readResolvedPair` 把引擎返回的整个包(`rules_version` + `dossier` + `raman_marriage_match`)同时写进 `report` 和 `structured_result` 两列。实测(爱因斯坦 × 乔布斯,公开盘):整包约 **490 KB**,其中两个 profile 的 `marriage_match` 合计只有约 **19 KB**。 + +问题: + +1. **与原单不符。** 原单 T5:「存结构结果(**两个 profile 的 `marriage_match`**)并返回证据包」;T4:加列存「结构化结果」。证据包是写作素材,不是要长期存的结构结果。 +2. **会过期。** 证据包里有 `parallel_timing_context` 等随「今天」变化的内容,而命中条件只有「两人出生资料指纹 + 规则版本」,不含日期。整包存下来,以后每次命中都会读到建档那天的时间背景。`marriage_match` 本身只依赖两张盘,没有这个问题。 +3. **一行近 1 MB。** 旧的合盘历史接口 `app/api/synastry-reports/route.ts` 的 GET 会 `select("id, partner_name, report, created_at")` 取最近 10 行;新行的 `report` 是整包,开了 `compatibility` 后一次就可能拉近 10 MB,而且形状不是旧卡片能读的。 + +修法: + +- `structured_result` 只存 `{ rules_version, default: , raman: }`(键名可定,写进进度)。 +- `report` 列(旧列,`not null` 且必须是对象)**不放证据包**:放同一份结构结果,或一个只含 `rules_version` 的小对象,二选一写进进度。 +- 函数返回值区分两种:现算时返回 `{ cached: false, structured, dossier }`;命中时返回 `{ cached: true, structured }`,**不返回旧证据包**。后续单 #82 需要证据包时自己调证据包接口(Python 侧有磁盘缓存)。在原单「后续单」一节或进度里写明这个约定。 +- 旧历史接口 GET 加过滤 `rules_version is null`,只列旧行;新行不进旧列表。 + +验收: + +- 单测:存进去的 `structured_result` 不含 `dossier` / `chart_snapshots` 键;大小断言(如 < 64 KB)用 golden 结构结果。 +- 单测:命中时不调引擎,返回值不含证据包。 +- 旧历史接口有一条测试证明新行不出现在 GET 结果里(可用源码文本断言或现有 route 测试模式,按 AGENTS §7.8 不弱化旧断言)。 + +## F2 · 并发同一对人时第二个写入报「存储失败」(必做,小) + +唯一索引 `synastry_reports_pair_fingerprint_uidx` 会让同时发起的第二次插入失败,现在直接抛 `store_failed`。改为:插入遇到唯一冲突(PostgreSQL `23505`)时重读一次已保存行并返回;重读仍为空才报 `store_failed`。加一条单测。 + +## 不要做 + +- 不改 `scripts/marriage_match.py`、`skills/jyotish-full-synastry-reading/**`(上游原样)。 +- 不做页面、不扣点(原单第六版)。 +- 迁移文件可以在同一份里改(还没上过 staging),不要另开第二份迁移去补救本单自己的列。 + +## 回报 + +issue #79 回报新 SHA,写明:结构结果的键名与大小、`report` 列选了哪种、旧历史接口的过滤和测试、`npm run test:db` 中 `database-marriage-match.test.ts` 的结果(Windows 已知失败名单逐条对照)。