historian(sqlserver): time-weighted Part 13 aggregates #671

Closed
opened 2026-10-09 15:26:12 -04:00 by dohertj2 · 1 comment
Owner

The SQL Server backend pushes aggregates down to T-SQL using simple per-sample definitions. Average is the arithmetic mean of samples, and there is no time weighting or interpolated bounding values (see docs/Historian.md §"SQL Server backend", aggregate table). These are not full Part 13 definitions.

Scope: add TimeAverage, TimeAverage2 and Interpolative, plus Total time-weighted, with Part 13 bounding-value semantics, pushed down using window functions (LAG/LEAD at the bucket edges). Pin each one with hand-computed literals.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

The SQL Server backend pushes aggregates down to T-SQL using simple per-sample definitions. Average is the arithmetic mean of samples, and there is no time weighting or interpolated bounding values (see `docs/Historian.md` §"SQL Server backend", aggregate table). These are not full Part 13 definitions. **Scope:** add `TimeAverage`, `TimeAverage2` and `Interpolative`, plus `Total` time-weighted, with Part 13 bounding-value semantics, pushed down using window functions (LAG/LEAD at the bucket edges). Pin each one with hand-computed literals. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
Author
Owner

Implemented in 3b739b98 (merged to master).

  • New shared evaluator Driver.Historian.Sql.Common/TimeWeightedAggregator.cs (both SQL backends, identical results) for the Part 13 time-weighted family: TimeAverage, TimeAverage2, Total, Total2, Interpolative (window + 4 bound rows streamed into C#). Sloped for numeric, stepped for Boolean; server-default AggregateConfiguration (client's ignored); status = the SDK AggregateCalculator time-based rule (Uncertain time counts as Good); Partial bit set; reverse Interpolative anchored at the later time.
  • ⚠️ Client-visible: Total is now the time-weighted integral (matching the Gateway backend), no longer SUM.
  • New ServerHistorian:MaxTimeWeightedRawRows (default 1,000,000) — past it ⇒ BadTooManyOperations for that node.

Live gate (docker-dev, gate 2) — PASS on SqlServer and QuestDB: sparse tag interpolates Good (not BadNoData); Total ≈ TimeAverage × seconds and Total2 = TimeAverage2 × good seconds; across an outage TimeAverage2/Total2 exclude the Bad span while TimeAverage interpolates, all-Bad buckets come back Bad; backends match (bucket times + statuses exact, values within ~5e-8 from QuestDB µs); reverse Interpolative anchored at the later time; the raw-row cap returns BadTooManyOperations with a WRN log while small windows are served.

Follow-ups filed: reverse anchoring for the other aggregates, per-sample Partial bit, AVEVA integral unit, client AggregateConfiguration, CLI aggregate list.

Implemented in `3b739b98` (merged to master). - New shared evaluator `Driver.Historian.Sql.Common/TimeWeightedAggregator.cs` (both SQL backends, identical results) for the Part 13 time-weighted family: **TimeAverage, TimeAverage2, Total, Total2, Interpolative** (window + 4 bound rows streamed into C#). Sloped for numeric, stepped for Boolean; server-default AggregateConfiguration (client's ignored); status = the SDK `AggregateCalculator` time-based rule (Uncertain time counts as Good); Partial bit set; reverse `Interpolative` anchored at the later time. - ⚠️ **Client-visible:** `Total` is now the time-weighted integral (matching the Gateway backend), no longer `SUM`. - New `ServerHistorian:MaxTimeWeightedRawRows` (default 1,000,000) — past it ⇒ `BadTooManyOperations` for that node. **Live gate (docker-dev, gate 2) — PASS on SqlServer and QuestDB:** sparse tag interpolates Good (not BadNoData); Total ≈ TimeAverage × seconds and Total2 = TimeAverage2 × good seconds; across an outage TimeAverage2/Total2 exclude the Bad span while TimeAverage interpolates, all-Bad buckets come back Bad; backends match (bucket times + statuses exact, values within ~5e-8 from QuestDB µs); reverse Interpolative anchored at the later time; the raw-row cap returns BadTooManyOperations with a WRN log while small windows are served. Follow-ups filed: reverse anchoring for the other aggregates, per-sample Partial bit, AVEVA integral unit, client AggregateConfiguration, CLI aggregate list.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#671