4c74cfa825
批次二文档半(道 gate-docs 执行)。先纠三个数的口径账,别再拿任何旧数当准:**234 处是准数** —— 卡片写 52(Lead 用了门禁 Checker 的引用集合,它**刻意排除属主文档内部的定义位**)、道里先报 265(裸正则把 `SDL2-2.32.10` 里的 `L2-2` 当成功能号,误报 10 处)。最终边界口径 = 带左边界 + 跳过反引号代码段。 · 19 只文件 HEAD 共 234 处 → 现 40 处,**40 处 100% 落在变更记录行**(冻结历史,按 Q3 一条没动), 非变更记录残留 **0**;改写 163 行 / 16 只文档。站点最多:规划文档 110→37、相量层单测说明 25→1、 采集与数据处理短板评测 21→0、App/algo/README 26→0。 (`App/algo/README.md` 由上一笔代码提交带上车,路径归属已在该笔正文注明,内容是本批改写) · **定义表首列由编号改成语义标识**(编号规范 §3.3「表首格就是短语」的完全形态):首列原为编号的表 共 **7 张**(不是 5 张),删掉与之重复的「语义名」列;表头标签按域词区分——三张 L3 保护**元件**表 保留 `| 元件 |`,四张 L1/L2 表用 `| 功能名 |`(把「元件」统一洗成功能名会丢掉领域信息)。 七张表逐张核:表头竖线数 == 分隔行 == 首条数据行 == 列数+1;与 HEAD 逐块对拍**净新增不一致块 0**, 并且正是这个对拍抓到第一版「三张表数据行已删列、表头没删」的破表。 · 7 处删号留下的残词/重复解释括号人工收口(如「L1-5(通道标定)」→「通道标定(一次系数 + 零漂)」), 复扫待收口站点 0;措辞逐字取自映射表,无第二种说法。 · tools/arch_diagram 三只生成器 4 处(3 处 docstring + 1 处图形文案)与产物同批:重跑后**只有位图那张 变 2 个 text 节点的换行位置**,画布 1580x740 与总行数不变,另两张逐字节未变;改前先干跑对照证明三张 svg 原本与生成器一致(不在 stale 基线里)——`check_generated_fresh --only docs-svg` PASS。 本笔另含 **F-* 半批的文档面**(同一轮 `git add docs tools/arch_diagram AGENTS.md` 一并收进 ,非误伤):BSD 函数调用关系里 9 处反引号紧邻指针、属主文档 §7 的停发句与导读行(**12 条定义行原样保留、不删登记族**)、 以及 `gen_net_instability_svg.py` 与它的产物 `网络不稳根因总览.svg`(「唯一读者红线」那条的框宽自适应 526 → 551,右缘 1411 未越界,画布 1560x1468 不变)——该半批的**代码注释面**在下一笔。 ⚠ 因 `docs/任务规划/登记簿.md` 与 `docs/任务规划/UI补齐.md` 正被另一张卡(UI 叶数复核)改动,本笔**不含 那两只文件**,批次二的 §9 存量收尾与它们同批提交。
235 lines
11 KiB
Python
235 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""生成 docs/images/采样有效性位图与掩码.svg —— 采样有效性校验出口位图与消费者掩码对照(纯标准库)。
|
||
|
||
为什么不用 Mermaid:本图的核心信息是**位号对齐**(同一 bit 在三张位图与四个消费者掩码
|
||
里落在同一列),必须等宽网格逐列对齐;Mermaid 无法控制列位。
|
||
|
||
用法(仓库根目录,任意 python3):
|
||
python tools/arch_diagram/gen_adc_health_bitmap_svg.py
|
||
输出:
|
||
docs/images/采样有效性位图与掩码.svg
|
||
改内容时:编辑本文件顶部的 GROUPS 数据后重跑。
|
||
"""
|
||
import html
|
||
import os
|
||
import xml.etree.ElementTree as ET
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
OUT = os.path.join(ROOT, "docs", "images", "采样有效性位图与掩码.svg")
|
||
|
||
W = 1580
|
||
PAD = 40
|
||
TITLE_FONT = 21
|
||
SUB_FONT = 13
|
||
ROW_LABEL_FONT = 12.5
|
||
BIT_FONT = 11
|
||
TAG_FONT = 10.5
|
||
NOTE_FONT = 11.5
|
||
HEAD_FONT = 12.5
|
||
|
||
LABEL_X, LABEL_W = PAD, 262
|
||
GRID_X = PAD + LABEL_W + 8
|
||
BIT_W, BIT_H, BIT_GAP = 94, 46, 4
|
||
NOTE_W = 320
|
||
GROUP_GAP = 26
|
||
|
||
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
|
||
INK, MUTED = "#0f172a", "#64748b"
|
||
C_LEVEL = ("#0d9488", "#f0fdfa") # 电平(可自愈)
|
||
C_LATCH = ("#e11d48", "#fff1f2") # 锁存(不自愈)
|
||
C_EVENT = ("#d97706", "#fffbeb") # 仅边沿事件
|
||
C_MASK = ("#7c3aed", "#f5f3ff") # 消费者掩码命中
|
||
C_NONE = ("#94a3b8", "#f8fafc") # 未使用 / 不适用
|
||
|
||
# ----------------------------- 数据(改这里) -----------------------------
|
||
# kind: level / latch / event / mask / none
|
||
GROUPS = [
|
||
dict(head="判定层写出的三张位图(adc_health.c)", rows=[
|
||
dict(label="flags 电平位", sub="adc_health_flags(),当前活跃",
|
||
note="protectTask 每 20 ms 读它做自检失败闭锁;bit8 由本任务自己判老后 OR 入(判定层不写)",
|
||
bits=[("bit0", "TPAFE_STALL", "level"), ("bit1", "STM32_STALL", "level"),
|
||
("bit2", "SEQ_BACKWARD", "latch"), ("bit3", "—", "none"),
|
||
("bit4", "—", "none"), ("bit5", "—", "none"),
|
||
("bit6", "—", "none"), ("bit7", "—", "none"),
|
||
("bit8", "STALE", "level")]),
|
||
dict(label="sticky 锁存位", sub="adc_health_sticky(),直到 init 才清",
|
||
note="只留痕不闭锁:曾停摆/曾出错必须可追溯",
|
||
bits=[("bit0", "TPAFE_STALL", "latch"), ("bit1", "STM32_STALL", "latch"),
|
||
("bit2", "SEQ_BACKWARD", "latch"), ("bit3", "FRSTDATA", "latch"),
|
||
("bit4", "STM32_ERR", "latch"), ("bit5", "HW_ERR", "latch"),
|
||
("bit6", "不入锁存", "none"), ("bit7", "不入锁存", "none"),
|
||
("bit8", "不入锁存", "none")]),
|
||
dict(label="events 事件位", sub="adc_health_check() 出参,仅当拍有效",
|
||
note="adc_health_report() 的唯一输入:按位分三级处置",
|
||
bits=[("bit0", "—", "none"), ("bit1", "—", "none"), ("bit2", "—", "none"),
|
||
("bit3", "FRSTDATA", "event"), ("bit4", "STM32_ERR", "event"),
|
||
("bit5", "HW_ERR", "event"), ("bit6", "TPAFE_GAP", "event"),
|
||
("bit7", "STM32_GAP", "event"), ("bit8", "—", "none")]),
|
||
]),
|
||
dict(head="消费者侧的掩码与记忆位图", rows=[
|
||
dict(label="PROT_HF_LOCK_MASK", sub="protect_task.c 编译期常量",
|
||
note="命中即 prot_elm_init() 复位元件 + 本拍不 eval;SOE 5/6 带位图",
|
||
bits=[("bit0", "闭锁", "mask"), ("bit1", "闭锁", "mask"),
|
||
("bit2", "闭锁", "mask"), ("bit3", "不闭锁", "none"),
|
||
("bit4", "不闭锁", "none"), ("bit5", "不闭锁", "none"),
|
||
("bit6", "—", "none"), ("bit7", "—", "none"),
|
||
("bit8", "闭锁", "mask")]),
|
||
dict(label="serious 严重事件", sub="adc_task.c 的 adc_health_report()",
|
||
note="三类计数增量恒应为 0 → 每次 DBG_ERROR + 自检留痕",
|
||
bits=[("bit0", "—", "none"), ("bit1", "—", "none"), ("bit2", "—", "none"),
|
||
("bit3", "FRSTDATA", "mask"), ("bit4", "STM32_ERR", "mask"),
|
||
("bit5", "HW_ERR", "mask"), ("bit6", "—", "none"),
|
||
("bit7", "—", "none"), ("bit8", "—", "none")]),
|
||
dict(label="gap 丢帧事件", sub="同一函数里的丢帧分支",
|
||
note="刻意不告警:超拍是常态(启动突发期即累计 23 次),只累计",
|
||
bits=[("bit0", "—", "none"), ("bit1", "—", "none"), ("bit2", "—", "none"),
|
||
("bit3", "—", "none"), ("bit4", "—", "none"), ("bit5", "—", "none"),
|
||
("bit6", "TPAFE_GAP", "mask"), ("bit7", "STM32_GAP", "mask"),
|
||
("bit8", "—", "none")]),
|
||
dict(label="s_health_logged", sub="编排层记忆位图(每类一次留痕)",
|
||
note=" |= (serious | rise):用同一位号把 SYSLOG 压成每类每次开机一条",
|
||
bits=[("bit0", "记过", "mask"), ("bit1", "记过", "mask"),
|
||
("bit2", "记过", "mask"), ("bit3", "记过", "mask"),
|
||
("bit4", "记过", "mask"), ("bit5", "记过", "mask"),
|
||
("bit6", "不记", "none"), ("bit7", "不记", "none"),
|
||
("bit8", "不记", "none")]),
|
||
]),
|
||
]
|
||
|
||
FOOT = ("bit3 / bit4 / bit5 在 events 与 sticky 中刻意用同一位号 → 消费者能把事件位直接"
|
||
"套在锁存位图上做每类一次留痕;bit8 = 保护侧兜底门专用(判定层不写它)。"
|
||
"详见 docs/算法/采样有效性校验设计说明.md")
|
||
|
||
KIND_COLOR = dict(level=C_LEVEL, latch=C_LATCH, event=C_EVENT, mask=C_MASK, none=C_NONE)
|
||
|
||
|
||
# ----------------------------- 工具函数 -----------------------------
|
||
def esc(s):
|
||
return html.escape(s, quote=True)
|
||
|
||
|
||
def text_w(s, fs):
|
||
return sum(fs * (1.0 if ord(ch) >= 0x2E80 else 0.58) for ch in s)
|
||
|
||
|
||
WARN = []
|
||
|
||
|
||
def fit(s, fs, avail, where):
|
||
if text_w(s, fs) > avail:
|
||
WARN.append("%s: %.0f > %.0f [%s]" % (where, text_w(s, fs), avail, s))
|
||
|
||
|
||
# ----------------------------- 生成 -----------------------------
|
||
def build():
|
||
# 位数列数由数据推导(曾硬编码 8 列,加 bit8 后备注框把新列盖住了)
|
||
NB = max(len(row["bits"]) for g in GROUPS for row in g["rows"])
|
||
body = []
|
||
addb = body.append
|
||
|
||
for g in GROUPS:
|
||
fit(g["head"], HEAD_FONT, W - 2 * PAD, "group head")
|
||
for r in g["rows"]:
|
||
fit(r["label"], ROW_LABEL_FONT, LABEL_W - 16, "row label")
|
||
fit(r["sub"], TAG_FONT, LABEL_W - 16, "row sub")
|
||
for ln in wrap(r["note"], NOTE_FONT, NOTE_W - 24):
|
||
fit(ln, NOTE_FONT, NOTE_W - 24, "row note")
|
||
for b, tag, kind in r["bits"]:
|
||
fit(tag, TAG_FONT, BIT_W - 12, "bit tag " + b)
|
||
fit(FOOT, NOTE_FONT, W - 2 * PAD - 32, "foot")
|
||
for w in WARN:
|
||
print("OVERFLOW WARN " + w)
|
||
|
||
row_h = BIT_H + 10
|
||
y = 178
|
||
|
||
for g in GROUPS:
|
||
addb('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="#334155">%s</text>'
|
||
% (PAD, y, HEAD_FONT, esc(g["head"])))
|
||
y += 22
|
||
for r in g["rows"]:
|
||
cy = y
|
||
addb('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||
% (PAD, cy + 20, ROW_LABEL_FONT, INK, esc(r["label"])))
|
||
addb('<text x="%d" y="%d" font-size="%d" fill="%s">%s</text>'
|
||
% (PAD, cy + 38, TAG_FONT, MUTED, esc(r["sub"])))
|
||
for i, (b, tag, kind) in enumerate(r["bits"]):
|
||
fg, bg = KIND_COLOR[kind]
|
||
x = GRID_X + i * (BIT_W + BIT_GAP)
|
||
addb('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="%s" '
|
||
'stroke="%s" stroke-opacity="%s"/>'
|
||
% (x, cy, BIT_W, BIT_H, bg, fg, "0.55" if kind != "none" else "0.35"))
|
||
addb('<text x="%d" y="%d" font-size="%d" font-weight="700" fill="%s">'
|
||
'%s</text>' % (x + 10, cy + 19, BIT_FONT, fg, esc(b)))
|
||
addb('<text x="%d" y="%d" font-size="%d" fill="#334155">%s</text>'
|
||
% (x + 10, cy + 36, TAG_FONT, esc(tag)))
|
||
nx = GRID_X + NB * (BIT_W + BIT_GAP) + 12
|
||
addb('<rect x="%d" y="%d" width="%d" height="%d" rx="8" fill="#f8fafc" '
|
||
'stroke="#64748b" stroke-opacity="0.25"/>' % (nx, cy, NOTE_W, BIT_H))
|
||
lines = wrap(r["note"], NOTE_FONT, NOTE_W - 24)
|
||
ty = cy + (BIT_H - (len(lines) - 1) * 15 + 4) / 2.0
|
||
for ln in lines:
|
||
addb('<text x="%d" y="%d" font-size="%d" fill="#475569">%s</text>'
|
||
% (nx + 12, ty, NOTE_FONT, esc(ln)))
|
||
ty += 15
|
||
y += row_h
|
||
y += GROUP_GAP
|
||
|
||
# 图例
|
||
lx, ly = GRID_X, 118
|
||
for label, kind in [("电平位(可自愈)", "level"), ("锁存位(不自愈)", "latch"),
|
||
("边沿事件位(仅当拍)", "event"), ("消费者掩码命中 / 记忆位", "mask"),
|
||
("不适用", "none")]:
|
||
fg, bg = KIND_COLOR[kind]
|
||
addb('<rect x="%d" y="%d" width="16" height="16" rx="4" fill="%s" stroke="%s" '
|
||
'stroke-width="1.6"/>' % (lx, ly - 12, bg, fg))
|
||
lx += 22
|
||
addb('<text x="%d" y="%d" font-size="12" fill="#475569">%s</text>'
|
||
% (lx, ly, esc(label)))
|
||
lx += text_w(label, 12) + 30
|
||
|
||
total_h = y + 74
|
||
foot_y = total_h - 58
|
||
body.insert(0, '<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
|
||
head = ['<text x="%d" y="44" font-size="%d" font-weight="700" fill="%s">%s</text>'
|
||
% (PAD, TITLE_FONT, INK, esc("采样有效性校验 · 出口位图与消费者掩码(逐位对齐)")),
|
||
'<text x="%d" y="70" font-size="%d" fill="%s">%s</text>'
|
||
% (PAD, SUB_FONT, MUTED,
|
||
esc("同一 bit 在三张位图与四个掩码里始终落在同一列:位号对齐 = 消费者可以位图代打表"))]
|
||
|
||
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
|
||
'viewBox="0 0 %d %d" font-family="%s">\n%s\n%s\n'
|
||
'<rect x="%d" y="%d" width="%d" height="44" rx="10" fill="#fffbeb" '
|
||
'stroke="#d97706" stroke-opacity="0.45"/>\n'
|
||
'<text x="%d" y="%d" font-size="%d" fill="#7c2d12">%s</text>\n</svg>\n'
|
||
% (W, total_h, W, total_h, FONT, "\n".join(head), "\n".join(body),
|
||
PAD, foot_y, W - 2 * PAD, PAD + 16, foot_y + 27, NOTE_FONT, esc(FOOT)))
|
||
|
||
d = os.path.dirname(OUT)
|
||
if not os.path.isdir(d):
|
||
os.makedirs(d)
|
||
with open(OUT, "w", encoding="utf-8", newline="\n") as fp:
|
||
fp.write(svg)
|
||
ET.fromstring(svg)
|
||
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, total_h, OUT))
|
||
if len(WARN):
|
||
print("!! %d 处文字可能溢出" % len(WARN))
|
||
|
||
|
||
def wrap(s, fs, avail):
|
||
"""按估算宽度做中文换行(无空格语言:逐字累计)。"""
|
||
lines, cur = [], ""
|
||
for ch in s:
|
||
if text_w(cur + ch, fs) > avail and cur:
|
||
lines.append(cur)
|
||
cur = ""
|
||
cur += ch
|
||
if cur:
|
||
lines.append(cur)
|
||
return lines
|
||
|
||
|
||
if __name__ == "__main__":
|
||
build()
|