diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/ConductorTemperatureDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/ConductorTemperatureDto.cs similarity index 91% rename from dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/ConductorTemperatureDto.cs rename to dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/ConductorTemperatureDto.cs index 17ddf5a..890544d 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/ConductorTemperatureDto.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/ConductorTemperatureDto.cs @@ -1,4 +1,4 @@ -namespace HeimdallPower.Api.Client.GridInsights.Lines; +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; public record ConductorTemperatureDto { diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/CurrentDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/CurrentDto.cs similarity index 86% rename from dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/CurrentDto.cs rename to dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/CurrentDto.cs index bc69cfd..63799d6 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/CurrentDto.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/CurrentDto.cs @@ -1,4 +1,4 @@ -namespace HeimdallPower.Api.Client.GridInsights.Lines; +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; public record CurrentDto { diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/IcingDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/IcingDto.cs new file mode 100644 index 0000000..15d236a --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/IcingDto.cs @@ -0,0 +1,14 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record IcingDto +{ + /// + /// The maximum icing data, i.e. max ice weight, max tension and max percentage of tensions, across all span phases on the line. + /// + public required MaxIcingDto Max { get; init; } + + /// + /// List of spans on the line with their icing data. + /// + public required IReadOnlyCollection Spans { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MaxIcingDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MaxIcingDto.cs new file mode 100644 index 0000000..eabd65f --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MaxIcingDto.cs @@ -0,0 +1,19 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record MaxIcingDto +{ + /// + /// The maximum mass of ice accumulated on the conductor. + /// + public required SpanPhaseMeasurementResult IceWeight { get; init; } + + /// + /// The maximum mechanical tension force in the conductor, which increases as ice accumulates. + /// + public required SpanPhaseMeasurementResult Tension { get; init; } + + /// + /// Maximum safety-critical metric showing how close the conductor is to its breaking point. + /// + public required SpanPhaseMeasurementResult TensionPercentageOfBreakStrength { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MeasurementResult.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MeasurementResult.cs new file mode 100644 index 0000000..7ee0d79 --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/MeasurementResult.cs @@ -0,0 +1,14 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record MeasurementResult() +{ + /// + /// The numerical value of the measurement. + /// + public double Value { get; init; } + + /// + /// The unit of the measurement. + /// + public required string Unit { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanIcingDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanIcingDto.cs new file mode 100644 index 0000000..eabc278 --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanIcingDto.cs @@ -0,0 +1,15 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record SpanIcingDto +{ + /// + /// The id of the span. + /// + /// 00000000-0000-0000-0000-000000000000 + public Guid SpanId { get; init; } + + /// + /// List of span phases (conductors) within this span. + /// + public required IReadOnlyCollection SpanPhases { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseIcingDto.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseIcingDto.cs new file mode 100644 index 0000000..6d6aea8 --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseIcingDto.cs @@ -0,0 +1,31 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record SpanPhaseIcingDto +{ + /// + /// The id of the span phase the measurement belongs to. + /// + /// 00000000-0000-0000-0000-000000000000 + public Guid SpanPhaseId { get; init; } + + /// + /// Time (UTC) when the icing measurements were calculated for the span phase. Timestamps may differ per conductor due to data availability. + /// + /// 2024-01-15T12:34:56Z + public DateTimeOffset Timestamp { get; init; } + + /// + /// The mass of ice accumulated on the conductor. + /// + public required MeasurementResult IceWeight { get; init; } + + /// + /// The mechanical tension force in the conductor, which increases as ice accumulates. + /// + public required MeasurementResult Tension { get; init; } + + /// + /// Safety-critical metric showing how close the conductor is to its breaking point. + /// + public required MeasurementResult TensionPercentageOfBreakStrength { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseMeasurementResult.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseMeasurementResult.cs new file mode 100644 index 0000000..3915373 --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/Dtos/SpanPhaseMeasurementResult.cs @@ -0,0 +1,26 @@ +namespace HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +public record SpanPhaseMeasurementResult +{ + /// + /// Time (UTC) when the icing measurements were calculated for the span phase. Timestamps may differ per conductor due to data availability. + /// + /// 2024-01-15T12:34:56Z + public DateTimeOffset Timestamp { get; init; } + + /// + /// The id of the span phase the measurement belongs to. + /// + /// 00000000-0000-0000-0000-000000000000 + public Guid SpanPhaseId { get; init; } + + /// + /// The numerical value of the measurement. + /// + public double Value { get; init; } + + /// + /// The unit of the measurement value. + /// + public required string Unit { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestConductorTemperatureResponse.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestConductorTemperatureResponse.cs index 375fdb9..d88f5ab 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestConductorTemperatureResponse.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestConductorTemperatureResponse.cs @@ -1,3 +1,5 @@ +using HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + namespace HeimdallPower.Api.Client.GridInsights.Lines; public record LatestConductorTemperatureResponse diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestCurrentResponse.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestCurrentResponse.cs index 8300660..457dbe9 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestCurrentResponse.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestCurrentResponse.cs @@ -1,3 +1,5 @@ +using HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + namespace HeimdallPower.Api.Client.GridInsights.Lines; public record LatestCurrentResponse diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestIcingResponse.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestIcingResponse.cs new file mode 100644 index 0000000..bb5ebdc --- /dev/null +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/GridInsights/Lines/LatestIcingResponse.cs @@ -0,0 +1,23 @@ +using HeimdallPower.Api.Client.GridInsights.Lines.Dtos; + +namespace HeimdallPower.Api.Client.GridInsights.Lines; + +public record LatestIcingResponse +{ + /// + /// The kind of data this response contains. + /// + /// Icing + public required string Metric { get; init; } + + /// + /// The unit description for the response (multiple units across measurements). + /// + /// Multiple (see measurements) + public required string Unit { get; init; } + + /// + /// The icing measurements including max and per-span/phase values. + /// + public required IcingDto Icing { get; init; } +} diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/HeimdallApiClient.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/HeimdallApiClient.cs index 7437fc7..31b0a53 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/HeimdallApiClient.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/HeimdallApiClient.cs @@ -90,6 +90,19 @@ public async Task GetLatestConductorTemperat return response.Data; } + /// + /// Get the most recent icing measurements for the line, including maximum values and per-span/phase metrics. + /// + /// Id of the line for which to retrieve the latest icing measurements. + /// The unit system for the measurements. "metric" uses kg/m, N, %, while "imperial" uses lb/ft, lbf, %. + /// Optional cutoff time (UTC). Only measurements at or after this instant are considered. Older data for a span phase is excluded. If omitted, defaults to 30 min ago. + public async Task GetLatestIcingAsync(Guid lineId, string unitSystem = "metric", DateTimeOffset? since = null) + { + var url = UrlBuilder.BuildLatestIcingUrl(lineId, unitSystem, since); + var response = await _heimdallApiClient.GetAsync>(url); + return response.Data; + } + /// /// Get the most recent Heimdall Dynamic Line Rating (DLR) for the line. /// Heimdall DLR is calculated according to our own proprietary method, based on the CIGRE TB-601 standard for thermal calculation for OHLs. diff --git a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/UrlBuilder.cs b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/UrlBuilder.cs index 7f3d717..6e25850 100644 --- a/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/UrlBuilder.cs +++ b/dotnet/HeimdallPower.Api.Client/HeimdallPower.Api.Client/UrlBuilder.cs @@ -25,6 +25,7 @@ internal static class UrlBuilder private const string HeimdallAar = "heimdall_aars/latest"; private const string HeimdallDlrForecast = "heimdall_dlrs/forecasts"; private const string HeimdallAarForecast = "heimdall_aars/forecasts"; + private const string IcingLatest = "icing/latest"; public static string BuildLatestConductorTemperatureUrl(Guid lineId, string unitSystem) { @@ -58,6 +59,17 @@ public static string BuildCircuitRatingForecastUrl(Guid facilityId) public static string BuildCircuitRatingUrl(Guid facilityId) => GetFullUrl(module: CapacityMonitoring, apiVersion: V1, resource: Facilities, resourceId: facilityId.ToString(), endpoint: CircuitRatingLatest); + public static string BuildLatestIcingUrl(Guid lineId, string unitSystem = "metric", DateTimeOffset? since = null) + { + var queryParams = new NameValueCollection() + .AddQueryParam("unit_system", unitSystem); + + if (since.HasValue) + queryParams.AddQueryParam("since", since.Value.ToUniversalTime().ToString("o")); + + return GetFullUrl(module: GridInsight, apiVersion: V1, resource: Lines, resourceId: lineId.ToString(), endpoint: IcingLatest, queryParams: queryParams); + } + private static string GetResourceUrl(string module, string apiVersion, string resource) => $"{module}/{apiVersion}/{resource}"; diff --git a/dotnet/examples/Api.Client.Examples/Program.cs b/dotnet/examples/Api.Client.Examples/Program.cs index 8a09c0e..3939f86 100644 --- a/dotnet/examples/Api.Client.Examples/Program.cs +++ b/dotnet/examples/Api.Client.Examples/Program.cs @@ -24,9 +24,11 @@ // Fetch Aggregated Measurements data var latestCurrent = await api.GetLatestCurrentAsync(line.Id); var latestConductorTemperature = await api.GetLatestConductorTemperatureAsync(line.Id); +var latestIcing = await api.GetLatestIcingAsync(line.Id); Console.WriteLine($"- Current: {latestCurrent.Current.Value} {latestCurrent.Unit} at {latestCurrent.Current.Timestamp}"); Console.WriteLine($"- Conductor Temperature: {latestConductorTemperature.ConductorTemperature.Max} {latestConductorTemperature.Unit} at {latestConductorTemperature.ConductorTemperature.Timestamp}"); +Console.WriteLine($"- Icing: Maximum Ice weight: {latestIcing.Icing.Max.IceWeight.Value} at span phase: {latestIcing.Icing.Max.IceWeight.SpanPhaseId}"); // Fetch DLR data var latestDlr = await api.GetLatestHeimdallDlrAsync(line.Id);