From f53500bcb584e22810745e80a5625c0a4326c0e5 Mon Sep 17 00:00:00 2001 From: kitos Date: Wed, 3 Jun 2026 10:06:30 +0200 Subject: [PATCH] fix(exec-dashboard): replace time-dependent throughput with Pipeline Conversion % MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'Validation Throughput (tests/week)' was time-dependent — director wanted an activity-based metric instead. New metric: Pipeline Conversion Rate formula: validated / (validated + rejected + in_review) × 100 unit: % (no time reference) meaning: 'of all tests that have entered validation, X% succeeded' trend: declining if in_review backlog > validated count, improving if conversion ≥ 80%, stable otherwise Backend: calculate_validation_throughput() rewritten — same API key (tests_per_week) kept for compatibility, new conversion_rate field added. Frontend: label → 'Pipeline Conversion', unit → '%', tooltip updated. --- .../services/operational_metrics_service.py | 78 +++++++++++-------- frontend/src/pages/ExecutiveDashboardPage.tsx | 8 +- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/backend/app/services/operational_metrics_service.py b/backend/app/services/operational_metrics_service.py index 2b81f93..531bf3f 100644 --- a/backend/app/services/operational_metrics_service.py +++ b/backend/app/services/operational_metrics_service.py @@ -237,45 +237,59 @@ def calculate_coverage_velocity(db: Session) -> dict: def calculate_validation_throughput(db: Session) -> dict: - """Calculate tests validated/rejected per week.""" - twelve_weeks_ago = datetime.utcnow() - timedelta(weeks=12) + """Pipeline Conversion Rate — activity-based, no time dependency. - # Tests validated - validated_weekly = ( - db.query( - func.date_trunc("week", Test.red_validated_at).label("week"), - func.count(Test.id).label("count"), - ) - .filter( - Test.red_validated_at >= twelve_weeks_ago, - Test.state.in_([TestState.validated, TestState.rejected]), - ) - .group_by(func.date_trunc("week", Test.red_validated_at)) - .order_by("week") - .all() + Measures what percentage of tests that have entered the validation + phase have been successfully approved (validated). + + formula: validated / (validated + rejected + in_review) * 100 + + 100% = every test that reached validation was approved. + 0% = nothing has been validated yet. + Lower = backlog or quality issues blocking approvals. + """ + validated_count = ( + db.query(func.count(Test.id)) + .filter(Test.state == TestState.validated) + .scalar() + ) or 0 + + rejected_count = ( + db.query(func.count(Test.id)) + .filter(Test.state == TestState.rejected) + .scalar() + ) or 0 + + in_review_count = ( + db.query(func.count(Test.id)) + .filter(Test.state == TestState.in_review) + .scalar() + ) or 0 + + total_in_pipeline = validated_count + rejected_count + in_review_count + conversion_rate = ( + round(validated_count / total_in_pipeline * 100, 1) + if total_in_pipeline > 0 + else 0.0 ) - if validated_weekly: - counts = [row.count for row in validated_weekly] - avg_per_week = round(sum(counts) / len(counts), 1) - recent = counts[-4:] if len(counts) >= 4 else counts - earlier = counts[-8:-4] if len(counts) >= 8 else counts[:len(counts) // 2] if counts else [] - - recent_avg = sum(recent) / len(recent) if recent else 0 - earlier_avg = sum(earlier) / len(earlier) if earlier else 0 - - if recent_avg > earlier_avg * 1.1: - trend = "improving" - elif recent_avg < earlier_avg * 0.9: - trend = "declining" - else: - trend = "stable" + # Trend: compare conversion rate when considering pending tests + # High pending backlog relative to validated = declining + if total_in_pipeline == 0: + trend = "stable" + elif in_review_count > validated_count: + trend = "declining" # backlog building up + elif conversion_rate >= 80: + trend = "improving" # most tests making it through else: - avg_per_week = 0 trend = "stable" return { - "tests_per_week": avg_per_week, + "tests_per_week": conversion_rate, # reuse key for API compat + "conversion_rate": conversion_rate, + "validated": validated_count, + "rejected": rejected_count, + "in_review": in_review_count, "trend": trend, } diff --git a/frontend/src/pages/ExecutiveDashboardPage.tsx b/frontend/src/pages/ExecutiveDashboardPage.tsx index 1e6f5ff..3973e99 100644 --- a/frontend/src/pages/ExecutiveDashboardPage.tsx +++ b/frontend/src/pages/ExecutiveDashboardPage.tsx @@ -498,11 +498,11 @@ export default function ExecutiveDashboardPage() { tooltip={{ description: "Percentage of executed attacks that Blue Team successfully detected. 100% means every simulated attack triggered an alert.", context: "Higher is better. Below 60% indicates significant detection gaps." }} />