Skip to content
Open
4 changes: 2 additions & 2 deletions .example.env
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Database Configuration
DB_URL=postgresql://postgres:postgres@localhost:5432/neso_solar

# country code for fetching data. Other options are "nl", "de"
COUNTRY="gb"
# country code for fetching data. Other options are "nld", "deu", "bel", "ind_rj"
COUNTRY="gbr_gb"

# ways to store the data. Other options are "csv", "site-db"
SAVE_METHOD="db"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ The package provides three main functionalities:
### Environment Variables: (Can be found in the .example.env / .env file)

- `DB_URL=postgresql://postgres:postgres@localhost:5432/neso_solar` : Database Configuration
- `COUNTRY="gb"` : Country code for fetching data. Currently, other options are ["be", "ind_rajasthan", "nl"]
- `COUNTRY="gbr_gb"` : Country code for fetching data. Currently, other options are ["bel", "deu", "ind_rj", "nld"]
- `SAVE_METHOD`: Ways to store the data. Options are ["db", "csv", "site-db"].
`site-db` is supported for NL, DE, and India (RUVNL).
- `CSV_DIR=None` : Directory to save CSV files if `SAVE_METHOD="csv"`.
Expand Down
22 changes: 13 additions & 9 deletions solar_consumer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def app(
db_url: str,
save_method: str,
csv_dir: str = None,
country: str = "gb",
country: str = "gbr_gb",
historic_or_forecast: str = "generation",
):
"""
Expand All @@ -44,22 +44,26 @@ async def app(
db_url (str): Database connection URL from an environment variable.
save_method (str): Method to save the forecast data. Options are "db" or "csv".
csv_dir (str, optional): Directory to save CSV files if save_method is "csv".
country (str): Country code for fetching data. Default is "gb".
country (str): Country code for fetching data. Default is "gbr_gb".
historic_or_forecast: (str): Type of data to fetch. Default is "generation".
"""
logger.info(f"Starting the NESO Solar Forecast pipeline (version: {__version__}).")

# Use the `Neso` class for hardcoded configuration]
if country == "gb":
if country == "gbr_gb":
model_tag = "neso-solar-forecast"
elif country == "nl":
elif country == "nld":
model_tag = "ned-nl-national"
elif country == "de":
elif country == "deu":
model_tag = "entsoe-de"
elif country == "be":
elif country == "bel":
model_tag = "elia-be-forecast"


else:
supported_countries = ["gbr_gb", "nld", "deu", "bel"]
raise ValueError(
f"Unsupported country code: {country!r}. "
f"Supported country codes are: {', '.join(supported_countries)}."
)
# Step 1: Fetch forecast data (returns as pd.Dataframe)
logger.info(f"Fetching {historic_or_forecast} data for {country}.")
data = fetch_data(country=country, historic_or_forecast=historic_or_forecast)
Expand Down Expand Up @@ -146,7 +150,7 @@ async def app(
if __name__ == "__main__":
# Step 1: Fetch the database URL from the environment variable
db_url = os.getenv("DB_URL") # Change from "DATABASE_URL" to "DB_URL"
country = os.getenv("COUNTRY", "gb")
country = os.getenv("COUNTRY", "gbr_gb")
save_method = os.getenv("SAVE_METHOD", "db").lower() # Default to "db"
csv_dir = os.getenv("CSV_DIR")
historic_or_forecast = os.getenv("HISTORIC_OR_FORECAST", "generation").lower()
Expand Down
34 changes: 17 additions & 17 deletions solar_consumer/data/locations.csv
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
name,latitude,longitude,region_id,region,country_code
nl_national,52.13,5.29,0,,nl
nl_groningen,53.22,6.74,1,,nl
nl_region_2_friesland,53.11,5.85,2,,nl
nl_region_3_drenthe,52.86,6.62,3,,nl
nl_region_4_overijssel,52.45,6.45,4,,nl
nl_region_5_flevoland,52.53,5.60,5,,nl
nl_region_6_gelderland,52.06,5.95,6,,nl
nl_region_7_utrecht,52.08,5.17,7,,nl
nl_region_8_noord_holland,52.58,4.87,8,,nl
nl_region_9_zuid_holland,51.94,4.47,9,,nl
nl_region_10_zeeland,51.45,3.84,10,,nl
nl_region_11_noord_brabant,51.56,5.20,11,,nl
nl_region_12_limburg,51.21,5.94,12,,nl
be_belgium,50.85,4.35,,Belgium,be
be_flanders,51.00,4.46,,Flanders,be
be_wallonia,50.50,4.70,,Wallonia,be
be_brussels,50.85,4.35,,Brussels,be
nl_national,52.13,5.29,0,,nld
nl_groningen,53.22,6.74,1,,nld
nl_region_2_friesland,53.11,5.85,2,,nld
nl_region_3_drenthe,52.86,6.62,3,,nld
nl_region_4_overijssel,52.45,6.45,4,,nld
nl_region_5_flevoland,52.53,5.60,5,,nld
nl_region_6_gelderland,52.06,5.95,6,,nld
nl_region_7_utrecht,52.08,5.17,7,,nld
nl_region_8_noord_holland,52.58,4.87,8,,nld
nl_region_9_zuid_holland,51.94,4.47,9,,nld
nl_region_10_zeeland,51.45,3.84,10,,nld
nl_region_11_noord_brabant,51.56,5.20,11,,nld
nl_region_12_limburg,51.21,5.94,12,,nld
be_belgium,50.85,4.35,,Belgium,bel
be_flanders,51.00,4.46,,Flanders,bel
be_wallonia,50.50,4.70,,Wallonia,bel
be_brussels,50.85,4.35,,Brussels,bel
20 changes: 11 additions & 9 deletions solar_consumer/fetch_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,25 @@
from solar_consumer.data.fetch_ind_rajasthan_data import fetch_ind_rajasthan_data


def fetch_data(country: str = "gb", historic_or_forecast: str = "forecast") -> pd.DataFrame:
def fetch_data(country: str = "gbr_gb", historic_or_forecast: str = "forecast") -> pd.DataFrame:
"""
Get data from different countries

:param country: "gb", "nl", "de", "ind_rajasthan", or "be"
:param historic_or_forecast: "generation" or "forecast"
:param country: "gbr_gb", "nld", "deu", "ind_rj", or "bel"
:param historic_or_forecast: Indicator passed through to the country-specific fetcher, e.g. "forecast",
"generation", or "historic". The exact accepted values are backend-specific; see the corresponding
fetch_<country>_data function for details.
:return: Pandas dataframe with the following columns:
target_datetime_utc: Combined date and time in UTC.
solar_generation_kw: Solar generation in kW. Can be a forecast, or historic values
"""

country_data_functions = {
"gb": fetch_gb_data,
"nl": fetch_nl_data,
"de": fetch_de_data,
"ind_rajasthan": fetch_ind_rajasthan_data,
"be": fetch_be_data
"gbr_gb": fetch_gb_data,
"nld": fetch_nl_data,
"deu": fetch_de_data,
"ind_rj": fetch_ind_rajasthan_data,
"bel": fetch_be_data
}

if country in country_data_functions:
Expand All @@ -48,7 +50,7 @@ def fetch_data(country: str = "gb", historic_or_forecast: str = "forecast") -> p
raise Exception(f"An error occurred while fetching data for {country}: {e}") from e

else:
print("Only UK (gb), Netherlands (nl), Germany (de), Belgium (be), and Rajasthan India (ind_rajasthan) data can be fetched at the moment")
print("Only UK (gbr_gb), Netherlands (nld), Germany (deu), Belgium (bel), and Rajasthan India (ind_rj) data can be fetched at the moment")

return pd.DataFrame() # Always return a DataFrame (never None)

Expand Down
24 changes: 12 additions & 12 deletions solar_consumer/save/save_data_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,27 @@
def _get_country_config(country: str) -> dict:
"""Get country-specific configuration for data platform operations."""
configs = {
"nl": {
"nld": {
"id_key": "region_id",
"location_type": dp.LocationType.NATION,
"metadata_type": "number",
"observer_name": "nednl",
},
"be": {
"bel": {
"id_key": "region",
"location_type": dp.LocationType.NATION,
"metadata_type": "string",
"observer_name": "elia_be",
},
"gb": {
"gbr_gb": {
"required_observers": {"pvlive_in_day", "pvlive_day_after"},
"id_key": "gsp_id",
"location_type": [dp.LocationType.GSP, dp.LocationType.NATION],
"metadata_type": "number",
"observer_name": None,
},
}
return configs.get(country, configs["gb"])
return configs.get(country, configs["gbr_gb"])


def _extract_metadata_value(metadata: dict, key: str, metadata_type: str) -> any:
Expand Down Expand Up @@ -156,25 +156,25 @@ async def _create_locations_from_csv(


async def save_generation_to_data_platform(
data_df: pd.DataFrame, client: dp.DataPlatformDataServiceStub, country: str = "gb"
data_df: pd.DataFrame, client: dp.DataPlatformDataServiceStub, country: str = "gbr_gb"
) -> None:
"""
Saves model data via the data platform.

Incoming data is enriched with location information from the data platform. Anything with zero
capacity, or without a corresponding entry in the data platform, is ignored.

For GB: Data is joined via the gsp_id, which is a column in the incoming data, and has to be
For GBR_GB: Data is joined via the gsp_id, which is a column in the incoming data, and has to be
extracted from the metadata field in the data platform location data.

For NL: Data is joined via the region_id.
For NLD: Data is joined via the region_id.

For BE: Data is joined via the region (string-based matching).
For BEL: Data is joined via the region (string-based matching).

Args:
data_df: DataFrame containing the generation data
client: Data platform client stub
country: Country identifier ('gb', 'nl', or 'be')
country: Country identifier ('gbr_gb', 'nld', or 'bel')
"""
tasks: list[asyncio.Task] = []
config = _get_country_config(country)
Expand Down Expand Up @@ -212,7 +212,7 @@ async def save_generation_to_data_platform(
await _execute_async_tasks(tasks)

# 1. Get locations and join to the incoming data.
if country in ["nl", "be"]:
if country in ["nld", "bel"]:
# NL and BE support CSV-based location creation
locations_data = await _list_locations(client, config["location_type"])

Expand All @@ -231,7 +231,7 @@ async def save_generation_to_data_platform(
data_df = data_df.copy()

# Extract metadata and create join key based on country
if country == "be":
if country == "bel":
# BE uses string matching with normalization
data_df["join_key"] = data_df[id_key]

Expand Down Expand Up @@ -339,7 +339,7 @@ async def save_generation_to_data_platform(
if len(tasks) > 0:
logging.info("updating %d %s location capacities", len(tasks), country.upper())
# NL was previously ignoring these exceptions
await _execute_async_tasks(tasks, ignore_exceptions=(country == "nl"))
await _execute_async_tasks(tasks, ignore_exceptions=(country == "nld"))

# 3. Generate the CreateObservationRequest objects from the DataFrame.
observations_by_loc: dict[str, list[dp.CreateObservationsRequestValue]] = defaultdict(list)
Expand Down
40 changes: 20 additions & 20 deletions solar_consumer/save/save_site_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def get_or_create_pvsite(
Parameters:
session (Session): CurrentSQLAlchemy session
pvsite (PVSite): Pydantic model with site metadata
country (str): Country code ('nl' or 'de')
country (str): Country code ('nld', 'deu', or 'ind_rj')
capacity_override_kw (Optional[int]): Force a specific capacity on creation

Returns:
Expand All @@ -88,14 +88,14 @@ def get_or_create_pvsite(
except Exception:
logger.info(f"Creating site {pvsite.client_site_name} in the database.")

# Choose capacity based on country; per-TSO for de; nl only has 20GW hard‑coded
# Choose capacity based on country; per-TSO for deu; nld only has 20GW hard‑coded
if capacity_override_kw is not None:
capacity = capacity_override_kw
elif country == "de":
elif country == "deu":
capacity = DE_TSO_CAPACITY[pvsite.client_site_name]
elif country == "nl":
elif country == "nld":
capacity = 20_000_000
else: #in
else: #ind_rj
capacity = capacity_override_kw or 0

site, _ = create_site(
Expand Down Expand Up @@ -137,7 +137,7 @@ def update_capacity(


def save_generation_to_site_db(
generation_data: pd.DataFrame, session: Session, country: str = "nl"
generation_data: pd.DataFrame, session: Session, country: str = "nld"
):
"""Save generation data to the database.

Expand All @@ -147,9 +147,9 @@ def save_generation_to_site_db(
- solar_generation_kw
- target_datetime_utc
- capacity_kw (optional, used when present)
- tso_zone (only when country="de")
- tso_zone (only when country="deu")
session (Session): SQLAlchemy session for database access.
country: (str): Country code for the generation data ('nl', 'de', 'ind_rajasthan')
country: (str): Country code for the generation data ('nld', 'deu', 'ind_rj')


Return:
Expand All @@ -162,27 +162,27 @@ def save_generation_to_site_db(
return

# Determine country
if country == "nl":
if country == "nld":
country_sites = NL_NATIONAL_AND_REGIONS
elif country == "de":
elif country == "deu":
country_sites = DE_TSO_SITES
elif country == "ind_rajasthan":
elif country == "ind_rj":
country_sites = IND_RAJASTHAN_SITES
else:
raise Exception(
"Only generation data from the following countries is supported "
"when saving: 'nl', 'de', 'ind_rajasthan'"
"when saving: 'nld', 'deu', 'ind_rj'"
)

# Loop per site
for key, pvsite in country_sites.items():

# Filter by TSO for Germany, or use all data for NL
if country == "de":
if country == "deu":
generation_data_tso_df = generation_data[generation_data["tso_zone"] == key].copy()
elif country == "nl":
elif country == "nld":
generation_data_tso_df = generation_data[generation_data["region_id"] == int(key)].copy()
elif country == "ind_rajasthan":
elif country == "ind_rj":
generation_data_tso_df = generation_data[generation_data["energy_type"] == key].copy()
else:
generation_data_tso_df = generation_data.copy()
Expand All @@ -207,7 +207,7 @@ def save_generation_to_site_db(
)

generation_data_tso_df = generation_data_tso_df.copy()
if country == "ind_rajasthan":
if country == "ind_rj":
generation_data_tso_df["energy_type"] = key
else:
generation_data_tso_df["energy_type"] = "solar"
Expand Down Expand Up @@ -242,7 +242,7 @@ def save_forecasts_to_site_db(
session: Session,
model_tag: str,
model_version: str,
country: str = "nl",
country: str = "nld",
):
"""Save generation data to the database.

Expand All @@ -252,14 +252,14 @@ def save_forecasts_to_site_db(
session (Session): SQLAlchemy session for database access.
model_tag (str): Model tag to fetch model metadata.
model_version (str): Model version to fetch model metadata.
country: (str): Country code for the generation data. Currently only 'nl' is supported.
country: (str): Country code for the generation data. Currently only 'nld' is supported.

Return:
None
"""

if country != "nl":
raise Exception("Only NL forecast data is supported when saving (atm).")
if country != "nld":
raise Exception("Only NLD forecast data is supported when saving (atm).")

site = get_or_create_pvsite(session, nl_national, country)

Expand Down
Loading