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:
|
def calculate_validation_throughput(db: Session) -> dict:
|
||||||
"""Calculate tests validated/rejected per week."""
|
"""Pipeline Conversion Rate — activity-based, no time dependency.
|
||||||
twelve_weeks_ago = datetime.utcnow() - timedelta(weeks=12)
|
|
||||||
|
|
||||||
# Tests validated
|
Measures what percentage of tests that have entered the validation
|
||||||
validated_weekly = (
|
phase have been successfully approved (validated).
|
||||||
db.query(
|
|
||||||
func.date_trunc("week", Test.red_validated_at).label("week"),
|
formula: validated / (validated + rejected + in_review) * 100
|
||||||
func.count(Test.id).label("count"),
|
|
||||||
)
|
100% = every test that reached validation was approved.
|
||||||
.filter(
|
0% = nothing has been validated yet.
|
||||||
Test.red_validated_at >= twelve_weeks_ago,
|
Lower = backlog or quality issues blocking approvals.
|
||||||
Test.state.in_([TestState.validated, TestState.rejected]),
|
"""
|
||||||
)
|
validated_count = (
|
||||||
.group_by(func.date_trunc("week", Test.red_validated_at))
|
db.query(func.count(Test.id))
|
||||||
.order_by("week")
|
.filter(Test.state == TestState.validated)
|
||||||
.all()
|
.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:
|
# Trend: compare conversion rate when considering pending tests
|
||||||
counts = [row.count for row in validated_weekly]
|
# High pending backlog relative to validated = declining
|
||||||
avg_per_week = round(sum(counts) / len(counts), 1)
|
if total_in_pipeline == 0:
|
||||||
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 = "stable"
|
||||||
|
elif in_review_count > validated_count:
|
||||||
|
trend = "declining" # backlog building up
|
||||||
|
elif conversion_rate >= 80:
|
||||||
|
trend = "improving" # most tests making it through
|
||||||
else:
|
else:
|
||||||
avg_per_week = 0
|
|
||||||
trend = "stable"
|
trend = "stable"
|
||||||
|
|
||||||
return {
|
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,
|
"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." }}
|
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
|
<KPICard
|
||||||
label="Validation Throughput"
|
label="Pipeline Conversion"
|
||||||
value={opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
value={opMetrics?.validation_throughput?.conversion_rate ?? opMetrics?.validation_throughput?.tests_per_week ?? 0}
|
||||||
unit="/week"
|
unit="%"
|
||||||
trend={opMetrics?.validation_throughput?.trend}
|
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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user