From 3be35f6b0aae281d1173516f59f9116bc22567c6 Mon Sep 17 00:00:00 2001 From: Semprini Date: Mon, 25 May 2026 19:13:10 +1200 Subject: [PATCH] Refactor _compute_metrics to improve longest job duration calculation and handle None values in due dates --- app/views/views.py | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/app/views/views.py b/app/views/views.py index 0088686..461c7bb 100644 --- a/app/views/views.py +++ b/app/views/views.py @@ -178,15 +178,14 @@ def _compute_metrics( avg_duration_today = (sum(durations_today) / len(durations_today)) if durations_today else None longest_today = None + longest_today_seconds = 0.0 if today_jobs: - finished_today = [ - j for j in today_jobs if j.start_datetime and j.finish_datetime - ] - if finished_today: - longest_today = max( - finished_today, - key=lambda j: (j.finish_datetime - j.start_datetime).total_seconds(), - ) + for j in today_jobs: + if j.start_datetime and j.finish_datetime: + secs = (j.finish_datetime - j.start_datetime).total_seconds() + if longest_today is None or secs > longest_today_seconds: + longest_today = j + longest_today_seconds = secs # On-time = job started within 15 min of due_datetime (when both known) on_time = late = 0 @@ -222,8 +221,8 @@ def _compute_metrics( # --- Upcoming today --- upcoming_today = sorted( (j for j in today_jobs - if j.start_datetime is None and j.due_datetime and j.due_datetime >= now), - key=lambda j: j.due_datetime, + if j.start_datetime is None and j.due_datetime is not None and j.due_datetime >= now), + key=lambda j: j.due_datetime or datetime.max, )[:5] return { @@ -248,8 +247,7 @@ def _compute_metrics( "avg_duration_today": _fmt_duration(avg_duration_today) if avg_duration_today is not None else None, "longest_today_name": longest_today.name if longest_today else None, "longest_today_duration": ( - _fmt_duration((longest_today.finish_datetime - longest_today.start_datetime).total_seconds()) - if longest_today else None + _fmt_duration(longest_today_seconds) if longest_today else None ), "on_time_rate_today": on_time_rate_today, "on_time": on_time, @@ -269,8 +267,11 @@ def _compute_metrics( "name": j.name, "as_at": j.as_at_date.isoformat(), "reason": j.status_reason or "—", - "when": (j.finish_datetime or j.start_datetime).isoformat(timespec="minutes") - if (j.finish_datetime or j.start_datetime) else "", + "when": ( + _when.isoformat(timespec="minutes") + if (_when := j.finish_datetime or j.start_datetime) is not None + else "" + ), } for j in recent_failures ], @@ -278,7 +279,7 @@ def _compute_metrics( { "id": j.id, "name": j.name, - "due": j.due_datetime.strftime("%H:%M"), + "due": j.due_datetime.strftime("%H:%M") if j.due_datetime else "", } for j in upcoming_today ], @@ -449,11 +450,16 @@ async def job_detail_view(request: Request, job_id: int): lateness_str = f"{_fmt_duration(delta)} late" # Aggregate stats across history - finished_related = [j for j in related if j.start_datetime and j.finish_datetime] - avg_dur = ( - sum((j.finish_datetime - j.start_datetime).total_seconds() for j in finished_related) - / len(finished_related) - ) if finished_related else None + finished_related = [ + j for j in related + if j.start_datetime is not None and j.finish_datetime is not None + ] + finished_durations = [ + (j.finish_datetime - j.start_datetime).total_seconds() + for j in finished_related + if j.start_datetime is not None and j.finish_datetime is not None + ] + avg_dur = (sum(finished_durations) / len(finished_durations)) if finished_durations else None success_count = sum(1 for j in related if j.status == "completed") fail_count = sum(1 for j in related if j.status == "failed") finished_count = success_count + fail_count