fix(metrics): prevent 0.0 falsy bug for sub-hour timing values
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

Root cause: avg times were ~2-3 minutes (< 1h). round(0.033, 1) = 0.0
which is falsy in JS, so the frontend showed N/A instead of the value.

Fix (backend): _safe_stats() and team metrics now convert to minutes
when avg < 1 hour, adding a 'unit' field ('min' or 'hrs').

Fix (frontend): use != null instead of truthy check for avg_completion_hours,
MTTD, MTTR — correctly shows 0.0 and uses the unit field to show 'min' or 'hrs'.
This commit is contained in:
kitos
2026-06-03 10:59:58 +02:00
parent 06e8effaa4
commit 5684484fdf
2 changed files with 28 additions and 14 deletions

View File

@@ -17,13 +17,19 @@ from app.models.enums import TestState, TestResult
def _safe_stats(values: list[float]) -> dict:
"""Compute mean, median, min, max from a list of floats."""
"""Compute mean, median, min, max from a list of floats (in hours).
For sub-hour averages, mean_hours is stored as minutes to avoid
rounding to 0.0 which is falsy in JavaScript."""
if not values:
return None
sorted_vals = sorted(values)
n = len(sorted_vals)
mean = sum(sorted_vals) / n
# Use minutes for sub-hour values to avoid JS falsy 0.0
mean_display = round(mean * 60, 1) if mean < 1 else round(mean, 1)
return {
"mean_hours": round(sum(sorted_vals) / n, 1),
"mean_hours": mean_display,
"unit": "min" if mean < 1 else "hrs",
"median_hours": round(sorted_vals[n // 2], 1),
"min_hours": round(sorted_vals[0], 1),
"max_hours": round(sorted_vals[-1], 1),
@@ -416,7 +422,9 @@ def get_metrics_by_team(db: Session) -> dict:
if net > 0:
red_times.append(net / 3600)
if red_times:
red_avg_time = round(sum(red_times) / len(red_times), 1)
avg_hours = sum(red_times) / len(red_times)
# Use minutes for sub-hour values so rounding to 0.0 doesn't hide data
red_avg_time = round(avg_hours * 60, 1) if avg_hours < 1 else round(avg_hours, 1)
# Blue team: count tests that reached the blue evaluation phase
blue_tests_completed = (
@@ -449,17 +457,23 @@ def get_metrics_by_team(db: Session) -> dict:
if net > 0:
blue_times.append(net / 3600)
if blue_times:
blue_avg_time = round(sum(blue_times) / len(blue_times), 1)
avg_hours = sum(blue_times) / len(blue_times)
blue_avg_time = round(avg_hours * 60, 1) if avg_hours < 1 else round(avg_hours, 1)
red_avg_raw = sum(red_times) / len(red_times) if red_times else None
blue_avg_raw = sum(blue_times) / len(blue_times) if blue_times else None
return {
"red_team": {
"tests_completed": red_tests_completed,
"avg_completion_hours": red_avg_time,
"avg_unit": "min" if (red_avg_raw is not None and red_avg_raw < 1) else "hrs",
"rejection_rate": calculate_rejection_rate(db)["by_red_lead"],
},
"blue_team": {
"tests_completed": blue_tests_completed,
"avg_completion_hours": blue_avg_time,
"avg_unit": "min" if (blue_avg_raw is not None and blue_avg_raw < 1) else "hrs",
"rejection_rate": calculate_rejection_rate(db)["by_blue_lead"],
},
}