2024-12-15 14:40:03 +01:00
"""Abstract and base classes for weather predictions.
Notes:
- Supported weather sources can be expanded by adding new fetch methods within the
WeatherForecast class.
- Ensure appropriate API keys or configurations are set up if required by external data sources.
"""
from abc import abstractmethod
from typing import List , Optional
import numpy as np
import pandas as pd
import pvlib
from pydantic import Field
from akkudoktoreos.prediction.predictionabc import PredictionProvider , PredictionRecord
class WeatherDataRecord ( PredictionRecord ):
"""Represents a weather data record containing various weather attributes at a specific datetime.
Attributes:
date_time (Optional[AwareDatetime]): The datetime of the record.
total_clouds (Optional[float]): Total cloud cover as a percentage of the sky obscured.
low_clouds (Optional[float]): Cloud cover in the lower atmosphere ( % s ky obscured).
medium_clouds (Optional[float]): Cloud cover in the middle atmosphere ( % s ky obscured).
high_clouds (Optional[float]): Cloud cover in the upper atmosphere ( % s ky obscured).
visibility (Optional[float]): Horizontal visibility in meters.
fog (Optional[float]): Fog cover percentage.
precip_type (Optional[str]): Type of precipitation (e.g., "rain", "snow").
precip_prob (Optional[float]): Probability of precipitation as a percentage.
precip_amt (Optional[float]): Precipitation amount in millimeters.
preciptable_water (Optional[float]): Precipitable water in centimeters.
wind_speed (Optional[float]): Wind speed in kilometers per hour.
wind_direction (Optional[float]): Wind direction in degrees (0-360°).
frost_chance (Optional[str]): Probability of frost.
temp_air (Optional[float]): Air temperature in degrees Celsius.
feels_like (Optional[float]): Feels-like temperature in degrees Celsius.
dew_point (Optional[float]): Dew point in degrees Celsius.
relative_humidity (Optional[float]): Relative humidity in percentage.
pressure (Optional[float]): Atmospheric pressure in millibars.
ozone (Optional[float]): Ozone concentration in Dobson units.
ghi (Optional[float]): Global Horizontal Irradiance in watts per square meter (W/m²).
dni (Optional[float]): Direct Normal Irradiance in watts per square meter (W/m²).
dhi (Optional[float]): Diffuse Horizontal Irradiance in watts per square meter (W/m²).
"""
weather_total_clouds : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Total Clouds (% Sky Obscured)" }
2024-12-15 14:40:03 +01:00
)
weather_low_clouds : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Low Clouds (% Sky Obscured)" }
2024-12-15 14:40:03 +01:00
)
weather_medium_clouds : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Medium Clouds (% Sky Obscured)" }
2024-12-15 14:40:03 +01:00
)
weather_high_clouds : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "High Clouds (% Sky Obscured)" }
)
weather_visibility : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Visibility (m)" }
)
weather_fog : Optional [ float ] = Field ( default = None , json_schema_extra = { "description" : "Fog (%)" })
weather_precip_type : Optional [ str ] = Field (
default = None , json_schema_extra = { "description" : "Precipitation Type" }
2024-12-15 14:40:03 +01:00
)
weather_precip_prob : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Precipitation Probability (%)" }
2024-12-15 14:40:03 +01:00
)
weather_precip_amt : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Precipitation Amount (mm)" }
2024-12-15 14:40:03 +01:00
)
weather_preciptable_water : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Precipitable Water (cm)" }
)
weather_wind_speed : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Wind Speed (kmph)" }
)
weather_wind_direction : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Wind Direction (°)" }
)
weather_frost_chance : Optional [ str ] = Field (
default = None , json_schema_extra = { "description" : "Chance of Frost" }
)
weather_temp_air : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Temperature (°C)" }
)
weather_feels_like : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Feels Like (°C)" }
)
weather_dew_point : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Dew Point (°C)" }
2024-12-15 14:40:03 +01:00
)
weather_relative_humidity : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Relative Humidity (%)" }
)
weather_pressure : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Pressure (mb)" }
)
weather_ozone : Optional [ float ] = Field (
default = None , json_schema_extra = { "description" : "Ozone (du)" }
2024-12-15 14:40:03 +01:00
)
weather_ghi : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Global Horizontal Irradiance (W/m2)" }
2024-12-15 14:40:03 +01:00
)
weather_dni : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Direct Normal Irradiance (W/m2)" }
2024-12-15 14:40:03 +01:00
)
weather_dhi : Optional [ float ] = Field (
2025-11-10 16:57:44 +01:00
default = None , json_schema_extra = { "description" : "Diffuse Horizontal Irradiance (W/m2)" }
2024-12-15 14:40:03 +01:00
)
2026-09-10 23:20:35 +02:00
class WeatherProvider ( PredictionProvider [ WeatherDataRecord ]):
2024-12-15 14:40:03 +01:00
"""Abstract base class for weather providers.
WeatherProvider is a thread-safe singleton, ensuring only one instance of this class is created.
Configuration variables:
2025-01-18 14:26:34 +01:00
provider (str): Prediction provider for weather.
2024-12-15 14:40:03 +01:00
Attributes:
2025-01-18 14:26:34 +01:00
hours (int, optional): The number of hours into the future for which predictions are generated.
historic_hours (int, optional): The number of past hours for which historical data is retained.
2024-12-15 14:40:03 +01:00
latitude (float, optional): The latitude in degrees, must be within -90 to 90.
longitude (float, optional): The longitude in degrees, must be within -180 to 180.
start_datetime (datetime, optional): The starting datetime for predictions, defaults to the current datetime if unspecified.
end_datetime (datetime, computed): The datetime representing the end of the prediction range,
2025-01-18 14:26:34 +01:00
calculated based on `start_datetime` and `hours`.
2024-12-15 14:40:03 +01:00
keep_datetime (datetime, computed): The earliest datetime for retaining historical data, calculated
2025-01-18 14:26:34 +01:00
based on `start_datetime` and `historic_hours`.
2024-12-15 14:40:03 +01:00
"""
# overload
records : List [ WeatherDataRecord ] = Field (
2025-11-10 16:57:44 +01:00
default_factory = list , json_schema_extra = { "description" : "List of WeatherDataRecord records" }
2024-12-15 14:40:03 +01:00
)
@classmethod
@abstractmethod
def provider_id ( cls ) -> str :
return "WeatherProvider"
def enabled ( self ) -> bool :
2025-01-18 14:26:34 +01:00
return self . provider_id () == self . config . weather . provider
2024-12-15 14:40:03 +01:00
@classmethod
def estimate_irradiance_from_cloud_cover (
cls , lat : float , lon : float , cloud_cover : pd . Series , offset : int = 35
) -> tuple :
"""Estimates irradiance values (GHI, DNI, DHI) based on cloud cover.
This method estimates solar irradiance in several steps:
1. **Clear Sky GHI Calculation**: Determines the Global Horizontal Irradiance (GHI) under clear sky conditions using the Ineichen model and climatological turbidity data.
2. **Cloudy Sky GHI Estimation**: Adjusts the clear sky GHI based on the provided cloud cover percentage to estimate cloudy sky GHI.
3. **Direct Normal Irradiance (DNI) Estimation**: Uses the DISC model to estimate the DNI from the adjusted GHI.
4. **Diffuse Horizontal Irradiance (DHI) Calculation**: Computes DHI from the estimated GHI and DNI values.
Args:
lat (float): Latitude of the location for irradiance estimation.
lon (float): Longitude of the location for irradiance estimation.
cloud_cover (pd.Series): Series of cloud cover values (0-100%) indexed by datetime.
offset (Optional[sint]): Baseline for GHI adjustment as a percentage (default is 35).
Returns:
tuple: Lists of estimated irradiance values in the order of GHI, DNI, and DHI.
Note:
This method is based on the implementation from PVLib and is adapted from
https://github.com/davidusb-geek/emhass/blob/master/src/emhass/forecast.py (MIT License).
"""
# Adjust offset percentage to scaling factor
offset_fraction = offset / 100.0
# Get cloud cover datetimes
cloud_cover_times = cloud_cover . index
# Create a location object
location = pvlib . location . Location ( latitude = lat , longitude = lon )
# Get solar position and clear-sky GHI using the Ineichen model
solpos = location . get_solarposition ( cloud_cover_times )
clear_sky = location . get_clearsky ( cloud_cover_times , model = "ineichen" )
# Convert cloud cover percentage to a scaling factor
cloud_cover_fraction = np . array ( cloud_cover ) / 100.0
# Calculate adjusted GHI with proportional offset adjustment
adjusted_ghi = clear_sky [ "ghi" ] * (
offset_fraction + ( 1 - offset_fraction ) * ( 1 - cloud_cover_fraction )
)
adjusted_ghi . fillna ( 0.0 , inplace = True )
# Apply DISC model to estimate Direct Normal Irradiance (DNI) from adjusted GHI
disc_output = pvlib . irradiance . disc ( adjusted_ghi , solpos [ "zenith" ], cloud_cover_times )
adjusted_dni = disc_output [ "dni" ]
adjusted_dni . fillna ( 0.0 , inplace = True )
# Calculate Diffuse Horizontal Irradiance (DHI) as DHI = GHI - DNI * cos(zenith)
zenith_rad = np . radians ( solpos [ "zenith" ])
adjusted_dhi = adjusted_ghi - adjusted_dni * np . cos ( zenith_rad )
adjusted_dhi . fillna ( 0.0 , inplace = True )
# Return GHI, DNI, DHI lists
ghi = adjusted_ghi . to_list ()
dni = adjusted_dni . to_list ()
dhi = adjusted_dhi . to_list ()
return ghi , dni , dhi
@classmethod
def estimate_preciptable_water (
cls , temperature : pd . Series , relative_humidity : pd . Series
) -> pd . Series :
return pvlib . atmosphere . gueymard94_pw ( temperature , relative_humidity )