5a878b78d4
Add missing <summary>/<param>/<returns>/<typeparam> tags and switch interface implementations to <inheritdoc/> across 106 files; strip project bookkeeping identifiers (Task NN, #05-TNN, PLAN-04, StoreAndForward-0NN) from shipped code comments while preserving the descriptive rationale. Comment-only: zero code-logic lines changed; solution builds 0/0. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
110 lines
5.7 KiB
C#
110 lines
5.7 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CLI;
|
|
|
|
/// <summary>
|
|
/// Serializes typed alarm-setpoint CLI flags into the trigger-config JSON the
|
|
/// server expects. Key names MUST stay in lockstep with the canonical codec at
|
|
/// src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs
|
|
/// (that codec is internal to CentralUI, so this is a deliberate CLI-side mirror;
|
|
/// the round-trip test verifies the JSON against the live server — the real contract).
|
|
/// </summary>
|
|
internal static class AlarmTriggerConfigJson
|
|
{
|
|
/// <summary>
|
|
/// Builds the trigger-config JSON for <paramref name="triggerType"/> from the typed
|
|
/// flags, or returns null when none are supplied (so the alarm is created without a
|
|
/// trigger config). Unknown/blank trigger types yield null.
|
|
/// </summary>
|
|
/// <param name="triggerType">The trigger type string (case-insensitive).</param>
|
|
/// <param name="attribute">Attribute name (non-Expression trigger types).</param>
|
|
/// <param name="matchValue">ValueMatch: value to compare against.</param>
|
|
/// <param name="notEquals">ValueMatch: invert the comparison.</param>
|
|
/// <param name="min">RangeViolation: minimum allowed value.</param>
|
|
/// <param name="max">RangeViolation: maximum allowed value.</param>
|
|
/// <param name="thresholdPerSecond">RateOfChange: rate threshold per second.</param>
|
|
/// <param name="windowSeconds">RateOfChange: sliding window in seconds.</param>
|
|
/// <param name="direction">RateOfChange: direction (rising|falling|either).</param>
|
|
/// <param name="loLo">HiLo: low-low setpoint.</param>
|
|
/// <param name="lo">HiLo: low setpoint.</param>
|
|
/// <param name="hi">HiLo: high setpoint.</param>
|
|
/// <param name="hiHi">HiLo: high-high setpoint.</param>
|
|
/// <param name="expression">Expression: boolean trigger expression.</param>
|
|
/// <param name="analysisKind">
|
|
/// Optional analysis kind for Expression triggers ("strict" → emits
|
|
/// <c>"analysisKind":"Strict"</c>; null/"advisory"/anything else → Advisory default,
|
|
/// key omitted). Ignored for non-Expression trigger types.
|
|
/// </param>
|
|
/// <returns>The trigger-config JSON string, or <c>null</c> when no typed flags were supplied or the trigger type is unrecognized.</returns>
|
|
internal static string? Build(
|
|
string triggerType, string? attribute,
|
|
string? matchValue, bool notEquals,
|
|
double? min, double? max,
|
|
double? thresholdPerSecond, double? windowSeconds, string? direction,
|
|
double? loLo, double? lo, double? hi, double? hiHi,
|
|
string? expression,
|
|
string? analysisKind = null)
|
|
{
|
|
var type = triggerType?.Trim();
|
|
var anyTyped = attribute is not null || matchValue is not null || notEquals
|
|
|| min.HasValue || max.HasValue || thresholdPerSecond.HasValue || windowSeconds.HasValue
|
|
|| direction is not null || loLo.HasValue || lo.HasValue || hi.HasValue || hiHi.HasValue
|
|
|| expression is not null;
|
|
if (!anyTyped) return null;
|
|
|
|
using var stream = new MemoryStream();
|
|
using (var w = new Utf8JsonWriter(stream))
|
|
{
|
|
w.WriteStartObject();
|
|
if (!string.Equals(type, "Expression", StringComparison.OrdinalIgnoreCase))
|
|
w.WriteString("attributeName", attribute ?? "");
|
|
|
|
switch (type?.ToLowerInvariant())
|
|
{
|
|
case "valuematch":
|
|
var mv = matchValue ?? "";
|
|
if (notEquals) mv = "!=" + mv;
|
|
w.WriteString("matchValue", mv);
|
|
break;
|
|
case "rangeviolation":
|
|
if (min.HasValue) w.WriteNumber("min", min.Value);
|
|
if (max.HasValue) w.WriteNumber("max", max.Value);
|
|
break;
|
|
case "rateofchange":
|
|
if (thresholdPerSecond.HasValue) w.WriteNumber("thresholdPerSecond", thresholdPerSecond.Value);
|
|
if (windowSeconds.HasValue) w.WriteNumber("windowSeconds", windowSeconds.Value);
|
|
w.WriteString("direction", NormalizeDirection(direction));
|
|
break;
|
|
case "hilo":
|
|
// Only the four setpoints are exposed as flags. The codec also accepts
|
|
// per-setpoint priorities/deadbands/messages — intentionally omitted here;
|
|
// use raw --trigger-config for those (see the YAGNI scope guard in the plan).
|
|
if (loLo.HasValue) w.WriteNumber("loLo", loLo.Value);
|
|
if (lo.HasValue) w.WriteNumber("lo", lo.Value);
|
|
if (hi.HasValue) w.WriteNumber("hi", hi.Value);
|
|
if (hiHi.HasValue) w.WriteNumber("hiHi", hiHi.Value);
|
|
break;
|
|
case "expression":
|
|
w.WriteString("expression", expression ?? "");
|
|
// Emit "analysisKind":"Strict" only when the caller passes
|
|
// --trigger-kind strict (case-insensitive); Advisory (the default) is
|
|
// expressed by omitting the key, matching ValidationService.IsStrictAnalysis.
|
|
if (string.Equals(analysisKind?.Trim(), "Strict", StringComparison.OrdinalIgnoreCase))
|
|
w.WriteString("analysisKind", "Strict");
|
|
break;
|
|
}
|
|
w.WriteEndObject();
|
|
}
|
|
return Encoding.UTF8.GetString(stream.ToArray());
|
|
}
|
|
|
|
// Mirrors AlarmTriggerConfigCodec.NormalizeDirection.
|
|
private static string NormalizeDirection(string? raw) => raw?.ToLowerInvariant() switch
|
|
{
|
|
"rising" or "up" or "positive" => "rising",
|
|
"falling" or "down" or "negative" => "falling",
|
|
_ => "either",
|
|
};
|
|
}
|