namespace StellaOps.Scanner.Benchmark.Metrics;
///
/// Benchmark metrics for comparing scanner accuracy.
///
public sealed record BenchmarkMetrics
{
///
/// Number of true positive findings.
///
public required int TruePositives { get; init; }
///
/// Number of false positive findings.
///
public required int FalsePositives { get; init; }
///
/// Number of true negative findings.
///
public required int TrueNegatives { get; init; }
///
/// Number of false negative findings (missed vulnerabilities).
///
public required int FalseNegatives { get; init; }
///
/// Precision = TP / (TP + FP).
///
public double Precision => TruePositives + FalsePositives > 0
? (double)TruePositives / (TruePositives + FalsePositives)
: 0;
///
/// Recall = TP / (TP + FN).
///
public double Recall => TruePositives + FalseNegatives > 0
? (double)TruePositives / (TruePositives + FalseNegatives)
: 0;
///
/// F1 Score = 2 * (Precision * Recall) / (Precision + Recall).
///
public double F1Score => Precision + Recall > 0
? 2 * (Precision * Recall) / (Precision + Recall)
: 0;
///
/// Accuracy = (TP + TN) / (TP + TN + FP + FN).
///
public double Accuracy
{
get
{
var total = TruePositives + TrueNegatives + FalsePositives + FalseNegatives;
return total > 0 ? (double)(TruePositives + TrueNegatives) / total : 0;
}
}
///
/// The scanner tool name.
///
public required string ToolName { get; init; }
///
/// The image reference that was scanned.
///
public string? ImageRef { get; init; }
///
/// Timestamp when the benchmark was run.
///
public required DateTimeOffset Timestamp { get; init; }
}
///
/// Aggregated metrics across multiple images.
///
public sealed record AggregatedMetrics
{
///
/// The scanner tool name.
///
public required string ToolName { get; init; }
///
/// Total images scanned.
///
public required int TotalImages { get; init; }
///
/// Sum of true positives across all images.
///
public required int TotalTruePositives { get; init; }
///
/// Sum of false positives across all images.
///
public required int TotalFalsePositives { get; init; }
///
/// Sum of true negatives across all images.
///
public required int TotalTrueNegatives { get; init; }
///
/// Sum of false negatives across all images.
///
public required int TotalFalseNegatives { get; init; }
///
/// Aggregate precision.
///
public double Precision => TotalTruePositives + TotalFalsePositives > 0
? (double)TotalTruePositives / (TotalTruePositives + TotalFalsePositives)
: 0;
///
/// Aggregate recall.
///
public double Recall => TotalTruePositives + TotalFalseNegatives > 0
? (double)TotalTruePositives / (TotalTruePositives + TotalFalseNegatives)
: 0;
///
/// Aggregate F1 score.
///
public double F1Score => Precision + Recall > 0
? 2 * (Precision * Recall) / (Precision + Recall)
: 0;
///
/// Breakdown by severity.
///
public IReadOnlyDictionary? BySeverity { get; init; }
///
/// Breakdown by ecosystem.
///
public IReadOnlyDictionary? ByEcosystem { get; init; }
///
/// Individual image metrics.
///
public IReadOnlyList? PerImageMetrics { get; init; }
///
/// Timestamp when the aggregation was computed.
///
public required DateTimeOffset Timestamp { get; init; }
}