Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using AasCore.Aas3_0;
using FluentAAS.Builder;
using FluentAAS.Templates.TechnicalData;
using Shouldly;

namespace FluentAAS.Templates.Tests.TechnicalData;

public class TechnicalDataBuilderTests
{
[Fact]
public void BuildTechnicalData_ShouldFail_WhenMandatoryParametersAreMissing()
{
var envBuilder = AasBuilder.Create();
var shellBuilder = envBuilder.AddShell("urn:aas:example:shell:technical-data", "DriveShell");

var sut = shellBuilder.AddTechnicalData("urn:aas:example:submodel:technical-data")
.WithMotorPerformance(motor => motor
.WithRatedVoltage(400m)
.WithRatedCurrent(82.5m));

var ex = Record.Exception(() => sut.BuildTechnicalData());

ex.ShouldBeOfType<InvalidOperationException>();
ex.Message.ShouldContain("MotorPerformance.RatedPower");
ex.Message.ShouldContain("MotorPerformance.RatedSpeed");
ex.Message.ShouldContain("BearingCharacteristics.InnerDiameter");
}

[Fact]
public void BuildTechnicalData_ShouldFail_WhenUnitIsInvalid()
{
var envBuilder = AasBuilder.Create();
var shellBuilder = envBuilder.AddShell("urn:aas:example:shell:technical-data", "DriveShell");

var ex = Record.Exception(() => shellBuilder.AddTechnicalData("urn:aas:example:submodel:technical-data")
.WithMotorPerformance(motor => motor.WithRatedVoltage(400m, "mm")));

ex.ShouldBeOfType<InvalidOperationException>();
ex.Message.ShouldContain("Unit 'mm' is invalid for 'RatedVoltage'");
}

[Fact]
public void BuildTechnicalData_ShouldFail_WhenSemanticIdDoesNotMatchEclassCatalogEntry()
{
var envBuilder = AasBuilder.Create();
var shellBuilder = envBuilder.AddShell("urn:aas:example:shell:technical-data", "DriveShell");

var ex = Record.Exception(() => shellBuilder.AddTechnicalData("urn:aas:example:submodel:technical-data")
.WithMotorPerformance(motor => motor.WithRatedVoltage(400m, "V", TechnicalDataSemantics.RatedCurrent)));

ex.ShouldBeOfType<InvalidOperationException>();
ex.Message.ShouldContain("Semantic ID mismatch for 'RatedVoltage'");
}

[Fact]
public void BuildTechnicalData_ShouldCreateStructuredGroups_WithEclassSemanticIds()
{
var envBuilder = AasBuilder.Create();
var shellBuilder = envBuilder.AddShell("urn:aas:example:shell:technical-data", "DriveShell");

shellBuilder.AddTechnicalData("urn:aas:example:submodel:technical-data")
.WithMotorPerformance(motor => motor
.WithRatedVoltage(400m, "V", TechnicalDataSemantics.RatedVoltage)
.WithRatedCurrent(82.5m, "A", TechnicalDataSemantics.RatedCurrent)
.WithRatedPower(45m, "kW", TechnicalDataSemantics.RatedPower)
.WithRatedSpeed(1470m, "1/min", TechnicalDataSemantics.RatedSpeed))
.WithBearingCharacteristics(bearing => bearing
.WithInnerDiameter(50m, "mm", TechnicalDataSemantics.InnerDiameter)
.WithOuterDiameter(90m, "mm", TechnicalDataSemantics.OuterDiameter)
.WithWidth(20m, "mm", TechnicalDataSemantics.Width)
.WithLimitingSpeed(6500m, "1/min", TechnicalDataSemantics.LimitingSpeed))
.BuildTechnicalData();

var env = envBuilder.Build();

var submodel = env.Submodels!.Single(sm => sm.Id == "urn:aas:example:submodel:technical-data");
submodel.SemanticId!.Keys.Single().Value.ShouldBe(TechnicalDataSemantics.SubmodelTechnicalData);

var motorGroup = submodel.SubmodelElements!
.OfType<SubmodelElementCollection>()
.Single(x => x.IdShort == TechnicalDataIdentifiers.MotorPerformance);

var ratedVoltage = motorGroup.Value!
.OfType<Property>()
.Single(x => x.IdShort == TechnicalDataIdentifiers.RatedVoltage);

ratedVoltage.Value.ShouldBe("400");
ratedVoltage.Category.ShouldBe("V");
ratedVoltage.SemanticId!.Keys.Single().Value.ShouldBe(TechnicalDataSemantics.RatedVoltage);
Comment on lines +83 to +89

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how Category is typically used in the AAS 3.0 library and existing codebase
rg -n -C3 'Category\s*=' --type=cs

Repository: Freezor/FluentAAS

Length of output: 3003


🌐 Web query:

AAS 3.0 Property Category field usage and IEC 61360 unit storage

💡 Result:

In AAS 3.0, the 'category' field is defined in the Referable abstract class, inherited by elements like Property. It provides meta-information about the element's class, affecting expected attributes and constraints (e.g., distinguishing measurement values). It is optional (string type, default null) and deprecated since V3.0, though still present for backward compatibility. Legacy values include PARAMETER, CONSTANT, VALUE for data elements like Properties, influencing validation (e.g., in ConceptDescription constraints referencing category, marked deprecated). IEC 61360 units for quantitative properties (e.g., dataType INTEGER_MEASURE, REAL_MEASURE) are stored in ConceptDescriptions via the DataSpecificationIec61360 template, using 'unit' (string, primary unit text) or 'unitId' (Reference to global unit identifier, e.g., IRDI). These are embedded in the semanticId-referenced ConceptDescription of the Property, not directly in the Property.value. Constraint AASc-3a-009 requires unit/unitId for measure types. The Property.valueType maps IEC data types to XSD (e.g., xs:integer for INTEGER_MEASURE).

Citations:


Remove Category field usage for unit storage; use ConceptDescription's DataSpecificationIec61360 instead.

The test asserts ratedVoltage.Category.ShouldBe("V"), but in AAS 3.0, the Category field is deprecated and meant for element classification (e.g., PARAMETER, CONSTANT, VALUE). Units should be stored in the ConceptDescription's DataSpecificationIec61360 template via the unit field, not in the Property's Category field. This applies to the builder implementation as well (TechnicalDataBuilder.cs:95).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@FluentAAS/FluentAAS.Templates.Tests/TechnicalData/TechnicalDataBuilderTests.cs`
around lines 83 - 89, The test currently asserts the deprecated
Property.Category ("ratedVoltage.Category.ShouldBe(\"V\")"); update the test and
builder so the unit is stored on the ConceptDescription's
DataSpecificationIec61360 instead: remove the Category assertion in
FluentAAS.Templates.Tests.TechnicalData.TechnicalDataBuilderTests (the
ratedVoltage variable usage) and add an assertion that the ConceptDescription
created by TechnicalDataBuilder contains a DataSpecificationIec61360 with unit
== "V"; update TechnicalDataBuilder (the code that currently sets
Property.Category around the TechnicalDataBuilder.Create... logic) to stop
populating Property.Category and instead populate the
ConceptDescription.DataSpecificationIec61360.unit field with the unit string.

}

[Fact]
public void InlineComposition_ShouldBuildEnvironment()
{
var envBuilder = AasBuilder.Create();
envBuilder.AddShell("urn:aas:example:drivetrain:1000", "DriveTrainAAS")
.AddTechnicalData("urn:aas:submodel:technical-data:drivetrain:1000")
.WithMotorPerformance(motor => motor
.WithRatedVoltage(400m, "V", TechnicalDataSemantics.RatedVoltage)
.WithRatedCurrent(82.5m, "A", TechnicalDataSemantics.RatedCurrent)
.WithRatedPower(45m, "kW", TechnicalDataSemantics.RatedPower)
.WithRatedSpeed(1470m, "1/min", TechnicalDataSemantics.RatedSpeed))
.WithBearingCharacteristics(bearing => bearing
.WithInnerDiameter(50m, "mm", TechnicalDataSemantics.InnerDiameter)
.WithOuterDiameter(90m, "mm", TechnicalDataSemantics.OuterDiameter)
.WithWidth(20m, "mm", TechnicalDataSemantics.Width)
.WithLimitingSpeed(6500m, "1/min", TechnicalDataSemantics.LimitingSpeed))
.BuildTechnicalData();

var env = envBuilder.Build();

env.AssetAdministrationShells.ShouldNotBeNull();
env.AssetAdministrationShells!.Count.ShouldBe(1);
env.Submodels.ShouldNotBeNull();
env.Submodels!.Count.ShouldBe(1);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading