Files
Jyotisha/docs/tasks/TASK-report-reference-perf-20261013.md
T
Jesse_ChenandClaude Opus 5.5 2994588ad4 docs(tasks): fold product decisions into audit briefs (BUG-1344..1346)
- dasha-unify: fix Ashtottari birth balance (BUG-1344)
- rect-scoring-perf: server-side candidate cap 241 (BUG-1345)
- node-runtime-resources: enable user-side session cookie cache, 300 s (BUG-1346)
- consult-prep-parallel: classifier/reserve concurrency declined
- answer-effort-ab: model key supplied by product at acceptance
- drop stricter-than-test line-count red lines (AGENTS.md section 6)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-14 00:41:02 +08:00

28 KiB
Raw Blame History

TASK · 长报告引擎出稿提速(中文渲染、哈希、同盘复用)(issue #待定)

  • 日期:2026-10-13
  • 来源:「2026-10-13 全仓屎山与性能排查」P1 条目「长报告提速」(/api/professional_report_reference)
  • 执行方:Grok(Windows + Docker),分支 codex/report-reference-perf-20261013,只推这个分支
  • 验收:Claude(Linux)
  • BUG 编号:BUG-1335~1336(开工时核对 docs/BUG_HISTORY.md 最大号;写作时最大号 BUG-1317。若已被占用,顺延并在 PROGRESS 写明)(另:TASK-upstream-sync7-20261014 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
  • 开工前置:无(第一批立即派发)
  • 主会话补充的串行链(2026-10-14):引擎/API 文件链按此顺序逐单合入后开工(upstream-sync7 也改 jyotish_engine.py / jyotish_api_server.py,若在途,后合一方 rebase):api-disk-cache-retention + report-reference-perf → api-chart-subprocess-gate → pl9-render-split → python-hygiene → dasha-unify → api-server-service-extract。本单的前置以这条链为准(与上一行不一致时以本行为准)。
  • 下游依赖:TASK-pl9-render-split-20261013 必须等本单合入 staging 才开工(两单都改 scripts/jyotish_engine.py)

1. 基线

  • 基线 commit:origin/staging 77057a819
  • 生产调用链(唯一的生产 HTTP 调用方):frontend/src/lib/personal-report-longform-generate.ts::fetchLongformMarkdown 以 { include_fact_tables: true, edition: "full_data", languages: ["zh", "en"], format: "markdown" } 调 /api/professional_report_reference(前端超时 ENGINE_TIMEOUT_MS = 180_000)→ scripts/jyotish_api_server.py 薄注册 → scripts/professional_report_reference.py::build_professional_report_reference:
    1. handler._high_rigor_birth_payload(body) → handler._compute_full_reading_for_thematic(birth)(cmd_full_reading)
    2. jyotish_engine.build_professional_report_reference_packet(建包、sanitize_professional_report_reference、bind_result_to_profile、full_report_quality_gate.evaluate_full_report(delivered, render_pl9_markdown(delivered))、shared_full_report_authority)
    3. _english_edition:copy.deepcopy(packet) → pl9_full_data_export.render_full_data_markdown(英文)
    4. render_full_data_markdown(packet)(中文)
    5. report_density_packet(packet)、render_dasha_applicability(packet)
  • 该路径受 JYOTISH_HEAVY_COMPUTE_CONCURRENCY(默认 2)限流:一份报告占一个重计算名额 15~19 秒。

2. 事故实证(77057a819,Linux 本机,虚构盘 1990-01-01 12:00 新德里 28.6139/77.2090 +5.5,today=2026-10-13)

2.1 端到端(进程内直接调 build_professional_report_reference,请求体同生产)

条件 3 次 中位数
装 PyJHora(开发机) 18.99 / 18.38 / 18.38 s 18.4 s
屏蔽 PyJHora(=生产镜像依赖) 17.89 / 18.59 / 16.94 s 17.9 s
  • 其中 cmd_full_reading 只占 0.8~2.1 s;三次输出 sha256 全同(中文 markdown 03f4d31d…,英文 d52c0f69…,整份响应同)——同输入同输出,但每次都从头算。
  • 响应(服务端 json.dumps(ensure_ascii=False) 口径)2.24 MB:中文 markdown 43 万字符、英文 85 万字符;进程峰值 RSS 约 129 MB。

2.2 分段(屏蔽 PyJHora,进程内单独计时,2 次)

段 耗时
render_full_data_markdown 中文 8.03 / 7.77 s
render_full_data_markdown 英文 1.02 / 0.96 s
build_professional_report_reference_packet(含 sanitize、哈希、质量门) 2.7~3.5 s
hash_bound_result(一次请求共 6 次调用) 合计 1.3~1.7 s;交付包单次 0.575 s
sanitize_professional_report_reference(递归 29 万次) 1.1~1.25 s
_english_edition 里的 copy.deepcopy(packet) 0.15~0.19 s
evaluate_full_report + 其中 render_pl9_markdown 0.4~0.5 s(渲染本身 0.05 s)

cProfile(屏蔽 PyJHora,有探针开销,只看比例):中文侧 _sanitize_pl9_ai_density_markdown 被调 2 次(结构化附录一次、正文一次),每次内部 normalize_zh_markdown_terms 跑 3 遍(pl9_full_data_export.py 中 _sanitize_pl9_ai_density_markdown 内三处调用),整份中文渲染共 6 遍全文术语替换;热点:

热点 次数 说明
pl9_language_terms.py::_thicken_zh_dasha_density 6 遍 × 每行 2 次 re.search 模式以 (.+?) 开头且无锚点,长表格行上逐位置回溯;每行还先拼一次 5 行 lookahead 字符串。约 5.5 万次 search,占中文渲染最大头
pl9_language_terms.py::replace_natural_language_term 327 次 × 每段 1 次 sub 每次 re.compile + 按反引号切分全文 + 逐段替换,绝大多数术语在全文里根本不存在
pl9_reader_export.py::_strip_pl9_user_engineering_markers 5 次 约 190 次整篇 re.sub
_sanitize_pl9_ai_density_markdown 自身 2 次 78 次整篇 re.sub
calculation_profile_contract.py::_drop_key_recursive 300 万次 excluded_paths 里 4 条 **.xxx 各做一次整包递归复制,再 canonicalize_result_payload 第 5 次遍历

2.3 原型(只在 scratchpad 里 monkeypatch,未改仓库)

  • 只加两处「字面量预检」:_thicken_zh_dasha_density 全文没有「主大运中的」「次子运,会在」就跳过逐行正则;replace_natural_language_term 在 raw not in text 时原样返回。端到端 13.65 s → 10.28 / 9.95 s(−26%),中英 markdown 与整份响应逐字节一致。
  • 哈希单遍投影(一次遍历同时去掉 4 个 ** 键并规范化键名,ai_and_audit.summary.elapsed_seconds 仍按原 dotted 路径处理):交付包单次 0.575 s → 0.237 s,result_hash 与旧算法、与包内已有 result_hash 三者相等。

2.4 复现命令

# 屏蔽 PyJHora(模拟生产镜像;子进程也生效)
mkdir -p "$SCRATCH/nojhora/jhora" && echo 'raise ImportError("masked")' > "$SCRATCH/nojhora/jhora/__init__.py"
PYTHONPATH="$SCRATCH/nojhora" python - <<'EOF'
import time, json, hashlib
from scripts.jyotish_engine import cmd_full_reading
from scripts.professional_report_reference import build_professional_report_reference
B = {"year":1990,"month":1,"day":1,"hour":12,"minute":0,"second":0,"lat":28.6139,"lon":77.2090,"tz":5.5,
     "node_mode":"mean","ayanamsa":"lahiri","today":"2026-10-13","transit_date":"2026-10-13",
     "target_year":2026,"age":36,"birth_time_accuracy":"confirmed"}
class H:
    def _high_rigor_birth_payload(self, b): return dict(B)
    def _compute_full_reading_for_thematic(self, b): return cmd_full_reading(type("A", (), dict(b))())
body = {"format":"markdown","edition":"full_data","include_fact_tables":True,"languages":["zh","en"]}
for _ in range(3):
    t = time.perf_counter(); r = build_professional_report_reference(H(), body)
    s = json.dumps(r, ensure_ascii=False, default=str)
    print(round(time.perf_counter()-t, 2), len(s.encode()), hashlib.sha256(s.encode()).hexdigest()[:12])
EOF

(Windows 下 $SCRATCH 用仓库外任意临时目录;跑完删掉工作树里生成的 scratch/。)

3. 根因

  1. 中文擦洗是「整篇文本 × 几百条规则 × 多遍」的累加(BUG-1335):_sanitize_pl9_ai_density_markdown(451 行)与 normalize_zh_markdown_terms 历次补丁都以「再加一条全文替换」的方式追加,从未有性能预算;同一篇正文被同一套规则跑 6 遍,其中 _thicken_zh_dasha_density 的无锚 (.+?) 正则在长表格行上回溯,replace_natural_language_term 对不存在的术语也做编译、切分、替换。不是回归,是从未设计。
  2. 溯源哈希重复遍历(并入 BUG-1335):result_payload_for_binding 对每条 **.key 排除路径各做一次整包递归复制;一次请求哈希 6 次(中间态 1 次、交付包 1 次、质量门重算 1 次等)。BUG-693 / BUG-694 只规定了「覆盖什么、排除什么」,实现没考虑大小。
  3. 同盘同日同版本不复用(BUG-1336):响应对相同输入逐字节确定,但没有任何结果缓存;任务重试(BUG-576 记录过 3 次重试都打到引擎)、同日重生成、英文版重拉都要重算 15~19 s 并占重计算名额。scripts/consult_full_data_cache.py 已有「按键单飞 + 落盘 + 两天清理」(BUG-1304),但只缓存咨询卡用的资料包,不覆盖本接口。

关联:BUG-693、BUG-694(哈希覆盖与排除集红线)、BUG-576(长报告引擎耗时与重试)、BUG-1300 / BUG-1304(重计算名额与单飞缓存)、BUG-610(render_pl9_markdown 围栏失败不得打断报告)。docs/BUG_HISTORY.md 检索 _sanitize_pl9_ai_density_markdown、normalize_zh_markdown_terms、render_full_data_markdown、hash_bound_result 无记录。

3.1 对排查结论的修正(执行方按本节,不按排查报告原文)

排查报告原说法 实测修正
「术语替换合并为预编译大正则或一次扫描」 不做。这些替换是有先后依赖的链(后一条吃前一条的输出,如「强est_第」「e特殊组合ly」两条就是修前一条的产物),合并成交替正则不保证逐字节一致。改为「字面量预检 + 预编译 + 惰性构造」,见 T2
「哈希改投影」 只许做等值投影:哈希载荷与覆盖范围不变、result_hash 数值不变,只把多次遍历合成一次。任何减少覆盖的投影都违反 BUG-693,本单不做
「_english_edition 整包 deepcopy 1.9 s」 实测 0.15~0.19 s(1.9 s 是 cProfile 探针放大),本单不改
「hash_bound_result 4.2 s」 墙钟合计 1.3~1.7 s(同上,探针放大);仍值得做,收益约 0.8~1 s
「响应 1.8 MB、峰值 170 MB」 本机该虚构盘 2.24 MB、峰值 RSS 约 129 MB;以 A/B 实测为准

4. 决策记录

  • 2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行;本单为 P1,必须做 A/B(耗时 + 中英两版输出逐字节一致)。
  • 本单不推翻任何既有红线。BUG-693 / BUG-694 的覆盖范围与排除集(ALLOWED_BINDING_EXCLUDED_TOP_KEYS、ALLOWED_BINDING_EXCLUDED_PATHS 及其字面量合同测试)原样保留。
  • 观察项(本单不做,需拍板):生产 full_data + markdown 路径里,build_professional_report_reference_packet 算出的 report_quality_gate 与 shared_full_report_authority(含一次 render_pl9_markdown)不进入响应(中文/英文 markdown、fact_table_packet、reader_dasha_applicability 都不读它们),约 0.5 s 纯开销。跳过它等于不再在这条路径上跑质量门,属于验证边界变化,必须产品拍板后另开单。

5. 硬红线

  1. 输出逐字节一致:对同一输入,新代码的整份响应(json.dumps(result, ensure_ascii=False, default=str) 字节)、中文 markdown、markdown_en、fact_table_packet、reader_dasha_applicability 必须与基线逐字节相同;format: "json"、edition: "reference" 两条非生产路径同样不得变。差一个字节即未通过,不得以「只是空白」「只是顺序」放行。
  2. result_hash 数值不变:同一包新旧算法得出同一个 result_hash;binding_scope 回执内容不变;full_report_quality_gate 的 provenance:result_binding / provenance:result_binding_scope 结论不变。不得改 ALLOWED_BINDING_EXCLUDED_* 常量或其字面量测试。
  3. 不删、不合并、不重排任何一条替换规则;不改任何文案。只允许:预编译、字面量预检(只对「每个匹配都必然包含」的大小写敏感字面量;带 re.IGNORECASE 或字面量不必然出现的模式不得预检)、惰性构造(例如 lookahead 只在命中时拼)、把重复的纯函数调用结果复用(必须证明输入相同)。每处预检旁写一行注释说明为何等价。
  4. 不减少擦洗遍数,除非能在 PROGRESS 里给出逐条等价论证且 A/B 全语料逐字节一致——默认不做(见让步顺序)。
  5. 缓存只覆盖 edition=full_data + format=markdown 路径;其他组合(含 format=json,其包内有 generated_at 墙钟)一律不缓存。缓存命中返回的响应字节必须与未命中时相同。
  6. 缓存键必须包含影响输出的全部输入:_high_rigor_birth_payload 规范化后的整份 birth(含 today、transit_date、candidate_range 等全部字段)、format、edition、packs、languages、include_fact_tables、REPORT_VERSION_FULL、代码版本(GITHUB_SHA)。GITHUB_SHA 缺失或为 unknown 时缓存默认关闭。文件名只用哈希;缓存文件放 scratch/local/ 下(容器内即 api_scratch 卷),与 consult_full_data_cache 同样两天清理;日志与 BUG 历史不得写入出生资料或缓存键原文。
  7. scripts/jyotish_api_server.py:行数只按 tests/test_api_server_growth_contract.py 的粗护栏(AGENTS.md §6:不另加比测试更严的行数红线,也不得压行凑数),类方法数与 JyotishAPIHandler.__new__ 伪造点不得增长(tests/test_api_server_growth_contract.py);本单预期对该文件零改动(缓存放在 professional_report_reference.py 内)。
  8. 不改重计算名额逻辑(scripts/api_heavy_compute_gate.py)、不改 .gitea/workflows/**、不动表结构与迁移、不升级依赖、不顺手修 warning;frontend/src/app/(app)/page.tsx 不碰(本单无前端改动)。
  9. 改既有断言按「原值 / 新值 / 原因」三栏写进 PROGRESS;测试总数不得低于开工时。
  10. 新测试的输入包必须由真实引擎现算(虚构盘,沿用 tests/test_report_english_edition.py::CASES / _packet),不得手造包形状。
  11. 验收必须在屏蔽 PyJHora 的环境下至少跑一次(生产镜像不装 PyJHora,见 requirements-reference-engines.txt)。

6. 任务分解

T1 · 开关与差分脚手架(先做,后面每步都靠它)

  • 在一个新模块 scripts/report_perf_flags.py 里放两个调用时读取环境变量的函数(不得在 import 时缓存值,否则同进程 A/B 切不了):
    • report_render_fast_enabled() ← JYOTISH_REPORT_RENDER_FAST(T2、T3 的新代码路径)
    • report_reference_cache_enabled() ← JYOTISH_REPORT_REFERENCE_CACHE(T4)
    • 默认值按 T5 的 A/B 结论定(预期 FAST 默认开、CACHE 默认开但 GITHUB_SHA 未知时关);本轮保留旧代码路径作为开关关闭分支,旧分支的退役列入「后续」。
  • 新增 scripts/research/report_reference_perf_ab.py:同一进程内对每个输入先 A(两个开关关)后 B(开),各重复 3 次,输出 JSON:每次总耗时与分段耗时(cmd_full_reading、建包、中文渲染、英文渲染、哈希合计)、tracemalloc 峰值、响应字节数,以及五个输出字段各自的 sha256;A 与 B 任一字段 sha 不同即退出码 1 并打印第一处差异的上下文(不打印整篇)。
    • 输入:tests/test_report_english_edition.py::CASES 的 day / night / south 三张虚构盘 + 本单 §2 的 1990-01-01 新德里虚构盘;请求体同生产。另跑 format=json、edition=reference+markdown 各一张盘只比 sha(这两条有墙钟字段时,用同一个 full_reading 并把 datetime.utcnow 固定后比;做不到就在 PROGRESS 写明只比除 generated_at 外的字节,并说明原因)。
    • 脚本支持 --mask-pyjhora:在本进程装 import 拦截,并为子进程设置 PYTHONPATH 垫片(同 §2.4),保证年运回放子进程也拿不到 jhora。
  • 验收:脚本在基线代码上(两开关都只是空壳)A==B、退出码 0;Grok 自跑。

T2 · 中文擦洗与术语替换的等值提速(BUG-1335 主体)

改 scripts/pl9_language_terms.py、scripts/pl9_full_data_export.py、scripts/pl9_reader_export.py(只动下列函数):

  1. _thicken_zh_dasha_density:两个正则改模块级预编译;每行先做字面量预检(AD 句必含「主大运中的」「子运,会把」「的自然主题带入当前阶段。」;PD 句必含「次子运,会在」「背景下呈现」「的短周期结果。」)再 search;lookahead 只在预检命中后才拼。英文侧 _thicken_en_dasha_density 同法处理(若其模式结构相同)。
  2. replace_natural_language_term:raw not in text 时直接返回原 text(split + join 不改字节,故等价);按 (raw, plural) 缓存编译结果(functools.lru_cache 即可)。
  3. _sanitize_pl9_ai_density_markdown、_strip_pl9_user_engineering_markers、_clean_pl9_user_table_placeholders、normalize_zh_markdown_terms 内的整篇 re.sub:逐条检查,可证明等价的加字面量预检、改预编译;不改顺序、不改模式、不改替换串。按 cProfile 的累计耗时从大到小做,做到中文渲染满足 §T5 目标即可停。
  4. 上述新代码只在 report_render_fast_enabled() 为真时生效;关闭时走原代码(可以是同函数内 if 分支,或保留 _xxx_legacy 原函数)。
  • 不改:任何替换规则的内容与先后、normalize_zh_markdown_terms 的调用次数、英文擦洗的规则。
  • 验收:
    • T1 脚本四张盘 A==B(中英全字段)、退出码 0。
    • 中文渲染(单独计时,屏蔽 PyJHora)中位数较 A 下降 ≥ 40%。
    • python -m pytest tests/test_report_english_edition.py tests/test_report_full_data_fix.py tests/test_report_chart_blank_columns.py tests/test_reader_appendix_language.py tests/test_report_reader_main.py tests/test_full_data_domain_reader.py tests/test_report_longform_parity.py tests/test_report_longform_gaps2.py tests/test_report_density_facts.py -q 全绿。
    • 新测试 tests/test_report_reference_perf.py:对 CASES["day"] 的包,开关开/关两次渲染中英 markdown 逐字节相等;对 replace_natural_language_term、_thicken_zh_dasha_density 各加「命中」「不命中」「反引号段内不替换」三种小样(输入为真实渲染结果中截取的行,不手造新句式)。
    • Grok 自跑 + Claude 验收复跑。

T3 · 溯源哈希单遍化(并入 BUG-1335)

改 scripts/calculation_profile_contract.py::result_payload_for_binding(及其私有辅助):

  • 开关开时:先按顶层排除键过滤,再按非 ** 的 dotted 路径删除,然后一次递归同时完成「删除所有 **.key 指定的键」与 canonicalize_result_payload 的键规范化;hash_bound_result 用这份载荷做与原来完全相同的 json.dumps(..., ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str) + sha256。
  • 删除 ** 键的比较语义与原 _drop_key_recursive 相同(只比字符串键;非字符串键先保留、再按原规则规范化)。
  • 可选(只有 T1 脚本证明 format=json 响应逐字节不变时才做,否则不做并在 PROGRESS 说明):build_professional_report_reference_packet 中第一次 attach_calculation_profile 对中间态的绑定会被末尾 bind_result_to_profile(delivered, profile) 覆盖。注意:跳过它会改变交付包里 result_hash / result_binding 两个键的插入顺序,format=json 的字节会变,所以大概率不能做。
  • 不改:ALLOWED_BINDING_EXCLUDED_TOP_KEYS、ALLOWED_BINDING_EXCLUDED_PATHS、RESULT_BINDING_ENVELOPE_KEYS、binding_scope 回执、质量门的重算逻辑、BUG-693 要求的「绑定发生在返回对象上」。
  • 验收:
    • 新测试(放 tests/test_calculation_profile_contract.py 末尾或新文件):在测试文件里保留一份旧算法的逐字副本作为参照,对真实引擎现算的交付包(CASES["day"])、以及在包里人为插入多层 elapsed_seconds / called_at / 非字符串键的变体,新旧 hash_bound_result 相等,且等于包内 result_hash。
    • tests/test_calculation_profile_contract.py、tests/test_full_report_quality_gate.py 全绿;BUG-694 的字面量锁测试不改。
    • 交付包单次 hash_bound_result 中位数下降 ≥ 50%(原型 0.575 → 0.237 s)。

T4 · 同盘同日同版本复用(BUG-1336)

  • scripts/consult_full_data_cache.py:把 get_or_build 的单飞与落盘泛化——加一个可选参数指定缓存子目录(默认仍是 consult_full_data_cache,咨询卡行为不变),或抽出同文件内的通用函数供两处调用。不改咨询卡的键、两天清理、预热限流(BUG-1304)。
  • scripts/professional_report_reference.py::build_professional_report_reference:仅当 edition == full_data 且 format == markdown 且 report_reference_cache_enabled() 时:
    1. 用 handler._high_rigor_birth_payload(body) 的结果 + §5 第 6 条所列字段算键(json.dumps(sort_keys=True, ensure_ascii=False, default=str) → sha256),在调用 _compute_full_reading_for_thematic 之前查缓存;
    2. 未命中时照旧计算,把整份响应以 json.dumps(result, ensure_ascii=False, default=str) 落盘(目录 scratch/local/report_reference_cache/);
    3. 命中时 json.loads 返回;同键并发只算一次(复用单飞);计算抛错不落盘、不缓存错误。
    4. 只打一行不含资料的日志:[report-reference-cache] hit|miss elapsed_ms=…。
  • 不改:jyotish_api_server.py(零行)、重计算名额(命中仍走名额,持有时间降为毫秒级)。
  • 验收:
    • 新测试:同一请求两次,第二次不调用 _compute_full_reading_for_thematic(用计数桩),两次响应 json.dumps 字节相同;改 today、languages、include_fact_tables、candidate_range、GITHUB_SHA 任一项都不命中;format=json 不落盘;开关关时不落盘;两个线程同键并发只构建 1 次;构建抛错时第二个调用会重新计算。
    • tests/test_consult_card_full_source.py、tests/test_api_heavy_compute_gate.py、tests/test_api_server_growth_contract.py 全绿。
    • 命中路径端到端(同进程、屏蔽 PyJHora)中位数 ≤ 1.5 s。

T5 · A/B 验证与默认值(P1 必做)

  • A = 两开关关(等同 origin/staging 原代码);B = 两开关开。用 T1 脚本,四张虚构盘 × 每组 3 次取中位数,--mask-pyjhora 必跑一轮,另跑一轮不屏蔽作参照。
  • 度量:端到端(未命中 / 命中分开)、分段(中文渲染、英文渲染、建包、哈希合计)、tracemalloc 峰值、响应字节数。
  • 正确性:五个输出字段 + 整份响应逐字节一致(任一不一致即 B 不得默认开启)。
  • 判定门槛:
    • JYOTISH_REPORT_RENDER_FAST 默认开 ⇔ 逐字节全一致 且 未命中端到端中位数降 ≥ 35%(参照原型:仅两处预检已 −26%)且 峰值内存不升 > 10%。
    • JYOTISH_REPORT_REFERENCE_CACHE 默认开 ⇔ 命中与未命中字节一致 且 命中中位数 ≤ 1.5 s;GITHUB_SHA 未知时一律关。
    • 不达标:开关默认关,代码保留,回报写「未达门槛」与差距,不得写通过。
  • 记录写 docs/testing/report-reference-perf-ab-20261013.md:机器(Windows / Linux)、Python 版本、是否屏蔽 PyJHora、每组 3 次原始数、中位数、sha 表。本单 A/B 不需要模型 key。
  • Grok 在 Windows 跑一遍并提交记录;Claude 在 Linux 复跑,两边都要逐字节一致,耗时只比各自机器上的 A/B 相对值。

T6 · 记录

  • docs/BUG_HISTORY.md:
    • BUG-1335 长报告中文渲染慢:同一篇正文跑 6 遍全文术语替换、无锚正则逐行回溯、不存在的术语也编译替换;溯源哈希每条排除路径一次整包复制。
    • BUG-1336 同盘同日同版本的长报告请求不复用,重试与重生成每次重算并占重计算名额。
    • 两条都写关联 BUG-693、BUG-694、BUG-576、BUG-1304;防复发写「T1 A/B 脚本 + 新测试」。
  • docs/tasks/PROGRESS-report-reference-perf-20261013.md:每步改动、A/B 摘要、改既有断言三栏表、未做项与原因。
  • CHANGELOG.md:一句话「个人长报告引擎出稿提速(同盘同日重试秒回)」,不 bump Skill 版本。
  • frontend/DESIGN.md:不改(无界面变化)。

7. 让步顺序(时间不够从后往前砍)

  1. 先保:T1 → T2 的第 1、2 项(原型已证实,最稳的 −26%)→ T5(A/B 记录)。
  2. 再做:T3 哈希单遍。
  3. 再做:T4 缓存。
  4. 最先砍:T2 第 3 项(其余整篇 re.sub 的逐条预检)、T3 的可选项。
  5. 任何一步 A/B 出现字节差异且一轮内查不清:回退该步,保留开关默认关,写进 PROGRESS 与回报,不得为了过 A/B 改测试或改输出。

8. 验收命令分工

谁 命令
Grok 自跑(工作树 .venv) python -m pytest tests/test_report_reference_perf.py tests/test_calculation_profile_contract.py tests/test_full_report_quality_gate.py tests/test_report_english_edition.py tests/test_professional_report_reference_api.py tests/test_report_full_data_fix.py tests/test_report_chart_blank_columns.py tests/test_reader_appendix_language.py tests/test_report_reader_main.py tests/test_full_data_domain_reader.py tests/test_report_longform_parity.py tests/test_report_longform_gaps2.py tests/test_report_density_facts.py tests/test_consult_card_full_source.py tests/test_api_heavy_compute_gate.py tests/test_api_server_growth_contract.py tests/test_repo_privacy_markers.py -q;python scripts/research/report_reference_perf_ab.py --mask-pyjhora(退出码 0)。Python 快速门、前端全量不跑
Claude 验收 上述全部 + --profile quick 快速门 + 全量 pytest 与基线逐名比对(本单不应改任何引擎数值,多出一条失败即未通过)+ 在 Linux 屏蔽 PyJHora 复跑 A/B + 用 §2.4 命令对比基线 sha
不需要 前端 tsc / lint / npm test(无前端改动;若意外改了前端则必须跑);test:db(不动数据库)

9. 开工前置命令

git fetch origin --prune
git worktree add -b codex/report-reference-perf-20261013 .worktrees/report-reference-perf-20261013 origin/staging
cd .worktrees/report-reference-perf-20261013
git log -1 --format=%h                      # 应为 77057a819 或其后代
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3   # 核对最大号(写作时 BUG-1317)
wc -l scripts/jyotish_api_server.py         # 记下基线行数,交付时必须 ≤ 此数
# 开工前先 grep 按源码文本断言的测试(AGENTS.md §7.8):
grep -rn "normalize_zh_markdown_terms\|_thicken_zh_dasha_density\|replace_natural_language_term\|_drop_key_recursive\|result_payload_for_binding\|get_or_build" tests
# 记录基线 sha(四张盘),作为 A 的外部对照:
python scripts/research/report_reference_perf_ab.py --mask-pyjhora --only-a   # T1 完成后

10. 交付物清单

  • 代码:scripts/report_perf_flags.py(新)、scripts/research/report_reference_perf_ab.py(新)、scripts/pl9_language_terms.py、scripts/pl9_full_data_export.py、scripts/pl9_reader_export.py(仅擦洗函数)、scripts/calculation_profile_contract.py、scripts/consult_full_data_cache.py、scripts/professional_report_reference.py;scripts/jyotish_api_server.py 零改动。
  • 测试:tests/test_report_reference_perf.py(新)及 T3 / T4 所述用例。
  • 记录:docs/tasks/PROGRESS-report-reference-perf-20261013.md、docs/testing/report-reference-perf-ab-20261013.md、docs/BUG_HISTORY.md(BUG-1335、BUG-1336)、CHANGELOG.md。
  • 回报:分支 head SHA、A/B 中位数表、五字段 sha 对照、两个开关的默认值与依据、未做项。

11. 后续(本单不做)

  • 下一轮退役开关关闭分支(旧擦洗实现、旧哈希实现),只保留新路径。
  • 观察项(需拍板):full_data 路径跳过无人读取的质量门与 authority(约 0.5 s)。
  • 擦洗函数整体退役由 TASK-pl9-render-split-20261013 的后续阶段处理。