# 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 页报告。 ## 事故实证 ### 一、对照物与我方同一组出生资料实测 用对照报告的同一组出生资料(真实个人资料,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 张。