217 lines
5.6 KiB
C#
217 lines
5.6 KiB
C#
namespace StellaOps.Metrics.Kpi;
|
|
|
|
/// <summary>
|
|
/// Quality KPIs for explainable triage.
|
|
/// </summary>
|
|
public sealed record TriageQualityKpis
|
|
{
|
|
/// <summary>
|
|
/// Reporting period start.
|
|
/// </summary>
|
|
public required DateTimeOffset PeriodStart { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reporting period end.
|
|
/// </summary>
|
|
public required DateTimeOffset PeriodEnd { get; init; }
|
|
|
|
/// <summary>
|
|
/// Tenant ID (null for global).
|
|
/// </summary>
|
|
public string? TenantId { get; init; }
|
|
|
|
/// <summary>
|
|
/// Reachability KPIs.
|
|
/// </summary>
|
|
public required ReachabilityKpis Reachability { get; init; }
|
|
|
|
/// <summary>
|
|
/// Runtime KPIs.
|
|
/// </summary>
|
|
public required RuntimeKpis Runtime { get; init; }
|
|
|
|
/// <summary>
|
|
/// Explainability KPIs.
|
|
/// </summary>
|
|
public required ExplainabilityKpis Explainability { get; init; }
|
|
|
|
/// <summary>
|
|
/// Replay/Determinism KPIs.
|
|
/// </summary>
|
|
public required ReplayKpis Replay { get; init; }
|
|
|
|
/// <summary>
|
|
/// Unknown budget KPIs.
|
|
/// </summary>
|
|
public required UnknownBudgetKpis Unknowns { get; init; }
|
|
|
|
/// <summary>
|
|
/// Operational KPIs.
|
|
/// </summary>
|
|
public required OperationalKpis Operational { get; init; }
|
|
}
|
|
|
|
public sealed record ReachabilityKpis
|
|
{
|
|
/// <summary>
|
|
/// Total findings analyzed.
|
|
/// </summary>
|
|
public required int TotalFindings { get; init; }
|
|
|
|
/// <summary>
|
|
/// Findings with non-UNKNOWN reachability.
|
|
/// </summary>
|
|
public required int WithKnownReachability { get; init; }
|
|
|
|
/// <summary>
|
|
/// Percentage with known reachability.
|
|
/// </summary>
|
|
public decimal PercentKnown => TotalFindings > 0
|
|
? (decimal)WithKnownReachability / TotalFindings * 100
|
|
: 0;
|
|
|
|
/// <summary>
|
|
/// Breakdown by reachability state.
|
|
/// </summary>
|
|
public required IReadOnlyDictionary<string, int> ByState { get; init; }
|
|
|
|
/// <summary>
|
|
/// Findings confirmed unreachable.
|
|
/// </summary>
|
|
public int ConfirmedUnreachable =>
|
|
ByState.GetValueOrDefault("ConfirmedUnreachable", 0);
|
|
|
|
/// <summary>
|
|
/// Noise reduction (unreachable / total).
|
|
/// </summary>
|
|
public decimal NoiseReductionPercent => TotalFindings > 0
|
|
? (decimal)ConfirmedUnreachable / TotalFindings * 100
|
|
: 0;
|
|
}
|
|
|
|
public sealed record RuntimeKpis
|
|
{
|
|
/// <summary>
|
|
/// Total findings in environments with sensors.
|
|
/// </summary>
|
|
public required int TotalWithSensorDeployed { get; init; }
|
|
|
|
/// <summary>
|
|
/// Findings with runtime observations.
|
|
/// </summary>
|
|
public required int WithRuntimeCorroboration { get; init; }
|
|
|
|
/// <summary>
|
|
/// Coverage percentage.
|
|
/// </summary>
|
|
public decimal CoveragePercent => TotalWithSensorDeployed > 0
|
|
? (decimal)WithRuntimeCorroboration / TotalWithSensorDeployed * 100
|
|
: 0;
|
|
|
|
/// <summary>
|
|
/// Breakdown by posture.
|
|
/// </summary>
|
|
public required IReadOnlyDictionary<string, int> ByPosture { get; init; }
|
|
}
|
|
|
|
public sealed record ExplainabilityKpis
|
|
{
|
|
/// <summary>
|
|
/// Total verdicts generated.
|
|
/// </summary>
|
|
public required int TotalVerdicts { get; init; }
|
|
|
|
/// <summary>
|
|
/// Verdicts with reason steps.
|
|
/// </summary>
|
|
public required int WithReasonSteps { get; init; }
|
|
|
|
/// <summary>
|
|
/// Verdicts with at least one proof pointer.
|
|
/// </summary>
|
|
public required int WithProofPointer { get; init; }
|
|
|
|
/// <summary>
|
|
/// Verdicts that are "complete" (both reason steps AND proof pointer).
|
|
/// </summary>
|
|
public required int FullyExplainable { get; init; }
|
|
|
|
/// <summary>
|
|
/// Explainability completeness percentage.
|
|
/// </summary>
|
|
public decimal CompletenessPercent => TotalVerdicts > 0
|
|
? (decimal)FullyExplainable / TotalVerdicts * 100
|
|
: 0;
|
|
}
|
|
|
|
public sealed record ReplayKpis
|
|
{
|
|
/// <summary>
|
|
/// Total replay attempts.
|
|
/// </summary>
|
|
public required int TotalAttempts { get; init; }
|
|
|
|
/// <summary>
|
|
/// Successful replays (identical verdict).
|
|
/// </summary>
|
|
public required int Successful { get; init; }
|
|
|
|
/// <summary>
|
|
/// Replay success rate.
|
|
/// </summary>
|
|
public decimal SuccessRate => TotalAttempts > 0
|
|
? (decimal)Successful / TotalAttempts * 100
|
|
: 0;
|
|
|
|
/// <summary>
|
|
/// Common failure reasons.
|
|
/// </summary>
|
|
public required IReadOnlyDictionary<string, int> FailureReasons { get; init; }
|
|
}
|
|
|
|
public sealed record UnknownBudgetKpis
|
|
{
|
|
/// <summary>
|
|
/// Total environments tracked.
|
|
/// </summary>
|
|
public required int TotalEnvironments { get; init; }
|
|
|
|
/// <summary>
|
|
/// Budget breaches by environment.
|
|
/// </summary>
|
|
public required IReadOnlyDictionary<string, int> BreachesByEnvironment { get; init; }
|
|
|
|
/// <summary>
|
|
/// Total overrides/exceptions granted.
|
|
/// </summary>
|
|
public required int OverridesGranted { get; init; }
|
|
|
|
/// <summary>
|
|
/// Average override age (days).
|
|
/// </summary>
|
|
public decimal AvgOverrideAgeDays { get; init; }
|
|
}
|
|
|
|
public sealed record OperationalKpis
|
|
{
|
|
/// <summary>
|
|
/// Median time to first verdict (seconds).
|
|
/// </summary>
|
|
public required double MedianTimeToVerdictSeconds { get; init; }
|
|
|
|
/// <summary>
|
|
/// Cache hit rate for graphs/proofs.
|
|
/// </summary>
|
|
public required decimal CacheHitRate { get; init; }
|
|
|
|
/// <summary>
|
|
/// Average evidence size per scan (bytes).
|
|
/// </summary>
|
|
public required long AvgEvidenceSizeBytes { get; init; }
|
|
|
|
/// <summary>
|
|
/// 95th percentile verdict time (seconds).
|
|
/// </summary>
|
|
public required double P95VerdictTimeSeconds { get; init; }
|
|
}
|