Skip to content

SSA pipeline quality hardening (Phases 1-6)#15

Open
mcxl wants to merge 9 commits into
mainfrom
claude/brave-mestorf-8f2596
Open

SSA pipeline quality hardening (Phases 1-6)#15
mcxl wants to merge 9 commits into
mainfrom
claude/brave-mestorf-8f2596

Conversation

@mcxl

@mcxl mcxl commented May 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds 4 quality guards to the SSA report-docx build path (deterministic post-pass, OOXML schema validation, LibreOffice render smoke, preflight) plus a minimal drift checker for paired constants. Lifted patterns from rpd-ssa-builder and adapted to gatekeeper's input contract.
  • Fixes a latent <w:tblBorders> schema-order bug in _apply_all_cell_borders (pims/services/ssa_pipeline.py:1089) that was emitting 4 schema errors per built docx. Word's recovery prompt tolerated it; the new OpenXmlValidator caught it.
  • Wires --check preflight CLI flag: python -m pims.scripts.run_ssa_pipeline --check <folder>. After successful full / from-state builds the report docx is round-tripped through headless LibreOffice as a smoke test.
  • 145 tests on this branch (was 108 on main). See docs/CHANGELOG-SSA.md for full surface and docs/plans/sdg-ssa-quality-hardening.md for the approved plan.

Test plan

  • python -m pytest tests/test_ssa_pipeline.py tests/test_ssa_determinism.py tests/test_ssa_oxml_validator.py tests/test_ssa_libreoffice_smoke.py tests/test_ssa_preflight.py tests/test_ssa_drift_checker.py tests/test_ssa_run_status.py tests/test_ssa_resume.py — full SSA suite green
  • python tools/check_ssa_drift.py — exits 0 cleanly
  • Manual: python -m pims.scripts.run_ssa_pipeline --check <real audit folder> on a real SDG audit folder
  • Manual: run a full SSA build, confirm output validates schema-clean and round-trips through LibreOffice

Notes for reviewer

  • Branch is 1 commit behind main (4a8380b pims: promote paths auto-create missing sites rows); will merge cleanly — no file overlap with this work.
  • OOXML validator reuses rpd-ssa-builder's cached DLL at %LOCALAPPDATA%\rpd-ssa-validator\ on machines that have it. Source path defaults to G:\My Drive\alan_mcxico\SSA-evidence\SSA rules\_validator\; override with GATEKEEPER_SSA_VALIDATOR_SOURCE env var for installs without rpd-ssa-builder.
  • Smoke wire-in only triggers on full-build / from-state paths (correct: --enrich-only exits before docx build; --from-report flows back to xlsx).
  • Phase 7 (separate sdg-ssa-builder repo) and a full drift checker (would need refactor-first pass on build_ssa_report_docx's inline placeholder dicts) remain deferred per the plan doc.

🤖 Generated with Claude Code

mcxl and others added 9 commits May 14, 2026 13:41
Captures the agreed plan for porting rpd-ssa-builder's quality
patterns into gatekeeper's SSA report-docx output path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two runs over the same inputs now produce byte-identical output;
docxcompose-style paraId drift and dcterms wall-clock stamps no
longer leak into the deliverable. Lifted from rpd-ssa-builder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps Microsoft's OpenXmlValidator (via rpd-ssa-builder's .NET CLI) and
runs it after the determinism post-pass. Findings surface on the
diagnostics dict and as log warnings; the build does not abort on
errors so callers retain hard-fail policy.

Soft-skips when dotnet is unreachable. Reuses rpd-ssa-builder's cached
DLL at %LOCALAPPDATA%\rpd-ssa-validator; rebuilds from its source on
miss (override path via GATEKEEPER_SSA_VALIDATOR_SOURCE).

The clean-output test is currently xfail(strict=True): current
gatekeeper output emits <w:tblBorders> in the wrong position inside
<w:tblPr>. Follow-up commit fixes the schema-order bug and unxfails.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_apply_all_cell_borders was emitting <w:tblBorders> at the end of
<w:tblPr> via plain .append(). The OOXML schema places tblBorders at
position 11; <w:tblLook> (position 15) is already on every template
table, so appending put tblBorders after tblLook and tripped four
Sch_UnexpectedElementContentExpectingComplex errors per built docx.
Word's recovery prompt tolerates this; OpenXmlValidator does not.

