From d6fe697c64f4827ea8b4ed2958bc85356012de4a Mon Sep 17 00:00:00 2001 From: Attila Kobor Date: Sat, 25 Apr 2026 20:42:13 +0200 Subject: [PATCH 1/2] Enhance report generation: add phase grouping functionality and update template rendering --- .../core/reporting/reporting.py | 16 +++++++++++++++- .../core/templates/report_template.html | 10 +++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/openutm_verification/core/reporting/reporting.py b/src/openutm_verification/core/reporting/reporting.py index a2a546ac..4f63f37a 100644 --- a/src/openutm_verification/core/reporting/reporting.py +++ b/src/openutm_verification/core/reporting/reporting.py @@ -10,7 +10,7 @@ from loguru import logger from openutm_verification.core.execution.config_models import AppConfig, ReportingConfig -from openutm_verification.core.flight_phase import FLIGHT_PHASE_LABELS +from openutm_verification.core.flight_phase import FLIGHT_PHASE_LABELS, FlightPhase from openutm_verification.core.reporting._viz_engine import ( extract_step_payload, label_from_step_id, @@ -194,8 +194,22 @@ def _generate_html_report(report_data: ReportData, output_dir: Path, base_filena loader=FileSystemLoader(template_dir), autoescape=select_autoescape(enabled_extensions=("html", "xml"), default_for_string=True, default=True), ) + _phase_order = [p.value for p in FlightPhase] + + def _phase_groupby(steps: list[dict]) -> list[tuple[str, list[dict]]]: + """Group steps by phase and return groups in canonical flight phase order.""" + groups: dict[str, list[dict]] = {} + for step in steps: + phase = step.get("phase") or "" + groups.setdefault(phase, []).append(step) + return sorted( + groups.items(), + key=lambda item: _phase_order.index(item[0]) if item[0] in _phase_order else len(_phase_order), + ) + env.filters["markdown"] = lambda text: markdown.markdown(text) if text else "" env.filters["default_phase"] = lambda steps: [{**s, "phase": s.get("phase") or ""} for s in steps] + env.filters["phase_groupby"] = _phase_groupby template = env.get_template("report_template.html") html_content = template.render( diff --git a/src/openutm_verification/core/templates/report_template.html b/src/openutm_verification/core/templates/report_template.html index cfc34d48..e78e139f 100644 --- a/src/openutm_verification/core/templates/report_template.html +++ b/src/openutm_verification/core/templates/report_template.html @@ -130,13 +130,13 @@

Documentation:

Steps:

{% set has_phases = result.steps | selectattr('phase') | list | length > 0 %} {% if has_phases %} - {% for phase_group in result.steps | default_phase | groupby('phase') %} + {% for phase_group in result.steps | default_phase | phase_groupby %} {% set phase_index = loop.index %}
- {% if phase_group.grouper %} + {% if phase_group[0] %}
- {{ phase_group.grouper }} - {{ (phase_labels or {}).get(phase_group.grouper, phase_group.grouper) }} + {{ phase_group[0] }} + {{ (phase_labels or {}).get(phase_group[0], phase_group[0]) }}
{% else %}
Other Steps
@@ -144,7 +144,7 @@

Steps:

- {% for step in phase_group.list %} + {% for step in phase_group[1] %} {% set result_json = step.result | tojson(indent=2) %} {% set line_count = result_json.split('\n') | length %} From 5c5bc418cd55f82c7f6ab37c5bd4ddae785bef52 Mon Sep 17 00:00:00 2001 From: Attila Kobor Date: Sat, 25 Apr 2026 20:46:50 +0200 Subject: [PATCH 2/2] Refactor report generation: use dictionary for phase ranking and update template for phase grouping --- src/openutm_verification/core/reporting/reporting.py | 4 ++-- .../core/templates/report_template.html | 10 +++++----- tests/test_reporting_output.py | 6 ++++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/openutm_verification/core/reporting/reporting.py b/src/openutm_verification/core/reporting/reporting.py index 4f63f37a..ea8c2cc5 100644 --- a/src/openutm_verification/core/reporting/reporting.py +++ b/src/openutm_verification/core/reporting/reporting.py @@ -194,7 +194,7 @@ def _generate_html_report(report_data: ReportData, output_dir: Path, base_filena loader=FileSystemLoader(template_dir), autoescape=select_autoescape(enabled_extensions=("html", "xml"), default_for_string=True, default=True), ) - _phase_order = [p.value for p in FlightPhase] + _phase_rank: dict[str, int] = {p.value: i for i, p in enumerate(FlightPhase)} def _phase_groupby(steps: list[dict]) -> list[tuple[str, list[dict]]]: """Group steps by phase and return groups in canonical flight phase order.""" @@ -204,7 +204,7 @@ def _phase_groupby(steps: list[dict]) -> list[tuple[str, list[dict]]]: groups.setdefault(phase, []).append(step) return sorted( groups.items(), - key=lambda item: _phase_order.index(item[0]) if item[0] in _phase_order else len(_phase_order), + key=lambda item: _phase_rank.get(item[0], len(_phase_rank)), ) env.filters["markdown"] = lambda text: markdown.markdown(text) if text else "" diff --git a/src/openutm_verification/core/templates/report_template.html b/src/openutm_verification/core/templates/report_template.html index e78e139f..67e730af 100644 --- a/src/openutm_verification/core/templates/report_template.html +++ b/src/openutm_verification/core/templates/report_template.html @@ -130,13 +130,13 @@

Documentation:

Steps:

{% set has_phases = result.steps | selectattr('phase') | list | length > 0 %} {% if has_phases %} - {% for phase_group in result.steps | default_phase | phase_groupby %} + {% for phase, steps in result.steps | default_phase | phase_groupby %} {% set phase_index = loop.index %}
- {% if phase_group[0] %} + {% if phase %}
- {{ phase_group[0] }} - {{ (phase_labels or {}).get(phase_group[0], phase_group[0]) }} + {{ phase }} + {{ (phase_labels or {}).get(phase, phase) }}
{% else %}
Other Steps
@@ -144,7 +144,7 @@

Steps:

Step NameStatusDuration (s)Result
- {% for step in phase_group[1] %} + {% for step in steps %} {% set result_json = step.result | tojson(indent=2) %} {% set line_count = result_json.split('\n') | length %} diff --git a/tests/test_reporting_output.py b/tests/test_reporting_output.py index 14a35908..ddc1f802 100644 --- a/tests/test_reporting_output.py +++ b/tests/test_reporting_output.py @@ -139,6 +139,12 @@ def test_report_html_groups_steps_by_phase(tmp_path: Path): assert "Stream telemetry" in html assert "Cleanup" in html + # Phase headers must appear in canonical flight order: PRE FLIGHT -> CRUISE -> POST FLIGHT + pre_pos = html.index(">PRE FLIGHT<") + cruise_pos = html.index(">CRUISE<") + post_pos = html.index(">POST FLIGHT<") + assert pre_pos < cruise_pos < post_pos, "Phase groups must appear in canonical flight phase order" + def test_report_html_no_phases_renders_flat_table(tmp_path: Path): app_config = _make_app_config(tmp_path)
Step NameStatusDuration (s)Result