fix(exec-dashboard): replace time-dependent throughput with Pipeline Conversion %
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
'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.
This commit is contained in:
@@ -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: 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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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." }}
|
||||
/>
|
||||
<KPICard
|
||||
label="Validation Throughput"
|
||||
value={opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
||||
unit="/week"
|
||||
label="Pipeline Conversion"
|
||||
value={opMetrics?.validation_throughput?.conversion_rate ?? opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
||||
unit="%"
|
||||
trend={opMetrics?.validation_throughput?.trend}
|
||||
tooltip={{ description: "Number of tests fully validated (approved by Red + Blue leads) per week. Shows how productive the security testing programme is.", context: "Increasing trend = programme is accelerating. Decreasing may indicate process bottlenecks." }}
|
||||
tooltip={{ description: "Percentage of tests that have entered the validation phase and been successfully approved. Formula: Validated ÷ (Validated + Rejected + In Review) × 100.", context: "100% = all reviewed tests approved. < 60% = quality or process issues. High backlog (many In Review) lowers this score." }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user