PPTX 基线回归(compare_pptx)
配合 审计 做「新改动有没有引入新问题」的回归:拿一份基线 PPTX 与一份 候选 PPTX 对比,聚合新增 / 已解决 / 变化的问题,以及形状增删 / 移动 / 缩放 / 文本变化。
- 基线:上次审核通过的产物(或历史版本)。
- 候选:本次改动后的产物。
- 候选相对基线「新增或恶化」的问题才触发门禁;基线里已有的历史问题放行。
CLI
# 回归对比:新增/恶化达到 MID 就退出码 1
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID
# 回归 + HTML 报告
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID \
--format html --out regression.html
--baseline PATH:给出后进入对比模式(缺省--format text)。--fail-on-new HIGH|MID|LOW:只针对候选新增或恶化的问题。达该严重度 → 退出码 1。--baseline必须搭配--fail-on-new(只给--baseline缺--fail-on-new→ 退出码 2);--fail-on-new没有--baseline也是误用 → 退出码 2。- 对比模式退出码与普通审计一致:0=未达门槛 / 1=门槛命中 / 2=参数或输入错 / 3=依赖或解析错。
Python API
from offipy import compare_pptx, AuditConfig
diff = compare_pptx(
"baseline.pptx",
"candidate.pptx",
audit_config=AuditConfig(safe_margin_in=0.2),
)
print(diff.gate_severity()) # Severity.MID / LOW / HIGH / None(无新增/恶化)
for f in diff.added_findings:
print("新增", f.rule_id, f.severity.name)
for f in diff.resolved_findings:
print("已解决", f.rule_id)
print(diff.to_json()) # 完全 JSON 安全
Shape 匹配链
每页按序匹配,前一级命中即匹配(compare.py 的 _match_slide):
- 同页相同
shape_id; name + shape_type;- 归一化文本 hash + 中心距离(≤1.0 英寸取最近);
- 图片内容 sha256(仅 PICTURE 可取得)。
仍未匹配 → 计入 unmatched_baseline / unmatched_candidate(低置信,由调用方决定是否告警)。
Shape ID 不能假设跨所有编辑永久稳定——它只是第一优先匹配键,插入/删除会让后续 shape_id 错位,因此必须靠后面的链逐级兜底。
PptxDiffReport 字段
| 字段 | 含义 |
|---|---|
baseline_path / candidate_path |
双路径 |
baseline_sha256 / candidate_sha256 |
双文件指纹 |
baseline_slide_count / candidate_slide_count |
页数(added_slides / removed_slides 为差值属性) |
baseline_findings / candidate_findings |
双方各自的完整审计结果 |
added_findings |
候选新增的 Finding |
resolved_findings |
基线有、候选已消失的 Finding |
changed_findings |
形状已匹配、同一 Finding 严重度变化(ChangedFinding.worsened 标记上升) |
added_shapes / removed_shapes |
形状增删 |
moved_shapes / resized_shapes / text_changes |
位置 / 尺寸 / 文本变化(带旧新几何,英寸) |
unmatched_baseline / unmatched_candidate |
匹配链兜不住的对象(低置信) |
warnings |
双方解析异常并集 |
门禁语义:
- new_or_worsened → 候选新增或恶化的 Finding 列表(--fail-on-new 只看这些)。
- gate_severity() → 候选新增或恶化的最高严重度;无则 None(门槛不触发)。
# 手工判断:候选是否引入 MID+ 的新增/恶化
if diff.gate_severity() is not None and diff.gate_severity() >= Severity.MID:
raise SystemExit("候选引入 MID+ 新增/恶化问题")
判定细节
- 严重度比较按整数值(
Severity是IntEnum),禁止字符串比较。 changed_findings里worsened=True才算「恶化」,进入new_or_worsened与gate_severity; 严重度下降的 change 不触发门禁(只记录)。- Finding 的匹配键是
(rule_id, primary(slide,shape_id), secondary);主/次形状有任一未匹配 则视为新增。
固定验收集(tests/fixtures/audit/)
baseline.pptx+candidate.pptx由生成脚本产出:候选新增越界bounds.partialMID、 已解决贴边margin.right、移动 / 缩放 / 文本变化、形状增删。tests/test_audit_fixtures.py::test_compare_finds_added_and_resolved断言:added_findings含bounds.partialMID;resolved_findings含margin.right;added_shapes==2/moved_shapes==2/resized_shapes==1/text_changes==1;gate_severity()==MID。
CI 用法
# 只阻断候选新增/恶化的 MID+ 问题(基线已有的历史问题放行)
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID
# 更严:任何新增/恶化即阻断
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new LOW
# 机器可读:把 diff 交给下游
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID --format json
rule_id是稳定机器键;message只给人类看。--format json的max_new_severity即gate_severity()的序列化名("LOW" / "MID" / "HIGH" / null)。