using Opc.Ua;
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
///
/// The Part 13 processed reads are evaluated under, and the rule for a
/// client-supplied one in ReadProcessedDetails.
///
///
///
/// The aggregate evaluators (AggregateEvaluator / TimeWeightedAggregator in
/// Driver.Historian.Sql.Common) are written against fixed settings, not parameterised: Uncertain time counts as
/// Good ( = false) in TimeAverage2 / Total2, while PercentGood / DurationGood count
/// Good time only and PercentBad / DurationBad Bad time only โ Uncertain in neither (Part 13 ยง5.4.3.31-34, Mantis
/// 0006166); the per-sample Average / Minimum /
/// Maximum / Range / *ActualTime / Count are defined by Part 13 over Good values whatever this setting, as the
/// SDK computes them under it; an interval is Good only when all of
/// it is ( = 100) and Bad only when none of it is non-Bad
/// ( = 100), otherwise UncertainDataSubNormal; the value past the last sample
/// is held (stepped, = false). Part 13's AggregateConfiguration defaults are
/// TreatUncertainAsBad = false, PercentDataBad = PercentDataGood = 100, UseSlopedExtrapolation = false (from the
/// spec as recalled, not re-read offline). The SDK matched these until Opc.Ua.Server 1.5.378.182, which flipped
/// TreatUncertainAsBad to true: that is a DELIBERATE DIVERGENCE we do not follow (consumers such as
/// ScadaBridge read these aggregates). OtOpcUaSdkServer.CreateAggregateManager pushes these values into
/// the SDK manager so live AggregateFilter subscriptions agree with history reads; a test pins both sides.
///
///
/// The live calculators are the application's frozen 1.5.378.182 copies (Compatibility/Aggregates), whose
/// ComputeDurationGoodBad counts Uncertain time as Good under = false. That
/// made live PercentGood / DurationGood disagree with HistoryRead whenever Uncertain data was present. #809
/// (owner-approved 2026-11-11) overrides exactly those two (GoodOnlyStatusAggregateCalculator), so live and
/// HistoryRead now agree on them โ pinned by
/// SdkAggregateParityTests.Live_AggregateFilter_PercentGood_and_DurationGood_equal_HistoryRead_over_Uncertain_samples.
/// Other known live-vs-HistoryRead differences (e.g. #821, an Uncertain first sample in a late-start interval) are
/// documented in docs/Historian.md.
///
///
/// A request asking for UseServerCapabilitiesDefaults, or spelling out exactly these values, is served;
/// any other configuration is refused per node with BadAggregateConfigurationRejected rather than silently
/// answered under settings the client did not ask for (it used to be ignored).
///
///
public static class ServerAggregateConfiguration
{
/// Uncertain data is not treated as Bad.
public const bool TreatUncertainAsBad = false;
/// An interval is Bad only when 100 % of it is Bad.
public const byte PercentDataBad = 100;
/// An interval is Good only when 100 % of it is Good.
public const byte PercentDataGood = 100;
/// The value past the last sample is held (stepped), not sloped.
public const bool UseSlopedExtrapolation = false;
/// The server defaults as an (explicit values, not "use defaults").
public static AggregateConfiguration Defaults => new()
{
UseServerCapabilitiesDefaults = false,
TreatUncertainAsBad = TreatUncertainAsBad,
PercentDataBad = PercentDataBad,
PercentDataGood = PercentDataGood,
UseSlopedExtrapolation = UseSlopedExtrapolation,
};
///
/// True when a processed read with can be served: none supplied,
/// UseServerCapabilitiesDefaults, or every setting equal to the server's.
///
/// The request's configuration (may be null).
/// Whether to serve; false โ BadAggregateConfigurationRejected.
public static bool IsServable(AggregateConfiguration? requested) =>
requested is null
|| requested.UseServerCapabilitiesDefaults
|| (requested.TreatUncertainAsBad == TreatUncertainAsBad
&& requested.PercentDataBad == PercentDataBad
&& requested.PercentDataGood == PercentDataGood
&& requested.UseSlopedExtrapolation == UseSlopedExtrapolation);
}