PPTX Baseline Regression (compare_pptx)
Pairs with audit to answer "did the new change introduce new problems": compare a baseline PPTX against a candidate PPTX, aggregating added / resolved / changed findings plus shape add / remove / move / resize / text changes.
- Baseline: the previously approved artifact (or a historical version).
- Candidate: the artifact after this change.
- Only problems the candidate adds or worsens relative to the baseline trigger the gate; pre-existing baseline issues pass.
CLI
# regression: exit code 1 when added/worsened reaches MID
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID
# regression + HTML report
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID \
--format html --out regression.html
--baseline PATH: entering compare mode when given (default--format text).--fail-on-new HIGH|MID|LOW: only for candidate added/worsened issues. Reaching that severity → exit code 1.--baselinerequires--fail-on-new(--baselinealone without--fail-on-new→ exit code 2);--fail-on-newwithout--baselineis also misuse → exit code 2.- Compare-mode exit codes match the normal audit: 0=threshold not reached / 1=gate hit / 2=argument or input error / 3=dependency or parse error.
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 (no added/worsened)
for f in diff.added_findings:
print("added", f.rule_id, f.severity.name)
for f in diff.resolved_findings:
print("resolved", f.rule_id)
print(diff.to_json()) # fully JSON-safe
Shape matching chain
Per page, in order, the first matching level claims the pair (_match_slide in compare.py):
- same
shape_idon the same page; name + shape_type;- normalized-text hash + center distance (≤1.0 inch, nearest);
- image content sha256 (only available for PICTURE).
Anything still unmatched lands in unmatched_baseline / unmatched_candidate
(low confidence; the caller decides whether to warn).
shape_id must not be assumed permanently stable across edits — it is only the first-priority match key; inserting/deleting shifts later shape_ids, so the later chain levels must back it up.
PptxDiffReport fields
| field | meaning |
|---|---|
baseline_path / candidate_path |
both paths |
baseline_sha256 / candidate_sha256 |
both file fingerprints |
baseline_slide_count / candidate_slide_count |
slide counts (added_slides / removed_slides are the delta properties) |
baseline_findings / candidate_findings |
each side's full audit results |
added_findings |
findings added by the candidate |
resolved_findings |
findings present in the baseline, gone in the candidate |
changed_findings |
same finding on a matched shape whose severity changed (ChangedFinding.worsened marks an increase) |
added_shapes / removed_shapes |
shape add/remove |
moved_shapes / resized_shapes / text_changes |
position / size / text changes (with old & new geometry, inches) |
unmatched_baseline / unmatched_candidate |
objects the matching chain could not claim (low confidence) |
warnings |
union of both sides' parse warnings |
Gate semantics:
- new_or_worsened → the list of candidate added-or-worsened findings (--fail-on-new only looks at these).
- gate_severity() → the highest severity among candidate added-or-worsened findings;
None if none (gate does not trigger).
# manual check: did the candidate introduce MID+ added/worsened issues?
if diff.gate_severity() is not None and diff.gate_severity() >= Severity.MID:
raise SystemExit("candidate introduced MID+ added/worsened issues")
Details
- Severity comparisons use integer values (
Severityis anIntEnum); string comparison is forbidden. - In
changed_findings, onlyworsened=Truecounts as a regression and entersnew_or_worsened/gate_severity; a downgrade change does not trigger the gate (recorded only). - A finding's match key is
(rule_id, primary(slide,shape_id), secondary); if either the primary or secondary shape fails to match, it is treated as added.
Fixed verification corpus (tests/fixtures/audit/)
baseline.pptx+candidate.pptxproduced by the generator: the candidate adds an out-of-boundsbounds.partialMID, resolves an edge-adjacentmargin.right, and includes move / resize / text changes plus shape add/remove.tests/test_audit_fixtures.py::test_compare_finds_added_and_resolvedasserts:added_findingscontainsbounds.partialMID;resolved_findingscontainsmargin.right;added_shapes==2/moved_shapes==2/resized_shapes==1/text_changes==1;gate_severity()==MID.
CI usage
# only block candidate added/worsened MID+ issues (pre-existing baseline issues pass)
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID
# stricter: block any added/worsened
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new LOW
# machine-readable: hand the diff to downstream
offipy audit candidate.pptx --baseline baseline.pptx --fail-on-new MID --format json
rule_idis the stable machine key;messageis for humans only. In--format json,max_new_severityis the serializedgate_severity()("LOW" / "MID" / "HIGH" / null).