diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d6bf4b..114a064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +#### NoaaClient Package +- **RTSW Client** - NOAA real-time solar wind integration + - `IRtswClient` with 1-minute magnetometer and plasma feeds + - Object-array JSON parsers for `rtsw_mag_1m.json` and `rtsw_wind_1m.json` + - Response records map the full NOAA RTSW schema (`BxGsm`, `ProtonDensity`, quality flags, etc.), not legacy DSCOVR field names + +### Removed + +#### NoaaClient Package +- **DSCOVR Client Surface** (breaking change) + - Removed `IDscovrClient`, related implementations, models, tests, and benchmarks + - Removed DSCOVR sample app menu entries + +### Changed + +#### NoaaClient Package +- **ACE client deprecated** — `IAceClient` and ACE response models marked `[Obsolete]` in favor of `IRtswClient`; removal tracked in [#3](https://github.com/Aurora-Science-Hub/Integrations/issues/3) +- **RTSW response models** (breaking change within unreleased RTSW surface) + - Magnetometer and wind records now use live NOAA RTSW property names + - Removed misleading aliases (`Latitude`/`Longitude`, `BulkSpeed`/`IonTemperature`) + - Optional JSON fields are parsed as nullable; only `time_tag`, `active`, and `source` are required + +#### Dependency Injection +- `AddNoaaClients()` now registers `IRtswClient` instead of `IDscovrClient` + ## [1.0.0] - 2026-02-05 ### Added @@ -48,4 +77,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GitHub Actions CI/CD pipeline for build and test [1.0.0]: https://github.com/Aurora-Science-Hub/Integrations/releases/tag/1.0.0 +[Unreleased]: https://github.com/Aurora-Science-Hub/Integrations/compare/1.0.0...HEAD diff --git a/Directory.Build.props b/Directory.Build.props index b16d8ca..aa8d725 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,7 @@ - 1.0.0 + 1.1.0 diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b13d31..676b3ee 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,8 +5,8 @@ true - - + + all @@ -16,9 +16,9 @@ - - - + + + @@ -31,4 +31,4 @@ runtime; build; native; contentfiles; analyzers - \ No newline at end of file + diff --git a/NuGet.Config b/NuGet.Config index 25fdb36..5061ff1 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -2,16 +2,19 @@ - + + - + + - + + diff --git a/README.md b/README.md index da1250f..62b2890 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Each package provides HTTP clients for accessing external data sources with stro | Component | Description | |---------------------|-----------------------------------------------------------------------------------------------------------------------------| | **ACE Client** | Advanced Composition Explorer satellite data - magnetometer and SWEPAM measurements | -| **DSCOVR Client** | Deep Space Climate Observatory data - solar wind and magnetic field measurements with multiple time ranges (2H, 1D, 3D, 7D) | +| **RTSW Client** | NOAA real-time solar wind feeds - 1-minute magnetometer and plasma data with `active` and `source` metadata | | **KP-Index Client** | Geomagnetic activity indices - nowcast and forecast data (3-day and 27-day) | See [detailed documentation](src/NoaaClient/README.md) for usage examples and API reference. diff --git a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/AuroraScienceHub.Integrations.Benchmarks.csproj b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/AuroraScienceHub.Integrations.Benchmarks.csproj index 11e3240..45490dc 100644 --- a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/AuroraScienceHub.Integrations.Benchmarks.csproj +++ b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/AuroraScienceHub.Integrations.Benchmarks.csproj @@ -10,7 +10,6 @@ - diff --git a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrMagnetometerDataParserBenchmark.cs b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrMagnetometerDataParserBenchmark.cs deleted file mode 100644 index 6cc1821..0000000 --- a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrMagnetometerDataParserBenchmark.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; -using BenchmarkDotNet.Attributes; - -namespace AuroraScienceHub.Integrations.Benchmarks.NoaaClient.Dscovr; - -/// -/// Benchmarks for (DSCOVR). -/// -[MemoryDiagnoser(false)] -public class DscovrMagnetometerDataParserBenchmark -{ - const string ResourceName = "AuroraScienceHub.Integrations.Benchmarks.NoaaClient.Dscovr.Samples.DscovrMagnetometerSample.json"; - - private string _text = string.Empty; - - [GlobalSetup] - public async Task Setup() - { - await using var stream = typeof(IBenchmarksMarker).Assembly - .GetManifestResourceStream(ResourceName) - ?? throw new FileNotFoundException($"Resource '{ResourceName}' not found."); - using var reader = new StreamReader(stream); - _text = await reader.ReadToEndAsync(); - } - - [Benchmark] - public void Parse() - { - MagnetometerDataParser.Parse(_text); - } -} diff --git a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrSolarWindPlasmaDataParserBenchmark.cs b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrSolarWindPlasmaDataParserBenchmark.cs deleted file mode 100644 index b9647f4..0000000 --- a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/DscovrSolarWindPlasmaDataParserBenchmark.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; -using BenchmarkDotNet.Attributes; - -namespace AuroraScienceHub.Integrations.Benchmarks.NoaaClient.Dscovr; - -/// -/// Benchmarks for (DSCOVR). -/// -[MemoryDiagnoser(false)] -public class DscovrSolarWindPlasmaDataParserBenchmark -{ - const string ResourceName = "AuroraScienceHub.Integrations.Benchmarks.NoaaClient.Dscovr.Samples.DscovrSolarWindPlasmaSample.json"; - - private string _text = string.Empty; - - [GlobalSetup] - public async Task Setup() - { - await using var stream = typeof(IBenchmarksMarker).Assembly - .GetManifestResourceStream(ResourceName) - ?? throw new FileNotFoundException($"Resource '{ResourceName}' not found."); - using var reader = new StreamReader(stream); - _text = await reader.ReadToEndAsync(); - } - - [Benchmark] - public void Parse() - { - SolarWindPlasmaDataParser.Parse(_text); - } -} diff --git a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json deleted file mode 100644 index 2072186..0000000 --- a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json +++ /dev/null @@ -1,1055 +0,0 @@ -[ - [ - "time_tag", - "bx_gsm", - "by_gsm", - "bz_gsm", - "lon_gsm", - "lat_gsm", - "bt" - ], - [ - "2024-05-19 17:07:00.000", - "-2.15", - "8.51", - "-2.98", - "104.17", - "-18.76", - "9.27" - ], - [ - "2024-05-19 17:08:00.000", - "-2.38", - "8.31", - "-3.24", - "105.97", - "-20.54", - "9.23" - ], - [ - "2024-05-19 17:09:00.000", - "-2.15", - "8.59", - "-2.39", - "104.03", - "-15.13", - "9.17" - ], - [ - "2024-05-19 17:10:00.000", - "-1.89", - "8.83", - "-1.79", - "102.09", - "-11.24", - "9.20" - ], - [ - "2024-05-19 17:11:00.000", - "-1.45", - "8.89", - "-1.99", - "99.27", - "-12.45", - "9.22" - ], - [ - "2024-05-19 17:12:00.000", - "-0.84", - "9.07", - "-1.86", - "95.30", - "-11.57", - "9.29" - ], - [ - "2024-05-19 17:13:00.000", - "-1.08", - "9.13", - "-1.70", - "96.74", - "-10.50", - "9.35" - ], - [ - "2024-05-19 17:14:00.000", - "-0.77", - "9.23", - "-1.21", - "94.79", - "-7.46", - "9.34" - ], - [ - "2024-05-19 17:15:00.000", - "-0.58", - "9.23", - "-0.73", - "93.59", - "-4.53", - "9.28" - ], - [ - "2024-05-19 17:16:00.000", - "-1.63", - "8.76", - "-1.84", - "100.57", - "-11.66", - "9.09" - ], - [ - "2024-05-19 17:17:00.000", - "-2.04", - "8.42", - "-2.67", - "103.58", - "-17.11", - "9.07" - ], - [ - "2024-05-19 17:18:00.000", - "-2.26", - "8.32", - "-2.78", - "105.21", - "-17.90", - "9.06" - ], - [ - "2024-05-19 17:19:00.000", - "-2.22", - "8.39", - "-2.70", - "104.83", - "-17.27", - "9.09" - ], - [ - "2024-05-19 17:20:00.000", - "-2.29", - "8.36", - "-2.70", - "105.29", - "-17.27", - "9.08" - ], - [ - "2024-05-19 17:21:00.000", - "-2.52", - "8.23", - "-2.83", - "107.03", - "-18.19", - "9.06" - ], - [ - "2024-05-19 17:22:00.000", - "-2.64", - "8.14", - "-3.06", - "107.97", - "-19.65", - "9.09" - ], - [ - "2024-05-19 17:23:00.000", - "-2.61", - "7.99", - "-3.43", - "108.09", - "-22.23", - "9.08" - ], - [ - "2024-05-19 17:24:00.000", - "-2.71", - "8.02", - "-3.30", - "108.66", - "-21.29", - "9.09" - ], - [ - "2024-05-19 17:25:00.000", - "-2.36", - "7.79", - "-4.01", - "106.87", - "-26.25", - "9.07" - ], - [ - "2024-05-19 17:26:00.000", - "-2.67", - "8.07", - "-3.33", - "108.32", - "-21.39", - "9.13" - ], - [ - "2024-05-19 17:27:00.000", - "-2.86", - "8.22", - "-2.78", - "109.16", - "-17.72", - "9.14" - ], - [ - "2024-05-19 17:28:00.000", - "-2.80", - "8.19", - "-2.78", - "108.89", - "-17.79", - "9.09" - ], - [ - "2024-05-19 17:29:00.000", - "-2.59", - "8.12", - "-3.02", - "107.72", - "-19.54", - "9.04" - ], - [ - "2024-05-19 17:30:00.000", - "-2.38", - "8.29", - "-2.59", - "106.05", - "-16.72", - "9.00" - ], - [ - "2024-05-19 17:31:00.000", - "-2.18", - "8.38", - "-2.49", - "104.56", - "-16.03", - "9.01" - ], - [ - "2024-05-19 17:32:00.000", - "-2.28", - "8.41", - "-2.49", - "105.20", - "-15.98", - "9.06" - ], - [ - "2024-05-19 17:33:00.000", - "-2.24", - "8.40", - "-2.58", - "104.91", - "-16.52", - "9.07" - ], - [ - "2024-05-19 17:34:00.000", - "-2.38", - "8.29", - "-2.78", - "105.99", - "-17.89", - "9.06" - ], - [ - "2024-05-19 17:35:00.000", - "-2.92", - "7.80", - "-4.11", - "110.53", - "-26.27", - "9.29" - ], - [ - "2024-05-19 17:36:00.000", - "-3.23", - "7.34", - "-4.78", - "113.72", - "-30.81", - "9.34" - ], - [ - "2024-05-19 17:37:00.000", - "-2.94", - "7.47", - "-4.74", - "111.48", - "-30.56", - "9.33" - ], - [ - "2024-05-19 17:38:00.000", - "-2.86", - "7.37", - "-4.89", - "111.24", - "-31.75", - "9.30" - ], - [ - "2024-05-19 17:39:00.000", - "-2.79", - "7.34", - "-4.63", - "110.79", - "-30.55", - "9.11" - ], - [ - "2024-05-19 17:40:00.000", - "-2.54", - "7.52", - "-4.01", - "108.66", - "-26.82", - "8.89" - ], - [ - "2024-05-19 17:41:00.000", - "-2.48", - "8.00", - "-2.90", - "107.19", - "-19.12", - "8.87" - ], - [ - "2024-05-19 17:42:00.000", - "-2.40", - "8.31", - "-1.97", - "106.12", - "-12.85", - "8.87" - ], - [ - "2024-05-19 17:43:00.000", - "-2.32", - "8.44", - "-1.74", - "105.38", - "-11.22", - "8.93" - ], - [ - "2024-05-19 17:44:00.000", - "-2.21", - "8.49", - "-2.02", - "104.61", - "-12.94", - "9.01" - ], - [ - "2024-05-19 17:45:00.000", - "-2.13", - "8.57", - "-1.81", - "103.93", - "-11.58", - "9.01" - ], - [ - "2024-05-19 17:46:00.000", - "-2.24", - "8.60", - "-1.57", - "104.60", - "-10.02", - "9.03" - ], - [ - "2024-05-19 17:47:00.000", - "-2.43", - "8.58", - "-1.38", - "105.78", - "-8.77", - "9.02" - ], - [ - "2024-05-19 17:48:00.000", - "-2.45", - "8.60", - "-1.15", - "105.91", - "-7.33", - "9.02" - ], - [ - "2024-05-19 17:49:00.000", - "-2.17", - "8.61", - "-1.00", - "104.16", - "-6.41", - "8.93" - ], - [ - "2024-05-19 17:50:00.000", - "-2.24", - "8.49", - "-1.49", - "104.79", - "-9.62", - "8.91" - ], - [ - "2024-05-19 17:51:00.000", - "-2.18", - "8.49", - "-1.34", - "104.38", - "-8.67", - "8.87" - ], - [ - "2024-05-19 17:52:00.000", - "-1.66", - "8.59", - "-1.21", - "100.93", - "-7.85", - "8.83" - ], - [ - "2024-05-19 17:53:00.000", - "-2.09", - "8.57", - "-1.04", - "103.70", - "-6.71", - "8.89" - ], - [ - "2024-05-19 17:54:00.000", - "-2.00", - "8.63", - "-1.29", - "103.03", - "-8.31", - "8.96" - ], - [ - "2024-05-19 17:55:00.000", - "-1.97", - "8.67", - "-1.47", - "102.78", - "-9.41", - "9.02" - ], - [ - "2024-05-19 17:56:00.000", - "-1.92", - "8.69", - "-1.82", - "102.44", - "-11.55", - "9.09" - ], - [ - "2024-05-19 17:57:00.000", - "-1.98", - "8.71", - "-1.70", - "102.81", - "-10.77", - "9.10" - ], - [ - "2024-05-19 17:58:00.000", - "-2.04", - "8.72", - "-1.68", - "103.18", - "-10.64", - "9.11" - ], - [ - "2024-05-19 17:59:00.000", - "-1.80", - "8.74", - "-1.69", - "101.67", - "-10.75", - "9.08" - ], - [ - "2024-05-19 18:00:00.000", - "-1.80", - "8.74", - "-1.71", - "101.64", - "-10.82", - "9.09" - ], - [ - "2024-05-19 18:01:00.000", - "-1.63", - "8.72", - "-1.89", - "100.62", - "-12.03", - "9.07" - ], - [ - "2024-05-19 18:02:00.000", - "-1.86", - "8.73", - "-1.66", - "102.01", - "-10.53", - "9.08" - ], - [ - "2024-05-19 18:03:00.000", - "-1.89", - "8.73", - "-1.55", - "102.20", - "-9.88", - "9.06" - ], - [ - "2024-05-19 18:04:00.000", - "-2.13", - "8.70", - "-1.25", - "103.75", - "-7.97", - "9.05" - ], - [ - "2024-05-19 18:05:00.000", - "-2.19", - "8.67", - "-1.23", - "104.19", - "-7.81", - "9.03" - ], - [ - "2024-05-19 18:06:00.000", - "-2.33", - "8.60", - "-1.30", - "105.15", - "-8.28", - "9.00" - ], - [ - "2024-05-19 18:07:00.000", - "-2.56", - "8.58", - "-1.40", - "106.61", - "-8.86", - "9.06" - ], - [ - "2024-05-19 18:08:00.000", - "-2.63", - "8.62", - "-1.51", - "106.96", - "-9.54", - "9.13" - ], - [ - "2024-05-19 18:09:00.000", - "-2.86", - "8.55", - "-1.78", - "108.48", - "-11.16", - "9.19" - ], - [ - "2024-05-19 18:10:00.000", - "-2.50", - "8.70", - "-1.37", - "106.04", - "-8.59", - "9.16" - ], - [ - "2024-05-19 18:11:00.000", - "-2.43", - "8.76", - "-0.88", - "105.51", - "-5.55", - "9.14" - ], - [ - "2024-05-19 18:12:00.000", - "-2.13", - "8.82", - "-0.96", - "103.58", - "-6.02", - "9.12" - ], - [ - "2024-05-19 18:13:00.000", - "-2.09", - "8.85", - "-0.62", - "103.31", - "-3.89", - "9.11" - ], - [ - "2024-05-19 18:14:00.000", - "-2.29", - "8.78", - "-0.99", - "104.62", - "-6.21", - "9.13" - ], - [ - "2024-05-19 18:15:00.000", - "-2.77", - "8.53", - "-1.57", - "107.96", - "-9.93", - "9.11" - ], - [ - "2024-05-19 18:16:00.000", - "-2.74", - "8.55", - "-1.45", - "107.78", - "-9.16", - "9.09" - ], - [ - "2024-05-19 18:17:00.000", - "-2.76", - "8.21", - "-1.54", - "108.56", - "-10.07", - "8.80" - ], - [ - "2024-05-19 18:18:00.000", - "-3.25", - "8.05", - "-1.54", - "111.97", - "-10.08", - "8.81" - ], - [ - "2024-05-19 18:19:00.000", - "-3.05", - "8.24", - "-1.56", - "110.34", - "-10.09", - "8.92" - ], - [ - "2024-05-19 18:20:00.000", - "-2.58", - "8.35", - "-1.08", - "107.21", - "-7.07", - "8.80" - ], - [ - "2024-05-19 18:21:00.000", - "-1.79", - "8.59", - "0.20", - "101.78", - "1.32", - "8.77" - ], - [ - "2024-05-19 18:22:00.000", - "-0.80", - "7.98", - "1.27", - "95.74", - "8.97", - "8.12" - ], - [ - "2024-05-19 18:23:00.000", - "-0.79", - "7.96", - "1.71", - "95.67", - "12.08", - "8.18" - ], - [ - "2024-05-19 18:24:00.000", - "-0.75", - "7.81", - "2.16", - "95.51", - "15.41", - "8.14" - ], - [ - "2024-05-19 18:25:00.000", - "-0.40", - "7.94", - "1.46", - "92.91", - "10.38", - "8.08" - ], - [ - "2024-05-19 18:26:00.000", - "0.12", - "8.02", - "1.03", - "89.11", - "7.32", - "8.09" - ], - [ - "2024-05-19 18:27:00.000", - "0.10", - "7.88", - "1.65", - "89.30", - "11.83", - "8.05" - ], - [ - "2024-05-19 18:28:00.000", - "0.61", - "7.76", - "2.01", - "85.50", - "14.51", - "8.04" - ], - [ - "2024-05-19 18:29:00.000", - "0.68", - "7.59", - "2.56", - "84.88", - "18.56", - "8.04" - ], - [ - "2024-05-19 18:30:00.000", - "0.67", - "7.22", - "3.02", - "84.68", - "22.64", - "7.85" - ], - [ - "2024-05-19 18:31:00.000", - "0.53", - "7.18", - "2.77", - "85.80", - "21.03", - "7.71" - ], - [ - "2024-05-19 18:32:00.000", - "0.42", - "7.21", - "2.70", - "86.65", - "20.50", - "7.72" - ], - [ - "2024-05-19 18:33:00.000", - "0.55", - "7.17", - "2.72", - "85.63", - "20.69", - "7.69" - ], - [ - "2024-05-19 18:34:00.000", - "0.68", - "7.11", - "2.70", - "84.52", - "20.70", - "7.63" - ], - [ - "2024-05-19 18:35:00.000", - "0.58", - "7.07", - "2.74", - "85.28", - "21.15", - "7.60" - ], - [ - "2024-05-19 18:36:00.000", - "0.74", - "6.90", - "3.18", - "83.85", - "24.64", - "7.63" - ], - [ - "2024-05-19 18:37:00.000", - "0.88", - "6.83", - "3.25", - "82.64", - "25.26", - "7.62" - ], - [ - "2024-05-19 18:38:00.000", - "-0.02", - "7.30", - "2.40", - "90.13", - "18.24", - "7.68" - ], - [ - "2024-05-19 18:39:00.000", - "-0.12", - "7.57", - "1.81", - "90.93", - "13.47", - "7.78" - ], - [ - "2024-05-19 18:41:00.000", - "0.56", - "7.00", - "2.52", - "85.46", - "19.77", - "7.47" - ], - [ - "2024-05-19 18:42:00.000", - "0.97", - "6.54", - "3.10", - "81.60", - "25.16", - "7.30" - ], - [ - "2024-05-19 18:43:00.000", - "1.39", - "6.21", - "3.45", - "77.36", - "28.45", - "7.24" - ], - [ - "2024-05-19 18:44:00.000", - "1.40", - "6.24", - "3.63", - "77.35", - "29.58", - "7.36" - ], - [ - "2024-05-19 18:45:00.000", - "1.29", - "6.33", - "3.58", - "78.49", - "29.01", - "7.39" - ], - [ - "2024-05-19 18:46:00.000", - "1.64", - "6.05", - "4.04", - "74.87", - "32.82", - "7.46" - ], - [ - "2024-05-19 18:47:00.000", - "1.66", - "5.92", - "4.15", - "74.35", - "34.03", - "7.42" - ], - [ - "2024-05-19 18:48:00.000", - "1.62", - "5.93", - "4.18", - "74.74", - "34.25", - "7.43" - ], - [ - "2024-05-19 18:49:00.000", - "1.58", - "5.94", - "4.20", - "75.13", - "34.37", - "7.45" - ], - [ - "2024-05-19 18:50:00.000", - "1.68", - "5.75", - "4.41", - "73.70", - "36.39", - "7.44" - ], - [ - "2024-05-19 18:51:00.000", - "1.88", - "5.54", - "4.55", - "71.27", - "37.88", - "7.41" - ], - [ - "2024-05-19 18:52:00.000", - "1.91", - "5.40", - "4.66", - "70.51", - "39.16", - "7.38" - ], - [ - "2024-05-19 18:53:00.000", - "1.81", - "5.39", - "4.68", - "71.45", - "39.48", - "7.36" - ], - [ - "2024-05-19 18:54:00.000", - "1.94", - "5.34", - "4.70", - "70.00", - "39.59", - "7.37" - ], - [ - "2024-05-19 18:55:00.000", - "2.09", - "5.29", - "4.75", - "68.42", - "39.88", - "7.41" - ], - [ - "2024-05-19 18:56:00.000", - "1.52", - "5.95", - "4.34", - "75.62", - "35.29", - "7.52" - ], - [ - "2024-05-19 18:57:00.000", - "1.63", - "6.78", - "2.64", - "76.52", - "20.73", - "7.46" - ], - [ - "2024-05-19 18:58:00.000", - "1.37", - "6.79", - "2.76", - "78.55", - "21.76", - "7.46" - ], - [ - "2024-05-19 18:59:00.000", - "1.85", - "5.73", - "4.07", - "72.10", - "34.04", - "7.26" - ], - [ - "2024-05-19 19:00:00.000", - "2.22", - "5.14", - "5.02", - "66.60", - "41.92", - "7.52" - ], - [ - "2024-05-19 19:01:00.000", - "0.00", - "7.63", - "1.83", - "90.00", - "13.52", - "7.84" - ], - [ - "2024-05-19 19:02:00.000", - "0.49", - "5.12", - "4.04", - "84.53", - "38.13", - "6.55" - ], - [ - "2024-05-19 19:03:00.000", - "1.03", - "4.41", - "5.01", - "76.82", - "47.86", - "6.75" - ] -] \ No newline at end of file diff --git a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json b/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json deleted file mode 100644 index f9a8971..0000000 --- a/benchmarks/AuroraScienceHub.Integrations.Benchmarks/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json +++ /dev/null @@ -1,710 +0,0 @@ -[ - [ - "time_tag", - "density", - "speed", - "temperature" - ], - [ - "2024-05-20 05:51:00.000", - "2.18", - "393.9", - "36967" - ], - [ - "2024-05-20 05:52:00.000", - "2.60", - "397.4", - "44618" - ], - [ - "2024-05-20 05:53:00.000", - "2.93", - "401.7", - "50222" - ], - [ - "2024-05-20 05:54:00.000", - "2.84", - "402.1", - "49766" - ], - [ - "2024-05-20 05:55:00.000", - "2.96", - "403.9", - "54639" - ], - [ - "2024-05-20 05:56:00.000", - "3.14", - "403.6", - "52343" - ], - [ - "2024-05-20 05:57:00.000", - "2.85", - "398.1", - "42124" - ], - [ - "2024-05-20 05:58:00.000", - "3.53", - "413.5", - "79750" - ], - [ - "2024-05-20 05:59:00.000", - "4.04", - "418.4", - "94403" - ], - [ - "2024-05-20 06:00:00.000", - "3.59", - "410.5", - "70257" - ], - [ - "2024-05-20 06:01:00.000", - "1.50", - "384.0", - "19719" - ], - [ - "2024-05-20 06:02:00.000", - "3.66", - "411.4", - "72816" - ], - [ - "2024-05-20 06:03:00.000", - "4.60", - "415.7", - "101427" - ], - [ - "2024-05-20 06:04:00.000", - "3.53", - "408.2", - "68041" - ], - [ - "2024-05-20 06:05:00.000", - "2.09", - "390.1", - "30888" - ], - [ - "2024-05-20 06:06:00.000", - "3.72", - "407.5", - "80549" - ], - [ - "2024-05-20 06:07:00.000", - "4.15", - "409.8", - "81947" - ], - [ - "2024-05-20 06:08:00.000", - "3.37", - "401.8", - "59059" - ], - [ - "2024-05-20 06:09:00.000", - "3.23", - "397.8", - "51255" - ], - [ - "2024-05-20 06:10:00.000", - "3.57", - "401.8", - "63432" - ], - [ - "2024-05-20 06:11:00.000", - "3.35", - "403.1", - "66628" - ], - [ - "2024-05-20 06:12:00.000", - "3.00", - "400.6", - "60324" - ], - [ - "2024-05-20 06:13:00.000", - "3.11", - "405.1", - "71978" - ], - [ - "2024-05-20 06:14:00.000", - "2.73", - "400.8", - "67951" - ], - [ - "2024-05-20 06:15:00.000", - "3.49", - "408.4", - "104656" - ], - [ - "2024-05-20 06:16:00.000", - "3.42", - "407.5", - "82744" - ], - [ - "2024-05-20 06:17:00.000", - "2.77", - "397.2", - "52336" - ], - [ - "2024-05-20 06:18:00.000", - "2.81", - "398.7", - "70191" - ], - [ - "2024-05-20 06:19:00.000", - "3.01", - "407.5", - "90879" - ], - [ - "2024-05-20 06:20:00.000", - "3.10", - "407.9", - "116975" - ], - [ - "2024-05-20 06:21:00.000", - "1.45", - "383.5", - "77142" - ], - [ - "2024-05-20 06:22:00.000", - "0.81", - "371.2", - "65281" - ], - [ - "2024-05-20 06:23:00.000", - "0.73", - "368.9", - "64933" - ], - [ - "2024-05-20 06:24:00.000", - "0.60", - "362.8", - "80676" - ], - [ - "2024-05-20 06:25:00.000", - "0.60", - "362.8", - "80676" - ], - [ - "2024-05-20 06:26:00.000", - "0.59", - "361.2", - "50890" - ], - [ - "2024-05-20 06:27:00.000", - "0.58", - "357.7", - "50132" - ], - [ - "2024-05-20 06:28:00.000", - "0.66", - "355.8", - "55686" - ], - [ - "2024-05-20 06:29:00.000", - "0.81", - "362.3", - "76460" - ], - [ - "2024-05-20 06:30:00.000", - "0.67", - "361.9", - "96422" - ], - [ - "2024-05-20 06:31:00.000", - "0.60", - "364.8", - "89577" - ], - [ - "2024-05-20 06:32:00.000", - "0.82", - "360.3", - "52768" - ], - [ - "2024-05-20 06:33:00.000", - "0.83", - "356.3", - "27929" - ], - [ - "2024-05-20 06:34:00.000", - "0.85", - "355.4", - "34516" - ], - [ - "2024-05-20 06:35:00.000", - "0.73", - "359.0", - "27452" - ], - [ - "2024-05-20 06:36:00.000", - "0.55", - "345.7", - "34811" - ], - [ - "2024-05-20 06:37:00.000", - "0.66", - "353.0", - "35145" - ], - [ - "2024-05-20 06:38:00.000", - "0.80", - "362.2", - "81131" - ], - [ - "2024-05-20 06:39:00.000", - "0.73", - "364.4", - "102429" - ], - [ - "2024-05-20 06:40:00.000", - "0.59", - "359.5", - "77349" - ], - [ - "2024-05-20 06:41:00.000", - "0.70", - "359.2", - "56324" - ], - [ - "2024-05-20 06:42:00.000", - "0.78", - "359.4", - "42705" - ], - [ - "2024-05-20 06:43:00.000", - "0.79", - "359.6", - "39952" - ], - [ - "2024-05-20 06:44:00.000", - "0.82", - "360.5", - "55489" - ], - [ - "2024-05-20 06:45:00.000", - "0.73", - "355.2", - "52068" - ], - [ - "2024-05-20 06:46:00.000", - "0.86", - "359.1", - "37104" - ], - [ - "2024-05-20 06:47:00.000", - "0.82", - "360.3", - "28677" - ], - [ - "2024-05-20 06:48:00.000", - "0.87", - "355.8", - "41992" - ], - [ - "2024-05-20 06:49:00.000", - "0.81", - "350.6", - "28254" - ], - [ - "2024-05-20 06:50:00.000", - "0.97", - "359.2", - "43745" - ], - [ - "2024-05-20 06:51:00.000", - "0.97", - "359.4", - "34669" - ], - [ - "2024-05-20 06:52:00.000", - "0.88", - "360.0", - "46481" - ], - [ - "2024-05-20 06:53:00.000", - "0.99", - "350.0", - "44946" - ], - [ - "2024-05-20 06:54:00.000", - "0.88", - "353.5", - "42201" - ], - [ - "2024-05-20 06:55:00.000", - "0.59", - "357.3", - "32648" - ], - [ - "2024-05-20 06:56:00.000", - "0.47", - "358.2", - "28213" - ], - [ - "2024-05-20 06:57:00.000", - "0.68", - "360.6", - "85443" - ], - [ - "2024-05-20 06:58:00.000", - "0.73", - "361.9", - "77703" - ], - [ - "2024-05-20 06:59:00.000", - "0.68", - "366.0", - "77807" - ], - [ - "2024-05-20 07:00:00.000", - "0.56", - "369.1", - "85208" - ], - [ - "2024-05-20 07:01:00.000", - "0.65", - "368.5", - "77965" - ], - [ - "2024-05-20 07:02:00.000", - "0.66", - "370.6", - "89680" - ], - [ - "2024-05-20 07:03:00.000", - "0.64", - "364.3", - "81903" - ], - [ - "2024-05-20 07:04:00.000", - "0.75", - "363.1", - "68216" - ], - [ - "2024-05-20 07:05:00.000", - "0.75", - "363.8", - "39073" - ], - [ - "2024-05-20 07:07:00.000", - "0.70", - "356.7", - "61982" - ], - [ - "2024-05-20 07:08:00.000", - "0.86", - "358.0", - "65453" - ], - [ - "2024-05-20 07:09:00.000", - "0.86", - "355.4", - "32897" - ], - [ - "2024-05-20 07:10:00.000", - "0.86", - "354.8", - "24625" - ], - [ - "2024-05-20 07:11:00.000", - "0.78", - "346.6", - "28705" - ], - [ - "2024-05-20 07:12:00.000", - "0.86", - "349.2", - "35810" - ], - [ - "2024-05-20 07:13:00.000", - "0.88", - "355.6", - "44336" - ], - [ - "2024-05-20 07:14:00.000", - "0.84", - "358.8", - "55106" - ], - [ - "2024-05-20 07:15:00.000", - "0.80", - "360.0", - "68165" - ], - [ - "2024-05-20 07:16:00.000", - "0.72", - "359.8", - "62071" - ], - [ - "2024-05-20 07:17:00.000", - "0.68", - "353.5", - "34599" - ], - [ - "2024-05-20 07:18:00.000", - "0.70", - "356.7", - "32300" - ], - [ - "2024-05-20 07:19:00.000", - "0.78", - "355.7", - "37786" - ], - [ - "2024-05-20 07:20:00.000", - "0.50", - "353.9", - "14791" - ], - [ - "2024-05-20 07:21:00.000", - "0.45", - "348.3", - "22851" - ], - [ - "2024-05-20 07:22:00.000", - "1.00", - "352.2", - "47357" - ], - [ - "2024-05-20 07:23:00.000", - "0.79", - "356.5", - "60315" - ], - [ - "2024-05-20 07:24:00.000", - "0.82", - "356.8", - "52856" - ], - [ - "2024-05-20 07:25:00.000", - "0.91", - "355.8", - "46918" - ], - [ - "2024-05-20 07:26:00.000", - "0.84", - "361.9", - "61155" - ], - [ - "2024-05-20 07:27:00.000", - "0.73", - "365.1", - "51227" - ], - [ - "2024-05-20 07:28:00.000", - "0.66", - "364.0", - "66302" - ], - [ - "2024-05-20 07:29:00.000", - "0.64", - "363.0", - "81527" - ], - [ - "2024-05-20 07:30:00.000", - "0.60", - "360.7", - "78187" - ], - [ - "2024-05-20 07:31:00.000", - "0.61", - "359.8", - "67269" - ], - [ - "2024-05-20 07:32:00.000", - "0.62", - "359.4", - "68661" - ], - [ - "2024-05-20 07:33:00.000", - "0.65", - "360.9", - "109078" - ], - [ - "2024-05-20 07:34:00.000", - "0.64", - "362.4", - "128036" - ], - [ - "2024-05-20 07:35:00.000", - "0.45", - "369.5", - "137350" - ], - [ - "2024-05-20 07:36:00.000", - "0.41", - "359.2", - "59948" - ], - [ - "2024-05-20 07:37:00.000", - "0.41", - "370.3", - "144742" - ], - [ - "2024-05-20 07:38:00.000", - "0.51", - "363.8", - "109968" - ], - [ - "2024-05-20 07:39:00.000", - "0.51", - "363.2", - "69724" - ], - [ - "2024-05-20 07:40:00.000", - "0.52", - "364.3", - "114640" - ], - [ - "2024-05-20 07:41:00.000", - "0.41", - "366.1", - "151774" - ], - [ - "2024-05-20 07:42:00.000", - "0.34", - "368.5", - "152852" - ], - [ - "2024-05-20 07:43:00.000", - "0.36", - "355.7", - "72072" - ], - [ - "2024-05-20 07:44:00.000", - "0.26", - "351.2", - "23548" - ], - [ - "2024-05-20 07:45:00.000", - "0.25", - "358.0", - "78363" - ], - [ - "2024-05-20 07:46:00.000", - "0.30", - "364.6", - "119952" - ], - [ - "2024-05-20 07:47:00.000", - "0.29", - "362.7", - "105402" - ], - [ - "2024-05-20 07:48:00.000", - "0.27", - "358.1", - "83667" - ] -] \ No newline at end of file diff --git a/docs/ReleaseNotes_Integrations_v1.md b/docs/ReleaseNotes_Integrations_v1.md index 5757a3a..3ebc240 100644 --- a/docs/ReleaseNotes_Integrations_v1.md +++ b/docs/ReleaseNotes_Integrations_v1.md @@ -19,11 +19,11 @@ Access real-time data from the Advanced Composition Explorer satellite positione - **Magnetometer Data** - Interplanetary magnetic field measurements (Bx, By, Bz components) - **Solar Wind Plasma Data** - Comprehensive particle measurements including density, speed, and temperature -### DSCOVR Spacecraft Integration -Retrieve data from the Deep Space Climate Observatory with flexible time ranges: -- **Multiple Time Windows** - 2-hour, 1-day, 3-day, and 7-day data access -- **Magnetometer Measurements** - High-precision magnetic field data -- **Solar Wind Parameters** - Critical inputs for space weather forecasting +### RTSW Integration +Retrieve data from NOAA real-time solar wind feeds: +- **1-minute Magnetometer Data** - Real-time interplanetary magnetic field data +- **1-minute Plasma Data** - Real-time bulk solar wind parameters +- **Source Metadata** - Includes `active` and `source` fields in contracts ### KP-Index Data Access Monitor and forecast geomagnetic activity with three specialized endpoints: @@ -33,6 +33,7 @@ Monitor and forecast geomagnetic activity with three specialized endpoints: ## Technical Highlights +- **Breaking Change Notice** - `IDscovrClient` and DSCOVR response models are removed in favor of RTSW-only client surface - **Built on .NET 10.0** - Leveraging the latest .NET features and performance improvements - **Strongly-Typed APIs** - Compile-time safety with comprehensive response models - **Dependency Injection** - Native support for ASP.NET Core and modern .NET applications @@ -57,16 +58,16 @@ Start accessing space weather data: public class SpaceWeatherService { private readonly IAceClient _aceClient; - private readonly IDscovrClient _dscovrClient; + private readonly IRtswClient _rtswClient; private readonly IKpIndexClient _kpIndexClient; public SpaceWeatherService( IAceClient aceClient, - IDscovrClient dscovrClient, + IRtswClient rtswClient, IKpIndexClient kpIndexClient) { _aceClient = aceClient; - _dscovrClient = dscovrClient; + _rtswClient = rtswClient; _kpIndexClient = kpIndexClient; } diff --git a/samples/NoaaClientSample/OutputFormatter.cs b/samples/NoaaClientSample/OutputFormatter.cs index 792aa56..df8e1ff 100644 --- a/samples/NoaaClientSample/OutputFormatter.cs +++ b/samples/NoaaClientSample/OutputFormatter.cs @@ -1,9 +1,9 @@ using AuroraScienceHub.Integrations.NoaaClient.KpIndex.Responses; using Spectre.Console; using AceMagnetometerRecord = AuroraScienceHub.Integrations.NoaaClient.Ace.Responses.MagnetometerRecord; -using DscovrMagnetometerRecord = AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses.MagnetometerRecord; using AceSolarWindPlasmaRecord = AuroraScienceHub.Integrations.NoaaClient.Ace.Responses.SolarWindPlasmaRecord; -using DscovrSolarWindPlasmaRecord = AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses.SolarWindPlasmaRecord; +using RtswMagnetometerRecord = AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses.MagnetometerRecord; +using RtswSolarWindPlasmaRecord = AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses.SolarWindPlasmaRecord; namespace AuroraScienceHub.Integrations.Samples.NoaaClientSample; @@ -73,56 +73,68 @@ public static void DisplayAceSwepam(IReadOnlyList data } /// - /// Displays DSCOVR magnetometer data in table format + /// Displays RTSW magnetometer data in table format /// - public static void DisplayDscovrMagnetometer(IReadOnlyList data, int limit) + public static void DisplayRtswMagnetometer(IReadOnlyList data, int limit) { var table = new Table() .Border(TableBorder.Rounded) - .Title($"[yellow]DSCOVR Magnetometer Data[/] [dim](showing {Math.Min(limit, data.Count)} of {data.Count})[/]") + .Title($"[yellow]RTSW Magnetometer Data[/] [dim](showing {Math.Min(limit, data.Count)} of {data.Count})[/]") .AddColumn("[cyan]DateTime[/]") - .AddColumn("[cyan]Bx (nT)[/]") - .AddColumn("[cyan]By (nT)[/]") - .AddColumn("[cyan]Bz (nT)[/]") + .AddColumn("[cyan]Bx GSM (nT)[/]") + .AddColumn("[cyan]By GSM (nT)[/]") + .AddColumn("[cyan]Bz GSM (nT)[/]") .AddColumn("[cyan]Bt (nT)[/]") - .AddColumn("[cyan]Lat[/]") - .AddColumn("[cyan]Lon[/]"); + .AddColumn("[cyan]Theta GSM[/]") + .AddColumn("[cyan]Phi GSM[/]") + .AddColumn("[cyan]Active[/]") + .AddColumn("[cyan]Source[/]") + .AddColumn("[cyan]Quality[/]"); foreach (var record in data.TakeLast(limit)) { table.AddRow( record.DateTime.ToString("yyyy-MM-dd HH:mm:ss"), - Format(record.Bx), - Format(record.By), - Format(record.Bz), + Format(record.BxGsm), + Format(record.ByGsm), + Format(record.BzGsm), Format(record.Bt), - Format(record.Latitude), - Format(record.Longitude)); + Format(record.ThetaGsm), + Format(record.PhiGsm), + record.Active.ToString(), + record.Source, + FormatInt(record.OverallQuality)); } AnsiConsole.Write(table); } /// - /// Displays DSCOVR solar wind plasma data in table format + /// Displays RTSW solar wind plasma data in table format /// - public static void DisplayDscovrPlasma(IReadOnlyList data, int limit) + public static void DisplayRtswPlasma(IReadOnlyList data, int limit) { var table = new Table() .Border(TableBorder.Rounded) - .Title($"[yellow]DSCOVR Solar Wind Plasma Data[/] [dim](showing {Math.Min(limit, data.Count)} of {data.Count})[/]") + .Title($"[yellow]RTSW Solar Wind Plasma Data[/] [dim](showing {Math.Min(limit, data.Count)} of {data.Count})[/]") .AddColumn("[cyan]DateTime[/]") .AddColumn("[cyan]Proton Density[/]") - .AddColumn("[cyan]Bulk Speed[/]") - .AddColumn("[cyan]Ion Temp[/]"); + .AddColumn("[cyan]Proton Speed[/]") + .AddColumn("[cyan]Proton Temp[/]") + .AddColumn("[cyan]Active[/]") + .AddColumn("[cyan]Source[/]") + .AddColumn("[cyan]Quality[/]"); foreach (var record in data.TakeLast(limit)) { table.AddRow( record.DateTime.ToString("yyyy-MM-dd HH:mm:ss"), Format(record.ProtonDensity), - Format(record.BulkSpeed), - Format(record.IonTemperature)); + Format(record.ProtonSpeed), + Format(record.ProtonTemperature), + record.Active.ToString(), + record.Source, + FormatInt(record.OverallQuality)); } AnsiConsole.Write(table); @@ -180,6 +192,8 @@ public static void DisplayKpNowcast(IReadOnlyList data, // Helper methods private static string Format(float? value) => value?.ToString("F2") ?? "[dim]N/A[/]"; + private static string FormatInt(int? value) => value?.ToString() ?? "[dim]N/A[/]"; + private static string GetActivityLevel(int kpIndex) => kpIndex switch { <= 2 => "Low", diff --git a/samples/NoaaClientSample/Program.cs b/samples/NoaaClientSample/Program.cs index 2c10240..b698f3c 100644 --- a/samples/NoaaClientSample/Program.cs +++ b/samples/NoaaClientSample/Program.cs @@ -1,7 +1,9 @@ -using AuroraScienceHub.Integrations.NoaaClient; +#pragma warning disable CS0618 // Sample still demonstrates deprecated IAceClient until issue #3. + +using AuroraScienceHub.Integrations.NoaaClient; using AuroraScienceHub.Integrations.NoaaClient.Ace; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr; using AuroraScienceHub.Integrations.NoaaClient.KpIndex; +using AuroraScienceHub.Integrations.NoaaClient.Rtsw; using AuroraScienceHub.Integrations.Samples.NoaaClientSample; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -16,8 +18,8 @@ // Get NOAA clients from DI var aceClient = host.Services.GetRequiredService(); -var dscovrClient = host.Services.GetRequiredService(); var kpIndexClient = host.Services.GetRequiredService(); +var rtswClient = host.Services.GetRequiredService(); // Display header AnsiConsole.Write(new FigletText("NOAA Client").Color(Color.Blue)); @@ -31,10 +33,10 @@ .Title("[green]Select an option:[/]") .PageSize(10) .AddChoices( - "ACE Magnetometer", - "ACE SWEPAM", - "DSCOVR Magnetometer", - "DSCOVR Solar Wind Plasma", + "ACE Magnetometer (deprecated)", + "ACE SWEPAM (deprecated)", + "RTSW Magnetometer", + "RTSW Solar Wind Plasma", "KP Index 27-Day Forecast", "KP Index 3-Day Forecast", "KP Index Nowcast", @@ -56,25 +58,25 @@ await AnsiConsole.Status() { await (choice switch { - "ACE Magnetometer" => FetchAndDisplay(async () => + "ACE Magnetometer (deprecated)" => FetchAndDisplay(async () => { var data = await aceClient.GetMagnetometerDataAsync(CancellationToken.None); OutputFormatter.DisplayAceMagnetometer(data, 10); }), - "ACE SWEPAM" => FetchAndDisplay(async () => + "ACE SWEPAM (deprecated)" => FetchAndDisplay(async () => { var data = await aceClient.GetSwepamDataAsync(CancellationToken.None); OutputFormatter.DisplayAceSwepam(data, 10); }), - "DSCOVR Magnetometer" => FetchAndDisplay(async () => + "RTSW Magnetometer" => FetchAndDisplay(async () => { - var data = await dscovrClient.GetMagnetometerData2HAsync(CancellationToken.None); - OutputFormatter.DisplayDscovrMagnetometer(data, 10); + var data = await rtswClient.GetMagnetometerDataAsync(CancellationToken.None); + OutputFormatter.DisplayRtswMagnetometer(data, 10); }), - "DSCOVR Solar Wind Plasma" => FetchAndDisplay(async () => + "RTSW Solar Wind Plasma" => FetchAndDisplay(async () => { - var data = await dscovrClient.GetSolarWindPlasmaData2HAsync(CancellationToken.None); - OutputFormatter.DisplayDscovrPlasma(data, 10); + var data = await rtswClient.GetSolarWindPlasmaDataAsync(CancellationToken.None); + OutputFormatter.DisplayRtswPlasma(data, 10); }), "KP Index 27-Day Forecast" => FetchAndDisplay(async () => { @@ -132,15 +134,15 @@ async Task ExecuteAllRequests() var data = await aceClient.GetSwepamDataAsync(CancellationToken.None); OutputFormatter.DisplayAceSwepam(data, 5); }), - ("DSCOVR Magnetometer", async () => + ("RTSW Magnetometer", async () => { - var data = await dscovrClient.GetMagnetometerData2HAsync(CancellationToken.None); - OutputFormatter.DisplayDscovrMagnetometer(data, 5); + var data = await rtswClient.GetMagnetometerDataAsync(CancellationToken.None); + OutputFormatter.DisplayRtswMagnetometer(data, 5); }), - ("DSCOVR Solar Wind", async () => + ("RTSW Solar Wind", async () => { - var data = await dscovrClient.GetSolarWindPlasmaData2HAsync(CancellationToken.None); - OutputFormatter.DisplayDscovrPlasma(data, 5); + var data = await rtswClient.GetSolarWindPlasmaDataAsync(CancellationToken.None); + OutputFormatter.DisplayRtswPlasma(data, 5); }), ("KP Index Nowcast", async () => { diff --git a/samples/NoaaClientSample/README.md b/samples/NoaaClientSample/README.md index 925e01c..9dd1ad0 100644 --- a/samples/NoaaClientSample/README.md +++ b/samples/NoaaClientSample/README.md @@ -10,9 +10,9 @@ This sample demonstrates all available NOAA client methods through an interactiv - **Magnetometer Data** - 1-minute averaged magnetic field measurements - **SWEPAM Data** - Solar Wind Electron Proton Alpha Monitor measurements -### DSCOVR (Deep Space Climate Observatory) Spacecraft -- **Magnetometer Data** - Magnetic field measurements (2-hour window) -- **Solar Wind Plasma Data** - Solar wind measurements (2-hour window) +### RTSW (Real-Time Solar Wind) Feeds +- **Magnetometer Data** - 1-minute real-time magnetic field measurements +- **Solar Wind Plasma Data** - 1-minute real-time solar wind measurements ### KP Index - **27-Day Forecast** - Extended KP index forecast @@ -65,8 +65,8 @@ You'll see an interactive menu with the following options: 1. **ACE Magnetometer** - Fetch ACE spacecraft magnetometer data 2. **ACE SWEPAM** - Fetch ACE solar wind plasma data -3. **DSCOVR Magnetometer** - Fetch DSCOVR magnetometer data -4. **DSCOVR Solar Wind Plasma** - Fetch DSCOVR plasma data +3. **RTSW Magnetometer** - Fetch RTSW magnetometer data +4. **RTSW Solar Wind Plasma** - Fetch RTSW plasma data 5. **KP Index 27-Day Forecast** - View extended KP forecast 6. **KP Index 3-Day Forecast** - View short-term KP forecast 7. **KP Index Nowcast** - View current KP measurements @@ -116,8 +116,8 @@ Interactive sample application for NOAA API clients ? Select an option: › ❯ ACE Magnetometer ACE SWEPAM - DSCOVR Magnetometer - DSCOVR Solar Wind Plasma + RTSW Magnetometer + RTSW Solar Wind Plasma KP Index 27-Day Forecast KP Index 3-Day Forecast KP Index Nowcast diff --git a/src/NoaaClient/Ace/AceClient.cs b/src/NoaaClient/Ace/AceClient.cs index 7c0751b..d808fa9 100644 --- a/src/NoaaClient/Ace/AceClient.cs +++ b/src/NoaaClient/Ace/AceClient.cs @@ -4,7 +4,10 @@ namespace AuroraScienceHub.Integrations.NoaaClient.Ace; +/// +#pragma warning disable CS0618 // Implements obsolete IAceClient until removal in issue #3. internal sealed class AceClient : IAceClient +#pragma warning restore CS0618 { private readonly HttpClient _httpClient; private readonly Uri _baseUrl; diff --git a/src/NoaaClient/Ace/Extensions/MagnetometerDataParser.cs b/src/NoaaClient/Ace/Extensions/MagnetometerDataParser.cs index 3ca0201..df8ae09 100644 --- a/src/NoaaClient/Ace/Extensions/MagnetometerDataParser.cs +++ b/src/NoaaClient/Ace/Extensions/MagnetometerDataParser.cs @@ -1,5 +1,6 @@ using AuroraScienceHub.Framework.Utilities.System; using AuroraScienceHub.Integrations.NoaaClient.Ace.Responses; +using AuroraScienceHub.Integrations.NoaaClient.Utilities; namespace AuroraScienceHub.Integrations.NoaaClient.Ace.Extensions; @@ -47,21 +48,16 @@ public static IReadOnlyList Parse(string text) second: 0, DateTimeKind.Utc), Status: trimmedLine[fieldsRange[6]].ParseIntInvariant(), - Bx: GetFloatOrNull(trimmedLine[fieldsRange[7]]), - By: GetFloatOrNull(trimmedLine[fieldsRange[8]]), - Bz: GetFloatOrNull(trimmedLine[fieldsRange[9]]), - Bt: GetFloatOrNull(trimmedLine[fieldsRange[10]]), - Latitude: GetFloatOrNull(trimmedLine[fieldsRange[11]]), - Longitude: GetFloatOrNull(trimmedLine[fieldsRange[12]]) + Bx: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[7]], MissingDataValue), + By: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[8]], MissingDataValue), + Bz: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[9]], MissingDataValue), + Bt: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[10]], MissingDataValue), + Latitude: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[11]], MissingDataValue), + Longitude: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[12]], MissingDataValue) ); records.Add(record); } return records; } - - private static float? GetFloatOrNull(ReadOnlySpan value) - => value.SequenceEqual(MissingDataValue) - ? null - : value.ParseFloatInvariant(); } diff --git a/src/NoaaClient/Ace/Extensions/SolarWindPlasmaDataParser.cs b/src/NoaaClient/Ace/Extensions/SolarWindPlasmaDataParser.cs index 05ac4a7..8f07a8f 100644 --- a/src/NoaaClient/Ace/Extensions/SolarWindPlasmaDataParser.cs +++ b/src/NoaaClient/Ace/Extensions/SolarWindPlasmaDataParser.cs @@ -1,5 +1,6 @@ using AuroraScienceHub.Framework.Utilities.System; using AuroraScienceHub.Integrations.NoaaClient.Ace.Responses; +using AuroraScienceHub.Integrations.NoaaClient.Utilities; namespace AuroraScienceHub.Integrations.NoaaClient.Ace.Extensions; @@ -48,23 +49,13 @@ public static IReadOnlyList Parse(string text) second: 0, DateTimeKind.Utc), Status: trimmedLine[fieldsRange[6]].ParseIntInvariant(), - ProtonDensity: GetFloatOrNull(trimmedLine[fieldsRange[7]]), - BulkSpeed: GetFloatOrNull(trimmedLine[fieldsRange[8]]), - IonTemperature: GetTemperatureOrNull(trimmedLine[fieldsRange[9]]) + ProtonDensity: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[7]], MissingDataValue), + BulkSpeed: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[8]], MissingDataValue), + IonTemperature: NoaaNumericParser.ParseNullableFloat(trimmedLine[fieldsRange[9]], MissingTemperatureValue) ); records.Add(record); } return records; } - - private static float? GetFloatOrNull(ReadOnlySpan value) - => value.SequenceEqual(MissingDataValue) - ? null - : value.ParseFloatInvariant(); - - private static float? GetTemperatureOrNull(ReadOnlySpan value) - => value.SequenceEqual(MissingTemperatureValue) - ? null - : value.ParseFloatInvariant(); } diff --git a/src/NoaaClient/Ace/IAceClient.cs b/src/NoaaClient/Ace/IAceClient.cs index 1d10d15..341896a 100644 --- a/src/NoaaClient/Ace/IAceClient.cs +++ b/src/NoaaClient/Ace/IAceClient.cs @@ -5,6 +5,7 @@ namespace AuroraScienceHub.Integrations.NoaaClient.Ace; /// /// ACE Spacecraft client /// +[Obsolete("Use IRtswClient instead. See https://github.com/Aurora-Science-Hub/Integrations/issues/3.")] public interface IAceClient { /// diff --git a/src/NoaaClient/Dscovr/DscovrClient.cs b/src/NoaaClient/Dscovr/DscovrClient.cs deleted file mode 100644 index 7b1a737..0000000 --- a/src/NoaaClient/Dscovr/DscovrClient.cs +++ /dev/null @@ -1,97 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; -using Microsoft.Extensions.Options; - -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr; - -internal sealed class DscovrClient : IDscovrClient -{ - private readonly HttpClient _httpClient; - private readonly Uri _baseUrl; - - public DscovrClient( - HttpClient httpClient, - IOptions options) - { - _httpClient = httpClient; - _baseUrl = options.Value.RequiredServerUrl; - } - - public async Task> GetMagnetometerData2HAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/mag-2-hour.json"); - return await GetMagnetometerDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetMagnetometerData1DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/mag-1-day.json"); - return await GetMagnetometerDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetMagnetometerData3DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/mag-3-day.json"); - return await GetMagnetometerDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetMagnetometerData7DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/mag-7-day.json"); - return await GetMagnetometerDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetSolarWindPlasmaData2HAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/plasma-2-hour.json"); - return await GetSolarWindPlasmaDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetSolarWindPlasmaData1DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/plasma-1-day.json"); - return await GetSolarWindPlasmaDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetSolarWindPlasmaData3DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/plasma-3-day.json"); - return await GetSolarWindPlasmaDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - public async Task> GetSolarWindPlasmaData7DAsync(CancellationToken cancellationToken) - { - var url = new Uri(_baseUrl, "products/solar-wind/plasma-7-day.json"); - return await GetSolarWindPlasmaDataAsync(url, cancellationToken) - .ConfigureAwait(false); - } - - private async Task> GetMagnetometerDataAsync( - Uri url, - CancellationToken cancellationToken) - { - var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - var text = await response.Content.ReadAsStringAsync(cancellationToken); - - return MagnetometerDataParser.Parse(text); - } - - private async Task> GetSolarWindPlasmaDataAsync( - Uri url, - CancellationToken cancellationToken) - { - var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - var text = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - - return SolarWindPlasmaDataParser.Parse(text); - } -} diff --git a/src/NoaaClient/Dscovr/Extensions/MagnetometerDataParser.cs b/src/NoaaClient/Dscovr/Extensions/MagnetometerDataParser.cs deleted file mode 100644 index 35625f3..0000000 --- a/src/NoaaClient/Dscovr/Extensions/MagnetometerDataParser.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Text.Json; -using AuroraScienceHub.Framework.Utilities.System; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; - -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; - -// DSCOVR Magnetometer data example: -// [ -// [ -// "time_tag", -// "bx_gsm", -// "by_gsm", -// "bz_gsm", -// "lon_gsm", -// "lat_gsm", -// "bt" -// ], -// [ -// "2024-05-19 16:50:00.000", -// "-2.25", -// "8.71", -// "-1.79", -// "104.50", -// "-11.25", -// "9.17" -// ] -// ] -internal static class MagnetometerDataParser -{ - private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fff"; - - public static IReadOnlyList Parse(string text) - { - using var jsonDoc = JsonDocument.Parse(text); - var rootElement = jsonDoc.RootElement; - - if (rootElement.ValueKind != JsonValueKind.Array) - { - return Array.Empty(); - } - - var arrayLength = rootElement.GetArrayLength(); - if (rootElement.GetArrayLength() < 2) - { - return Array.Empty(); - } - - var fieldNames = rootElement[0]; - var magnetometerRecords = new List(arrayLength); - - var timeTagIndex = GetIndex(fieldNames, "time_tag"); - var bxGsmIndex = GetIndex(fieldNames, "bx_gsm"); - var byGsmIndex = GetIndex(fieldNames, "by_gsm"); - var bzGsmIndex = GetIndex(fieldNames, "bz_gsm"); - var btIndex = GetIndex(fieldNames, "bt"); - var latGsmIndex = GetIndex(fieldNames, "lat_gsm"); - var lonGsmIndex = GetIndex(fieldNames, "lon_gsm"); - - foreach (var record in rootElement.EnumerateArray().Skip(1)) - { - magnetometerRecords.Add(new MagnetometerRecord( - DateTime: DateTime.ParseExact( - record[timeTagIndex].GetString().Required(), DateTimeFormat, - provider: null, - style: System.Globalization.DateTimeStyles.AssumeUniversal | - System.Globalization.DateTimeStyles.AdjustToUniversal), - Bx: record[bxGsmIndex].GetString()?.ParseFloatInvariant(), - By: record[byGsmIndex].GetString()?.ParseFloatInvariant(), - Bz: record[bzGsmIndex].GetString()?.ParseFloatInvariant(), - Bt: record[btIndex].GetString()?.ParseFloatInvariant(), - Latitude: record[latGsmIndex].GetString()?.ParseFloatInvariant(), - Longitude: record[lonGsmIndex].GetString()?.ParseFloatInvariant() - )); - } - - return magnetometerRecords; - } - - private static int GetIndex(JsonElement fieldNames, string fieldName) - { - var index = 0; - foreach (var name in fieldNames.EnumerateArray()) - { - if (string.Equals(name.GetString(), fieldName, StringComparison.OrdinalIgnoreCase)) - { - return index; - } - index++; - } - - throw new InvalidOperationException($"Field '{fieldName}' not found."); - } -} diff --git a/src/NoaaClient/Dscovr/Extensions/SolarWindPlasmaDataParser.cs b/src/NoaaClient/Dscovr/Extensions/SolarWindPlasmaDataParser.cs deleted file mode 100644 index 6f121cd..0000000 --- a/src/NoaaClient/Dscovr/Extensions/SolarWindPlasmaDataParser.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text.Json; -using AuroraScienceHub.Framework.Utilities.System; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; - -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; - -// DSCOVR Solar wind plasma data example: -// [ -// [ -// "time_tag", -// "density", -// "speed", -// "temperature" -// ], -// [ -// "2024-05-20 05:51:00.000", -// "2.18", -// "393.9", -// "36967" -// ], -// [ -// "2024-05-20 05:52:00.000", -// "2.60", -// "397.4", -// "44618" -// ] -// ] -internal static class SolarWindPlasmaDataParser -{ - private const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.fff"; - - public static IReadOnlyList Parse(string text) - { - using var jsonDoc = JsonDocument.Parse(text); - var rootElement = jsonDoc.RootElement; - - if (rootElement.ValueKind != JsonValueKind.Array) - { - return Array.Empty(); - } - - var arrayLength = rootElement.GetArrayLength(); - if (rootElement.GetArrayLength() < 2) - { - return Array.Empty(); - } - - var fieldNames = rootElement[0]; - var solarWindPlasmaRecords = new List(arrayLength); - - var timeTagIndex = GetIndex(fieldNames, "time_tag"); - var densityIndex = GetIndex(fieldNames, "density"); - var speedIndex = GetIndex(fieldNames, "speed"); - var temperatureIndex = GetIndex(fieldNames, "temperature"); - - foreach (var record in rootElement.EnumerateArray().Skip(1)) - { - solarWindPlasmaRecords.Add(new SolarWindPlasmaRecord( - DateTime: DateTime.ParseExact( - record[timeTagIndex].GetString().Required(), DateTimeFormat, - provider: null, - style: System.Globalization.DateTimeStyles.AssumeUniversal | - System.Globalization.DateTimeStyles.AdjustToUniversal), - ProtonDensity: record[densityIndex].GetString()?.ParseFloatInvariant(), - BulkSpeed: record[speedIndex].GetString()?.ParseFloatInvariant(), - IonTemperature: record[temperatureIndex].GetString()?.ParseFloatInvariant() - )); - } - - return solarWindPlasmaRecords; - } - - private static int GetIndex(JsonElement fieldNames, string fieldName) - { - var index = 0; - foreach (var name in fieldNames.EnumerateArray()) - { - if (string.Equals(name.GetString(), fieldName, StringComparison.OrdinalIgnoreCase)) - { - return index; - } - index++; - } - - throw new InvalidOperationException($"Field '{fieldName}' not found."); - } -} diff --git a/src/NoaaClient/Dscovr/IDscovrClient.cs b/src/NoaaClient/Dscovr/IDscovrClient.cs deleted file mode 100644 index 71f5726..0000000 --- a/src/NoaaClient/Dscovr/IDscovrClient.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; - -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr; - -/// -/// DSCOVR Spacecraft client -/// -public interface IDscovrClient -{ - /// - /// Get DSCOVR Magnetometer data (2 hours) - /// - Task> GetMagnetometerData2HAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Magnetometer data (1 day) - /// - Task> GetMagnetometerData1DAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Magnetometer data (3 days) - /// - Task> GetMagnetometerData3DAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Magnetometer data (7 days) - /// - Task> GetMagnetometerData7DAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Solar Wind Electron Proton Alpha Monitor data (2 hours) - /// - Task> GetSolarWindPlasmaData2HAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Solar Wind Electron Proton Alpha Monitor data (1 day) - /// - Task> GetSolarWindPlasmaData1DAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Solar Wind Electron Proton Alpha Monitor data (3 days) - /// - Task> GetSolarWindPlasmaData3DAsync(CancellationToken cancellationToken); - - /// - /// Get DSCOVR Solar Wind Electron Proton Alpha Monitor data (7 days) - /// - Task> GetSolarWindPlasmaData7DAsync(CancellationToken cancellationToken); -} diff --git a/src/NoaaClient/Dscovr/Responses/MagnetometerRecord.cs b/src/NoaaClient/Dscovr/Responses/MagnetometerRecord.cs deleted file mode 100644 index 6ef18b7..0000000 --- a/src/NoaaClient/Dscovr/Responses/MagnetometerRecord.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; - -/// -/// 1-minute averaged Real-time Interplanetary Magnetic Field Values (DSCOVR) -/// -/// Date and time of record (UTC) -/// IMF GSM x-component -/// IMF GSM y-component -/// IMF GSM z-component -/// IMF module -/// Spacecraft latitude -/// Spacecraft longitude -public record MagnetometerRecord( - DateTime DateTime, - float? Bx, - float? By, - float? Bz, - float? Bt, - float? Latitude, - float? Longitude); diff --git a/src/NoaaClient/Dscovr/Responses/SolarWindPlasmaRecord.cs b/src/NoaaClient/Dscovr/Responses/SolarWindPlasmaRecord.cs deleted file mode 100644 index 9659fd0..0000000 --- a/src/NoaaClient/Dscovr/Responses/SolarWindPlasmaRecord.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; - -/// -/// 1-minute averaged Real-time Bulk Parameters of the Solar Wind Plasma (DSCOVR) -/// -/// Date and time of record (UTC) -/// Solar Wind proton density -/// Solar Wind plasma speed -/// Solar Wind Ion Temp -public record SolarWindPlasmaRecord( - DateTime DateTime, - float? ProtonDensity, - float? BulkSpeed, - float? IonTemperature -); diff --git a/src/NoaaClient/KpIndex/KpIndexClient.cs b/src/NoaaClient/KpIndex/KpIndexClient.cs index 943be75..35a978d 100644 --- a/src/NoaaClient/KpIndex/KpIndexClient.cs +++ b/src/NoaaClient/KpIndex/KpIndexClient.cs @@ -4,6 +4,7 @@ namespace AuroraScienceHub.Integrations.NoaaClient.KpIndex; +/// internal sealed class KpIndexClient : IKpIndexClient { private readonly HttpClient _client; diff --git a/src/NoaaClient/NoaaClient.csproj b/src/NoaaClient/NoaaClient.csproj index 785fbc0..ed48fd3 100644 --- a/src/NoaaClient/NoaaClient.csproj +++ b/src/NoaaClient/NoaaClient.csproj @@ -3,7 +3,7 @@ AuroraScienceHub.Integrations.NoaaClient AuroraScienceHub.Integrations.NoaaClient - NOAA space weather data integration client for ACE, DSCOVR spacecraft and KP-index data + NOAA space weather data integration client for ACE, RTSW feeds and KP-index data @@ -15,9 +15,9 @@ - Provides unified HTTP client interfaces for accessing NOAA space weather data including solar wind measurements, magnetometer data, and geomagnetic activity indices from ACE and DSCOVR spacecraft with KP-index forecasts and nowcast data. + Provides unified HTTP client interfaces for accessing NOAA space weather data including solar wind measurements, magnetometer data, and geomagnetic activity indices from ACE and RTSW feeds with KP-index forecasts and nowcast data. - noaa;space-weather;ace;dscovr;kp-index;solar-wind;magnetometer;geomagnetic;aurora;integration + noaa;space-weather;ace;rtsw;kp-index;solar-wind;magnetometer;geomagnetic;aurora;integration diff --git a/src/NoaaClient/NoaaClientOptions.cs b/src/NoaaClient/NoaaClientOptions.cs index 0f2ce28..c30e1f4 100644 --- a/src/NoaaClient/NoaaClientOptions.cs +++ b/src/NoaaClient/NoaaClientOptions.cs @@ -8,7 +8,7 @@ public sealed class NoaaClientOptions /// /// Configuration option key /// - public const string OptionKey = "NoaaClient"; + public const string OptionKey = "Noaa"; private const string EmptyServerUrlMessage = $"Configuration value '{OptionKey}:{nameof(ServerUrl)}' is not set."; diff --git a/src/NoaaClient/README.md b/src/NoaaClient/README.md index 0a7a2ed..d45e322 100644 --- a/src/NoaaClient/README.md +++ b/src/NoaaClient/README.md @@ -1,6 +1,6 @@ # AuroraScienceHub.Integrations.Noaa -NOAA space weather data integration with support for ACE, DSCOVR spacecraft and KP-index data. +NOAA space weather data integration with support for ACE, RTSW and KP-index data. ## Overview @@ -8,8 +8,8 @@ Provides unified interfaces for accessing NOAA space weather data including sola ## Key Features -- **ACE Spacecraft** - Access to Magnetometer and SWEPAM (Solar Wind) data -- **DSCOVR Spacecraft** - Magnetometer and Solar Wind Plasma data with multiple time ranges +- **ACE Spacecraft** - Legacy magnetometer and SWEPAM text feeds (**deprecated**; use RTSW instead — see [#3](https://github.com/Aurora-Science-Hub/Integrations/issues/3)) +- **RTSW Feed** - 1-minute real-time magnetometer and solar wind plasma data - **KP-Index** - Geomagnetic activity forecasts and nowcast data - **Unified Interfaces** - Consistent API across all NOAA data sources @@ -28,21 +28,33 @@ dotnet add package AuroraScienceHub.Integrations.Noaa builder.Services.AddNoaaClients(); ``` -### ACE Client +### ACE Client (deprecated) + +`IAceClient` is obsolete. Prefer `IRtswClient` for magnetometer and solar wind plasma data. ```csharp var magnetometerData = await aceClient.GetMagnetometerDataAsync(cancellationToken); var solarWindData = await aceClient.GetSwepamDataAsync(cancellationToken); ``` -### DSCOVR Client +Migration: use `IRtswClient` and filter by `Active` or `Source == "ACE"` when ACE-specific rows are required. + +### RTSW Client ```csharp -// Available time ranges: 2H, 1D, 3D, 7D -var magnetometerData = await dscovrClient.GetMagnetometerData1DAsync(cancellationToken); -var solarWindData = await dscovrClient.GetSolarWindPlasmaData1DAsync(cancellationToken); +var magnetometerData = await rtswClient.GetMagnetometerDataAsync(cancellationToken); +var solarWindData = await rtswClient.GetSolarWindPlasmaDataAsync(cancellationToken); ``` +RTSW response models map the full NOAA `rtsw_mag_1m.json` and `rtsw_wind_1m.json` schema (not legacy DSCOVR field names). + +| Magnetometer (`MagnetometerRecord`) | Wind (`SolarWindPlasmaRecord`) | +| --- | --- | +| `DateTime`, `Active`, `Source` | `DateTime`, `Active`, `Source` | +| `Range`, `Scale`, `Sensitivity`, `ManualMode`, `SampleSize` | `ProtonSpeed`, `ProtonTemperature`, `ProtonDensity` | +| `Bt`, `BxGse`–`PhiGse`, `BxGsm`–`PhiGsm` | `ProtonVxGse`–`ProtonVzGsm`, `ProtonSampleSize` | +| `MaxTelemetryFlag`, `MaxDataFlag`, `OverallQuality` | `AlphaSpeed`–`AlphaSampleSize`, quality flags (`MaxConvergenceFlag`–`OverallQuality`) | + ### KP-Index Client ```csharp diff --git a/src/NoaaClient/Rtsw/IRtswClient.cs b/src/NoaaClient/Rtsw/IRtswClient.cs new file mode 100644 index 0000000..1653122 --- /dev/null +++ b/src/NoaaClient/Rtsw/IRtswClient.cs @@ -0,0 +1,19 @@ +using AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses; + +namespace AuroraScienceHub.Integrations.NoaaClient.Rtsw; + +/// +/// NOAA real-time solar wind (RTSW) client. +/// +public interface IRtswClient +{ + /// + /// Gets 1-minute real-time interplanetary magnetic field values. + /// + Task> GetMagnetometerDataAsync(CancellationToken cancellationToken); + + /// + /// Gets 1-minute real-time solar wind plasma values. + /// + Task> GetSolarWindPlasmaDataAsync(CancellationToken cancellationToken); +} diff --git a/src/NoaaClient/Rtsw/Responses/MagnetometerRecord.cs b/src/NoaaClient/Rtsw/Responses/MagnetometerRecord.cs new file mode 100644 index 0000000..f908bbb --- /dev/null +++ b/src/NoaaClient/Rtsw/Responses/MagnetometerRecord.cs @@ -0,0 +1,52 @@ +using System.Text.Json.Serialization; + +namespace AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses; + +/// +/// 1-minute averaged real-time interplanetary magnetic field values (RTSW). +/// +/// Date and time of record (UTC) +/// Whether this source is the active feed for the timestamp +/// Data source identifier reported by NOAA +/// Instrument range metadata +/// Instrument scale metadata +/// Instrument sensitivity metadata +/// Manual mode flag reported by NOAA +/// Number of samples averaged in the record +/// Total magnetic field magnitude +/// IMF GSE x-component +/// IMF GSE y-component +/// IMF GSE z-component +/// IMF GSE theta angle +/// IMF GSE phi angle +/// IMF GSM x-component +/// IMF GSM y-component +/// IMF GSM z-component +/// IMF GSM theta angle +/// IMF GSM phi angle +/// Maximum telemetry quality flag +/// Maximum data quality flag +/// Overall quality indicator +public record MagnetometerRecord( + [property: JsonPropertyName("time_tag")] DateTime DateTime, + [property: JsonPropertyName("active")] bool Active, + [property: JsonPropertyName("source")] string Source, + [property: JsonPropertyName("range")] float? Range, + [property: JsonPropertyName("scale")] float? Scale, + [property: JsonPropertyName("sensitivity")] float? Sensitivity, + [property: JsonPropertyName("manual_mode")] bool? ManualMode, + [property: JsonPropertyName("sample_size")] int? SampleSize, + [property: JsonPropertyName("bt")] float? Bt, + [property: JsonPropertyName("bx_gse")] float? BxGse, + [property: JsonPropertyName("by_gse")] float? ByGse, + [property: JsonPropertyName("bz_gse")] float? BzGse, + [property: JsonPropertyName("theta_gse")] float? ThetaGse, + [property: JsonPropertyName("phi_gse")] float? PhiGse, + [property: JsonPropertyName("bx_gsm")] float? BxGsm, + [property: JsonPropertyName("by_gsm")] float? ByGsm, + [property: JsonPropertyName("bz_gsm")] float? BzGsm, + [property: JsonPropertyName("theta_gsm")] float? ThetaGsm, + [property: JsonPropertyName("phi_gsm")] float? PhiGsm, + [property: JsonPropertyName("max_telemetry_flag")] int? MaxTelemetryFlag, + [property: JsonPropertyName("max_data_flag")] int? MaxDataFlag, + [property: JsonPropertyName("overall_quality")] int? OverallQuality); diff --git a/src/NoaaClient/Rtsw/Responses/SolarWindPlasmaRecord.cs b/src/NoaaClient/Rtsw/Responses/SolarWindPlasmaRecord.cs new file mode 100644 index 0000000..6aef3bb --- /dev/null +++ b/src/NoaaClient/Rtsw/Responses/SolarWindPlasmaRecord.cs @@ -0,0 +1,70 @@ +using System.Text.Json.Serialization; + +namespace AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses; + +/// +/// 1-minute averaged real-time bulk parameters of the solar wind plasma (RTSW). +/// +/// Date and time of record (UTC) +/// Whether this source is the active feed for the timestamp +/// Data source identifier reported by NOAA +/// Proton bulk speed +/// Proton temperature +/// Proton density +/// Proton velocity x-component in GSE +/// Proton velocity y-component in GSE +/// Proton velocity z-component in GSE +/// Proton velocity x-component in GSM +/// Proton velocity y-component in GSM +/// Proton velocity z-component in GSM +/// Proton sample size +/// Alpha particle bulk speed +/// Alpha particle temperature +/// Alpha particle density +/// Alpha velocity x-component in GSE +/// Alpha velocity y-component in GSE +/// Alpha velocity z-component in GSE +/// Alpha velocity x-component in GSM +/// Alpha velocity y-component in GSM +/// Alpha velocity z-component in GSM +/// Alpha sample size +/// Maximum convergence quality flag +/// Maximum data quality flag +/// Maximum error count quality flag +/// Maximum processing quality flag +/// Maximum range quality flag +/// Maximum sample count quality flag +/// Maximum telemetry quality flag +/// Overall quality indicator +public record SolarWindPlasmaRecord( + [property: JsonPropertyName("time_tag")] DateTime DateTime, + [property: JsonPropertyName("active")] bool Active, + [property: JsonPropertyName("source")] string Source, + [property: JsonPropertyName("proton_speed")] float? ProtonSpeed, + [property: JsonPropertyName("proton_temperature")] float? ProtonTemperature, + [property: JsonPropertyName("proton_density")] float? ProtonDensity, + [property: JsonPropertyName("proton_vx_gse")] float? ProtonVxGse, + [property: JsonPropertyName("proton_vy_gse")] float? ProtonVyGse, + [property: JsonPropertyName("proton_vz_gse")] float? ProtonVzGse, + [property: JsonPropertyName("proton_vx_gsm")] float? ProtonVxGsm, + [property: JsonPropertyName("proton_vy_gsm")] float? ProtonVyGsm, + [property: JsonPropertyName("proton_vz_gsm")] float? ProtonVzGsm, + [property: JsonPropertyName("proton_sample_size")] int? ProtonSampleSize, + [property: JsonPropertyName("alpha_speed")] float? AlphaSpeed, + [property: JsonPropertyName("alpha_temperature")] float? AlphaTemperature, + [property: JsonPropertyName("alpha_density")] float? AlphaDensity, + [property: JsonPropertyName("alpha_vx_gse")] float? AlphaVxGse, + [property: JsonPropertyName("alpha_vy_gse")] float? AlphaVyGse, + [property: JsonPropertyName("alpha_vz_gse")] float? AlphaVzGse, + [property: JsonPropertyName("alpha_vx_gsm")] float? AlphaVxGsm, + [property: JsonPropertyName("alpha_vy_gsm")] float? AlphaVyGsm, + [property: JsonPropertyName("alpha_vz_gsm")] float? AlphaVzGsm, + [property: JsonPropertyName("alpha_sample_size")] int? AlphaSampleSize, + [property: JsonPropertyName("max_convergence_flag")] int? MaxConvergenceFlag, + [property: JsonPropertyName("max_data_flag")] int? MaxDataFlag, + [property: JsonPropertyName("max_error_count_flag")] int? MaxErrorCountFlag, + [property: JsonPropertyName("max_processing_flag")] int? MaxProcessingFlag, + [property: JsonPropertyName("max_range_flag")] int? MaxRangeFlag, + [property: JsonPropertyName("max_sample_count_flag")] int? MaxSampleCountFlag, + [property: JsonPropertyName("max_telemetry_flag")] int? MaxTelemetryFlag, + [property: JsonPropertyName("overall_quality")] int? OverallQuality); diff --git a/src/NoaaClient/Rtsw/RtswClient.cs b/src/NoaaClient/Rtsw/RtswClient.cs new file mode 100644 index 0000000..a47fcee --- /dev/null +++ b/src/NoaaClient/Rtsw/RtswClient.cs @@ -0,0 +1,43 @@ +using AuroraScienceHub.Framework.Http; +using AuroraScienceHub.Integrations.NoaaClient.Rtsw.Responses; +using Microsoft.Extensions.Options; + +namespace AuroraScienceHub.Integrations.NoaaClient.Rtsw; + +/// +internal sealed class RtswClient : IRtswClient +{ + private readonly HttpClient _httpClient; + private readonly Uri _baseUrl; + + /// + /// Initializes a new instance of the class. + /// + public RtswClient( + HttpClient httpClient, + IOptions options) + { + _httpClient = httpClient; + _baseUrl = options.Value.RequiredServerUrl; + } + + public async Task> GetMagnetometerDataAsync(CancellationToken cancellationToken) + { + var url = new Uri(_baseUrl, "json/rtsw/rtsw_mag_1m.json"); + var records = await _httpClient + .GetFromJsonOrDefaultAsync>(url, cancellationToken) + .ConfigureAwait(false); + + return records ?? []; + } + + public async Task> GetSolarWindPlasmaDataAsync(CancellationToken cancellationToken) + { + var url = new Uri(_baseUrl, "json/rtsw/rtsw_wind_1m.json"); + var records = await _httpClient + .GetFromJsonOrDefaultAsync>(url, cancellationToken) + .ConfigureAwait(false); + + return records ?? []; + } +} diff --git a/src/NoaaClient/ServiceCollectionExtensions.cs b/src/NoaaClient/ServiceCollectionExtensions.cs index a1b752a..127aac5 100644 --- a/src/NoaaClient/ServiceCollectionExtensions.cs +++ b/src/NoaaClient/ServiceCollectionExtensions.cs @@ -1,6 +1,6 @@ using AuroraScienceHub.Integrations.NoaaClient.Ace; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr; using AuroraScienceHub.Integrations.NoaaClient.KpIndex; +using AuroraScienceHub.Integrations.NoaaClient.Rtsw; using Microsoft.Extensions.DependencyInjection; namespace AuroraScienceHub.Integrations.NoaaClient; @@ -13,14 +13,19 @@ public static class ServiceCollectionExtensions /// /// Adds NOAA clients to the service collection. /// + /// + /// Registers for backward compatibility only; prefer . + /// public static IServiceCollection AddNoaaClients(this IServiceCollection services) { services.AddOptions() .BindConfiguration(NoaaClientOptions.OptionKey); +#pragma warning disable CS0618 // ACE client registration pending removal in issue #3. services.AddHttpClient(); - services.AddHttpClient(); +#pragma warning restore CS0618 services.AddHttpClient(); + services.AddHttpClient(); return services; } diff --git a/src/NoaaClient/Utilities/NoaaNumericParser.cs b/src/NoaaClient/Utilities/NoaaNumericParser.cs new file mode 100644 index 0000000..6870a52 --- /dev/null +++ b/src/NoaaClient/Utilities/NoaaNumericParser.cs @@ -0,0 +1,43 @@ +using AuroraScienceHub.Framework.Utilities.System; + +namespace AuroraScienceHub.Integrations.NoaaClient.Utilities; + +internal static class NoaaNumericParser +{ + /// + /// Parses nullable float from span value with optional null markers. + /// + public static float? ParseNullableFloat(ReadOnlySpan value, params string[] nullMarkers) + { + foreach (var nullMarker in nullMarkers) + { + if (value.SequenceEqual(nullMarker)) + { + return null; + } + } + + return value.ParseFloatInvariant(); + } + + /// + /// Parses nullable float from string value with optional null markers. + /// + public static float? ParseNullableFloat(string? value, params string[] nullMarkers) + { + if (value is null) + { + return null; + } + + foreach (var nullMarker in nullMarkers) + { + if (string.Equals(value, nullMarker, StringComparison.Ordinal)) + { + return null; + } + } + + return value.ParseFloatInvariant(); + } +} diff --git a/tests/UnitTests/NoaaClient/Ace/MagnetometerDataParserTests.cs b/tests/UnitTests/NoaaClient/Ace/MagnetometerDataParserTests.cs index dee856a..72c544b 100644 --- a/tests/UnitTests/NoaaClient/Ace/MagnetometerDataParserTests.cs +++ b/tests/UnitTests/NoaaClient/Ace/MagnetometerDataParserTests.cs @@ -10,7 +10,7 @@ namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.Ace; /// public sealed class MagnetometerDataParserTests { - [Theory] + [Theory(DisplayName = "ACE magnetometer parser returns expected records for valid payload")] [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/Ace/Samples/AceMagnetometerSample.txt")] public void Parse_WhenTextIsValid_ReturnsRecords(string text) { diff --git a/tests/UnitTests/NoaaClient/Ace/SolarWindPlasmaDataParserTests.cs b/tests/UnitTests/NoaaClient/Ace/SolarWindPlasmaDataParserTests.cs index 49c3438..bc981fb 100644 --- a/tests/UnitTests/NoaaClient/Ace/SolarWindPlasmaDataParserTests.cs +++ b/tests/UnitTests/NoaaClient/Ace/SolarWindPlasmaDataParserTests.cs @@ -10,7 +10,7 @@ namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.Ace; /// public sealed class SolarWindPlasmaDataParserTests { - [Theory] + [Theory(DisplayName = "ACE SWEPAM parser returns expected records for valid payload")] [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/Ace/Samples/AceSwepamSample.txt")] public void Parse_WhenTextIsValid_ReturnsRecords(string text) { diff --git a/tests/UnitTests/NoaaClient/Dscovr/MagnetometerDataParserTests.cs b/tests/UnitTests/NoaaClient/Dscovr/MagnetometerDataParserTests.cs deleted file mode 100644 index f927dcd..0000000 --- a/tests/UnitTests/NoaaClient/Dscovr/MagnetometerDataParserTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; -using AuroraScienceHub.Integrations.UnitTests.Utils; -using Shouldly; - -namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.Dscovr; - -/// -/// Unit tests for . -/// -public sealed class MagnetometerDataParserTests -{ - [Theory] - [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json")] - public void Parse_WhenTextIsValid_ReturnsRecords(string text) - { - // Arrange, Act - var result = MagnetometerDataParser.Parse(text); - - // Assert - result.ShouldNotBeEmpty(); - result.ShouldBe([ - new MagnetometerRecord - ( - DateTime: new DateTime(2024, 5, 19, 17, 7, 0, DateTimeKind.Utc), - Bx: -2.15f, - By: 8.51f, - Bz: -2.98f, - Bt: 9.27f, - Latitude: -18.76f, - Longitude: 104.17f - ), - new MagnetometerRecord - ( - DateTime: new DateTime(2024, 5, 19, 17, 8, 0, DateTimeKind.Utc), - Bx: -2.38f, - By: 8.31f, - Bz: -3.24f, - Bt: 9.23f, - Latitude: -20.54f, - Longitude: 105.97f - ) - ]); - } -} diff --git a/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json b/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json deleted file mode 100644 index 9de4324..0000000 --- a/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrMagnetometerSample.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - [ - "time_tag", - "bx_gsm", - "by_gsm", - "bz_gsm", - "lon_gsm", - "lat_gsm", - "bt" - ], - [ - "2024-05-19 17:07:00.000", - "-2.15", - "8.51", - "-2.98", - "104.17", - "-18.76", - "9.27" - ], - [ - "2024-05-19 17:08:00.000", - "-2.38", - "8.31", - "-3.24", - "105.97", - "-20.54", - "9.23" - ] -] diff --git a/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json b/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json deleted file mode 100644 index cb5af74..0000000 --- a/tests/UnitTests/NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - [ - "time_tag", - "density", - "speed", - "temperature" - ], - [ - "2024-05-19 05:51:00.000", - "2.18", - "393.9", - "36967" - ], - [ - "2024-05-19 05:52:00.000", - "2.60", - "397.4", - "44618" - ] -] diff --git a/tests/UnitTests/NoaaClient/Dscovr/SolarWindPlasmaDataParserTests.cs b/tests/UnitTests/NoaaClient/Dscovr/SolarWindPlasmaDataParserTests.cs deleted file mode 100644 index b64e7f9..0000000 --- a/tests/UnitTests/NoaaClient/Dscovr/SolarWindPlasmaDataParserTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Extensions; -using AuroraScienceHub.Integrations.NoaaClient.Dscovr.Responses; -using AuroraScienceHub.Integrations.UnitTests.Utils; -using Shouldly; - -namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.Dscovr; - -/// -/// Unit tests for . -/// -public sealed class SolarWindPlasmaDataParserTests -{ - [Theory] - [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/Dscovr/Samples/DscovrSolarWindPlasmaSample.json")] - public void Parse_WhenTextIsValid_ReturnsRecords(string text) - { - // Arrange, Act - var result = SolarWindPlasmaDataParser.Parse(text); - - // Assert - result.ShouldNotBeEmpty(); - result.ShouldBe([ - new SolarWindPlasmaRecord - ( - DateTime: new DateTime(2024, 5, 19, 5, 51, 0, DateTimeKind.Utc), - ProtonDensity: 2.18f, - BulkSpeed: 393.9f, - IonTemperature: 36967f - ), - new SolarWindPlasmaRecord - ( - DateTime: new DateTime(2024, 5, 19, 5, 52, 0, DateTimeKind.Utc), - ProtonDensity: 2.60f, - BulkSpeed: 397.4f, - IonTemperature: 44618f - ) - ]); - } -} diff --git a/tests/UnitTests/NoaaClient/KpIndex/KpIndex27DayDataParserTests.cs b/tests/UnitTests/NoaaClient/KpIndex/KpIndex27DayDataParserTests.cs index 53c7154..1c0170b 100644 --- a/tests/UnitTests/NoaaClient/KpIndex/KpIndex27DayDataParserTests.cs +++ b/tests/UnitTests/NoaaClient/KpIndex/KpIndex27DayDataParserTests.cs @@ -10,7 +10,7 @@ namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.KpIndex; /// public sealed class KpIndex27DayDataParserTests { - [Theory] + [Theory(DisplayName = "KP 27-day parser returns expected forecast records")] [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/KpIndex/Samples/KpIndex27DayForecastSample.txt")] public void Parse_WhenTextIsValid_ReturnsRecords(string text) { diff --git a/tests/UnitTests/NoaaClient/KpIndex/KpIndex3DayDataParserTests.cs b/tests/UnitTests/NoaaClient/KpIndex/KpIndex3DayDataParserTests.cs index 8d6844a..9afd53b 100644 --- a/tests/UnitTests/NoaaClient/KpIndex/KpIndex3DayDataParserTests.cs +++ b/tests/UnitTests/NoaaClient/KpIndex/KpIndex3DayDataParserTests.cs @@ -10,7 +10,7 @@ namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.KpIndex; /// public sealed class KpIndex3DayDataParserTests { - [Theory] + [Theory(DisplayName = "KP 3-day parser returns expected forecast records")] [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/KpIndex/Samples/KpIndex3DayForecastSample.json")] public void Parse_WhenTextIsValid_ReturnsRecords(string text) { diff --git a/tests/UnitTests/NoaaClient/KpIndex/KpIndexNowcastDataParserTests.cs b/tests/UnitTests/NoaaClient/KpIndex/KpIndexNowcastDataParserTests.cs index b3d4c94..bd328bb 100644 --- a/tests/UnitTests/NoaaClient/KpIndex/KpIndexNowcastDataParserTests.cs +++ b/tests/UnitTests/NoaaClient/KpIndex/KpIndexNowcastDataParserTests.cs @@ -10,7 +10,7 @@ namespace AuroraScienceHub.Integrations.UnitTests.NoaaClient.KpIndex; /// public sealed class KpIndexNowcastDataParserTests { - [Theory] + [Theory(DisplayName = "KP nowcast parser returns expected records")] [EmbeddedResourceData("AuroraScienceHub.Integrations.UnitTests.NoaaClient/KpIndex/Samples/KpIndexNowcastSample.json")] public void Parse_WhenTextIsValid_ReturnsRecords(string text) { diff --git a/tests/UnitTests/UnitTests.csproj b/tests/UnitTests/UnitTests.csproj index b629956..3e81d4d 100644 --- a/tests/UnitTests/UnitTests.csproj +++ b/tests/UnitTests/UnitTests.csproj @@ -12,7 +12,6 @@ -