Files
Jyotisha/docs/tasks/TASK-report-density-20260922.md
T
jesse-uxandClaude Code a12f2c0d26
Independent Staging Quality Gate / validate (push) Failing after 11m4s
Independent Staging Quality Gate / publish (push) Skipped
fix(report): make density facts readable and printable
Unify reader cleanup rules, lock writer table guards, and register the exact fictional timestamp collision. Preserve existing ordinary-report safety contracts and source-data gaps.

Validation: report Node 165/165, final safety 29/29, Python 101/101, Chrome 28/28; both PDFs retain all 130 rows. Full Node 3704 tests with the same 91 baseline failures. Privacy test: 62 passed, 1 failed due to 17 protected-file READ_ERRORs; not a green gate. Build, DB, manual checklist and controlled-login gaps remain documented. User explicitly authorized staging push with these gaps disclosed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 15:26:25 +08:00

199 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK-report-density-20260922 · 个人报告信息密度与原始附录
## 基线 commit
- 基线:`origin/staging` = `f77a1b5a`(2026-09-22 拉取)
- 开发分支:`codex/report-density-20260922`,worktree `.worktrees/report-density-20260922`
- 对照物:用户在本机提供的一份对照报告 Markdown(不入仓,路径与文件名含个人信息,不在此记录),由**源头 skill 仓** `/workspace/yinduzhanxing` 的 `scripts/pl9_bilingual_batch_export.py` → `jyotish_engine.py` 生成。它对标商业软件 Parashara's Light 9 的 204 页报告。
## 执行状态(2026-09-23)
原实现 `bbd96d3b` 已提交并进入 staging,但原验收未通过;当前从 `0d37bec15` 在独立工作树执行 [验收修复单](TASK-report-density-fix-20260923.md)。实施与验证见 [进度记录](PROGRESS-report-density-20260922.md)。F3 新建真人清单被工具拒绝、文件仍缺,不能记为文档齐备;无受控浏览器或本轮部署通过结论。
范围更正:任务 3 的 Sade Sati 源数据只有阶段框架和当前状态,没有三轮起止日期;原“已逐项确认存在”的日期部分有误,新增日期计算按修复单明确列为范围外。下文保留原事故与规格历史,不作为该能力已完成的证据。
## 事故实证
### 一、对照物与我方同一组出生资料实测
用对照报告的同一组出生资料(真实个人资料,AGENTS.md §8 不得入仓;Raman / mean / target-year 2026)分别跑对照物与我方 `render_pl9_markdown`:
| 指标 | 对照报告 | 我方 staging 实测 |
|---|---:|---:|
| 字符 | 847,632 | 309,357 |
| 行数 | 13,287 | 3,559 |
| 表格行 | 8,044 | 1,478 |
| 含日期的表格行 | ~6,000 | 227 |
| 分盘数值表 | 22 张(D1–D60) | 20 张(带中文盘名) |
我方**已有**且质量不低于对照物的:20 张分盘全表、KP 四表(Lord/Sub、显著星 ABCD、Ruling Planets、Cuspal Promise)、Ashtakavarga 四类(BAV / SAV / Sodhita / Prastara / Pinda)、Shadbala 六分量 + Declination 中间量、Bhava Bala、Avastha、Upagraha + 特殊上升点 + 敏感派生点、Sahams、Yoga/Dosha、Vimshottari 主大运表、Yogini 周期表、Kala Chakra、Narayana、Chara、年度 Mudda / Patyayini / Saham / Monthly Windows / 三年流月星历。
我方**缺**的:Vimshottari AD / PD 子行(对照物约 4,500 行)、10 个替代大运族周期表(Sthira / Drig / Shoola / Tribhagi / Tribhagi-40 / Satabdika / Shattrimshatsama / Niryaana / Navamsha / Lagna-Kendradi)、Sade Sati 三轮表、Bhavesh 十二宫主逐宫段、逐 MD/AD/PD 解释长文。
### 二、产品页报告的契约上限(层 1 的真正瓶颈)
`frontend/src/lib/personal-report-contract.ts`:
- `CHART_IDS`(符号定位第 48 行)只有 9 种:`D1 D2 D6 D8 D9 D10 D11 D24 D30`
- `reportDocumentV2Schema.charts`(符号定位第 286 行)`.max(CHART_IDS.length)`,即上限 9 张
- `evidenceAppendixSchema.techniqueAudit` 第 170 行 `.max(100)`;`calculationEvidence` 第 172 行 `.max(100)`
- `REPORT_SECTION_KINDS`(`personal-report-plan.ts` 第 7 行)十种,**没有任何表格类型**
结论:上面第一节列出的"我方已有"的几十张表,在产品页报告里**没有可以落脚的字段**。它们只存在于引擎导出,不在用户打开的那份报告里。
### 三、原始附录的工程词泄漏
`scripts/professional_report_reference.py` → `build_professional_report_reference_packet` → `render_pl9_markdown` 的输出(312,050 字符)实测:
| 泄漏项 | 出现次数 |
|---|---:|
| 不同的 snake_case 标识符 | 925 种 |
| snake_case 总出现 | 3,797 处(占 3,559 行中的 1,408 行) |
| `parameter_sensitive` | 697 |
| `pyjhora_behavior_only` | 321 |
| `not_multiengine_parity` | 318 |
| `unresolved_external_tuple_boundary` | 192 |
| `cmd_full_reading`(内部函数名) | 68 |
| `PyJHora` / `JHora` | 35 |
| `PL9`(含"第 83-88 页"式页码) | 59 |
对照物同项扫描为 0。源头仓 `docs/research/pl9_reader_main_information_coverage_audit_2026_09_09.md` 记录他们已完成同类清理,用户版不出现 `blocked`、`parameter_sensitive`、`PyJHora`、`producer`、`schema`、`source_path`。
## 根因
1. **层 1**:报告契约是"结论 + 证据 id"的叙述式文档,`charts` 之外没有事实表字段。引擎算出的表无处可放,不是算不出来。
2. **层 2**:`render_pl9_markdown` 同时服务内部审计与对外附录两种用途,状态词、内部 producer 名与第三方产品页码是审计需要的字面量,直接外流。
3. **层 3(新发现的冲突)**:BUG-999(2026-09-22,同日)已建立 `frontend/src/lib/report-public-projection.ts`,按 allowlist 把普通报告投影成纯正文。其 `INTERNAL_KEY_TOKEN`(第 124 行)显式包含 `parameter_sensitive` **与中文「参数敏感」**,命中即整行剔除;`personal-report-longform-download.ts` 第 16 行已改为读 `GET /api/reports/:id` 并投影。该记录的防复发条款写明"专业参考不得再被普通下载当作 fallback"。
## 决策记录
产品负责人 2026-09-22 就三个问题拍板:
1. **层 1 表格清单按本文件第 1 项执行**(8 组表 + 分盘扩容)。
2. **层 2 原始附录允许直接给 C 端用户**,先全部显示,不分研究/付费档。
3. **B 类(引擎算得出但被自身标 blocked 的大运族)要显示出来**。
**本决策推翻 BUG-999 的两条红线,执行方按本节执行,不得以 BUG-999 为由拒改:**
- 推翻"专业参考不得再被普通下载当作 fallback":原始附录成为 C 端可下载产物,但必须是**独立入口**,不得替换 BUG-999 建立的普通正文投影。普通报告正文仍然只走 `projectOrdinaryReportDocument`。
- 推翻"`parameter_sensitive` / 参数敏感 命中即整行剔除"**仅限原始附录这一条通道**。普通正文的剔除规则不变。原始附录改为把状态词翻译成用户语言后保留,而不是删除整行——删除会让整张表少掉状态列,比留着更难读。
未授权、不得顺手做的:不调整任何 `assertionLevel` / `claimStatus` 的升级规则;不把附录里的替代大运族日期写进正文结论;不改 BUG-999 的 XSS / 危险 URL / `jyotish-chart` 围栏合同。
## 硬红线
1. 表格层是**服务端确定性装配**,不是 Skill writer 生成。`skills/jyotish-personal-report/SKILL.md` 的"不得计算星盘"约束不变:writer 不得撰写、改写、补全任何表格单元格。
2. 对照物的数字不得照搬。源头自己的台账 `docs/research/pl9_complete_replication_gap_matrix_2026_09_15.md` 列 5 项 open accuracy gap(3 项 P0:p33 declination/Kranti/KP cusp、p43-44 Shadbala 六分量、p61-120 Dasha 日期),`pl9_residual_loop_status_2026_09_18.md` 记 p109–p120 尚有 506 行 date mismatch、`production_tuning_allowed=false`、`formal_dasha_case_count=0`。替代大运族与 Shadbala 分量在源头仍未闭环,我方展示时必须保持既有状态标注,不得因为"对照物这么写"而升级。
3. 不得学对照物的双向解释模板("When supported… When afflicted…"两边都写)。
4. 不得动 `scripts/jyotish_api_server.py` 的类方法数与 `JyotishAPIHandler.__new__` 伪造点(AGENTS.md §6,`tests/test_api_server_growth_contract.py` 执行)。附录相关逻辑进 `scripts/` 独立模块。
5. 原始附录里不得出现内部函数名、第三方引擎名、第三方产品页码。
6. 出生资料不进仓:本轮所有实测用 `/tmp` scratchpad,任何真实出生数据不得写进测试、fixture、进度记录或 Bug 历史(AGENTS.md §8)。
## 任务分解
### 任务 1 · 原始附录工程词清洗(BUG-1003)
在 `scripts/` 新增独立模块,把 `render_pl9_markdown` 的对外副本清洗后再返回;内部审计路径与 CLI 保持原样。
替换范围:判断状态词(`parameter_sensitive` → 参数敏感、`pyjhora_behavior_only / not_multiengine_parity` → 仅单一外部参照,未做多引擎核对、`unresolved_external_tuple_boundary` → 外部边界未对齐、`partial_verified` → 部分核验、`raw_appendix_only` → 仅原始附录可见、`missing_in_local` → 本地暂无等)、内部标识符(`cmd_full_reading`)、第三方引擎名(`PyJHora` / `JHora` → 外部参照引擎)、第三方产品页码(`PL9 第 N 页` / `PL9 pN` / `PL9` → 外部参照资料)。
**保留**:原始字段名(`sign_cn`、`degree_in_sign`、`nakshatra_lord` 等)。附录的价值就是可以拿数字去和引擎对,改列名会让它更难核。
`blocked` / `executed` / `available` / `computed` 这类同时出现在英文散文里的词,只在"整个 markdown 单元格就是这个词"时替换,不得全局替换。
**验收标准**:
- 清洗后 `PyJHora`、`JHora`、`PL9`、`cmd_full_reading`、本文件列出的七个判断状态词,在对外副本中出现次数为 0;
- 行数不变(不得整行删除);
- 原始字段名一行不少;
- 英文散文句不被破坏(如 `Raw output is visible; this status alone does not promote it.` 原样);
- 定向测试覆盖上述每一条。
**参考实现已在本 worktree 落地**:`scripts/reader_appendix_language.py` + `tests/test_reader_appendix_language.py`,已接入 `scripts/professional_report_reference.py` 的 markdown 分支,`python3 -m pytest tests/test_reader_appendix_language.py tests/test_professional_report_reference_api.py -q` 为 29 passed。执行方可直接采用或重写,但验收标准不变。
### 任务 2 · 原始附录的 C 端独立入口(BUG-1004)
按决策记录第 2 条,给报告页加**独立**的原始附录下载入口,与 BUG-999 的普通正文导出并列,不替换它。
**验收标准**:
- 普通正文导出行为与 staging 基线逐字一致(`projectOrdinaryReportMarkdown` 的输出不变);
- 原始附录入口产出任务 1 清洗后的全文,表格行数与引擎导出一致(当前实测 1,478 行,不得因投影而减少);
- 入口文案对照 `frontend/docs/VOICE.md`,不写接口路径、状态码、"正在加载";
- `frontend/tests/report-public-projection.test.ts` 既有断言不得弱化;新增断言覆盖"附录通道不经过普通正文投影"与"普通通道仍然剔除内部词"。
### 任务 3 · 报告契约增加事实表层(BUG-1005)
`personal-report-contract.ts` 增加 `factTables`(服务端装配,与 `charts` 同级,不进 Skill writer 的可写范围);`personal-report-plan.ts` 增加对应 section kind 只用于排序与位置;`report-evidence-bundle-v2.ts` 携带表数据;`personal-report-generation.ts` 从 workflow packet 装配。
首批 8 组表(已逐项确认 packet 中存在,来源键见括号):
1. Vimshottari 主大运全表 + 当前主运下的 AD / PD(`timing_and_predictive_systems.dasha.timeline[].antardasha_timeline[].pratyantar_dasha_timeline`)
2. SAV / BAV(`strengths_and_scores.ashtakavarga`)
3. Shadbala 六分量(`strengths_and_scores.shadbala`)
4. 功能吉凶(`strengths_and_scores.functional_benefic_malefic`)
5. Avastha(`advanced_systems.avasthas`)
6. Upagraha + 特殊上升点 + 敏感派生点(`divisional_and_special_charts.upagrahas` / `special_lagnas` / `sensitive_points`)
7. Sade Sati 三轮(`advanced_systems.yogas_doshas.sade_sati`)
8. 当年 Varshaphala + Saham(`timing_and_predictive_systems.annual_tajika_pack`、`advanced_systems.sahams`)
**验收标准**:
- 每张表的每一行都能追溯到 packet 中的真实键,不得手造 fixture(AGENTS.md §7.4,合同测试 fixture 必须来自真实引擎响应);
- Skill writer 的 schema 不包含 `factTables`,writer 输出里出现表格视为 fail closed;
- 每张表带 `claimStatus`,取值沿用既有枚举,不新增、不升级;
- `tsc --noEmit` 0 错、`npm run lint` 0 error;
- 前端测试总数不低于开工时 `origin/staging` 实测。
### 任务 4 · 分盘扩容到 16 张(BUG-1006)
`CHART_IDS` 从 9 种扩到 16 种:`D1 D2 D3 D4 D7 D9 D10 D11 D12 D16 D20 D24 D27 D30 D40 D60`。引擎已全部排得出且带中文盘名(`Vargas I` 段实测 20 张)。`charts` 上限随 `CHART_IDS.length` 自动到 16。
**验收标准**:
- 16 张盘在报告页都能渲染,`VedicChartSvg` 不重叠(参照 BUG-616/617 的教训:`float` + 负 margin 曾让 22 张盘全部叠在一起);
- 滚动不触发整篇重建(BUG-617);
- `next build` 后 `/` 仍为 `○ Static`;首屏 gzip 变化在 ±2% 内,超出要在进度记录里给出原因;
- 既有 `REPORT_DOCUMENT_V1_CHART_IDS`(D1/D9/D10)的存量 v1 报告仍可读。
### 任务 5 · B 类大运族对用户可见(BUG-1007)
Yogini / Kala Chakra 的周期表在附录中已经出得来(实测 Yogini 完整周期表、Kala Chakra 可用);Ashtottari 因适用性规则不满足而 blocked,**这是正确行为,不得为了"显示出来"而绕过适用性判定**。Mudda 已返回年度阶段,Patyayini 为 `missing_in_local`。
**验收标准**:
- 附录中每个大运族都出现一行"是否适用 / 为什么不适用"的用户语言说明,不再只给一个状态词;
- 适用性判定本身不得放宽;
- 全部标注"第二时间轴,仅供交叉核对,不单独生成应期"。
## 让步顺序
资源不够时按此顺序砍,不得自行改变:
1. 先交任务 1 + 任务 2(原始附录清洗与入口)。这是用户今天就能拿到的东西,且修的是正在外流的工程词。
2. 再交任务 3 的前 4 组表(Vimshottari 全表、SAV/BAV、Shadbala 六分量、功能吉凶)。
3. 再交任务 4 的分盘扩容。
4. 任务 3 的后 4 组表(Avastha、Upagraha、Sade Sati、年度)与任务 5 最后。
任务 3 与任务 4 都改 `personal-report-contract.ts`,**必须串行**,顺序为 3 → 4。任务 2 与任务 3 都改报告页组件,任务 2 先。
## 开工前置命令
```bash
git fetch origin --prune
git status -sb # 确认分支,主检出常被别的会话切走
git worktree add -b codex/report-density-20260922 .worktrees/report-density-20260922 origin/staging
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
```
预检已知基线失败(与本轮无关,不得算作本轮引入):`tests/test_preflight_fragment_scan.py::test_preflight_fragment_scan_preserves_audit_capability_and_oracle_boundaries`,断言 `4 >= (0 + 8)`,由主检出的未跟踪残留文件导致。
## BUG 编号起点
`docs/BUG_HISTORY.md` 当前最大号 **BUG-1002**(共 832 条)。本任务书编号从 **BUG-1003** 起,开工时需重新核对最大号。
关联记录:**BUG-999**(普通报告导出仍带出内部技法与工作流字段)。本轮任务 1 与 BUG-999 属同一现象族——内部词外流。BUG-999 的解法是普通正文按 allowlist 投影,未覆盖 `/api/professional_report_reference` 的原始附录通道,因此该通道的 3,797 处标识符没有被拦住。任务 1 不是把 BUG-999 推翻,而是补上它没覆盖的第二条通道;决策记录里写明的两处推翻只限附录通道。
## 本任务书据以成立的实测记录与三处更正
本任务书之前的口头分析有三处数字错误,均已更正,执行方以本文件为准:
1. "我方导出 163,320 字符 / 530 表格行 / 0 个日期行" —— 该次是在主检出的旧分支 `codex/unified-loading-20260902`(9-02)上跑的。`origin/staging` 实测为 309,357 字符 / 1,478 表格行 / 227 日期行,Vimshottari 主大运表存在。
2. "全仓没有任何 API 或前端调用 PL9 导出" —— 错。`/api/professional_report_reference` 存在,`frontend/src/lib/personal-report-longform-generate.ts` 第 141 行在调。
3. "产品页报告 charts 上限 6 张" —— 错。`origin/staging` 上是 `.max(CHART_IDS.length)` = 9 张。