diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/ElectricityMapsDataSource.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/ElectricityMapsDataSource.cs
index 01e494bbd..acfc1339e 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/ElectricityMapsDataSource.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/ElectricityMapsDataSource.cs
@@ -72,7 +72,7 @@ private static EmissionsForecast ToEmissionsForecast(Location location, Forecast
}
///
- public async Task GetCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
+ public async Task GetHistoricalCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
{
await Task.Run(() => true);
throw new NotImplementedException();
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs
index b07f96cd8..73d432861 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/IWattTimeClient.cs
@@ -49,11 +49,11 @@ internal interface IWattTimeClient
///
/// Async method to get generated forecast at requested time and balancing authority.
///
- /// Balancing authority abbreviation
+ /// Balancing authority abbreviation
/// The historical time used to fetch the most recent forecast generated as of that time.
/// An which contains forecasted emissions data points or null if no Forecast generated at the requested time.
/// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes.
- Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt);
+ Task GetForecastOnDateAsync(string region, DateTimeOffset requestedAt);
///
/// Async method to get generated forecast at requested time and balancing authority.
@@ -62,7 +62,7 @@ internal interface IWattTimeClient
/// The historical time used to fetch the most recent forecast generated as of that time.
/// An which contains forecasted emissions data points or null if no Forecast generated at the requested time.
/// Can be thrown when errors occur connecting to WattTime client. See the WattTimeClientException class for documentation of expected status codes.
- Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt);
+ Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt);
///
/// Async method to get the balancing authority for a given location.
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs
index 8e02c7349..2d0b50951 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Client/WattTimeClient.cs
@@ -103,9 +103,6 @@ public async Task GetCurrentForecastAsync(string
var result = await this.MakeRequestGetStreamAsync(Paths.Forecast, parameters, tags);
- var sr = new StreamReader(result);
- var s = sr.ReadToEnd();
-
var forecast = await JsonSerializer.DeserializeAsync(result, _options) ?? throw new WattTimeClientException($"Error getting forecast for {region}");
return forecast;
@@ -118,32 +115,33 @@ public Task GetCurrentForecastAsync(RegionRespons
}
///
- public async Task GetForecastOnDateAsync(string balancingAuthorityAbbreviation, DateTimeOffset requestedAt)
+ public async Task GetForecastOnDateAsync(string region, DateTimeOffset requestedAt)
{
- _log.LogInformation($"Requesting forecast from balancingAuthority {balancingAuthorityAbbreviation} generated at {requestedAt}.");
+ _log.LogInformation($"Requesting forecast from balancingAuthority {region} generated at {requestedAt}.");
var parameters = new Dictionary()
{
- { QueryStrings.Region, balancingAuthorityAbbreviation },
+ { QueryStrings.Region, region },
+ { QueryStrings.SignalType, SignalTypes.co2_moer },
{ QueryStrings.StartTime, requestedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) },
{ QueryStrings.EndTime, requestedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) }
};
var tags = new Dictionary()
{
- { QueryStrings.Region, balancingAuthorityAbbreviation }
+ { QueryStrings.Region, region }
};
- using (var result = await this.MakeRequestGetStreamAsync(Paths.Forecast, parameters, tags))
+ using (var result = await this.MakeRequestGetStreamAsync(Paths.ForecastHistorical, parameters, tags))
{
- var forecasts = await JsonSerializer.DeserializeAsync>(result, _options) ?? throw new WattTimeClientException($"Error getting forecasts for {balancingAuthorityAbbreviation}");
- return forecasts.FirstOrDefault();
+ var historicalForecastResponse = await JsonSerializer.DeserializeAsync(result, _options) ?? throw new WattTimeClientException($"Error getting forecasts for {region}");
+ return historicalForecastResponse;
}
}
///
- public Task GetForecastOnDateAsync(RegionResponse balancingAuthority, DateTimeOffset requestedAt)
+ public Task GetForecastOnDateAsync(RegionResponse region, DateTimeOffset requestedAt)
{
- return this.GetForecastOnDateAsync(balancingAuthority.Region, requestedAt);
+ return this.GetForecastOnDateAsync(region.Region, requestedAt);
}
///
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Constants/Paths.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Constants/Paths.cs
index 61c3092f9..8704d8bfb 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Constants/Paths.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Constants/Paths.cs
@@ -4,6 +4,8 @@ internal class Paths
{
public const string Data = "historical";
public const string Forecast = "forecast";
+
+ public const string ForecastHistorical = "forecast/historical";
public const string BalancingAuthorityFromLocation = "region-from-loc";
public const string Login = "login";
public const string Historical = "historical";
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalEmissionsData.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalEmissionsData.cs
new file mode 100644
index 000000000..5c82f01a6
--- /dev/null
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalEmissionsData.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace CarbonAware.DataSources.WattTime.Model;
+
+[Serializable]
+internal class HistoricalEmissionsData
+{
+ [JsonPropertyName("generated_at")]
+ public DateTimeOffset GeneratedAt { get; set; } = DateTimeOffset.MinValue;
+
+ [JsonPropertyName("forecast")]
+ public List Forecast { get; set; } = new List();
+
+}
+
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalForecastEmissionsDataResponse.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalForecastEmissionsDataResponse.cs
new file mode 100644
index 000000000..212e8170a
--- /dev/null
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/Model/HistoricalForecastEmissionsDataResponse.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace CarbonAware.DataSources.WattTime.Model;
+
+[Serializable]
+internal record HistoricalForecastEmissionsDataResponse
+{
+ [JsonPropertyName("data")]
+ public List Data { get; set; } = new List();
+
+
+ [JsonPropertyName("meta")]
+ public GridEmissionsMetaData Meta { get; set; } = new GridEmissionsMetaData();
+}
+
+
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs
index c879df636..39a114777 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/src/WattTimeDataSource.cs
@@ -87,7 +87,7 @@ public async Task GetCurrentCarbonIntensityForecastAsync(Loca
}
///
- public async Task GetCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
+ public async Task GetHistoricalCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
{
this.Logger.LogInformation($"Getting carbon intensity forecast for location {location} requested at {requestedAt}");
var balancingAuthority = await this.GetBalancingAuthority(location);
@@ -100,7 +100,26 @@ public async Task GetCarbonIntensityForecastAsync(Location lo
throw ex;
}
// keep input from the user.
- return ForecastToEmissionsForecast(forecast, location, requestedAt);
+ return HistoricalForecastToEmissionsForecast(forecast, location, requestedAt);
+ }
+
+ private EmissionsForecast HistoricalForecastToEmissionsForecast(HistoricalForecastEmissionsDataResponse historicalForecast, Location location, DateTimeOffset requestedAt)
+ {
+ var duration = GetDurationFromGridEmissionDataPoints(historicalForecast.Data[0].Forecast);
+ var forecastData = historicalForecast.Data[0].Forecast.Select(e => new EmissionsData()
+ {
+ Location = historicalForecast.Meta.Region,
+ Rating = ConvertMoerToGramsPerKilowattHour(e.Value),
+ Time = e.PointTime,
+ Duration = duration
+ });
+ var emissionsForecast = new EmissionsForecast()
+ {
+ GeneratedAt = historicalForecast.Data[0].GeneratedAt,
+ Location = location,
+ ForecastData = forecastData
+ };
+ return emissionsForecast;
}
private EmissionsForecast ForecastToEmissionsForecast(ForecastEmissionsDataResponse forecast, Location location, DateTimeOffset requestedAt)
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs
index a79ca8b56..52d244a9f 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/Client/WattTimeClientTests.cs
@@ -252,12 +252,12 @@ public async Task GetForecastOnDateAsync_DeserializesExpectedResponse()
var overloadedForecast = await client.GetForecastOnDateAsync(ba, new DateTimeOffset(2022, 4, 22, 0, 0, 0, TimeSpan.Zero));
Assert.AreEqual(forecastResponse!.Meta.GeneratedAt, overloadedForecast!.Meta.GeneratedAt);
- Assert.AreEqual(forecastResponse.Data.First(), overloadedForecast.Data.First());
+ Assert.AreEqual(forecastResponse.Data[0].Forecast.First(), overloadedForecast.Data[0].Forecast.First());
Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastResponse.Meta.GeneratedAt);
Assert.AreEqual("region", forecastResponse.Meta.Region);
- var forecastDataPoint = forecastResponse.Data.ToList().First();
+ var forecastDataPoint = forecastResponse.Data[0].Forecast.ToList().First();
Assert.AreEqual(new DateTimeOffset(2099, 1, 1, 0, 0, 0, TimeSpan.Zero), forecastDataPoint.PointTime);
Assert.AreEqual("999.99", forecastDataPoint.Value.ToString("0.00", CultureInfo.InvariantCulture)); //Format float to avoid precision issues
Assert.AreEqual("1.0", forecastDataPoint.Version);
diff --git a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs
index 0ddf84dcb..4e9942efb 100644
--- a/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs
+++ b/src/CarbonAware.DataSources/CarbonAware.DataSources.WattTime/test/WattTimeDataSourceTests.cs
@@ -127,6 +127,9 @@ public async Task GetCarbonIntensityForecastAsync_ReturnsResultsWhenRecordsFound
var forecastResponse = GenerateForecastResponse(2, value: lbsPerMwhEmissions);
forecastResponse.Meta.GeneratedAt = generatedAt;
+ var historicalForecastResponse = GenerateHistoricalForecastResponse(2, value: lbsPerMwhEmissions);
+ historicalForecastResponse.Meta.GeneratedAt = generatedAt;
+
EmissionsForecast result;
if (getCurrentForecast)
@@ -140,10 +143,10 @@ public async Task GetCarbonIntensityForecastAsync_ReturnsResultsWhenRecordsFound
else
{
this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, generatedAt)
- ).ReturnsAsync(() => forecastResponse);
+ ).ReturnsAsync(() => historicalForecastResponse);
// Act
- result = await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt);
+ result = await this.DataSource.GetHistoricalCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt);
}
// Assert
@@ -174,18 +177,18 @@ public void GetCarbonIntensityForecastAsync_ThrowsWhenRegionNotFound()
this.LocationSource.Setup(l => l.ToGeopositionLocationAsync(this.DefaultLocation)).Throws();
Assert.ThrowsAsync(async () => await this.DataSource.GetCurrentCarbonIntensityForecastAsync(this.DefaultLocation));
- Assert.ThrowsAsync(async () => await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, new DateTimeOffset()));
+ Assert.ThrowsAsync(async () => await this.DataSource.GetHistoricalCarbonIntensityForecastAsync(this.DefaultLocation, new DateTimeOffset()));
}
[Test]
- public void GetCarbonIntensityForecastAsync_ThrowsWhenNoForecastFoundForReuqestedTime()
+ public void GetHistoricalCarbonIntensityForecastAsync_ThrowsWhenNoForecastFoundForReuqestedTime()
{
var generatedAt = new DateTimeOffset();
- this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, generatedAt)).Returns(Task.FromResult(null));
+ this.WattTimeClient.Setup(w => w.GetForecastOnDateAsync(this.DefaultRegion, generatedAt)).Returns(Task.FromResult(null));
// The datasource throws an exception if no forecasts are found at the requested generatedAt time.
- Assert.ThrowsAsync(async () => await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt));
+ Assert.ThrowsAsync(async () => await this.DataSource.GetHistoricalCarbonIntensityForecastAsync(this.DefaultLocation, generatedAt));
}
[TestCase(0, TestName = "GetCurrentCarbonIntensityForecastAsync throws for: No datapoints")]
@@ -211,7 +214,7 @@ public async Task GetCarbonIntensityForecastAsync_RequiredAtRounded(string reque
var requestedAt = DateTimeOffset.Parse(requested);
var expectedAt = DateTimeOffset.Parse(expected);
- var forecastResponse = GenerateForecastResponse(2, startTime: requestedAt);
+ var forecastResponse = GenerateHistoricalForecastResponse(2, startTime: requestedAt);
forecastResponse.Meta.GeneratedAt = expectedAt;
@@ -219,7 +222,7 @@ public async Task GetCarbonIntensityForecastAsync_RequiredAtRounded(string reque
).ReturnsAsync(() => forecastResponse);
// Act
- var result = await this.DataSource.GetCarbonIntensityForecastAsync(this.DefaultLocation, requestedAt);
+ var result = await this.DataSource.GetHistoricalCarbonIntensityForecastAsync(this.DefaultLocation, requestedAt);
// Assert
Assert.IsNotNull(result);
@@ -305,6 +308,31 @@ private GridEmissionsDataResponse GenerateGridEmissionsResponse(int numberOfData
return response;
}
+ private HistoricalForecastEmissionsDataResponse GenerateHistoricalForecastResponse(int numberOfDatapoints, float value = 10, DateTimeOffset startTime = default)
+ {
+ var data = GenerateDataPoints(numberOfDatapoints, value, startTime);
+ var meta = new GridEmissionsMetaData()
+ {
+ Region = this.DefaultRegion.Region,
+ SignalType = SignalTypes.co2_moer
+ };
+
+ var response = new HistoricalForecastEmissionsDataResponse()
+ {
+ Data = new List()
+ {
+ new HistoricalEmissionsData()
+ {
+ Forecast = data,
+ GeneratedAt = DateTimeOffset.Now
+ }
+ },
+ Meta = meta
+ };
+
+ return response;
+ }
+
private ForecastEmissionsDataResponse GenerateForecastResponse(int numberOfDatapoints, float value = 10, DateTimeOffset startTime = default)
{
var data = GenerateDataPoints(numberOfDatapoints, value, startTime);
diff --git a/src/CarbonAware/src/Interfaces/IForecastDataSource.cs b/src/CarbonAware/src/Interfaces/IForecastDataSource.cs
index cd8d06c08..964b0e18e 100644
--- a/src/CarbonAware/src/Interfaces/IForecastDataSource.cs
+++ b/src/CarbonAware/src/Interfaces/IForecastDataSource.cs
@@ -14,5 +14,5 @@ internal interface IForecastDataSource
/// The location that should be used for getting the forecast.
/// The historical time used to fetch the most recent forecast generated as of that time.
/// A forecasted emissions object for the given location generated at the given time.
- Task GetCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt);
+ Task GetHistoricalCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt);
}
\ No newline at end of file
diff --git a/src/CarbonAware/src/NullForecastDataSource.cs b/src/CarbonAware/src/NullForecastDataSource.cs
index 7b4288dbd..9492e3d31 100644
--- a/src/CarbonAware/src/NullForecastDataSource.cs
+++ b/src/CarbonAware/src/NullForecastDataSource.cs
@@ -4,7 +4,7 @@ namespace CarbonAware;
internal class NullForecastDataSource : IForecastDataSource
{
- public Task GetCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
+ public Task GetHistoricalCarbonIntensityForecastAsync(Location location, DateTimeOffset requestedAt)
{
throw new ArgumentException("ForecastDataSource is not configured");
}
diff --git a/src/GSF.CarbonAware/src/Handlers/ForecastHandler.cs b/src/GSF.CarbonAware/src/Handlers/ForecastHandler.cs
index adc40f4de..fa8b413ab 100644
--- a/src/GSF.CarbonAware/src/Handlers/ForecastHandler.cs
+++ b/src/GSF.CarbonAware/src/Handlers/ForecastHandler.cs
@@ -73,7 +73,7 @@ public async Task GetForecastByDateAsync(string location, Dat
{
parameters.SetRequiredProperties(PropertyName.SingleLocation, PropertyName.Requested);
parameters.Validate();
- var forecast = await _forecastDataSource.GetCarbonIntensityForecastAsync(parameters.SingleLocation, parameters.Requested);
+ var forecast = await _forecastDataSource.GetHistoricalCarbonIntensityForecastAsync(parameters.SingleLocation, parameters.Requested);
var emissionsForecast = ProcessAndValidateForecast(forecast, parameters);
return emissionsForecast;
}
diff --git a/src/GSF.CarbonAware/test/Handlers/ForecastHandlerTests.cs b/src/GSF.CarbonAware/test/Handlers/ForecastHandlerTests.cs
index d0057fe48..d87733311 100644
--- a/src/GSF.CarbonAware/test/Handlers/ForecastHandlerTests.cs
+++ b/src/GSF.CarbonAware/test/Handlers/ForecastHandlerTests.cs
@@ -207,7 +207,7 @@ private static Mock CreateForecastByDateDataSource(global::
{
var datasource = new Mock();
datasource
- .Setup(x => x.GetCarbonIntensityForecastAsync(It.IsAny(), requested))
+ .Setup(x => x.GetHistoricalCarbonIntensityForecastAsync(It.IsAny(), requested))
.ReturnsAsync(data);
return datasource;
@@ -221,7 +221,7 @@ private static Mock SetupMockDataSourceThatThrows()
.ThrowsAsync(new CarbonAware.Exceptions.CarbonAwareException(""));
datasource
- .Setup(x => x.GetCarbonIntensityForecastAsync(It.IsAny(), It.IsAny()))
+ .Setup(x => x.GetHistoricalCarbonIntensityForecastAsync(It.IsAny(), It.IsAny()))
.ThrowsAsync(new CarbonAware.Exceptions.CarbonAwareException("", It.IsAny()));
return datasource;