diff --git a/Services/Iec61850ValueFormatter.cs b/Services/Iec61850ValueFormatter.cs index 4821e8868..478765177 100644 --- a/Services/Iec61850ValueFormatter.cs +++ b/Services/Iec61850ValueFormatter.cs @@ -14,6 +14,13 @@ public static string Format(object? value, string dataType, string unit) if (TryExtractStructuredScalar(value, dataType, out var structuredScalar)) value = structuredScalar; + return FormatNormalizedValue(value, dataType, unit); + } + + // Both entry points normalize any legacy structure exactly once before formatting. + // Keep the established generic and report-specific state vocabularies separate. + private static string FormatNormalizedValue(object? value, string dataType, string unit) + { if (IsDbposDataType(dataType) && TryNormalizeDbpos(value, out var dbpos)) return FormatDbpos(dbpos); @@ -74,7 +81,7 @@ public static string FormatReportProcessValue( return boolean ? "True [1]" : "False [0]"; } - return Format(normalizedValue, dataType, unit); + return FormatNormalizedValue(normalizedValue, dataType, unit); } private static bool IsPositionSemantic(string dataType, string category, string reference) diff --git a/tests/ARSAS.Tests/Iec61850ValueFormatterTests.cs b/tests/ARSAS.Tests/Iec61850ValueFormatterTests.cs index 7dd39e51f..c196ad5b6 100644 --- a/tests/ARSAS.Tests/Iec61850ValueFormatterTests.cs +++ b/tests/ARSAS.Tests/Iec61850ValueFormatterTests.cs @@ -133,6 +133,33 @@ public void Format_Does_Not_Collapse_Indexed_Structure_When_Metadata_Is_Not_Nume Assert.Equal(value, formatted); } + [Theory] + [InlineData("Structure(3) {stVal=false, q=Quality{V=1,D=0}}", "Boolean", "", "Status", "IEDLD/GGIO1.Ind1.stVal", "False [0]")] + [InlineData("Structure(3) {stVal=2, q=Quality{V=1,D=0}}", "Dbpos", "", "Position", "IEDLD/CSWI1.Pos.stVal", "Close [10]")] + [InlineData("Structure(5) {[0]=12345, [1]=Quality{V=1,D=0}}", "Int64", "Wh", "Measurement", "IEDLD/MMXU1.Wh.instMag.i", "12345 Wh")] + public void FormatReportProcessValue_NormalizesLegacyScalarAndKeepsOperatorSemantics( + string value, + string dataType, + string unit, + string category, + string reference, + string expected) + { + Assert.Equal(expected, + Iec61850ValueFormatter.FormatReportProcessValue( + value, dataType, unit, category, reference)); + } + + [Fact] + public void FormatReportProcessValue_DoesNotCollapseIndexedStructureWithoutNumericMetadata() + { + const string value = "Structure(2) {[0]=12345, [1]=Quality{V=1,D=0}}"; + + Assert.Equal(value, + Iec61850ValueFormatter.FormatReportProcessValue( + value, "Structure", string.Empty, "Measurement", "IEDLD/GGIO1.Raw")); + } + [Fact] public void Format_Does_Not_Collapse_NonStVal_Structures() {