Fixed by inserting before the first child whose tag falls in the
positions-after-tblBorders set (shd, tblLayout, tblCellMar, tblLook,
tblCaption, tblDescription, tblPrChange). Removes the xfail mark on
test_validate_docx_clean_output_has_no_errors — current output now
validates clean against Microsoft 365.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone module wrapping `soffice --headless --convert-to pdf` so
the pipeline can catch "python-docx accepts it but LibreOffice / Word
won't render it" defects that the OOXML schema validator may miss.

Mirrors the OOXML validator's soft-skip pattern: returns
SmokeResult(available=False) when soffice is not installed; never
raises. A PDF that's missing or zero-byte counts as a failure even
when soffice exits 0.

Module is standalone for this commit — no production wire-in into
build_ssa_report_docx or run_ssa_pipeline.py yet. Wiring lands with
the --check preflight in Phase 4 (call-site level, not per-build) so
the 14 build_ssa_report_docx unit tests don't each spawn soffice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gatekeeper-native preflight module. Checks:
  - folder name matches YYYY-MM-DD-{RPD,SDG}[-NN]
  - Evidence_Master.csv exists in folder root
  - Evidence_Master.csv has the (timestamp, observation, filename) headers
  - Evidence_Master.csv has >= 1 data row
  - folder contains >= 1 .jpg / .jpeg / .png
  - SSA report template readable at configured path
  - LibreOffice reachable (informational)
  - dotnet reachable (informational)
  - ANTHROPIC_API_KEY set (informational)

Borrows the CheckResult / run_preflight / format_results shape from
rpd-ssa-builder's src/preflight.py but replaces all RPD-specific
checks (SafetyCulture docx, score pattern, Site_Visit_Report xlsx)
with the gatekeeper input contract.

Module is standalone for this commit. --check CLI wiring into
run_ssa_pipeline.py lands in the follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eline

--check runs run_preflight on the audit folder and exits without
building deliverables. Hard failures return exit 1; informational
warnings don't block.

After a successful full or from-state build, the report docx is
smoke-tested via headless LibreOffice. Wire-in is at CLI-main level
(not inside run_once) so the build_ssa_report_docx unit tests don't
each spawn soffice.

Also fixes a latent circular import: preflight.py now lazy-imports
SSA_REPORT_TEMPLATE inside _check_template. Top-level import was OK
when tests entered via test_ssa_preflight.py but blew up when entry
was run_ssa_pipeline.py (different cycle traversal order).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tools/check_ssa_drift.py exits 0 if all paired constants agree, 1
otherwise. v1 covers the one drift case introduced in Phase 4a:
ssa_pipeline._REQUIRED_HEADER must equal preflight._EXPECTED_CSV_HEADERS.

A full drift checker on the rpd-ssa-builder model would need a
refactor-first pass to extract gatekeeper's inline placeholder dicts
(build_ssa_report_docx body_replacements / footer_replacements) into
a named constants surface. Deferred until that placeholder convention
matures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scoped CHANGELOG-SSA.md covering the determinism / OOXML validator /
LibreOffice smoke / preflight / drift-checker landings plus the
tblBorders schema-order fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e41015d8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1647 to +1648
else:
print(f"libreoffice smoke: FAILED - {smoke.failure_detail}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail the CLI when LibreOffice smoke fails

When LibreOffice is installed and the generated report cannot be converted, this branch only prints FAILED and then falls through to the unconditional return 0. That means CI/wrappers or operators running the full/from-state build will treat a malformed DOCX as a successful run and may ship it despite the smoke test detecting the defect; the failure path should return non-zero or raise while still soft-skipping only when LibreOffice is unavailable.

Useful? React with 👍 / 👎.

Comment on lines +200 to +204
if result.returncode == 3:
return ValidationResult(
available=True, errors=[],
skip_reason=f"validator crashed: {result.stderr.strip()}",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't report validator crashes as clean results

If the validator executable crashes with return code 3, available=True with an empty errors list makes callers treat the document as successfully validated; build_ssa_report_docx records [] for oxml_validation_errors and ignores skip_reason. In environments where the cached validator is broken or hits an unhandled DOCX case, the schema guard silently passes instead of surfacing that validation did not run successfully.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant