Skip to content

Redaction rationale: HTML report rendering (AI-inferred vs rule-match badges) #69

Description

@LegalMarc

Design doc (read in full first): docs/design/redaction_rationale_reporting.md — "Report Schema Changes" → HTML report, and mitigation items 1 & 2 in "Mitigations required before shipping".

This is the HTML/report presentation layer for Redaction Rationale Reporting. The JSON data layer (schema, origin enum, generation) lands in #68 and is a prerequisite. This issue only renders what that issue produces.

Scope

Render the rationale object in the HTML audit report, visually distinguishing AI-inferred rationale from deterministic rule rationale, in a way that survives print/export.

Extend the existing .source-badge CSS pattern (report.py:734–751, classes .source-badge.rule / .source-badge.llm) inside _generate_html_audit_report() (report.py:163); the per-span row is rendered around report.py:351.

Non-negotiable requirements (from the design doc)

  • Persistent "AI-INFERRED, NOT VERIFIED" treatment on every llm_*-origin rationale, placed directly adjacent to the explanation text itself — not only in a one-time legend elsewhere on the page. The caveat must travel with the text if a single row is copied, screenshotted, or printed.
  • A distinct, neutral "Rule match" treatment for rule_deterministic — the two must not be merged into one undifferentiated "AI explanation" bucket. A reader must be able to tell at a glance whether an explanation is unverified model prose or a deterministic rule description.
  • unavailable renders as an explicit visible state ("No rationale recorded for this entity"), never as an omitted/blank cell — so its absence is never ambiguous with "the redaction needed no justification".
  • The distinction must survive the print/export path. The report already supports print/export (report.py print handling around the HTML generator); styling-only cues that vanish in print are not acceptable — verify the labeling text is present in printed/exported output, not just on screen.
  • Copy/UI language must make clear that inferred explanations are unverified prose, not extracted facts, even when correctly labeled AI-generated (the risk analysis is explicit that prose reads as more authoritative than a confidence float).

Acceptance criteria

  • HTML report renders each span's rationale with an origin-appropriate badge/treatment reusing the .source-badge pattern.
  • llm_validation rows carry a persistent, adjacent "AI-inferred, not verified" label; rule_deterministic rows carry a neutral "rule match" label; unavailable shows the explicit "no rationale recorded" state.
  • A test asserts the "not verified" caveat text is present in the generated HTML for an llm_* row (so it survives copy/print, which is text-level, not style-level).
  • Rendering degrades cleanly for older/disabled reports that have no rationale field (no crash, no misleading blank).

Verification gate (all PRs in this set)

  • PYTHONPATH=src/python python3 -m pytest -q
  • swift build --package-path src/swift/MarcutApp
  • swift test --package-path src/swift/MarcutApp
  • bash scripts/release_preflight.sh

Dependencies

Blocked by #68 (needs the rationale JSON fields to render).


Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #32.

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkAutonomous workflow-loop eligible

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions