From 8edca842c79148fe916756c177e36163dd75f908 Mon Sep 17 00:00:00 2001 From: Emily Price Date: Wed, 27 Nov 2024 20:56:35 +0000 Subject: [PATCH] Add twice-daily forecasts. One timestep per day for daily --- CHANGELOG.md | 3 + src/datapoint/Forecast.py | 78 +- src/datapoint/Manager.py | 13 +- tests/integration/test_manager.py | 39 + .../reference_data_test_forecast.py | 1085 +++++++++++++++-- tests/unit/test_forecast.py | 111 +- 6 files changed, 1186 insertions(+), 143 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b69bac..3bf4e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ++ Use one timestep per day for daily forecasts. ++ Add twice-daily forecast option to split daily forecasts into day and night + ## [0.11.0] - 2024-11-26 + Correct elements to camelCase for daily forecasts. diff --git a/src/datapoint/Forecast.py b/src/datapoint/Forecast.py index 0bcb463..6aeafe4 100644 --- a/src/datapoint/Forecast.py +++ b/src/datapoint/Forecast.py @@ -108,7 +108,8 @@ class Forecast: def __init__(self, frequency, api_data, convert_weather_code): """ - :param frequency: Frequency of forecast: 'hourly', 'three-hourly' or 'daily' + :param frequency: Frequency of forecast: 'hourly', 'three-hourly', + 'twice-daily', 'daily' :param api_data: Data returned from API call :param: convert_weather_code: Convert numeric weather codes to string description :type frequency: string @@ -149,14 +150,14 @@ def __init__(self, frequency, api_data, convert_weather_code): forecasts = api_data["features"][0]["properties"]["timeSeries"] parameters = api_data["parameters"][0] - if frequency == "daily": - self.timesteps = self._build_timesteps_from_daily(forecasts, parameters) + if frequency == "twice-daily": + self.timesteps = self._build_twice_daily_timesteps(forecasts, parameters) else: self.timesteps = [] for forecast in forecasts: self.timesteps.append(self._build_timestep(forecast, parameters)) - def _build_timesteps_from_daily(self, forecasts, parameters): + def _build_twice_daily_timesteps(self, forecasts, parameters): """Build individual timesteps from forecasts and metadata Take the forecast data from DataHub and combine with unit information @@ -188,38 +189,25 @@ def _build_timesteps_from_daily(self, forecasts, parameters): for element, value in forecast.items(): if element.startswith("midday"): - trimmed_element = element.replace("midday", "") - case_corrected_element = ( - trimmed_element[0].lower() + trimmed_element[1:] - ) - day_step[case_corrected_element] = { + day_step[element] = { "value": value, "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], "unit_symbol": parameters[element]["unit"]["symbol"]["type"], } elif element.startswith("midnight"): - trimmed_element = element.replace("midnight", "") - case_corrected_element = ( - trimmed_element[0].lower() + trimmed_element[1:] - ) - night_step[case_corrected_element] = { + night_step[element] = { "value": value, "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], "unit_symbol": parameters[element]["unit"]["symbol"]["type"], } elif element.startswith("day"): - trimmed_element = element.replace("day", "") - case_corrected_element = ( - trimmed_element[0].lower() + trimmed_element[1:] - ) - if ( - case_corrected_element == "significantWeatherCode" + element == "daySignificantWeatherCode" and self.convert_weather_code ): - day_step[case_corrected_element] = { + day_step[element] = { "value": WEATHER_CODES[str(value)], "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], @@ -229,7 +217,7 @@ def _build_timesteps_from_daily(self, forecasts, parameters): } else: - day_step[case_corrected_element] = { + day_step[element] = { "value": value, "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], @@ -238,16 +226,11 @@ def _build_timesteps_from_daily(self, forecasts, parameters): ], } elif element.startswith("night"): - trimmed_element = element.replace("night", "") - case_corrected_element = ( - trimmed_element[0].lower() + trimmed_element[1:] - ) - if ( - case_corrected_element == "significantWeatherCode" + element == "nightSignificantWeatherCode" and self.convert_weather_code ): - night_step[case_corrected_element] = { + night_step[element] = { "value": WEATHER_CODES[str(value)], "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], @@ -257,7 +240,7 @@ def _build_timesteps_from_daily(self, forecasts, parameters): } else: - night_step[case_corrected_element] = { + night_step[element] = { "value": value, "description": parameters[element]["description"], "unit_name": parameters[element]["unit"]["label"], @@ -305,7 +288,14 @@ def _build_timestep(self, forecast, parameters): forecast["time"], "%Y-%m-%dT%H:%M%z" ) - elif element == "significantWeatherCode" and self.convert_weather_code: + elif ( + element + in ( + "significantWeatherCode", + "daySignificantWeatherCode", + "nightSignificantWeatherCode", + ) + ) and self.convert_weather_code: timestep[element] = { "value": WEATHER_CODES[str(value)], "description": parameters[element]["description"], @@ -366,6 +356,19 @@ def _check_requested_time(self, target): raise APIException(err_str) + # If we have a twice-daily forecast, check that the requested time is + # at most 6 hours before the first datetime we have a forecast for. + if self.frequency == "twice-daily" and target < self.timesteps[0][ + "time" + ] - datetime.timedelta(hours=6): + err_str = ( + "There is no forecast available for the requested time. " + "The requested time is more than 6 hours before the first " + "available forecast." + ) + + raise APIException(err_str) + # If we have an hourly forecast, check that the requested time is at # most 30 minutes after the final datetime we have a forecast for if self.frequency == "hourly" and target > ( @@ -405,6 +408,19 @@ def _check_requested_time(self, target): raise APIException(err_str) + # If we have a twice-daily forecast, then the target must be within 6 hours + # of the last timestep + if self.frequency == "twice-daily" and target > ( + self.timesteps[-1]["time"] + datetime.timedelta(hours=6) + ): + err_str = ( + "There is no forecast available for the requested time. The " + "requested time is more than 6 hours after the first available " + "forecast." + ) + + raise APIException(err_str) + def at_datetime(self, target): """Return the timestep closest to the target datetime diff --git a/src/datapoint/Manager.py b/src/datapoint/Manager.py index 68c7556..a1665fe 100644 --- a/src/datapoint/Manager.py +++ b/src/datapoint/Manager.py @@ -210,11 +210,15 @@ def get_forecast( self, latitude, longitude, frequency="daily", convert_weather_code=True ): """ - Get a forecast for the provided site + Get a forecast for the provided site. Three frequencies are supported + by DataHub: hourly, three-hourly and daily. The 'twice-daily' option is + for convenience and splits a daily forecast into two steps, one for day + and one for night. :parameter latitude: Latitude of forecast location :parameter longitude: Longitude of forecast location - :parameter frequency: Forecast frequency. One of 'hourly', 'three-hourly, 'daily' + :parameter frequency: Forecast frequency. One of 'hourly', + 'three-hourly,'twice-daily', 'daily' :parameter convert_weather_code: Convert numeric weather codes to string description :type latitude: float :type longitude: float @@ -224,9 +228,10 @@ def get_forecast( :return: :class: `Forecast ` object :rtype: datapoint.Forecast """ - if frequency not in ["hourly", "three-hourly", "daily"]: + if frequency not in ["hourly", "three-hourly", "twice-daily", "daily"]: raise ValueError( - "frequency must be set to one of 'hourly', 'three-hourly', 'daily'" + "frequency must be set to one of 'hourly', 'three-hourly', " + "'twice-daily', 'daily'" ) data = self.__call_api(latitude, longitude, frequency) forecast = Forecast( diff --git a/tests/integration/test_manager.py b/tests/integration/test_manager.py index 27b7e70..d875619 100644 --- a/tests/integration/test_manager.py +++ b/tests/integration/test_manager.py @@ -98,11 +98,25 @@ def daily_forecast(_mock_response_daily): return f +@pytest.fixture +def twice_daily_forecast(_mock_response_daily): + m = Manager(api_key="aaaaaaaaaaaaaaaaaaaaaaaaa") + f = m.get_forecast( + 50.9992, 0.0154, frequency="twice-daily", convert_weather_code=True + ) + return f + + @pytest.fixture def expected_first_daily_timestep(): return reference_data_test_forecast.EXPECTED_FIRST_DAILY_TIMESTEP +@pytest.fixture +def expected_first_twice_daily_timestep(): + return reference_data_test_forecast.EXPECTED_FIRST_TWICE_DAILY_TIMESTEP + + class TestHourly: def test_location_name(self, hourly_forecast): assert hourly_forecast.name == "Sheffield Park" @@ -178,3 +192,28 @@ def test_forecast_first_timestep( self, daily_forecast, expected_first_daily_timestep ): assert daily_forecast.timesteps[0] == expected_first_daily_timestep + + +class TestTwiceDaily: + def test_forecast_frequency(self, twice_daily_forecast): + assert twice_daily_forecast.frequency == "twice-daily" + + def test_forecast_location_name(self, twice_daily_forecast): + assert twice_daily_forecast.name == "Sheffield Park" + + def test_forecast_location_latitude(self, twice_daily_forecast): + assert twice_daily_forecast.forecast_latitude == 50.9992 + + def test_forecast_location_longitude(self, twice_daily_forecast): + assert twice_daily_forecast.forecast_longitude == 0.0154 + + def test_forecast_distance_from_request(self, twice_daily_forecast): + assert twice_daily_forecast.distance_from_requested_location == 1081.5349 + + def test_forecast_elevation(self, twice_daily_forecast): + assert twice_daily_forecast.elevation == 37.0 + + def test_forecast_first_timestep( + self, twice_daily_forecast, expected_first_twice_daily_timestep + ): + assert twice_daily_forecast.timesteps[0] == expected_first_twice_daily_timestep diff --git a/tests/reference_data/reference_data_test_forecast.py b/tests/reference_data/reference_data_test_forecast.py index d30eed6..84555a7 100644 --- a/tests/reference_data/reference_data_test_forecast.py +++ b/tests/reference_data/reference_data_test_forecast.py @@ -419,121 +419,187 @@ } EXPECTED_FIRST_DAILY_TIMESTEP = { "time": datetime.datetime(2024, 2, 16, 0, 0, tzinfo=datetime.timezone.utc), - "10MWindSpeed": { + "midday10MWindSpeed": { + "value": 5.04, + "description": "10m Wind Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindSpeed": { "value": 1.39, "description": "10m Wind Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "10MWindDirection": { + "midday10MWindDirection": { + "value": 273, + "description": "10m Wind Direction at Local Midday", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindDirection": { "value": 243, "description": "10m Wind Direction at Local Midnight", "unit_name": "degrees", "unit_symbol": "deg", }, - "10MWindGust": { + "midday10MWindGust": { + "value": 8.75, + "description": "10m Wind Gust Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindGust": { "value": 7.2, "description": "10m Wind Gust Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "visibility": { + "middayVisibility": { + "value": 28772, + "description": "Visibility at Local Midday", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightVisibility": { "value": 27712, "description": "Visibility at Local Midnight", "unit_name": "metres", "unit_symbol": "m", }, - "relativeHumidity": { + "middayRelativeHumidity": { + "value": 75.21, + "description": "Relative Humidity at Local Midday", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightRelativeHumidity": { "value": 80.91, "description": "Relative Humidity at Local Midnight", "unit_name": "percentage", "unit_symbol": "%", }, - "mslp": { + "middayMslp": { + "value": 101680, + "description": "Mean Sea Level Pressure at Local Midday", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "midnightMslp": { "value": 102640, "description": "Mean Sea Level Pressure at Local Midnight", "unit_name": "pascals", "unit_symbol": "Pa", }, - "significantWeatherCode": { + "nightSignificantWeatherCode": { "value": "Cloudy", "description": "Night Significant Weather Code", "unit_name": "dimensionless", "unit_symbol": "1", }, - "minScreenTemperature": { + "dayMaxScreenTemperature": { + "value": 12.82, + "description": "Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinScreenTemperature": { "value": 5.32, "description": "Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinTemp": { + "dayUpperBoundMaxTemp": { + "value": 14.1, + "description": "Upper Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { "value": 9.17, "description": "Upper Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinTemp": { + "dayLowerBoundMaxTemp": { + "value": 11.97, + "description": "Lower Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { "value": 3.56, "description": "Lower Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "minFeelsLikeTemp": { + "nightMinFeelsLikeTemp": { "value": 6.27, "description": "Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinFeelsLikeTemp": { + "dayUpperBoundMaxFeelsLikeTemp": { + "value": 12.47, + "description": "Upper Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { "value": 8.74, "description": "Upper Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinFeelsLikeTemp": { + "dayLowerBoundMaxFeelsLikeTemp": { + "value": 10.01, + "description": "Lower Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { "value": 2.75, "description": "Lower Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "probabilityOfPrecipitation": { + "nightProbabilityOfPrecipitation": { "value": 11, "description": "Probability of Precipitation During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSnow": { + "nightProbabilityOfSnow": { "value": 0, "description": "Probability of Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavySnow": { + "nightProbabilityOfHeavySnow": { "value": 0, "description": "Probability of Heavy Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfRain": { + "nightProbabilityOfRain": { "value": 10, "description": "Probability of Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavyRain": { + "nightProbabilityOfHeavyRain": { "value": 0, "description": "Probability of Heavy Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHail": { + "nightProbabilityOfHail": { "value": 0, "description": "Probability of Hail During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSferics": { + "nightProbabilityOfSferics": { "value": 0, "description": "Probability of Sferics During The Night", "unit_name": "percentage", @@ -543,121 +609,187 @@ EXPECTED_FIRST_DAILY_TIMESTEP_RAW_WEATHER_CODE = { "time": datetime.datetime(2024, 2, 16, 0, 0, tzinfo=datetime.timezone.utc), - "10MWindSpeed": { + "midday10MWindSpeed": { + "value": 5.04, + "description": "10m Wind Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindSpeed": { "value": 1.39, "description": "10m Wind Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "10MWindDirection": { + "midday10MWindDirection": { + "value": 273, + "description": "10m Wind Direction at Local Midday", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindDirection": { "value": 243, "description": "10m Wind Direction at Local Midnight", "unit_name": "degrees", "unit_symbol": "deg", }, - "10MWindGust": { + "midday10MWindGust": { + "value": 8.75, + "description": "10m Wind Gust Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindGust": { "value": 7.2, "description": "10m Wind Gust Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "visibility": { + "middayVisibility": { + "value": 28772, + "description": "Visibility at Local Midday", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightVisibility": { "value": 27712, "description": "Visibility at Local Midnight", "unit_name": "metres", "unit_symbol": "m", }, - "relativeHumidity": { + "middayRelativeHumidity": { + "value": 75.21, + "description": "Relative Humidity at Local Midday", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightRelativeHumidity": { "value": 80.91, "description": "Relative Humidity at Local Midnight", "unit_name": "percentage", "unit_symbol": "%", }, - "mslp": { + "middayMslp": { + "value": 101680, + "description": "Mean Sea Level Pressure at Local Midday", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "midnightMslp": { "value": 102640, "description": "Mean Sea Level Pressure at Local Midnight", "unit_name": "pascals", "unit_symbol": "Pa", }, - "significantWeatherCode": { + "nightSignificantWeatherCode": { "value": 7, "description": "Night Significant Weather Code", "unit_name": "dimensionless", "unit_symbol": "1", }, - "minScreenTemperature": { + "dayMaxScreenTemperature": { + "value": 12.82, + "description": "Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinScreenTemperature": { "value": 5.32, "description": "Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinTemp": { + "dayUpperBoundMaxTemp": { + "value": 14.1, + "description": "Upper Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { "value": 9.17, "description": "Upper Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinTemp": { + "dayLowerBoundMaxTemp": { + "value": 11.97, + "description": "Lower Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { "value": 3.56, "description": "Lower Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "minFeelsLikeTemp": { + "nightMinFeelsLikeTemp": { "value": 6.27, "description": "Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinFeelsLikeTemp": { + "dayUpperBoundMaxFeelsLikeTemp": { + "value": 12.47, + "description": "Upper Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { "value": 8.74, "description": "Upper Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinFeelsLikeTemp": { + "dayLowerBoundMaxFeelsLikeTemp": { + "value": 10.01, + "description": "Lower Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { "value": 2.75, "description": "Lower Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "probabilityOfPrecipitation": { + "nightProbabilityOfPrecipitation": { "value": 11, "description": "Probability of Precipitation During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSnow": { + "nightProbabilityOfSnow": { "value": 0, "description": "Probability of Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavySnow": { + "nightProbabilityOfHeavySnow": { "value": 0, "description": "Probability of Heavy Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfRain": { + "nightProbabilityOfRain": { "value": 10, "description": "Probability of Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavyRain": { + "nightProbabilityOfHeavyRain": { "value": 0, "description": "Probability of Heavy Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHail": { + "nightProbabilityOfHail": { "value": 0, "description": "Probability of Hail During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSferics": { + "nightProbabilityOfSferics": { "value": 0, "description": "Probability of Sferics During The Night", "unit_name": "percentage", @@ -667,255 +799,502 @@ EXPECTED_AT_DATETIME_DAILY_TIMESTEP = { "time": datetime.datetime(2024, 2, 17, 0, 0, tzinfo=datetime.timezone.utc), - "10MWindSpeed": { + "midday10MWindSpeed": { + "value": 4.32, + "description": "10m Wind Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindSpeed": { "value": 6.1, "description": "10m Wind Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "10MWindDirection": { + "midday10MWindDirection": { + "value": 230, + "description": "10m Wind Direction at Local Midday", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindDirection": { "value": 218, "description": "10m Wind Direction at Local Midnight", "unit_name": "degrees", "unit_symbol": "deg", }, - "10MWindGust": { + "midday10MWindGust": { + "value": 8.75, + "description": "10m Wind Gust Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindGust": { "value": 12.98, "description": "10m Wind Gust Speed at Local Midnight", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "visibility": { + "middayVisibility": { + "value": 4158, + "description": "Visibility at Local Midday", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightVisibility": { "value": 5915, "description": "Visibility at Local Midnight", "unit_name": "metres", "unit_symbol": "m", }, - "relativeHumidity": { + "middayRelativeHumidity": { + "value": 97.38, + "description": "Relative Humidity at Local Midday", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightRelativeHumidity": { "value": 93.62, "description": "Relative Humidity at Local Midnight", "unit_name": "percentage", "unit_symbol": "%", }, - "mslp": { + "middayMslp": { + "value": 103140, + "description": "Mean Sea Level Pressure at Local Midday", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "midnightMslp": { "value": 102800, "description": "Mean Sea Level Pressure at Local Midnight", "unit_name": "pascals", "unit_symbol": "Pa", }, - "significantWeatherCode": { + "maxUvIndex": { + "value": 1, + "description": "Day Maximum UV Index", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "daySignificantWeatherCode": { + "value": "Overcast", + "description": "Day Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "nightSignificantWeatherCode": { "value": "Heavy rain", "description": "Night Significant Weather Code", "unit_name": "dimensionless", "unit_symbol": "1", }, - "minScreenTemperature": { + "dayMaxScreenTemperature": { + "value": 12.0, + "description": "Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinScreenTemperature": { "value": 9.96, "description": "Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinTemp": { + "dayUpperBoundMaxTemp": { + "value": 13.71, + "description": "Upper Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { "value": 10.71, "description": "Upper Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinTemp": { + "dayLowerBoundMaxTemp": { + "value": 10.23, + "description": "Lower Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { "value": 9.04, "description": "Lower Bound on Night Minimum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "minFeelsLikeTemp": { + "dayMaxFeelsLikeTemp": { + "value": 10.6, + "description": "Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinFeelsLikeTemp": { "value": 7.76, "description": "Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMinFeelsLikeTemp": { + "dayUpperBoundMaxFeelsLikeTemp": { + "value": 11.49, + "description": "Upper Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { "value": 8.28, "description": "Upper Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMinFeelsLikeTemp": { + "dayLowerBoundMaxFeelsLikeTemp": { + "value": 8.38, + "description": "Lower Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { "value": 7.04, "description": "Lower Bound on Night Minimum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "probabilityOfPrecipitation": { + "dayProbabilityOfPrecipitation": { + "value": 18, + "description": "Probability of Precipitation During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfPrecipitation": { "value": 97, "description": "Probability of Precipitation During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSnow": { + "dayProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSnow": { "value": 0, "description": "Probability of Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavySnow": { + "dayProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavySnow": { "value": 0, "description": "Probability of Heavy Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfRain": { + "dayProbabilityOfRain": { + "value": 18, + "description": "Probability of Rain During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfRain": { "value": 97, "description": "Probability of Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavyRain": { + "dayProbabilityOfHeavyRain": { + "value": 5, + "description": "Probability of Heavy Rain During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavyRain": { "value": 96, "description": "Probability of Heavy Rain During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHail": { + "dayProbabilityOfHail": { + "value": 0, + "description": "Probability of Hail During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHail": { "value": 20, "description": "Probability of Hail During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSferics": { + "dayProbabilityOfSferics": { + "value": 0, + "description": "Probability of Sferics During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSferics": { "value": 10, "description": "Probability of Sferics During The Night", "unit_name": "percentage", "unit_symbol": "%", }, } + EXPECTED_AT_DATETIME_DAILY_FINAL_TIMESTEP = { - "time": datetime.datetime(2024, 2, 23, 12, 0, tzinfo=datetime.timezone.utc), - "10MWindSpeed": { - "value": 7.11, + "time": datetime.datetime(2024, 2, 18, 0, 0, tzinfo=datetime.timezone.utc), + "midday10MWindSpeed": { + "value": 4.07, "description": "10m Wind Speed at Local Midday", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "10MWindDirection": { - "value": 231, + "midnight10MWindSpeed": { + "value": 2.84, + "description": "10m Wind Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midday10MWindDirection": { + "value": 292, "description": "10m Wind Direction at Local Midday", "unit_name": "degrees", "unit_symbol": "deg", }, - "10MWindGust": { - "value": 13.38, + "midnight10MWindDirection": { + "value": 281, + "description": "10m Wind Direction at Local Midnight", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midday10MWindGust": { + "value": 7.55, "description": "10m Wind Gust Speed at Local Midday", "unit_name": "metres per second", "unit_symbol": "m/s", }, - "visibility": { - "value": 23049, + "midnight10MWindGust": { + "value": 6.36, + "description": "10m Wind Gust Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "middayVisibility": { + "value": 25425, "description": "Visibility at Local Midday", "unit_name": "metres", "unit_symbol": "m", }, - "relativeHumidity": { - "value": 73.25, + "midnightVisibility": { + "value": 17183, + "description": "Visibility at Local Midnight", + "unit_name": "metres", + "unit_symbol": "m", + }, + "middayRelativeHumidity": { + "value": 86.19, "description": "Relative Humidity at Local Midday", "unit_name": "percentage", "unit_symbol": "%", }, - "mslp": { - "value": 98974, + "midnightRelativeHumidity": { + "value": 94.68, + "description": "Relative Humidity at Local Midnight", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "middayMslp": { + "value": 102625, "description": "Mean Sea Level Pressure at Local Midday", "unit_name": "pascals", "unit_symbol": "Pa", }, + "midnightMslp": { + "value": 103041, + "description": "Mean Sea Level Pressure at Local Midnight", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, "maxUvIndex": { - "value": 2, + "value": 1, "description": "Day Maximum UV Index", "unit_name": "dimensionless", "unit_symbol": "1", }, - "significantWeatherCode": { - "value": "Light rain shower", + "daySignificantWeatherCode": { + "value": "Light rain", "description": "Day Significant Weather Code", "unit_name": "dimensionless", "unit_symbol": "1", }, - "maxScreenTemperature": { - "value": 8.57, + "nightSignificantWeatherCode": { + "value": "Cloudy", + "description": "Night Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "dayMaxScreenTemperature": { + "value": 13.89, "description": "Day Maximum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMaxTemp": { - "value": 10.67, + "nightMinScreenTemperature": { + "value": 7.15, + "description": "Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayUpperBoundMaxTemp": { + "value": 14.73, "description": "Upper Bound on Day Maximum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMaxTemp": { - "value": 6.67, + "nightUpperBoundMinTemp": { + "value": 9.16, + "description": "Upper Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayLowerBoundMaxTemp": { + "value": 12.4, "description": "Lower Bound on Day Maximum Screen Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "maxFeelsLikeTemp": { - "value": 4.42, + "nightLowerBoundMinTemp": { + "value": 5.31, + "description": "Lower Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayMaxFeelsLikeTemp": { + "value": 11.75, "description": "Day Maximum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "upperBoundMaxFeelsLikeTemp": { - "value": 7.38, + "nightMinFeelsLikeTemp": { + "value": 5.34, + "description": "Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayUpperBoundMaxFeelsLikeTemp": { + "value": 13.47, "description": "Upper Bound on Day Maximum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "lowerBoundMaxFeelsLikeTemp": { - "value": 3.89, + "nightUpperBoundMinFeelsLikeTemp": { + "value": 7.12, + "description": "Upper Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayLowerBoundMaxFeelsLikeTemp": { + "value": 10.8, "description": "Lower Bound on Day Maximum Feels Like Air Temperature", "unit_name": "degrees Celsius", "unit_symbol": "Cel", }, - "probabilityOfPrecipitation": { - "value": 52, + "nightLowerBoundMinFeelsLikeTemp": { + "value": 5.06, + "description": "Lower Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayProbabilityOfPrecipitation": { + "value": 55, "description": "Probability of Precipitation During The Day", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSnow": { - "value": 0, - "description": "Probability of Snow During The Day", + "nightProbabilityOfPrecipitation": { + "value": 9, + "description": "Probability of Precipitation During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Night", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavySnow": { + "dayProbabilityOfHeavySnow": { "value": 0, "description": "Probability of Heavy Snow During The Day", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfRain": { - "value": 52, + "nightProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfRain": { + "value": 55, "description": "Probability of Rain During The Day", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHeavyRain": { - "value": 48, + "nightProbabilityOfRain": { + "value": 9, + "description": "Probability of Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfHeavyRain": { + "value": 36, "description": "Probability of Heavy Rain During The Day", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfHail": { - "value": 10, + "nightProbabilityOfHeavyRain": { + "value": 2, + "description": "Probability of Heavy Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfHail": { + "value": 4, "description": "Probability of Hail During The Day", "unit_name": "percentage", "unit_symbol": "%", }, - "probabilityOfSferics": { - "value": 11, + "nightProbabilityOfHail": { + "value": 0, + "description": "Probability of Hail During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfSferics": { + "value": 3, "description": "Probability of Sferics During The Day", "unit_name": "percentage", "unit_symbol": "%", }, + "nightProbabilityOfSferics": { + "value": 0, + "description": "Probability of Sferics During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, } EXPECTED_FIRST_THREE_HOURLY_TIMESTEP = { @@ -1307,3 +1686,505 @@ "unit_symbol": "%", }, } + +EXPECTED_FIRST_TWICE_DAILY_TIMESTEP = { + "time": datetime.datetime(2024, 2, 16, 0, 0, tzinfo=datetime.timezone.utc), + "midnight10MWindSpeed": { + "value": 1.39, + "description": "10m Wind Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindDirection": { + "value": 243, + "description": "10m Wind Direction at Local Midnight", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindGust": { + "value": 7.2, + "description": "10m Wind Gust Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnightVisibility": { + "value": 27712, + "description": "Visibility at Local Midnight", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightRelativeHumidity": { + "value": 80.91, + "description": "Relative Humidity at Local Midnight", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightMslp": { + "value": 102640, + "description": "Mean Sea Level Pressure at Local Midnight", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "nightSignificantWeatherCode": { + "value": "Cloudy", + "description": "Night Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "nightMinScreenTemperature": { + "value": 5.32, + "description": "Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { + "value": 9.17, + "description": "Upper Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { + "value": 3.56, + "description": "Lower Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinFeelsLikeTemp": { + "value": 6.27, + "description": "Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { + "value": 8.74, + "description": "Upper Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { + "value": 2.75, + "description": "Lower Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightProbabilityOfPrecipitation": { + "value": 11, + "description": "Probability of Precipitation During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfRain": { + "value": 10, + "description": "Probability of Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavyRain": { + "value": 0, + "description": "Probability of Heavy Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHail": { + "value": 0, + "description": "Probability of Hail During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSferics": { + "value": 0, + "description": "Probability of Sferics During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, +} + +EXPECTED_AT_DATETIME_TWICE_DAILY_TIMESTEP = { + "time": datetime.datetime(2024, 2, 17, 0, 0, tzinfo=datetime.timezone.utc), + "midnight10MWindSpeed": { + "value": 6.1, + "description": "10m Wind Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindDirection": { + "value": 218, + "description": "10m Wind Direction at Local Midnight", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindGust": { + "value": 12.98, + "description": "10m Wind Gust Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnightVisibility": { + "value": 5915, + "description": "Visibility at Local Midnight", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightRelativeHumidity": { + "value": 93.62, + "description": "Relative Humidity at Local Midnight", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightMslp": { + "value": 102800, + "description": "Mean Sea Level Pressure at Local Midnight", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "nightSignificantWeatherCode": { + "value": "Heavy rain", + "description": "Night Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "nightMinScreenTemperature": { + "value": 9.96, + "description": "Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { + "value": 10.71, + "description": "Upper Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { + "value": 9.04, + "description": "Lower Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinFeelsLikeTemp": { + "value": 7.76, + "description": "Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { + "value": 8.28, + "description": "Upper Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { + "value": 7.04, + "description": "Lower Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightProbabilityOfPrecipitation": { + "value": 97, + "description": "Probability of Precipitation During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfRain": { + "value": 97, + "description": "Probability of Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavyRain": { + "value": 96, + "description": "Probability of Heavy Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHail": { + "value": 20, + "description": "Probability of Hail During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSferics": { + "value": 10, + "description": "Probability of Sferics During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, +} + +EXPECTED_AT_DATETIME_TWICE_DAILY_FINAL_TIMESTEP = { + "time": datetime.datetime(2024, 2, 17, 12, 0, tzinfo=datetime.timezone.utc), + "midday10MWindSpeed": { + "value": 4.32, + "description": "10m Wind Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midday10MWindDirection": { + "value": 230, + "description": "10m Wind Direction at Local Midday", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midday10MWindGust": { + "value": 8.75, + "description": "10m Wind Gust Speed at Local Midday", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "middayVisibility": { + "value": 4158, + "description": "Visibility at Local Midday", + "unit_name": "metres", + "unit_symbol": "m", + }, + "middayRelativeHumidity": { + "value": 97.38, + "description": "Relative Humidity at Local Midday", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "middayMslp": { + "value": 103140, + "description": "Mean Sea Level Pressure at Local Midday", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "maxUvIndex": { + "value": 1, + "description": "Day Maximum UV Index", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "daySignificantWeatherCode": { + "value": "Overcast", + "description": "Day Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "dayMaxScreenTemperature": { + "value": 12.0, + "description": "Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayUpperBoundMaxTemp": { + "value": 13.71, + "description": "Upper Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayLowerBoundMaxTemp": { + "value": 10.23, + "description": "Lower Bound on Day Maximum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayMaxFeelsLikeTemp": { + "value": 10.6, + "description": "Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayUpperBoundMaxFeelsLikeTemp": { + "value": 11.49, + "description": "Upper Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayLowerBoundMaxFeelsLikeTemp": { + "value": 8.38, + "description": "Lower Bound on Day Maximum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "dayProbabilityOfPrecipitation": { + "value": 18, + "description": "Probability of Precipitation During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfRain": { + "value": 18, + "description": "Probability of Rain During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfHeavyRain": { + "value": 5, + "description": "Probability of Heavy Rain During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfHail": { + "value": 0, + "description": "Probability of Hail During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "dayProbabilityOfSferics": { + "value": 0, + "description": "Probability of Sferics During The Day", + "unit_name": "percentage", + "unit_symbol": "%", + }, +} + +EXPECTED_FIRST_TWICE_DAILY_TIMESTEP_RAW_WEATHER_CODE = { + "time": datetime.datetime(2024, 2, 16, 0, 0, tzinfo=datetime.timezone.utc), + "midnight10MWindSpeed": { + "value": 1.39, + "description": "10m Wind Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnight10MWindDirection": { + "value": 243, + "description": "10m Wind Direction at Local Midnight", + "unit_name": "degrees", + "unit_symbol": "deg", + }, + "midnight10MWindGust": { + "value": 7.2, + "description": "10m Wind Gust Speed at Local Midnight", + "unit_name": "metres per second", + "unit_symbol": "m/s", + }, + "midnightVisibility": { + "value": 27712, + "description": "Visibility at Local Midnight", + "unit_name": "metres", + "unit_symbol": "m", + }, + "midnightRelativeHumidity": { + "value": 80.91, + "description": "Relative Humidity at Local Midnight", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "midnightMslp": { + "value": 102640, + "description": "Mean Sea Level Pressure at Local Midnight", + "unit_name": "pascals", + "unit_symbol": "Pa", + }, + "nightSignificantWeatherCode": { + "value": 7, + "description": "Night Significant Weather Code", + "unit_name": "dimensionless", + "unit_symbol": "1", + }, + "nightMinScreenTemperature": { + "value": 5.32, + "description": "Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinTemp": { + "value": 9.17, + "description": "Upper Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinTemp": { + "value": 3.56, + "description": "Lower Bound on Night Minimum Screen Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightMinFeelsLikeTemp": { + "value": 6.27, + "description": "Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightUpperBoundMinFeelsLikeTemp": { + "value": 8.74, + "description": "Upper Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightLowerBoundMinFeelsLikeTemp": { + "value": 2.75, + "description": "Lower Bound on Night Minimum Feels Like Air Temperature", + "unit_name": "degrees Celsius", + "unit_symbol": "Cel", + }, + "nightProbabilityOfPrecipitation": { + "value": 11, + "description": "Probability of Precipitation During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSnow": { + "value": 0, + "description": "Probability of Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavySnow": { + "value": 0, + "description": "Probability of Heavy Snow During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfRain": { + "value": 10, + "description": "Probability of Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHeavyRain": { + "value": 0, + "description": "Probability of Heavy Rain During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfHail": { + "value": 0, + "description": "Probability of Hail During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, + "nightProbabilityOfSferics": { + "value": 0, + "description": "Probability of Sferics During The Night", + "unit_name": "percentage", + "unit_symbol": "%", + }, +} diff --git a/tests/unit/test_forecast.py b/tests/unit/test_forecast.py index 40c71a2..d5fa9ef 100644 --- a/tests/unit/test_forecast.py +++ b/tests/unit/test_forecast.py @@ -41,6 +41,16 @@ def daily_forecast_raw_weather_code(load_daily_json): return Forecast.Forecast("daily", load_daily_json, convert_weather_code=False) +@pytest.fixture +def twice_daily_forecast(load_daily_json): + return Forecast.Forecast("twice-daily", load_daily_json, convert_weather_code=True) + + +@pytest.fixture +def twice_daily_forecast_raw_weather_code(load_daily_json): + return Forecast.Forecast("twice-daily", load_daily_json, convert_weather_code=False) + + @pytest.fixture def hourly_forecast(load_hourly_json): return Forecast.Forecast("hourly", load_hourly_json, convert_weather_code=True) @@ -112,6 +122,28 @@ def expected_at_datetime_daily_final_timestep(): return reference_data_test_forecast.EXPECTED_AT_DATETIME_DAILY_FINAL_TIMESTEP +@pytest.fixture +def expected_first_twice_daily_timestep(): + return reference_data_test_forecast.EXPECTED_FIRST_TWICE_DAILY_TIMESTEP + + +@pytest.fixture +def expected_first_twice_daily_timestep_raw_weather_code(): + return ( + reference_data_test_forecast.EXPECTED_FIRST_TWICE_DAILY_TIMESTEP_RAW_WEATHER_CODE + ) + + +@pytest.fixture +def expected_at_datetime_twice_daily_timestep(): + return reference_data_test_forecast.EXPECTED_AT_DATETIME_TWICE_DAILY_TIMESTEP + + +@pytest.fixture +def expected_at_datetime_twice_daily_final_timestep(): + return reference_data_test_forecast.EXPECTED_AT_DATETIME_TWICE_DAILY_FINAL_TIMESTEP + + @pytest.fixture def expected_first_three_hourly_timestep(): return reference_data_test_forecast.EXPECTED_FIRST_THREE_HOURLY_TIMESTEP @@ -217,14 +249,15 @@ def test_forecast_first_timestep( ): assert daily_forecast.timesteps[0] == expected_first_daily_timestep - def test_build_timesteps_from_daily( + # Need a new test_build_timestep function here + def test_build_timesteps( self, daily_forecast, load_daily_json, expected_first_daily_timestep ): - timesteps = daily_forecast._build_timesteps_from_daily( - load_daily_json["features"][0]["properties"]["timeSeries"], + timestep = daily_forecast._build_timestep( + load_daily_json["features"][0]["properties"]["timeSeries"][0], load_daily_json["parameters"][0], ) - assert timesteps[0] == expected_first_daily_timestep + assert timestep == expected_first_daily_timestep def test_at_datetime(self, daily_forecast, expected_at_datetime_daily_timestep): ts = daily_forecast.at_datetime(datetime.datetime(2024, 2, 16, 19, 15)) @@ -233,7 +266,7 @@ def test_at_datetime(self, daily_forecast, expected_at_datetime_daily_timestep): def test_at_datetime_final_timestamp( self, daily_forecast, expected_at_datetime_daily_final_timestep ): - ts = daily_forecast.at_datetime(datetime.datetime(2024, 2, 23, 17)) + ts = daily_forecast.at_datetime(datetime.datetime(2024, 2, 17, 17)) assert ts == expected_at_datetime_daily_final_timestep def test_requested_time_too_early(self, daily_forecast): @@ -244,7 +277,7 @@ def test_requested_time_too_late(self, daily_forecast): with pytest.raises(APIException): daily_forecast.at_datetime(datetime.datetime(2024, 2, 23, 19)) - def test_forecast_fist_timestep__raw_weather_code( + def test_forecast_first_timestep_raw_weather_code( self, daily_forecast_raw_weather_code, expected_first_daily_timestep_raw_weather_code, @@ -255,6 +288,72 @@ def test_forecast_fist_timestep__raw_weather_code( ) +class TestTwiceDailyForecast: + def test_forecast_frequency(self, twice_daily_forecast): + assert twice_daily_forecast.frequency == "twice-daily" + + def test_forecast_location_name(self, twice_daily_forecast): + assert twice_daily_forecast.name == "Sheffield Park" + + def test_forecast_location_latitude(self, twice_daily_forecast): + assert twice_daily_forecast.forecast_latitude == 50.9992 + + def test_forecast_location_longitude(self, twice_daily_forecast): + assert twice_daily_forecast.forecast_longitude == 0.0154 + + def test_forecast_distance_from_request(self, twice_daily_forecast): + assert twice_daily_forecast.distance_from_requested_location == 1081.5349 + + def test_forecast_elevation(self, twice_daily_forecast): + assert twice_daily_forecast.elevation == 37.0 + + def test_forecast_first_timestep( + self, twice_daily_forecast, expected_first_twice_daily_timestep + ): + assert twice_daily_forecast.timesteps[0] == expected_first_twice_daily_timestep + + # twice-daily forecasts take daily data from DataHub + def test_build_twice_daily_timesteps( + self, twice_daily_forecast, load_daily_json, expected_first_twice_daily_timestep + ): + timesteps = twice_daily_forecast._build_twice_daily_timesteps( + load_daily_json["features"][0]["properties"]["timeSeries"], + load_daily_json["parameters"][0], + ) + assert timesteps[0] == expected_first_twice_daily_timestep + + def test_at_datetime( + self, twice_daily_forecast, expected_at_datetime_twice_daily_timestep + ): + ts = twice_daily_forecast.at_datetime(datetime.datetime(2024, 2, 16, 19, 15)) + assert ts == expected_at_datetime_twice_daily_timestep + + def test_at_datetime_final_timestep( + self, twice_daily_forecast, expected_at_datetime_twice_daily_final_timestep + ): + ts = twice_daily_forecast.at_datetime(datetime.datetime(2024, 2, 17, 17)) + assert ts == expected_at_datetime_twice_daily_final_timestep + + def test_requested_time_too_early(self, twice_daily_forecast): + with pytest.raises(APIException): + twice_daily_forecast.at_datetime(datetime.datetime(2024, 2, 15, 17)) + + def test_requested_time_too_late(self, twice_daily_forecast): + with pytest.raises(APIException): + twice_daily_forecast.at_datetime(datetime.datetime(2024, 2, 23, 19)) + + def test_forecast_first_timestep_raw_weather_code( + self, + twice_daily_forecast_raw_weather_code, + expected_first_twice_daily_timestep_raw_weather_code, + ): + print(twice_daily_forecast_raw_weather_code.timesteps[0]) + assert ( + twice_daily_forecast_raw_weather_code.timesteps[0] + == expected_first_twice_daily_timestep_raw_weather_code + ) + + class TestThreeHourlyForecast: def test_forecast_frequency(self, three_hourly_forecast): assert three_hourly_forecast.frequency == "three-hourly"