EOS/tests/test_pv_forecast.py
Dominique Lasserre f195edb07d pvforecast: Some values optional. Closes #292 (#293)
* temperature, relativehumidity_2m, windspeed_10m can be null
 * prettify pv_forecast_input_1.json (and add null value)
2024-12-29 16:43:37 +01:00

287 lines
11 KiB
Python

"""Test Module for PV Power Forecasting Module.
This test module is designed to verify the functionality of the `PVForecast` class
and its methods in the `prediction.pv_forecast` module. The tests include validation for
forecast data processing, updating AC power measurements, retrieving forecast data,
and caching behavior.
Fixtures:
sample_forecast_data: Provides sample forecast data in JSON format for testing.
pv_forecast_instance: Provides an instance of `PVForecast` class with sample data loaded.
Test Cases:
- test_generate_cache_filename: Verifies correct cache filename generation based on URL and date.
- test_update_ac_power_measurement: Tests updating AC power measurement for a matching date.
- test_update_ac_power_measurement_no_match: Ensures no updates occur when there is no matching date.
- test_get_temperature_forecast_for_date: Tests retrieving the temperature forecast for a specific date.
- test_get_pv_forecast_for_date_range: Verifies retrieval of AC power forecast for a specified date range.
- test_get_forecast_dataframe: Ensures forecast data can be correctly converted into a Pandas DataFrame.
- test_cache_loading: Tests loading forecast data from a cached file to ensure caching works as expected.
Usage:
This test module uses `pytest` and requires the `akkudoktoreos.prediction.pv_forecast.py` module to be present.
Run the tests using the command: `pytest test_pv_forecast.py`.
"""
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
import pytest
from akkudoktoreos.prediction.pv_forecast import PVForecast, validate_pv_forecast_data
from akkudoktoreos.utils.datetimeutil import to_datetime
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
FILE_TESTDATA_PV_FORECAST_INPUT_1 = DIR_TESTDATA.joinpath("pv_forecast_input_1.json")
FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1.txt")
@pytest.fixture
def sample_forecast_data():
"""Fixture that returns sample forecast data."""
with open(FILE_TESTDATA_PV_FORECAST_INPUT_1, "r") as f_in:
input_data = json.load(f_in)
return input_data
@pytest.fixture
def sample_forecast_report():
"""Fixture that returns sample forecast data report."""
with open(FILE_TESTDATA_PV_FORECAST_RESULT_1, "r") as f_res:
input_data = f_res.read()
return input_data
@pytest.fixture
def sample_forecast_start(sample_forecast_data):
"""Fixture that returns the start date of the sample forecast data."""
forecast_start_str = sample_forecast_data["values"][0][0]["datetime"]
assert forecast_start_str == "2024-10-06T00:00:00.000+02:00"
timezone_name = sample_forecast_data["meta"]["timezone"]
assert timezone_name == "Europe/Berlin"
forecast_start = to_datetime(forecast_start_str, to_timezone=timezone_name, to_naiv=True)
assert forecast_start == datetime(2024, 10, 6)
return forecast_start
@pytest.fixture
def pv_forecast_empty_instance():
"""Fixture that returns an empty instance of PVForecast."""
empty_instance = PVForecast()
assert empty_instance.get_forecast_start() is None
return empty_instance
@pytest.fixture
def pv_forecast_instance(sample_forecast_data, sample_forecast_start):
"""Fixture that returns an instance of PVForecast with sample data loaded."""
pv_forecast = PVForecast(
data=sample_forecast_data,
forecast_start=sample_forecast_start,
prediction_hours=48,
)
return pv_forecast
def test_validate_pv_forecast_data(sample_forecast_data):
"""Test validation of PV forecast data on sample data."""
ret = validate_pv_forecast_data({})
assert ret is None
ret = validate_pv_forecast_data(sample_forecast_data)
assert ret == "Akkudoktor"
def test_process_data(sample_forecast_data, sample_forecast_start):
"""Test data processing using sample data."""
pv_forecast_instance = PVForecast(forecast_start=sample_forecast_start)
# Assure the start date is correctly set by init funtion
forecast_start = pv_forecast_instance.get_forecast_start()
expected_start = sample_forecast_start
assert forecast_start == expected_start
# Assure the prediction hours are unset
assert pv_forecast_instance.prediction_hours is None
# Load forecast with sample data - throws exceptions on error
pv_forecast_instance.process_data(data=sample_forecast_data)
def test_update_ac_power_measurement(pv_forecast_instance, sample_forecast_start):
"""Test updating AC power measurement for a specific date."""
forecast_start = pv_forecast_instance.get_forecast_start()
assert forecast_start == sample_forecast_start
updated = pv_forecast_instance.update_ac_power_measurement(1000, forecast_start)
assert updated is True
forecast_data = pv_forecast_instance.get_forecast_data()
assert forecast_data[0].ac_power_measurement == 1000
def test_update_ac_power_measurement_no_match(pv_forecast_instance):
"""Test updating AC power measurement where no date matches."""
date_time = datetime(2023, 10, 2, 1, 0, 0)
updated = pv_forecast_instance.update_ac_power_measurement(1000, date_time)
assert not updated
def test_get_temperature_forecast_for_date(pv_forecast_instance, sample_forecast_start):
"""Test fetching temperature forecast for a specific date."""
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(sample_forecast_start)
assert len(forecast_temps) == 24
assert forecast_temps[0] is None
assert forecast_temps[1] == 6.5
assert forecast_temps[2] == 6.0
# Assure function bails out if there is no timezone name available for the system.
tz_name = pv_forecast_instance._tz_name
pv_forecast_instance._tz_name = None
with pytest.raises(Exception) as exc_info:
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(
sample_forecast_start
)
pv_forecast_instance._tz_name = tz_name
assert (
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
)
def test_get_temperature_for_date_range(pv_forecast_instance, sample_forecast_start):
"""Test fetching temperature forecast for a specific date range."""
end_date = sample_forecast_start + timedelta(hours=24)
forecast_temps = pv_forecast_instance.get_temperature_for_date_range(
sample_forecast_start, end_date
)
assert len(forecast_temps) == 48
assert forecast_temps[0] is None
assert forecast_temps[1] == 6.5
assert forecast_temps[2] == 6.0
# Assure function bails out if there is no timezone name available for the system.
tz_name = pv_forecast_instance._tz_name
pv_forecast_instance._tz_name = None
with pytest.raises(Exception) as exc_info:
forecast_temps = pv_forecast_instance.get_temperature_for_date_range(
sample_forecast_start, end_date
)
pv_forecast_instance._tz_name = tz_name
assert (
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
)
def test_get_forecast_for_date_range(pv_forecast_instance, sample_forecast_start):
"""Test fetching AC power forecast for a specific date range."""
end_date = sample_forecast_start + timedelta(hours=24)
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(sample_forecast_start, end_date)
assert len(forecast) == 48
assert forecast[0] == 0.0
assert forecast[1] == 0.0
assert forecast[2] == 0.0
# Assure function bails out if there is no timezone name available for the system.
tz_name = pv_forecast_instance._tz_name
pv_forecast_instance._tz_name = None
with pytest.raises(Exception) as exc_info:
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(
sample_forecast_start, end_date
)
pv_forecast_instance._tz_name = tz_name
assert (
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
)
def test_get_forecast_dataframe(pv_forecast_instance):
"""Test converting forecast data to a DataFrame."""
df = pv_forecast_instance.get_forecast_dataframe()
assert len(df) == 288
assert list(df.columns) == ["date_time", "dc_power", "ac_power", "windspeed_10m", "temperature"]
assert df.iloc[0]["dc_power"] == 0.0
assert df.iloc[1]["ac_power"] == 0.0
assert df.iloc[2]["temperature"] == 6.0
def test_load_data_from_file(server, pv_forecast_empty_instance):
"""Test loading data from file."""
# load from valid address file path
filepath = FILE_TESTDATA_PV_FORECAST_INPUT_1
data = pv_forecast_empty_instance.load_data_from_file(filepath)
assert len(data) > 0
def test_load_data_from_url(server, pv_forecast_empty_instance):
"""Test loading data from url."""
# load from valid address of our server
url = f"{server}/gesamtlast_simple?year_energy=2000&"
data = pv_forecast_empty_instance.load_data_from_url(url)
assert len(data) > 0
# load from invalid address of our server
url = f"{server}/invalid?"
data = pv_forecast_empty_instance.load_data_from_url(url)
assert data == f"Failed to load data from `{url}`. Status Code: 404"
def test_load_data_from_url_with_caching(
server, pv_forecast_empty_instance, sample_forecast_data, sample_forecast_start
):
"""Test loading data from url with cache."""
# load from valid address of our server
url = f"{server}/gesamtlast_simple?year_energy=2000&"
data = pv_forecast_empty_instance.load_data_from_url_with_caching(url)
assert len(data) > 0
# load from invalid address of our server
url = f"{server}/invalid?"
data = pv_forecast_empty_instance.load_data_from_url_with_caching(url)
assert data == f"Failed to load data from `{url}`. Status Code: 404"
def test_report_ac_power_and_measurement(pv_forecast_instance, sample_forecast_report):
"""Test reporting."""
report = pv_forecast_instance.report_ac_power_and_measurement()
assert report == sample_forecast_report
@pytest.mark.skipif(
sys.platform.startswith("win"), reason="'other_timezone' fixture not supported on Windows"
)
def test_timezone_behaviour(
pv_forecast_instance, sample_forecast_report, sample_forecast_start, other_timezone
):
"""Test PVForecast in another timezone."""
current_time = datetime.now()
# Test updating AC power measurement for a specific date.
date_time = pv_forecast_instance.get_forecast_start()
assert date_time == sample_forecast_start
updated = pv_forecast_instance.update_ac_power_measurement(1000, date_time)
assert updated is True
forecast_data = pv_forecast_instance.get_forecast_data()
assert forecast_data[0].ac_power_measurement == 1000
# Test fetching temperature forecast for a specific date.
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(sample_forecast_start)
assert len(forecast_temps) == 24
assert forecast_temps[0] is None
assert forecast_temps[1] == 6.5
assert forecast_temps[2] == 6.0
# Test fetching AC power forecast
end_date = sample_forecast_start + timedelta(hours=24)
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(sample_forecast_start, end_date)
assert len(forecast) == 48
assert forecast[0] == 1000.0 # changed before
assert forecast[1] == 0.0
assert forecast[2] == 0.0