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; } }