using System.Globalization; using System.Security.Cryptography; using System.Text.Json; using Opc.Ua; using Opc.Ua.Server; if (args.Length != 1) throw new ArgumentException("Pass a fresh output JSON path."); if (File.Exists(args[0])) throw new IOException("Refusing to overwrite existing baseline evidence."); var epoch = new DateTime(2026, 10, 1, 8, 0, 0, DateTimeKind.Utc).AddTicks(1234567); var telemetry = DefaultTelemetry.Create(_ => { }); var offsets = new double[] { -10, 0, 5, 15, 25, 35, 50, 80 }; var data = new Dictionary { ["double"] = offsets.Select((s, i) => new Sample(s, "Double", (10 + i * 7 % 13).ToString(CultureInfo.InvariantCulture), StatusCodes.Good)).ToArray(), ["uncertain"] = offsets.Select((s, i) => new Sample(s, i == 4 ? "Null" : "Double", i == 4 ? null : i == 2 ? "100" : (10 + i * 7 % 13).ToString(CultureInfo.InvariantCulture), i == 2 ? StatusCodes.UncertainDataSubNormal : i == 4 ? StatusCodes.BadNoCommunication : StatusCodes.Good)).ToArray(), ["integer"] = offsets.Select((s, i) => new Sample(s, "Int64", (i * 7 - 10).ToString(CultureInfo.InvariantCulture), i == 3 ? StatusCodes.UncertainDataSubNormal : StatusCodes.Good)).ToArray(), ["boolean"] = offsets.Select((s, i) => new Sample(s, "Boolean", (i % 2 == 0).ToString(), i == 2 ? StatusCodes.UncertainDataSubNormal : StatusCodes.Good)).ToArray(), ["sparse"] = [new(-10, "Double", "10", StatusCodes.Good), new(7, "Double", "20", StatusCodes.Good), new(18, "Null", null, StatusCodes.BadNoCommunication), new(45, "Double", "30", StatusCodes.Good)], ["bad"] = offsets.Select(s => new Sample(s, "Null", null, StatusCodes.BadNoCommunication)).ToArray(), ["empty"] = [], }; var names = new string[] { "AggregateFunction_Interpolative", "AggregateFunction_Average", "AggregateFunction_TimeAverage", "AggregateFunction_TimeAverage2", "AggregateFunction_Total", "AggregateFunction_Total2", "AggregateFunction_Minimum", "AggregateFunction_Maximum", "AggregateFunction_MinimumActualTime", "AggregateFunction_MaximumActualTime", "AggregateFunction_Range", "AggregateFunction_Minimum2", "AggregateFunction_Maximum2", "AggregateFunction_MinimumActualTime2", "AggregateFunction_MaximumActualTime2", "AggregateFunction_Range2", "AggregateFunction_Count", "AggregateFunction_AnnotationCount", "AggregateFunction_DurationInStateZero", "AggregateFunction_DurationInStateNonZero", "AggregateFunction_NumberOfTransitions", "AggregateFunction_Start", "AggregateFunction_End", "AggregateFunction_Delta", "AggregateFunction_StartBound", "AggregateFunction_EndBound", "AggregateFunction_DeltaBounds", "AggregateFunction_DurationGood", "AggregateFunction_DurationBad", "AggregateFunction_PercentGood", "AggregateFunction_PercentBad", "AggregateFunction_WorstQuality", "AggregateFunction_WorstQuality2", "AggregateFunction_StandardDeviationPopulation", "AggregateFunction_VariancePopulation", "AggregateFunction_StandardDeviationSample", "AggregateFunction_VarianceSample", }; var vectors = new List(); foreach (var name in names) foreach (var dataset in data) foreach (var reverse in new[] { false, true }) foreach (var stepped in new[] { false, true }) foreach (var uncertainAsBad in new[] { false, true }) foreach (var incremental in new[] { false, true }) { var id = (NodeId)typeof(ObjectIds).GetField(name)!.GetValue(null)!; var start = epoch.AddSeconds(reverse ? 70 : 0); var end = epoch.AddSeconds(reverse ? 0 : 70); var config = new AggregateConfiguration { TreatUncertainAsBad = uncertainAsBad, PercentDataBad = 100, PercentDataGood = 100, UseSlopedExtrapolation = false, UseServerCapabilitiesDefaults = false }; var calculator = Aggregators.CreateStandardCalculator(id, start, end, 20_000, stepped, config, telemetry); var output = new List(); string? error = null; try { var samples = dataset.Value.OrderBy(x => x.Seconds); foreach (var sample in reverse ? samples.Reverse() : samples) { var time = epoch.AddSeconds(sample.Seconds); calculator.QueueRawValue(new DataValue(new Variant(Payload(sample)!), sample.Status, time, time)); if (incremental) Drain(false); } Drain(true); } catch (Exception ex) { error = ex.GetType().FullName; } vectors.Add(new(name, dataset.Key, reverse, stepped, uncertainAsBad, incremental, output.ToArray(), error)); void Drain(bool partial) { for (var guard = 0; guard < 100; guard++) { var value = calculator.GetProcessedValue(partial); if (value is null) return; output.Add(new(partial, value.Value?.GetType().Name, Format(value.Value), value.StatusCode.Code, value.SourceTimestamp.Ticks, value.ServerTimestamp.Ticks, value.SourcePicoseconds, value.ServerPicoseconds)); } throw new InvalidOperationException("Baseline calculator did not terminate."); } } var assembly = typeof(Aggregators).Assembly.Location; var baseline = new { sdk = "1.5.378.182", assemblySha256 = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(assembly))), epochTicks = epoch.Ticks, intervalMilliseconds = 20_000, data, vectors }; Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(args[0]))!); File.WriteAllText(args[0], JsonSerializer.Serialize(baseline, new JsonSerializerOptions { WriteIndented = true }) + "\n"); Console.WriteLine($"Generated {vectors.Count} vectors for {names.Length} aggregate IDs; baseline assembly SHA256 {baseline.assemblySha256}."); static object? Payload(Sample sample) => sample.Type switch { "Double" => double.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Int64" => long.Parse(sample.Payload!, CultureInfo.InvariantCulture), "Boolean" => bool.Parse(sample.Payload!), _ => null, }; static string? Format(object? value) => value switch { null => null, double number => number.ToString("R", CultureInfo.InvariantCulture), float number => number.ToString("R", CultureInfo.InvariantCulture), IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture), _ => value.ToString(), }; record Sample(double Seconds, string Type, string? Payload, uint Status); record Value(bool PartialRequested, string? Type, string? Payload, uint Status, long SourceTicks, long ServerTicks, ushort SourcePicoseconds, ushort ServerPicoseconds); record Vector(string Aggregate, string DataSet, bool Reverse, bool Stepped, bool UncertainAsBad, bool Incremental, Value[] Output, string? Exception);