refactor(kpi): K4/K10/K12 review fixups — test data-race + faulted-tick liveness, dead-branch/unused removal, NaN-guard assertions, value clamp + doc

This commit is contained in:
Joseph Doherty
2026-06-17 20:15:47 -04:00
parent 5613a5efb7
commit cb2a516187
6 changed files with 105 additions and 39 deletions
@@ -82,9 +82,6 @@ public partial class KpiTrendChart
/// <summary>Exactly one sample — show the single-sample note, not a polyline.</summary>
private bool HasSingleSample => IsAvailable && Series.Count == 1;
/// <summary>Available + ≥1 point but not a full chart, or unavailable / empty.</summary>
private bool ShowPlaceholder => !IsAvailable || Series.Count == 0;
/// <summary>
/// Stable Playwright hook: <c>kpi-trend-&lt;slug&gt;</c> where the slug is the
/// title lowercased with each run of non-alphanumerics collapsed to a single
@@ -175,13 +172,17 @@ public partial class KpiTrendChart
var p = Series[i];
// X — by time fraction, or even index spacing when all timestamps equal.
// n >= 2 is guaranteed by the HasChart precondition (Series.Count >= 2),
// so the (n - 1) divisor is always ≥ 1 and the n == 1 arm is unreachable.
double xFrac = timeSpanTicks > 0
? (p.BucketStartUtc.Ticks - firstTicks) / timeSpanTicks
: (n == 1 ? 0.0 : (double)i / (n - 1));
: (double)i / (n - 1);
var x = PadX + (xFrac * plotW);
// Y — baseline at 0, top at max. Flat at baseline when max == 0.
double yFrac = max > 0 ? p.Value / max : 0.0;
// Clamp to non-negative so a stale negative Value cannot push a point
// above the baseline or outside the viewBox.
double yFrac = max > 0 ? Math.Max(0, p.Value) / max : 0.0;
var y = baselineY - (yFrac * plotH);
if (i > 0)
@@ -27,7 +27,8 @@ public static class KpiSeriesBucketer
/// An <see cref="IReadOnlyList{T}"/> of at most <paramref name="maxPoints"/> bucketed points,
/// ordered by <see cref="KpiSeriesPoint.BucketStartUtc"/> ascending.
/// Returns <paramref name="raw"/> unchanged (same reference) when
/// <c>raw.Count &lt;= maxPoints</c>.
/// <c>raw.Count &lt;= maxPoints</c>; callers must not mutate the underlying
/// collection in that case, as it is the same object passed in.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="maxPoints"/> &lt; 2 or
@@ -87,9 +87,9 @@ public class KpiHistoryRecorderActor : ReceiveActor, IWithTimers
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
Receive<SampleTick>(_ => HandleSampleTick());
Receive<SampleComplete>(_ => { });
Receive<SampleComplete>(_ => { }); // best-effort: no actor state to reset on completion
Receive<PurgeTick>(_ => HandlePurgeTick());
Receive<PurgeComplete>(_ => { });
Receive<PurgeComplete>(_ => { }); // best-effort: no actor state to reset on completion
}
/// <inheritdoc />