Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changes/271.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Extract ``phase_dot_class()`` from Jinja2 template into a Python function so
that dot-color logic is unit-tested directly rather than through full-HTML
string matching, which previously matched CSS class definitions in the
``<style>`` block (vacuous assertions). The vacuous ``test_superseded_phase_css_rule_defined``
assertion (``".phase-status-superseded" in content``) is replaced with a
line-level check that confirms the CSS rule body includes ``opacity``.
45 changes: 45 additions & 0 deletions src/raki/report/html_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,50 @@
]


def phase_dot_class(phase: PhaseResult) -> str:
"""Return the CSS class suffix for the phase status dot in the HTML timeline.

Priority rules (highest to lowest):
1. ``failed``, ``skipped``, ``superseded`` status always wins over any verdict.
2. ``verify`` phase with ``output_structured.verdict``:
- ``"pass"`` (case-insensitive) → ``"completed"`` (green dot)
- any other value → ``"failed"`` (red dot)
3. ``review`` phase with ``output_structured.verdict``:
- ``"approve"``, ``"pass"``, ``"pass-with-follow-ups"`` → ``"completed"``
- ``"rework"`` → ``"rework"`` (yellow dot)
- any other value → ``"failed"``
4. All other phases: generation > 1 and ``status == "completed"`` → ``"rework"``,
otherwise return ``phase.status`` verbatim.

Returns one of: ``"completed"``, ``"rework"``, ``"failed"``, ``"skipped"``,
``"superseded"``.
"""
if phase.status in ("failed", "skipped", "superseded"):
return phase.status

verdict: str | None = None
if isinstance(phase.output_structured, dict):
raw_verdict = phase.output_structured.get("verdict")
if isinstance(raw_verdict, str):
verdict = raw_verdict

if verdict is not None and phase.name == "verify":
return "completed" if verdict.lower() == "pass" else "failed"

if verdict is not None and phase.name == "review":
verdict_lc = verdict.lower()
if verdict_lc in ("approve", "pass", "pass-with-follow-ups"):
return "completed"
if verdict_lc == "rework":
return "rework"
return "failed"

if phase.status == "completed" and phase.generation > 1:
return "rework"

return phase.status


def sort_phases(
phases: list[PhaseResult],
pipeline_phases: list[str] | None = None,
Expand Down Expand Up @@ -831,6 +875,7 @@ def color_name_fn(score: float | None, metric_name: str = "") -> str:
needs_attention_count=needs_attention_count,
format_duration=_format_duration,
sort_phases=sort_phases,
phase_dot_class=phase_dot_class,
no_data_metrics=no_data_metrics,
agent_models=agent_models,
judge_cost=judge_cost,
Expand Down
26 changes: 3 additions & 23 deletions src/raki/report/templates/report.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -1381,29 +1381,9 @@
{% if sorted_phases %}
<div class="phase-list">
{% for phase in sorted_phases %}
{# Compute dot_class: failed/skipped/superseded status always wins; for verify/review
phases with structured output, use verdict to pick the color; otherwise fall back to
the existing gen>1 → rework logic. #}
{% if phase.status in ('failed', 'skipped', 'superseded') %}
{% set dot_class = phase.status %}
{% elif phase.output_structured and phase.output_structured is mapping
and phase.output_structured.verdict is defined
and phase.name == 'verify' %}
{% set dot_class = 'completed' if phase.output_structured.verdict | lower == 'pass' else 'failed' %}
{% elif phase.output_structured and phase.output_structured is mapping
and phase.output_structured.verdict is defined
and phase.name == 'review' %}
{% set verdict_lc = phase.output_structured.verdict | lower %}
{% if verdict_lc in ('approve', 'pass', 'pass-with-follow-ups') %}
{% set dot_class = 'completed' %}
{% elif verdict_lc == 'rework' %}
{% set dot_class = 'rework' %}
{% else %}
{% set dot_class = 'failed' %}
{% endif %}
{% else %}
{% set dot_class = 'rework' if phase.status == 'completed' and phase.generation > 1 else phase.status %}
{% endif %}
{# dot_class delegates to phase_dot_class() in html_report.py so the logic
is unit-tested directly without string-matching the full HTML output. #}
{% set dot_class = phase_dot_class(phase) %}
<div class="phase-item">
<span class="phase-status phase-status-{{ dot_class }}"></span>
<span>{{ phase.name }} (gen {{ phase.generation }})</span>
Expand Down
Loading
Loading