Auto-commit local changes before build (2026-01-03 14:15:04) #21
@ -1 +1 @@
|
||||
v20260103-07-reports-advanced-reporting-foundation
|
||||
v20260103-08-reports-stats-endpoint-fix
|
||||
|
||||
@ -229,117 +229,6 @@ def api_reports_columns():
|
||||
}
|
||||
|
||||
|
||||
@main_bp.route("/api/reports/<int:report_id>/stats", methods=["GET"])
|
||||
@login_required
|
||||
def api_reports_stats(report_id: int):
|
||||
"""Return lightweight KPI + chart datasets for a report.
|
||||
|
||||
Data is derived from report_object_snapshots, which is generated by
|
||||
POST /api/reports/<id>/generate.
|
||||
"""
|
||||
err = _require_reporting_role()
|
||||
if err is not None:
|
||||
return err
|
||||
|
||||
ReportDefinition.query.get_or_404(report_id)
|
||||
|
||||
# KPI counts
|
||||
# We treat missed runs as their own bucket, regardless of status string.
|
||||
row = db.session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
COUNT(*)::INTEGER AS total_runs,
|
||||
SUM(CASE WHEN missed = TRUE THEN 1 ELSE 0 END)::INTEGER AS missed_runs,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'success%' THEN 1 ELSE 0 END)::INTEGER AS success_runs,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'warning%' THEN 1 ELSE 0 END)::INTEGER AS warning_runs,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'fail%' THEN 1 ELSE 0 END)::INTEGER AS failed_runs,
|
||||
SUM(CASE WHEN override_applied = TRUE THEN 1 ELSE 0 END)::INTEGER AS override_runs
|
||||
FROM report_object_snapshots
|
||||
WHERE report_id = :rid
|
||||
"""
|
||||
),
|
||||
{"rid": report_id},
|
||||
).fetchone()
|
||||
|
||||
total_runs = int(row.total_runs or 0) if row else 0
|
||||
success_runs = int(row.success_runs or 0) if row else 0
|
||||
warning_runs = int(row.warning_runs or 0) if row else 0
|
||||
failed_runs = int(row.failed_runs or 0) if row else 0
|
||||
missed_runs = int(row.missed_runs or 0) if row else 0
|
||||
override_runs = int(row.override_runs or 0) if row else 0
|
||||
|
||||
success_rate = 0.0
|
||||
if total_runs > 0:
|
||||
# Consider overrides as success for success_rate.
|
||||
success_rate = ((success_runs + override_runs) / float(total_runs)) * 100.0
|
||||
|
||||
# Trend datasets (per day)
|
||||
trend_rows = db.session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT
|
||||
DATE_TRUNC('day', run_at) AS day,
|
||||
COUNT(*)::INTEGER AS total,
|
||||
SUM(CASE WHEN missed = TRUE THEN 1 ELSE 0 END)::INTEGER AS missed,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'success%' THEN 1 ELSE 0 END)::INTEGER AS success,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'warning%' THEN 1 ELSE 0 END)::INTEGER AS warning,
|
||||
SUM(CASE WHEN missed = FALSE AND COALESCE(status,'') ILIKE 'fail%' THEN 1 ELSE 0 END)::INTEGER AS failed
|
||||
FROM report_object_snapshots
|
||||
WHERE report_id = :rid
|
||||
AND run_at IS NOT NULL
|
||||
GROUP BY DATE_TRUNC('day', run_at)
|
||||
ORDER BY DATE_TRUNC('day', run_at) ASC
|
||||
"""
|
||||
),
|
||||
{"rid": report_id},
|
||||
).fetchall()
|
||||
|
||||
trend = []
|
||||
for tr in trend_rows or []:
|
||||
day = tr.day
|
||||
day_iso = day.date().isoformat() if hasattr(day, "date") else str(day)
|
||||
total = int(tr.total or 0)
|
||||
succ = int(tr.success or 0)
|
||||
fail = int(tr.failed or 0)
|
||||
succ_rate = 0.0
|
||||
if total > 0:
|
||||
succ_rate = (succ / float(total)) * 100.0
|
||||
trend.append(
|
||||
{
|
||||
"day": day_iso,
|
||||
"total": total,
|
||||
"success": succ,
|
||||
"warning": int(tr.warning or 0),
|
||||
"failed": fail,
|
||||
"missed": int(tr.missed or 0),
|
||||
"success_rate": succ_rate,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"kpis": {
|
||||
"total_runs": total_runs,
|
||||
"success_runs": success_runs,
|
||||
"warning_runs": warning_runs,
|
||||
"failed_runs": failed_runs,
|
||||
"missed_runs": missed_runs,
|
||||
"override_runs": override_runs,
|
||||
"success_rate": float(success_rate),
|
||||
},
|
||||
"charts": {
|
||||
"status_distribution": {
|
||||
"success": success_runs + override_runs,
|
||||
"warning": warning_runs,
|
||||
"failed": failed_runs,
|
||||
"missed": missed_runs,
|
||||
},
|
||||
"trend": trend,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@main_bp.route("/api/reports/<int:report_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
def api_reports_delete(report_id: int):
|
||||
|
||||
@ -62,6 +62,14 @@
|
||||
### Fixed
|
||||
- Ensured report deletion flow remains compatible with extended report definition handling.
|
||||
|
||||
---
|
||||
|
||||
## v20260103-08-reports-stats-endpoint-fix
|
||||
|
||||
- Fixed application startup crash caused by duplicate registration of the `api_reports_stats` endpoint.
|
||||
- Removed the redundant `/api/reports/<report_id>/stats` route definition to ensure the endpoint is registered only once.
|
||||
- Restored proper Gunicorn boot sequence by resolving Flask endpoint name collision.
|
||||
|
||||
================================================================================================================================================
|
||||
|
||||
## v0.1.15
|
||||
|
||||
Loading…
Reference in New Issue
Block a user