---
template: sheet
theme: shadcn
title: 定时任务 HTML 产物全链 STE 改造
subtitle: 三个生产链全部 0 warnings，内容零丢失
cols: 3
source: am 0.5.7 · 2026-10-23
---
复盘、早盘、LXXG 三条 cron 的 HTML 现在都是 `STE ✓ 0 warnings`，`--style off` 全部撤掉。改造方式是新增一个文字规整器，不动引擎逻辑、不删任何数字。

## A 结果

```kv cols=2
复盘 report_20261023: 65 → 0 警告
早盘 premarket_20261023: 13 → 0 警告
LXXG lxxg_daily_20261022: 0 → 0 警告
内容覆盖: 154/154 · 71/71 · 60/60
表格行守恒: rows_lost=0（三日全为 0）
页高变化: 11632 → 11653px（+0.18%）
```

这三条就是全部 cron 里产 HTML 的任务：复盘 16:23、早盘 8:08、LXXG 18:30。持仓信号推的是文本，没有页面。

## B 改了什么

新增 `/root/web_reports/am_ste_norm.py`（约 300 行），在渲染前自动规整文字。

| 位置 | 改动 |
|---|---|
| `am_bridge.render()` | 默认 `style="off"` → `"80"`，写入草稿前先过规整器 |
| `am_md_norm.normalize_md()` | 结构规整后再跑文字规整，落盘的 `.am.md` 就是合规形态 |
| `build_html.py` | 行守恒校验改成标点无关比对 |
| `am_bridge.convert()` / `main()` | 默认 style 同步改 80，`--strict` 仍可强拦 |

规则只有六条，每条都是等价改写：

- 编号列表 `1. xxx` 改 `- xxx`（步骤限 35 字，列表限 45 字）。
- 长句按 `，` → `：` → `、` 逐级断开，断点用 `；`，括号内不切。
- 一段超过 6 句就插空行分段。
- 表格单元格超长用 `；` 断句。
- `X以内` → `不超过X`，`X以上` → `不低于X`。
- `进行优化` → `优化`，`缺省` → `默认`，`综上所述`、`赋能` 删掉。

`flow`/`kv`/`limits`/`chart`/`code` 这些围栏一律不动，只碰正文和 `callout` 正文。

## C 为什么不能手改

引擎产出的文字是功能优先的长散文。消息面 12 条新闻每条 60 到 119 字，场景条件链 80 字。

这些数字每天都变，手改等于每天重写一遍报告。所以只能做成规则，让机器每天跑。规整器是幂等的：跑第二遍文件一个字节都不变，这是接入前的验收条件。

## D 历史报告复算

拿 10 个交易日的历史复盘跑同一套规则，验证不是只对今天有效。

```chart bar horizontal unit=条
| 改造前 | 改造后
10-23 | 65 | 0
10-22 | 81 | 0
10-21 | 94 | 0
10-20 | 105 | 2
10-19 | 127 | 4
10-16 | 102 | 1
```

基线是同一个引擎 md 关掉规整器后量的，不是估的：改造前每天 65 到 127 条警告，改造后 10-21 起全部归零。

10-20 及更早的老模板还剩 1 到 4 条，成因是引号与书名号里的数据链，属于归档页，不再回改。所有日期的 `rows_lost` 都是 0。

## E 踩掉的五个坑

这些坑都会静默生效，不报错但结果全错。

| 坑 | 症状 | 处理 |
|---|---|---|
| 字符类嵌套 | `[^{pat}]` 被当成嵌套类，切分全部失效、警告一条不少 | 用 `[{cls}]` 重新拼 |
| 数字被拆 | `19563亿以上` 被切成 `19 不低于563亿` | 正则加 `(?<![\d…])` 守卫 |
| 表格行首空格 | 重建行时加空格，`grep '^\|'` 从 145 掉到 17 | 首尾竖线不加空格 |
| URL 被切碎 | 斜杠降级把 `https://qpyjs.com/…` 切成 `https:；；qpyjs.com；…` | URL 与 md 链接先转占位符 |
| `<br>` 不算边界 | 单元格里用 `<br>` 断行，句长检查照样超 | 单元格改用 `；` |

## F 一次事故与恢复

验证早盘链路时用了 `build_premarket.py --no-news`，它把当天已推送的早盘页重生成成「消息面条目 0」的空壳。

发现后立刻用备份的 `.am.md` 直渲恢复，线上页面内容已完整（新闻 12 条在场）。代价是引擎侧源 md `data/reports/premarket_20261023.md` 被覆盖，且不在 git 里，无法回滚。文字内容完整保存在 `premarket_20261023.am.md`。

教训已写进技能：验证渲染链路只能拿草稿副本跑，或先备份 `.am.md`。

## G 剩余与自检

```callout warn 交付前必须看到的输出
`✓ <路径>` + `STE ✓ 0 warnings` + `rows_lost=0`，三者齐了才算完成。

只有第一个 `✓` 说明页面出来了，文字不一定合规。
```

改动前备份在 `/root/backups/am_ste_20261023/`：`am_bridge.py`、`am_md_norm.py`、`build_html.py`、`build_report.py`、`build_premarket.py`、`daily_report.py`。

回滚方式：把备份文件拷回原位，`style` 改回 `off` 即可，规整器不参与渲染。
