Time-weighted aggregates: forward differences from the SDK AggregateCalculator (TimeAverage/Total status, Total at start of data, Partial on truncated) #680
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the in-process SDK parity test added for #677 (
SdkAggregateParityTests, which drives the OPC UA .NET Standard SDK's ownAggregateCalculators against the SQL backends' results). Reverse anchoring now matches the SDK; these forward differences pre-date #677 and are masked in the parity test and documented indocs/Historian.md:Smaller, also masked: reverse Interpolative exactly at a sample (SDK flags Interpolated, we return the raw sample); per-sample Count on an empty interval (SDK 0/Good, counts Good samples only); the SDK's forward end-of-data Partial rule is not implemented.
Decide per item: align with the SDK (client-visible status/value change on SqlServer/QuestDB) or keep and document as an intentional deviation.
Fixed — merged
f6a26286(time-weighted lane),11f79253(per-sample lane),d266ff83(live-gate fix). Part 13 is treated as authoritative; the SDKAggregateCalculator(1.5.378, decompiled) is the parity reference where it agrees.Aligned (client-visible on SqlServer / QuestDB):
MaxTimeWeightedRawRows.BadNoData; a recovery value after an outage marker / a tag's only sample ⇒ its value, Uncertain + Partial.0/Good.Kept, documented as intentional: Partial on truncated intervals (spec §5.3.3.2 Case 3 requires it; SDK non-conformant); reverse Interpolative exactly at a sample (SDK asymmetry); Partial never on Bad results; TimeAverage2/Total2 all-outage interval generic Bad (the docs' "SDK all-Bad result" wording was wrong); outage marker as last sample runs Bad to window end; Boolean stepped values; Min/Max-at-interval-start Raw vs Calculated bit; integer tags keep Double interpolation.
⚠️ Report-by-exception consequence: a tag with nothing after its last sample reads
BadNoDatafor TA2/Total2/PercentGood/PercentBad and Uncertain for TA/Total in its trailing intervals; they change on re-read once a new sample arrives. Caching consumers should re-read recent intervals.Verification:
SdkAggregateParityTestsmasks removed (new datasets: EndsInside, OutageInside, OutageLast, RecoveryEnds, BoolSwitch, Gappy, Bracketed, EarlyEnd, EndOnEdge, OutageEnds); every production change falsified; full suite green. Live gate 8 (docker-dev, both backends, central-1/2): D1 seeded edge cases vs the SDK calculator — every mismatch is a documented deviation; D2 live tag — SqlServer/QuestDB identical on 434 result rows; D3 percent row cap ⇒ BadTooManyOperations; D4 #679.Gate defect F1 (fixed
2ceed561): on QuestDB the new Start/End/Delta query referenced its CTE 4 times; a row committed between readers left a null first/last ⇒InvalidCastException⇒ the node's read failed (~1 in 17k–90k reads of a tag being written). Now one GROUP BY scan (QuestDB) / one windowed pass (SqlServer), and the bucket readers treat a missing pick as "no sample". Opt-in stress test (HISTORIAN_QUESTDB_STRESS_SECONDS): old code 6 faults / ~105k reads, new 0 / 106k.Follow-ups: #683 (QuestDB data-range lookup is a partition scan; SqlServer StartEnd cost), #684 (Average/Min/Max include Uncertain samples; spec/SDK use Good only).