feat(insights): hit-rate breakdown by detector kind
Adds a "By detector kind" breakdown to the anomaly outcome report + Insights page, answering "which directional detector actually wins?". Only directional kinds (SuspensionFlip, SteamMove) resolve to hit/miss, so the breakdown naturally shows just those — mirrors the existing by-sport / by-severity bucketers and rendering. - AnomalyOutcomeReport.ByKind + EvaluateAnomalyOutcomesUseCase.BuildKindBuckets (keyed OutcomeBucketKeys.KindPrefix + enum name); threaded through the insights VM/service. - Insights page: new section + BucketRenderKind.Kind label case (resolves the enum name to the Anomaly.Kind.* resx); en/ru section heading. - 2 tests: empty-report ByKind + directional-kind grouping.
This commit is contained in:
@@ -124,6 +124,7 @@ public sealed class EvaluateAnomalyOutcomesUseCase
|
||||
BySeverity: BuildSeverityBuckets(resolvedOrdered),
|
||||
BySport: BuildSportBuckets(resolvedOrdered),
|
||||
ByScoreBin: BuildScoreBins(resolvedOrdered),
|
||||
ByKind: BuildKindBuckets(resolvedOrdered),
|
||||
Resolved: resolvedOrdered,
|
||||
Unresolved: unresolvedOrdered,
|
||||
EventTitles: eventTitles);
|
||||
@@ -171,6 +172,20 @@ public sealed class EvaluateAnomalyOutcomesUseCase
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<OutcomeBucket> BuildKindBuckets(
|
||||
IReadOnlyCollection<ResolvedAnomaly> resolved)
|
||||
{
|
||||
// Only directional kinds resolve to a hit/miss (the evaluator leaves the rest
|
||||
// Unresolved), so this naturally shows just the directional detectors.
|
||||
return resolved
|
||||
.GroupBy(r => r.Kind)
|
||||
.OrderBy(g => (int)g.Key)
|
||||
.Select(g => BuildBucket(
|
||||
key: OutcomeBucketKeys.KindPrefix + g.Key,
|
||||
items: g))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<OutcomeBucket> BuildScoreBins(
|
||||
IReadOnlyCollection<ResolvedAnomaly> resolved)
|
||||
{
|
||||
@@ -239,6 +254,7 @@ public sealed class EvaluateAnomalyOutcomesUseCase
|
||||
BySeverity: Array.Empty<OutcomeBucket>(),
|
||||
BySport: Array.Empty<OutcomeBucket>(),
|
||||
ByScoreBin: Array.Empty<OutcomeBucket>(),
|
||||
ByKind: Array.Empty<OutcomeBucket>(),
|
||||
Resolved: Array.Empty<ResolvedAnomaly>(),
|
||||
Unresolved: Array.Empty<ResolvedAnomaly>(),
|
||||
EventTitles: new Dictionary<DomainEventId, string>());
|
||||
|
||||
Reference in New Issue
Block a user