- 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
28 KiB
28 KiB
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/staging77057a819 - 生产调用链(唯一的生产 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:handler._high_rigor_birth_payload(body)→handler._compute_full_reading_for_thematic(birth)(cmd_full_reading)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)_english_edition:copy.deepcopy(packet)→pl9_full_data_export.render_full_data_markdown(英文)render_full_data_markdown(packet)(中文)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全同(中文 markdown03f4d31d…,英文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. 根因
- 中文擦洗是「整篇文本 × 几百条规则 × 多遍」的累加(BUG-1335):
_sanitize_pl9_ai_density_markdown(451 行)与normalize_zh_markdown_terms历次补丁都以「再加一条全文替换」的方式追加,从未有性能预算;同一篇正文被同一套规则跑 6 遍,其中_thicken_zh_dasha_density的无锚(.+?)正则在长表格行上回溯,replace_natural_language_term对不存在的术语也做编译、切分、替换。不是回归,是从未设计。 - 溯源哈希重复遍历(并入 BUG-1335):
result_payload_for_binding对每条**.key排除路径各做一次整包递归复制;一次请求哈希 6 次(中间态 1 次、交付包 1 次、质量门重算 1 次等)。BUG-693 / BUG-694 只规定了「覆盖什么、排除什么」,实现没考虑大小。 - 同盘同日同版本不复用(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. 硬红线
- 输出逐字节一致:对同一输入,新代码的整份响应(
json.dumps(result, ensure_ascii=False, default=str)字节)、中文markdown、markdown_en、fact_table_packet、reader_dasha_applicability必须与基线逐字节相同;format: "json"、edition: "reference"两条非生产路径同样不得变。差一个字节即未通过,不得以「只是空白」「只是顺序」放行。 result_hash数值不变:同一包新旧算法得出同一个result_hash;binding_scope回执内容不变;full_report_quality_gate的provenance:result_binding/provenance:result_binding_scope结论不变。不得改ALLOWED_BINDING_EXCLUDED_*常量或其字面量测试。- 不删、不合并、不重排任何一条替换规则;不改任何文案。只允许:预编译、字面量预检(只对「每个匹配都必然包含」的大小写敏感字面量;带
re.IGNORECASE或字面量不必然出现的模式不得预检)、惰性构造(例如 lookahead 只在命中时拼)、把重复的纯函数调用结果复用(必须证明输入相同)。每处预检旁写一行注释说明为何等价。 - 不减少擦洗遍数,除非能在 PROGRESS 里给出逐条等价论证且 A/B 全语料逐字节一致——默认不做(见让步顺序)。
- 缓存只覆盖
edition=full_data+format=markdown路径;其他组合(含format=json,其包内有generated_at墙钟)一律不缓存。缓存命中返回的响应字节必须与未命中时相同。 - 缓存键必须包含影响输出的全部输入:
_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 历史不得写入出生资料或缓存键原文。 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内)。- 不改重计算名额逻辑(
scripts/api_heavy_compute_gate.py)、不改.gitea/workflows/**、不动表结构与迁移、不升级依赖、不顺手修 warning;frontend/src/app/(app)/page.tsx不碰(本单无前端改动)。 - 改既有断言按「原值 / 新值 / 原因」三栏写进 PROGRESS;测试总数不得低于开工时。
- 新测试的输入包必须由真实引擎现算(虚构盘,沿用
tests/test_report_english_edition.py::CASES/_packet),不得手造包形状。 - 验收必须在屏蔽 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(只动下列函数):
_thicken_zh_dasha_density:两个正则改模块级预编译;每行先做字面量预检(AD 句必含「主大运中的」「子运,会把」「的自然主题带入当前阶段。」;PD 句必含「次子运,会在」「背景下呈现」「的短周期结果。」)再search;lookahead只在预检命中后才拼。英文侧_thicken_en_dasha_density同法处理(若其模式结构相同)。replace_natural_language_term:raw not in text时直接返回原text(split+join不改字节,故等价);按(raw, plural)缓存编译结果(functools.lru_cache即可)。_sanitize_pl9_ai_density_markdown、_strip_pl9_user_engineering_markers、_clean_pl9_user_table_placeholders、normalize_zh_markdown_terms内的整篇re.sub:逐条检查,可证明等价的加字面量预检、改预编译;不改顺序、不改模式、不改替换串。按 cProfile 的累计耗时从大到小做,做到中文渲染满足 §T5 目标即可停。- 上述新代码只在
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()时:- 用
handler._high_rigor_birth_payload(body)的结果 + §5 第 6 条所列字段算键(json.dumps(sort_keys=True, ensure_ascii=False, default=str)→ sha256),在调用_compute_full_reading_for_thematic之前查缓存; - 未命中时照旧计算,把整份响应以
json.dumps(result, ensure_ascii=False, default=str)落盘(目录scratch/local/report_reference_cache/); - 命中时
json.loads返回;同键并发只算一次(复用单飞);计算抛错不落盘、不缓存错误。 - 只打一行不含资料的日志:
[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. 让步顺序(时间不够从后往前砍)
- 先保:T1 → T2 的第 1、2 项(原型已证实,最稳的 −26%)→ T5(A/B 记录)。
- 再做:T3 哈希单遍。
- 再做:T4 缓存。
- 最先砍:T2 第 3 项(其余整篇
re.sub的逐条预检)、T3 的可选项。 - 任何一步 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的后续阶段处理。