feat(report): birth-sky report cover bound to the report's birth fingerprint (F1–F3)
Product decision A (TASK-birth-sky-followup-20260928): - every generation path writes subject.birthFingerprint (stored-row fingerprint, same function as the chart page's profileFingerprint); never into the evidence bundle the model reads - GET /api/reports/:id compares on the server and returns only report.coverSubject (subject id or null); legacy reports never get one - report page shows the night cover above the body, paper cover alone on the first printed A4 page; silent on any failure, no waiting state - TS / JSON schema / Python contract accept the optional field - BLOCKED T5 struck through; DESIGN §17, CHANGELOG, checklist, PROGRESS Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
14ef18ac24
commit
a9a917d50c
@@ -109,3 +109,38 @@ JS 的 +773 B 是首页为 T6 引入的 `useBirthSkyReveal`、`startBirthSkyReve
|
||||
- 无 `.venv`:Python 用系统 `python3`。
|
||||
- 无头 Chrome 的 `--screenshot` 在本机对这页时好时坏(挂起),海报改用 `canvas.toDataURL` + `--dump-dom` 取出,是导出 PNG 的真实像素。
|
||||
- 未推送、未部署。
|
||||
|
||||
---
|
||||
|
||||
## 追加单(`TASK-birth-sky-followup-20260928`)
|
||||
|
||||
- 任务书:`docs/tasks/TASK-birth-sky-followup-20260928.md`(staging-docs)。产品授权直接执行(Claude 子代理)。
|
||||
- 基线:开工 `git fetch origin --prune` 后 `origin/staging` 仍是 `d670521f`,无需 rebase;本分支在前三个提交(`d277cbd8`、`028aef27`、`14ef18ac`)之上再加两个提交。
|
||||
- BUG 编号:开工时最大 BUG-1079;新功能,未开 BUG。
|
||||
|
||||
### F1 · 报告写入出生资料指纹
|
||||
|
||||
- 新模块 `frontend/src/lib/report-birth-binding.ts`:`subjectBirthFingerprint(row)` = `resolveServerOwnedChartBirth(row).fingerprint`。
|
||||
- **与星盘页同一种算法、同一种输入**:星盘页的 `profileFingerprint` 来自 `prepareChartViewProfile`,它取的是**存储行**的指纹(`stored.fingerprint`,在内存补全时区之前)。报告两处加载点(`POST /api/reports` 与 worker 的 `loadReportWorkerProfile`)都先 `loadSubjectBirth` 取存储行、再补时区,所以在补全之前用 `stampReportBirthFingerprint(resolved.row, filled)` 把指纹挂在内存行的 `report_birth_fingerprint` 上(只在服务端内存里,不落库、不进引擎输入)。测试证明:缺时区的行,补全前后指纹不同,补全前的等于星盘页。
|
||||
- 三条生成路径都写 `subject.birthFingerprint`:
|
||||
- 路由内联生成(`resolveReportCreate` → `generatePersonalReport`);
|
||||
- worker writer 路径(`generateWriterReport` → `generatePersonalReport`,单次调用与分章两种组装都在 guard 之前写入);
|
||||
- worker 长报告路径(`generatePersonalReportLongform` → `buildLongformCoverDocument`)。
|
||||
- 指纹**不进证据包**(bundle 会被发给模型),只在组装后的文档上写:`generatePersonalReport` 新增可选 `birthFingerprint`,在 `applyReportGuard` 之前写到候选文档上,随后照常走最终 server parse。
|
||||
- Schema:TS `subjectSchema`(v1/v2 共用)、`contracts/personal-report/report-document.v1|v2.schema.json`、Python `scripts/personal_report_contract.py` 都加可选 `birthFingerprint`(`^[0-9a-f]{32}$`)。旧文档不含该字段照常解析;格式不对的拒绝。
|
||||
- 测试:`frontend/tests/report-birth-binding.test.ts`(7 条:同算法 / 补时区前取;worker 加载器写入;三条路径接线;长报告写入与缺省;旧文档解析与坏格式;coverSubject 判定;GET 路由在服务端比对)、`personal-report-api.test.ts` 追加「inline generation writes subject.birthFingerprint」、`personal-report-generation-v2.test.ts` 追加「single-call and sectioned generation write subject.birthFingerprint only when given」(并断言分章调用发给模型的 bundle 不含指纹)、`tests/test_personal_report_contract.py` 追加 1 条。
|
||||
|
||||
### F2 · 服务端比对
|
||||
|
||||
- `reportView(row, job, failure, sections, coverSubject?)`:新增第 5 个可选参数,传入时输出 `coverSubject`。`resolveReportRead` 只在 `ready` 分支计算:读 `row.reportDocument.subject.birthFingerprint` → 没有就直接 `null`(不查库)→ 有则调新依赖 `loadCurrentBirthFingerprint(userId, subjectId)`(`subjectId` = `chart_profile_id ?? "self"`)→ 相等给 subject id,否则 `null`;加载器抛错(不是自己的人物、已删除、读不到)一律 `null`。
|
||||
- `GET /api/reports/:id` 用 `loadSubjectBirth(supabase, userId, subjectId)`(与星盘页同一个人物加载器,按 `user_id` 限定)取存储行再算指纹。
|
||||
- 响应里不含指纹:`reportDocument` 走 `projectOrdinaryReportDocument`,本来就不带 `subject`。
|
||||
- 测试(`personal-report-api.test.ts` 追加 4 条):一致(本人 `self`、家人给人物 id,序列化响应不含指纹、出生日期、经纬度、时区、地名);不一致;缺字段(且不查库);非本人(加载器抛错 / 返回空)。
|
||||
|
||||
### F3 · 报告封面
|
||||
|
||||
- `classifyReportEnvelope` 在 ready 态读 `report.coverSubject`(非空字符串才带上);`PersonalReportPage` 在 `.personal-report-reader-body` 正文最上方渲染 `<ReportSkyCover subjectId=…/>`(`next/dynamic`,`ssr: false`)。
|
||||
- `components/birth-sky/report-sky-cover.tsx` + `report-sky-cover.css`:挂载后(正文已上屏)才取数;`lib/birth-sky/report-cover.ts` `loadReportSkyCover` 取天空并用同一个 `renderBirthSkyPng` 画 `night`(屏幕)与 `paper`(打印)两张;任何失败返回 `null`,组件什么也不渲染。无按钮、无占位、无等待态。
|
||||
- 打印:`.report-sky-cover-screen` 隐藏、`.report-sky-cover-print` 显示;`.report-sky-cover { break-after: page }`,图高 246mm 居中(A4 减报告 `@page` 边距后版心 186 × 270mm,3:4 在 186mm 宽时 248mm 高)。
|
||||
- `BLOCKED.md` 的 T5 条目已划掉并注明「→ 已解除:产品 2026-09-28 选 A」。
|
||||
- 测试:`frontend/tests/report-sky-cover.test.ts`(5 条:coverSubject 有 / 无;封面在正文之上且懒加载、无按钮无等待;night + paper 两张;取数失败 / 空 / 画图失败 / abort 都返回 null;打印 CSS 的 paper 与分页)。
|
||||
|
||||
@@ -27,12 +27,18 @@
|
||||
|
||||
## 报告封面
|
||||
|
||||
本期**未上线**(报告与出生资料没有可靠绑定,见 `BLOCKED.md`)。验收时确认:打开任何报告,正文最上方**没有**天空封面、也没有空白占位;打印 PDF 第一页仍是原来的报告封面。
|
||||
~~本期**未上线**(报告与出生资料没有可靠绑定,见 `BLOCKED.md`)。验收时确认:打开任何报告,正文最上方**没有**天空封面、也没有空白占位;打印 PDF 第一页仍是原来的报告封面。~~ → 追加单(`TASK-birth-sky-followup-20260928`)已上线报告封面,原 11、12 条作废,改按下表。
|
||||
|
||||
| # | 设备 / 浏览器 | 操作 | 期望 | 结果 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 11 | 桌面 Chrome | 打开「我的报告」任一份 | 没有天空封面,没有占位 | |
|
||||
| 12 | 桌面 Chrome | 该报告「打印 → 存为 PDF」 | 第一页与上线前相同 | |
|
||||
| ~~11~~ | ~~桌面 Chrome~~ | ~~打开「我的报告」任一份~~ | ~~没有天空封面,没有占位~~ | 作废 |
|
||||
| ~~12~~ | ~~桌面 Chrome~~ | ~~该报告「打印 → 存为 PDF」~~ | ~~第一页与上线前相同~~ | 作废 |
|
||||
| 18 | 桌面 Chrome | 部署后**新生成**一份报告,打开 | 正文最上方一张深色 3:4 天空封面,宽与正文一致,下面没有按钮;正文先出来,封面随后出现,其间没有占位或转圈 | |
|
||||
| 19 | 桌面 Chrome | 该报告「打印 → 存为 PDF」 | 第一页只有一张浅色纸面版天空封面;正文从第二页开始 | |
|
||||
| 20 | 桌面 Chrome | 打开部署**之前**生成的旧报告 | 没有封面,没有空白占位 | |
|
||||
| 21 | 任一 | 新生成一份报告 → 设置里改自己的出生时间(或出生地)→ 回来打开那份报告 | 没有封面;改完后再新生成的报告有封面 | |
|
||||
| 22 | 桌面 Chrome | 打开有封面的报告,DevTools → Network 看 `/api/reports/<id>` 的响应 | 响应里只有 `coverSubject`(`"self"` 或人物 id),没有 `birthFingerprint`、没有出生日期 / 时刻 / 经纬度 / 地名 | |
|
||||
| 23 | 桌面 Chrome | 打开有封面的报告前,在 Request blocking 加一条 `*api/birth-sky*` | 报告正文照常显示,没有封面,也没有报错 | |
|
||||
|
||||
## 首次建盘揭幕
|
||||
|
||||
|
||||
Reference in New Issue
Block a user