fix(historian): #680 lane B — reverse end-of-data Partial (strictly inside), drop dead per-sample Percent cases

Per-sample aggregates (all but Average) now set Partial on a reverse read's interval
that strictly contains the tag's latest sample — the SDK's reverse m_startOfData
(verified vs the SDK harness), matching lane A's time-weighted rule. NeedsDataRange
therefore applies in both directions for every per-sample aggregate but Average.
PercentGood/Bad left the per-sample evaluator (lane A moved them to the time-weighted
family); Evaluate refuses non-per-sample aggregates up front. Comments fixed.
This commit is contained in:
Joseph Doherty
2026-10-11 16:00:13 -04:00
parent dbfe2fc4de
commit b8344bb151
8 changed files with 75 additions and 75 deletions
@@ -156,15 +156,18 @@ public sealed class BucketPlan
earliestSampleUtc is { } e && e.Ticks > LowerTicks(index) && e.Ticks < UpperTicks(index);
/// <summary>
/// True when the latest stored sample of the tag lies in bucket <paramref name="index"/> (its earlier edge
/// included): the data ends part-way through the interval, so Part 13 §5.3.3.2 / the SDK set the Partial bit on a
/// forward read (#680, the SDK's <c>m_endOfData &gt;= startTime &amp;&amp; m_endOfData &lt; endTime</c>).
/// True when the latest stored sample of the tag lies in bucket <paramref name="index"/>: the data ends part-way
/// through the interval, so Part 13 §5.3.3.2 / the SDK set the Partial bit (#680). Forward the earlier edge is
/// included (the SDK's <c>m_endOfData &gt;= startTime &amp;&amp; m_endOfData &lt; endTime</c>); reverse the sample
/// must lie strictly inside (the SDK's reverse read keys <c>m_startOfData</c> — a strict test — on the first value it
/// is fed, the latest).
/// </summary>
/// <param name="index">Zero-based ascending bucket index.</param>
/// <param name="latestSampleUtc">The tag's latest stored sample (any quality), or null when it has none.</param>
/// <returns>Whether the data ends inside the bucket.</returns>
public bool DataEndsInside(long index, DateTime? latestSampleUtc) =>
latestSampleUtc is { } l && l.Ticks >= LowerTicks(index) && l.Ticks < UpperTicks(index);
latestSampleUtc is { } l
&& (Reversed ? l.Ticks > LowerTicks(index) : l.Ticks >= LowerTicks(index)) && l.Ticks < UpperTicks(index);
/// <summary>
/// True when bucket <paramref name="index"/> overlaps the tag's data range <c>[earliest, latest]</c> — neither