feat(kpihistory): add hourly rollup fold + query + purge repository methods (plan #22 T3)
Add FoldHourlyRollupsAsync (in-memory grouped, per-metric gauge-last/rate-sum, idempotent upsert on the series+hour key with null-ScopeKey equality, exclusive upper hour bound so the in-progress hour is never folded), GetHourlySeriesAsync (same KpiSeriesPoint contract as GetRawSeriesAsync), and PurgeRollupsOlderThanAsync (one-hour-sliced batched DELETE mirroring PurgeOlderThanAsync). Adds 7 repo tests. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -50,4 +50,75 @@ public interface IKpiHistoryRepository
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task resolving to the number of rows deleted.</returns>
|
||||
Task<int> PurgeOlderThanAsync(DateTime before, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Folds the raw <c>KpiSample</c> rows in the window into the hourly
|
||||
/// <c>KpiRollupHourly</c> table — one row per
|
||||
/// (<c>Source</c>, <c>Metric</c>, <c>Scope</c>, <c>ScopeKey</c>) per UTC hour.
|
||||
/// The window is truncated to whole-hour boundaries: only the complete hours in
|
||||
/// <c>[truncate(fromHourUtc), truncate(toHourUtc))</c> are folded — the upper bound
|
||||
/// is <em>exclusive</em>, so a caller that passes the current hour's start as
|
||||
/// <paramref name="toHourUtc"/> never folds the in-progress (incomplete) hour.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For each (series, hour) group the fold records <c>SampleCount</c> (raw rows in the
|
||||
/// group), <c>MinValue</c>/<c>MaxValue</c> (min/max raw value), and a <c>Value</c>
|
||||
/// whose aggregation is chosen per metric by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiMetricAggregationCatalog.Resolve(string, string)"/>:
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiRollupAggregation.Gauge"/> stores the
|
||||
/// last (latest-timestamp) value in the hour; <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiRollupAggregation.Rate"/>
|
||||
/// stores the sum of values in the hour.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The write is an idempotent upsert keyed on
|
||||
/// (<c>Source</c>, <c>Metric</c>, <c>Scope</c>, <c>ScopeKey</c>, <c>HourStartUtc</c>)
|
||||
/// with <c>null</c> <c>ScopeKey</c> participating in equality: an existing row is updated
|
||||
/// in place, otherwise a new row is inserted. Re-running the fold over the same window
|
||||
/// yields identical rows (no duplicates, no double-counting), so a singleton-failover
|
||||
/// handover that missed a tick self-heals on the next re-fold. Metrics that are only
|
||||
/// conditionally emitted (missing in some minutes) are handled naturally by grouping.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="fromHourUtc">Inclusive lower bound of the fold window (UTC), truncated to the hour.</param>
|
||||
/// <param name="toHourUtc">Exclusive upper bound of the fold window (UTC), truncated to the hour; pass the current hour's start to exclude the in-progress hour.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous fold + upsert.</returns>
|
||||
Task FoldHourlyRollupsAsync(
|
||||
DateTime fromHourUtc, DateTime toHourUtc, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hourly rollup points for one series in <c>[fromUtc, toUtc]</c>, ordered by
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiRollupHourly.HourStartUtc"/>
|
||||
/// ascending. Same contract and return shape as
|
||||
/// <see cref="GetRawSeriesAsync"/> (each point carries the hour start plus the folded
|
||||
/// <c>Value</c>) so the downstream bucketer is agnostic to raw-vs-rollup source.
|
||||
/// </summary>
|
||||
/// <param name="source">Source identifier — a value from <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiSources"/>.</param>
|
||||
/// <param name="metric">Metric name from the source's catalog.</param>
|
||||
/// <param name="scope">Scope discriminator — a value from <see cref="ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi.KpiScopes"/>.</param>
|
||||
/// <param name="scopeKey">Scope qualifier (site id / node name); <c>null</c> for the Global scope.</param>
|
||||
/// <param name="fromUtc">Inclusive lower bound of the time window (UTC).</param>
|
||||
/// <param name="toUtc">Inclusive upper bound of the time window (UTC).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A task resolving to the hourly series points in ascending hour order; empty when the
|
||||
/// series has no rollups in the window.
|
||||
/// </returns>
|
||||
Task<IReadOnlyList<KpiSeriesPoint>> GetHourlySeriesAsync(
|
||||
string source, string metric, string scope, string? scopeKey,
|
||||
DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk-deletes hourly rollup rows whose
|
||||
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi.KpiRollupHourly.HourStartUtc"/> is
|
||||
/// strictly older than <paramref name="before"/> (rollup retention purge). Mirrors
|
||||
/// <see cref="PurgeOlderThanAsync"/>: the purge is sliced into one-hour DELETE batches
|
||||
/// so a large catch-up never runs as a single lock/log-heavy transaction
|
||||
/// (arch-review 04, P4).
|
||||
/// </summary>
|
||||
/// <param name="before">UTC cut-off; rollups whose hour start is before this instant are deleted.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A task that represents the asynchronous purge.</returns>
|
||||
Task PurgeRollupsOlderThanAsync(DateTime before, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user