Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common/BucketRow.cs
T
Joseph Doherty c0d6bacd9c fix(historian): #684 review — Uncertain non-numeric samples do not refuse the Good-value aggregates; real-SQL Uncertain-above-max test
- Average/Minimum/Maximum/Range/*ActualTime refuse (BadAggregateInvalidInputs) only a GOOD
  String/DateTime sample (new GoodNonNumericCount / good_non_numeric_count column); an Uncertain
  one is skipped and makes the min/max family Uncertain, matching the SDK (Average skips it,
  ComputeMinMax flags the uncastable value). Delta keeps its usable-sample refusal.
- ProcessedConformance: an Uncertain value above the Good maximum (pins UncMax/unc_max through
  the real SQL) and an Uncertain String through the real SQL.
- The_uncertain_dataset_is_not_vacuous also proves the min/max STATUS discrimination.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-12 09:54:51 -04:00

95 lines
4.6 KiB
C#

using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common;
/// <summary>
/// Per-bucket statistics one aggregate query row produces (one non-empty bucket). Every backend's SQL fills the same
/// fields; <see cref="AggregateEvaluator"/> turns them into the OPC UA value and status.
/// </summary>
/// <remarks>
/// Sample classes: <i>usable</i> = severity Good or Uncertain AND a stored value (an Empty sample is an outage marker
/// and counts as Bad whatever its code); <i>good</i> = usable with severity Good; <i>bad</i> = not usable.
/// <see cref="Avg"/> / <see cref="Min"/> / <see cref="Max"/> / <see cref="Sum"/> / <see cref="Extreme"/> are computed over
/// <b>Good</b> numeric samples only (#684 — Part 13 defines Average / Minimum / Maximum / Range / *ActualTime over Good
/// raw values, as the SDK's calculators do); <see cref="UncertainMin"/> / <see cref="UncertainMax"/> over the Uncertain
/// ones (the Part 13 Minimum/Maximum/Range status rule); <see cref="First"/> / <see cref="Last"/> over usable samples;
/// <see cref="FirstAny"/> / <see cref="LastAny"/> over every sample.
/// </remarks>
public sealed class BucketRow
{
/// <summary>Zero-based bucket index within the window.</summary>
public long Bucket { get; set; }
/// <summary>All samples in the bucket.</summary>
public int Total { get; set; }
/// <summary>Usable samples.</summary>
public int Usable { get; set; }
/// <summary>Good samples.</summary>
public int Good { get; set; }
/// <summary>Bad (not usable) samples.</summary>
public int Bad { get; set; }
/// <summary>Usable String / DateTime samples (Delta's refusal check).</summary>
public int NonNumeric { get; set; }
/// <summary>
/// <b>Good</b> String / DateTime samples — the refusal check of the Good-value aggregates (Average / Minimum / Maximum /
/// Range / *ActualTime, #684 review): an Uncertain non-numeric sample is not part of their value set. Filled by the
/// Simple and ActualTime queries; null (a row that does not carry it) falls back to <see cref="NonNumeric"/>, the
/// pre-#684 (stricter) check.
/// </summary>
public int? GoodNonNumeric { get; set; }
/// <summary><see cref="GoodNonNumeric"/>, or <see cref="NonNumeric"/> when the row does not carry it.</summary>
internal int GoodNonNumericOrAll => GoodNonNumeric ?? NonNumeric;
/// <summary>Average of the Good numeric values (null when none — #684).</summary>
public double? Avg { get; set; }
/// <summary>Minimum of the Good numeric values (null when none — #684).</summary>
public double? Min { get; set; }
/// <summary>Maximum of the Good numeric values (null when none — #684).</summary>
public double? Max { get; set; }
/// <summary>Sum of the Good numeric values (null when none — #684).</summary>
public double? Sum { get; set; }
/// <summary>First usable sample's value by time (Empty when none).</summary>
public HistorianValue First { get; set; }
/// <summary>Last usable sample's value by time (Empty when none).</summary>
public HistorianValue Last { get; set; }
/// <summary>Extreme (min or max) Good numeric value (null when none — #684).</summary>
public double? Extreme { get; set; }
/// <summary>Time of <see cref="Extreme"/> (ties ⇒ earliest; null when none).</summary>
public DateTime? ExtremeTime { get; set; }
/// <summary>
/// Minimum of the bucket's <b>Uncertain</b> (usable, not Good) numeric values (null when none). Not a result value:
/// Part 13 makes Minimum / MinimumActualTime / Range <c>Uncertain_DataSubNormal</c> when an Uncertain value is less
/// than the minimum Good value (#684).
/// </summary>
public double? UncertainMin { get; set; }
/// <summary>
/// Maximum of the bucket's Uncertain numeric values (null when none): Maximum / MaximumActualTime / Range are
/// Uncertain when one is greater than the maximum Good value (#684).
/// </summary>
public double? UncertainMax { get; set; }
/// <summary>
/// The bucket's first sample by time of <b>any</b> quality (null when the bucket is empty): Start returns it raw, and
/// Delta is Uncertain when it is not usable (#680, Part 13 Tables 73/74 — the SDK's <c>StartEndAggregateCalculator</c>).
/// </summary>
public StoredSample? FirstAny { get; set; }
/// <summary>The bucket's last sample by time of any quality (null when the bucket is empty): End, Delta (#680).</summary>
public StoredSample? LastAny { get; set; }
}