mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-09-13 07:21:16 +00:00
Rename settings variables (remove prefixes)
This commit is contained in:
@@ -24,9 +24,9 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 24}}
|
||||
{"prediction": {"hours": 48}, "optimization": {"hours": 24}}
|
||||
)
|
||||
assert config_eos.prediction.prediction_hours == 48
|
||||
assert config_eos.prediction.hours == 48
|
||||
|
||||
# Initialize the battery and the inverter
|
||||
akku = Battery(
|
||||
@@ -41,7 +41,7 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
devices_eos.add_device(akku)
|
||||
|
||||
inverter = Inverter(
|
||||
InverterParameters(device_id="inverter1", max_power_wh=10000, battery=akku.device_id)
|
||||
InverterParameters(device_id="inverter1", max_power_wh=10000, battery_id=akku.device_id)
|
||||
)
|
||||
devices_eos.add_device(inverter)
|
||||
|
||||
@@ -62,7 +62,7 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
device_id="ev1", capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
|
||||
),
|
||||
)
|
||||
eauto.set_charge_per_hour(np.full(config_eos.prediction.prediction_hours, 1))
|
||||
eauto.set_charge_per_hour(np.full(config_eos.prediction.hours, 1))
|
||||
devices_eos.add_device(eauto)
|
||||
|
||||
devices_eos.post_setup()
|
||||
|
@@ -23,9 +23,9 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 24}}
|
||||
{"prediction": {"hours": 48}, "optimization": {"hours": 24}}
|
||||
)
|
||||
assert config_eos.prediction.prediction_hours == 48
|
||||
assert config_eos.prediction.hours == 48
|
||||
|
||||
# Initialize the battery and the inverter
|
||||
akku = Battery(
|
||||
@@ -37,7 +37,7 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
devices_eos.add_device(akku)
|
||||
|
||||
inverter = Inverter(
|
||||
InverterParameters(device_id="iv1", max_power_wh=10000, battery=akku.device_id)
|
||||
InverterParameters(device_id="iv1", max_power_wh=10000, battery_id=akku.device_id)
|
||||
)
|
||||
devices_eos.add_device(inverter)
|
||||
|
||||
@@ -63,11 +63,11 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
devices_eos.post_setup()
|
||||
|
||||
# Parameters based on previous example data
|
||||
pv_prognose_wh = [0.0] * config_eos.prediction.prediction_hours
|
||||
pv_prognose_wh = [0.0] * config_eos.prediction.hours
|
||||
pv_prognose_wh[10] = 5000.0
|
||||
pv_prognose_wh[11] = 5000.0
|
||||
|
||||
strompreis_euro_pro_wh = [0.001] * config_eos.prediction.prediction_hours
|
||||
strompreis_euro_pro_wh = [0.001] * config_eos.prediction.hours
|
||||
strompreis_euro_pro_wh[0:10] = [0.00001] * 10
|
||||
strompreis_euro_pro_wh[11:15] = [0.00005] * 4
|
||||
strompreis_euro_pro_wh[20] = 0.00001
|
||||
@@ -141,10 +141,10 @@ def create_ems_instance(devices_eos, config_eos) -> EnergieManagementSystem:
|
||||
home_appliance=home_appliance,
|
||||
)
|
||||
|
||||
ac = np.full(config_eos.prediction.prediction_hours, 0.0)
|
||||
ac = np.full(config_eos.prediction.hours, 0.0)
|
||||
ac[20] = 1
|
||||
ems.set_akku_ac_charge_hours(ac)
|
||||
dc = np.full(config_eos.prediction.prediction_hours, 0.0)
|
||||
dc = np.full(config_eos.prediction.hours, 0.0)
|
||||
dc[11] = 1
|
||||
ems.set_akku_dc_charge_hours(dc)
|
||||
|
||||
|
@@ -50,7 +50,7 @@ def test_optimize(
|
||||
"""Test optimierung_ems."""
|
||||
# Assure configuration holds the correct values
|
||||
config_eos.merge_settings_from_dict(
|
||||
{"prediction": {"prediction_hours": 48}, "optimization": {"optimization_hours": 48}}
|
||||
{"prediction": {"hours": 48}, "optimization": {"hours": 48}}
|
||||
)
|
||||
|
||||
# Load input and output data
|
||||
|
@@ -23,7 +23,7 @@ FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON = DIR_TESTDATA.joinpath(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def elecprice_provider(monkeypatch, config_eos):
|
||||
def provider(monkeypatch, config_eos):
|
||||
"""Fixture to create a ElecPriceProvider instance."""
|
||||
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "ElecPriceAkkudoktor")
|
||||
config_eos.reset_settings()
|
||||
@@ -49,17 +49,17 @@ def cache_store():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(elecprice_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that ElecPriceForecast behaves as a singleton."""
|
||||
another_instance = ElecPriceAkkudoktor()
|
||||
assert elecprice_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(elecprice_provider, monkeypatch):
|
||||
"""Test requesting an unsupported elecprice_provider."""
|
||||
def test_invalid_provider(provider, monkeypatch):
|
||||
"""Test requesting an unsupported provider."""
|
||||
monkeypatch.setenv("EOS_ELECPRICE__ELECPRICE_PROVIDER", "<invalid>")
|
||||
elecprice_provider.config.reset_settings()
|
||||
assert not elecprice_provider.enabled()
|
||||
provider.config.reset_settings()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -68,16 +68,16 @@ def test_invalid_provider(elecprice_provider, monkeypatch):
|
||||
|
||||
|
||||
@patch("akkudoktoreos.prediction.elecpriceakkudoktor.logger.error")
|
||||
def test_validate_data_invalid_format(mock_logger, elecprice_provider):
|
||||
def test_validate_data_invalid_format(mock_logger, provider):
|
||||
"""Test validation for invalid Akkudoktor data."""
|
||||
invalid_data = '{"invalid": "data"}'
|
||||
with pytest.raises(ValueError):
|
||||
elecprice_provider._validate_data(invalid_data)
|
||||
provider._validate_data(invalid_data)
|
||||
mock_logger.assert_called_once_with(mock_logger.call_args[0][0])
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_request_forecast(mock_get, elecprice_provider, sample_akkudoktor_1_json):
|
||||
def test_request_forecast(mock_get, provider, sample_akkudoktor_1_json):
|
||||
"""Test requesting forecast from Akkudoktor."""
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
@@ -86,10 +86,10 @@ def test_request_forecast(mock_get, elecprice_provider, sample_akkudoktor_1_json
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
# Preset, as this is usually done by update()
|
||||
elecprice_provider.config.update()
|
||||
provider.config.update()
|
||||
|
||||
# Test function
|
||||
akkudoktor_data = elecprice_provider._request_forecast()
|
||||
akkudoktor_data = provider._request_forecast()
|
||||
|
||||
assert isinstance(akkudoktor_data, AkkudoktorElecPrice)
|
||||
assert akkudoktor_data.values[0] == AkkudoktorElecPriceValue(
|
||||
@@ -104,7 +104,7 @@ def test_request_forecast(mock_get, elecprice_provider, sample_akkudoktor_1_json
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_update_data(mock_get, elecprice_provider, sample_akkudoktor_1_json, cache_store):
|
||||
def test_update_data(mock_get, provider, sample_akkudoktor_1_json, cache_store):
|
||||
"""Test fetching forecast from Akkudoktor."""
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
@@ -117,28 +117,28 @@ def test_update_data(mock_get, elecprice_provider, sample_akkudoktor_1_json, cac
|
||||
# Call the method
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime("2024-12-11 00:00:00", in_timezone="Europe/Berlin"))
|
||||
elecprice_provider.update_data(force_enable=True, force_update=True)
|
||||
provider.update_data(force_enable=True, force_update=True)
|
||||
|
||||
# Assert: Verify the result is as expected
|
||||
mock_get.assert_called_once()
|
||||
assert (
|
||||
len(elecprice_provider) == 73
|
||||
len(provider) == 73
|
||||
) # we have 48 datasets in the api response, we want to know 48h into the future. The data we get has already 23h into the future so we need only 25h more. 48+25=73
|
||||
|
||||
# Assert we get prediction_hours prioce values by resampling
|
||||
np_price_array = elecprice_provider.key_to_array(
|
||||
# Assert we get hours prioce values by resampling
|
||||
np_price_array = provider.key_to_array(
|
||||
key="elecprice_marketprice_wh",
|
||||
start_datetime=elecprice_provider.start_datetime,
|
||||
end_datetime=elecprice_provider.end_datetime,
|
||||
start_datetime=provider.start_datetime,
|
||||
end_datetime=provider.end_datetime,
|
||||
)
|
||||
assert len(np_price_array) == elecprice_provider.total_hours
|
||||
assert len(np_price_array) == provider.total_hours
|
||||
|
||||
# with open(FILE_TESTDATA_ELECPRICEAKKUDOKTOR_2_JSON, "w") as f_out:
|
||||
# f_out.write(elecprice_provider.to_json())
|
||||
# f_out.write(provider.to_json())
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_update_data_with_incomplete_forecast(mock_get, elecprice_provider):
|
||||
def test_update_data_with_incomplete_forecast(mock_get, provider):
|
||||
"""Test `_update_data` with incomplete or missing forecast data."""
|
||||
incomplete_data: dict = {"meta": {}, "values": []}
|
||||
mock_response = Mock()
|
||||
@@ -146,7 +146,7 @@ def test_update_data_with_incomplete_forecast(mock_get, elecprice_provider):
|
||||
mock_response.content = json.dumps(incomplete_data)
|
||||
mock_get.return_value = mock_response
|
||||
with pytest.raises(ValueError):
|
||||
elecprice_provider._update_data(force_update=True)
|
||||
provider._update_data(force_update=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -155,7 +155,7 @@ def test_update_data_with_incomplete_forecast(mock_get, elecprice_provider):
|
||||
)
|
||||
@patch("requests.get")
|
||||
def test_request_forecast_status_codes(
|
||||
mock_get, elecprice_provider, sample_akkudoktor_1_json, status_code, exception
|
||||
mock_get, provider, sample_akkudoktor_1_json, status_code, exception
|
||||
):
|
||||
"""Test handling of various API status codes."""
|
||||
mock_response = Mock()
|
||||
@@ -167,31 +167,31 @@ def test_request_forecast_status_codes(
|
||||
mock_get.return_value = mock_response
|
||||
if exception:
|
||||
with pytest.raises(exception):
|
||||
elecprice_provider._request_forecast()
|
||||
provider._request_forecast()
|
||||
else:
|
||||
elecprice_provider._request_forecast()
|
||||
provider._request_forecast()
|
||||
|
||||
|
||||
@patch("akkudoktoreos.utils.cacheutil.CacheFileStore")
|
||||
def test_cache_integration(mock_cache, elecprice_provider):
|
||||
def test_cache_integration(mock_cache, provider):
|
||||
"""Test caching of 8-day electricity price data."""
|
||||
mock_cache_instance = mock_cache.return_value
|
||||
mock_cache_instance.get.return_value = None # Simulate no cache
|
||||
elecprice_provider._update_data(force_update=True)
|
||||
provider._update_data(force_update=True)
|
||||
mock_cache_instance.create.assert_called_once()
|
||||
mock_cache_instance.get.assert_called_once()
|
||||
|
||||
|
||||
def test_key_to_array_resampling(elecprice_provider):
|
||||
def test_key_to_array_resampling(provider):
|
||||
"""Test resampling of forecast data to NumPy array."""
|
||||
elecprice_provider.update_data(force_update=True)
|
||||
array = elecprice_provider.key_to_array(
|
||||
provider.update_data(force_update=True)
|
||||
array = provider.key_to_array(
|
||||
key="elecprice_marketprice_wh",
|
||||
start_datetime=elecprice_provider.start_datetime,
|
||||
end_datetime=elecprice_provider.end_datetime,
|
||||
start_datetime=provider.start_datetime,
|
||||
end_datetime=provider.end_datetime,
|
||||
)
|
||||
assert isinstance(array, np.ndarray)
|
||||
assert len(array) == elecprice_provider.total_hours
|
||||
assert len(array) == provider.total_hours
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -200,12 +200,12 @@ def test_key_to_array_resampling(elecprice_provider):
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="For development only")
|
||||
def test_akkudoktor_development_forecast_data(elecprice_provider):
|
||||
def test_akkudoktor_development_forecast_data(provider):
|
||||
"""Fetch data from real Akkudoktor server."""
|
||||
# Preset, as this is usually done by update_data()
|
||||
elecprice_provider.start_datetime = to_datetime("2024-10-26 00:00:00")
|
||||
provider.start_datetime = to_datetime("2024-10-26 00:00:00")
|
||||
|
||||
akkudoktor_data = elecprice_provider._request_forecast()
|
||||
akkudoktor_data = provider._request_forecast()
|
||||
|
||||
with open(FILE_TESTDATA_ELECPRICEAKKUDOKTOR_1_JSON, "w") as f_out:
|
||||
json.dump(akkudoktor_data, f_out, indent=4)
|
||||
|
@@ -13,14 +13,14 @@ FILE_TESTDATA_ELECPRICEIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.jso
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def elecprice_provider(sample_import_1_json, config_eos):
|
||||
def provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a ElecPriceProvider instance."""
|
||||
settings = {
|
||||
"elecprice": {
|
||||
"elecprice_provider": "ElecPriceImport",
|
||||
"provider": "ElecPriceImport",
|
||||
"provider_settings": {
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"elecpriceimport_json": json.dumps(sample_import_1_json),
|
||||
"import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"import_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -43,24 +43,24 @@ def sample_import_1_json():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(elecprice_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that ElecPriceForecast behaves as a singleton."""
|
||||
another_instance = ElecPriceImport()
|
||||
assert elecprice_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(elecprice_provider, config_eos):
|
||||
"""Test requesting an unsupported elecprice_provider."""
|
||||
def test_invalid_provider(provider, config_eos):
|
||||
"""Test requesting an unsupported provider."""
|
||||
settings = {
|
||||
"elecprice": {
|
||||
"elecprice_provider": "<invalid>",
|
||||
"provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"elecpriceimport_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
"import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not elecprice_provider.enabled()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -81,35 +81,33 @@ def test_invalid_provider(elecprice_provider, config_eos):
|
||||
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
|
||||
],
|
||||
)
|
||||
def test_import(elecprice_provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
def test_import(provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
"""Test fetching forecast from Import."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.elecprice.provider_settings.elecpriceimport_json = None
|
||||
assert config_eos.elecprice.provider_settings.elecpriceimport_json is None
|
||||
config_eos.elecprice.provider_settings.import_json = None
|
||||
assert config_eos.elecprice.provider_settings.import_json is None
|
||||
else:
|
||||
config_eos.elecprice.provider_settings.elecpriceimport_file_path = None
|
||||
assert config_eos.elecprice.provider_settings.elecpriceimport_file_path is None
|
||||
elecprice_provider.clear()
|
||||
config_eos.elecprice.provider_settings.import_file_path = None
|
||||
assert config_eos.elecprice.provider_settings.import_file_path is None
|
||||
provider.clear()
|
||||
|
||||
# Call the method
|
||||
elecprice_provider.update_data()
|
||||
provider.update_data()
|
||||
|
||||
# Assert: Verify the result is as expected
|
||||
assert elecprice_provider.start_datetime is not None
|
||||
assert elecprice_provider.total_hours is not None
|
||||
assert compare_datetimes(elecprice_provider.start_datetime, ems_eos.start_datetime).equal
|
||||
assert provider.start_datetime is not None
|
||||
assert provider.total_hours is not None
|
||||
assert compare_datetimes(provider.start_datetime, ems_eos.start_datetime).equal
|
||||
values = sample_import_1_json["elecprice_marketprice_wh"]
|
||||
value_datetime_mapping = elecprice_provider.import_datetimes(
|
||||
ems_eos.start_datetime, len(values)
|
||||
)
|
||||
value_datetime_mapping = provider.import_datetimes(ems_eos.start_datetime, len(values))
|
||||
for i, mapping in enumerate(value_datetime_mapping):
|
||||
assert i < len(elecprice_provider.records)
|
||||
assert i < len(provider.records)
|
||||
expected_datetime, expected_value_index = mapping
|
||||
expected_value = values[expected_value_index]
|
||||
result_datetime = elecprice_provider.records[i].date_time
|
||||
result_value = elecprice_provider.records[i]["elecprice_marketprice_wh"]
|
||||
result_datetime = provider.records[i].date_time
|
||||
result_value = provider.records[i]["elecprice_marketprice_wh"]
|
||||
|
||||
# print(f"{i}: Expected: {expected_datetime}:{expected_value}")
|
||||
# print(f"{i}: Result: {result_datetime}:{result_value}")
|
||||
|
@@ -24,7 +24,9 @@ def inverter(mock_battery, devices_eos) -> Inverter:
|
||||
return_value=mock_self_consumption_predictor,
|
||||
):
|
||||
iv = Inverter(
|
||||
InverterParameters(device_id="iv1", max_power_wh=500.0, battery=mock_battery.device_id),
|
||||
InverterParameters(
|
||||
device_id="iv1", max_power_wh=500.0, battery_id=mock_battery.device_id
|
||||
),
|
||||
)
|
||||
devices_eos.add_device(iv)
|
||||
devices_eos.post_setup()
|
||||
|
@@ -14,11 +14,11 @@ from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def load_provider(config_eos):
|
||||
def provider(config_eos):
|
||||
"""Fixture to initialise the LoadAkkudoktor instance."""
|
||||
settings = {
|
||||
"load": {
|
||||
"load_provider": "LoadAkkudoktor",
|
||||
"provider": "LoadAkkudoktor",
|
||||
"provider_settings": {
|
||||
"load_name": "Akkudoktor Profile",
|
||||
"loadakkudoktor_year_energy": "1000",
|
||||
@@ -41,8 +41,8 @@ def measurement_eos():
|
||||
measurement.records.append(
|
||||
MeasurementDataRecord(
|
||||
date_time=dt,
|
||||
measurement_load0_mr=load0_mr,
|
||||
measurement_load1_mr=load1_mr,
|
||||
load0_mr=load0_mr,
|
||||
load1_mr=load1_mr,
|
||||
)
|
||||
)
|
||||
dt += interval
|
||||
@@ -76,13 +76,13 @@ def test_loadakkudoktor_settings_validator():
|
||||
assert settings.loadakkudoktor_year_energy == 1234.56
|
||||
|
||||
|
||||
def test_loadakkudoktor_provider_id(load_provider):
|
||||
def test_loadakkudoktor_provider_id(provider):
|
||||
"""Test the `provider_id` class method."""
|
||||
assert load_provider.provider_id() == "LoadAkkudoktor"
|
||||
assert provider.provider_id() == "LoadAkkudoktor"
|
||||
|
||||
|
||||
@patch("akkudoktoreos.prediction.loadakkudoktor.np.load")
|
||||
def test_load_data_from_mock(mock_np_load, mock_load_profiles_file, load_provider):
|
||||
def test_load_data_from_mock(mock_np_load, mock_load_profiles_file, provider):
|
||||
"""Test the `load_data` method."""
|
||||
# Mock numpy load to return data similar to what would be in the file
|
||||
mock_np_load.return_value = {
|
||||
@@ -91,19 +91,19 @@ def test_load_data_from_mock(mock_np_load, mock_load_profiles_file, load_provide
|
||||
}
|
||||
|
||||
# Test data loading
|
||||
data_year_energy = load_provider.load_data()
|
||||
data_year_energy = provider.load_data()
|
||||
assert data_year_energy is not None
|
||||
assert data_year_energy.shape == (365, 2, 24)
|
||||
|
||||
|
||||
def test_load_data_from_file(load_provider):
|
||||
def test_load_data_from_file(provider):
|
||||
"""Test `load_data` loads data from the profiles file."""
|
||||
data_year_energy = load_provider.load_data()
|
||||
data_year_energy = provider.load_data()
|
||||
assert data_year_energy is not None
|
||||
|
||||
|
||||
@patch("akkudoktoreos.prediction.loadakkudoktor.LoadAkkudoktor.load_data")
|
||||
def test_update_data(mock_load_data, load_provider):
|
||||
def test_update_data(mock_load_data, provider):
|
||||
"""Test the `_update` method."""
|
||||
mock_load_data.return_value = np.random.rand(365, 2, 24)
|
||||
|
||||
@@ -112,27 +112,27 @@ def test_update_data(mock_load_data, load_provider):
|
||||
ems_eos.set_start_datetime(pendulum.datetime(2024, 1, 1))
|
||||
|
||||
# Assure there are no prediction records
|
||||
load_provider.clear()
|
||||
assert len(load_provider) == 0
|
||||
provider.clear()
|
||||
assert len(provider) == 0
|
||||
|
||||
# Execute the method
|
||||
load_provider._update_data()
|
||||
provider._update_data()
|
||||
|
||||
# Validate that update_value is called
|
||||
assert len(load_provider) > 0
|
||||
assert len(provider) > 0
|
||||
|
||||
|
||||
def test_calculate_adjustment(load_provider, measurement_eos):
|
||||
def test_calculate_adjustment(provider, measurement_eos):
|
||||
"""Test `_calculate_adjustment` for various scenarios."""
|
||||
data_year_energy = np.random.rand(365, 2, 24)
|
||||
|
||||
# Call the method and validate results
|
||||
weekday_adjust, weekend_adjust = load_provider._calculate_adjustment(data_year_energy)
|
||||
weekday_adjust, weekend_adjust = provider._calculate_adjustment(data_year_energy)
|
||||
assert weekday_adjust.shape == (24,)
|
||||
assert weekend_adjust.shape == (24,)
|
||||
|
||||
data_year_energy = np.zeros((365, 2, 24))
|
||||
weekday_adjust, weekend_adjust = load_provider._calculate_adjustment(data_year_energy)
|
||||
weekday_adjust, weekend_adjust = provider._calculate_adjustment(data_year_energy)
|
||||
|
||||
assert weekday_adjust.shape == (24,)
|
||||
expected = np.array(
|
||||
@@ -197,7 +197,7 @@ def test_calculate_adjustment(load_provider, measurement_eos):
|
||||
np.testing.assert_array_equal(weekend_adjust, expected)
|
||||
|
||||
|
||||
def test_load_provider_adjustments_with_mock_data(load_provider):
|
||||
def test_provider_adjustments_with_mock_data(provider):
|
||||
"""Test full integration of adjustments with mock data."""
|
||||
with patch(
|
||||
"akkudoktoreos.prediction.loadakkudoktor.LoadAkkudoktor._calculate_adjustment"
|
||||
@@ -205,5 +205,5 @@ def test_load_provider_adjustments_with_mock_data(load_provider):
|
||||
mock_adjust.return_value = (np.zeros(24), np.zeros(24))
|
||||
|
||||
# Test execution
|
||||
load_provider._update_data()
|
||||
provider._update_data()
|
||||
assert mock_adjust.called
|
||||
|
@@ -17,33 +17,33 @@ def measurement_eos():
|
||||
measurement.records = [
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=0),
|
||||
measurement_load0_mr=100,
|
||||
measurement_load1_mr=200,
|
||||
load0_mr=100,
|
||||
load1_mr=200,
|
||||
),
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=1),
|
||||
measurement_load0_mr=150,
|
||||
measurement_load1_mr=250,
|
||||
load0_mr=150,
|
||||
load1_mr=250,
|
||||
),
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=2),
|
||||
measurement_load0_mr=200,
|
||||
measurement_load1_mr=300,
|
||||
load0_mr=200,
|
||||
load1_mr=300,
|
||||
),
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=3),
|
||||
measurement_load0_mr=250,
|
||||
measurement_load1_mr=350,
|
||||
load0_mr=250,
|
||||
load1_mr=350,
|
||||
),
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=4),
|
||||
measurement_load0_mr=300,
|
||||
measurement_load1_mr=400,
|
||||
load0_mr=300,
|
||||
load1_mr=400,
|
||||
),
|
||||
MeasurementDataRecord(
|
||||
date_time=datetime(2023, 1, 1, hour=5),
|
||||
measurement_load0_mr=350,
|
||||
measurement_load1_mr=450,
|
||||
load0_mr=350,
|
||||
load1_mr=450,
|
||||
),
|
||||
]
|
||||
return measurement
|
||||
@@ -79,7 +79,7 @@ def test_interval_count_invalid_non_positive_interval(measurement_eos):
|
||||
|
||||
def test_energy_from_meter_readings_valid_input(measurement_eos):
|
||||
"""Test _energy_from_meter_readings with valid inputs and proper alignment of load data."""
|
||||
key = "measurement_load0_mr"
|
||||
key = "load0_mr"
|
||||
start_datetime = datetime(2023, 1, 1, 0)
|
||||
end_datetime = datetime(2023, 1, 1, 5)
|
||||
interval = duration(hours=1)
|
||||
@@ -94,7 +94,7 @@ def test_energy_from_meter_readings_valid_input(measurement_eos):
|
||||
|
||||
def test_energy_from_meter_readings_empty_array(measurement_eos):
|
||||
"""Test _energy_from_meter_readings with no data (empty array)."""
|
||||
key = "measurement_load0_mr"
|
||||
key = "load0_mr"
|
||||
start_datetime = datetime(2023, 1, 1, 0)
|
||||
end_datetime = datetime(2023, 1, 1, 5)
|
||||
interval = duration(hours=1)
|
||||
@@ -116,7 +116,7 @@ def test_energy_from_meter_readings_empty_array(measurement_eos):
|
||||
|
||||
def test_energy_from_meter_readings_misaligned_array(measurement_eos):
|
||||
"""Test _energy_from_meter_readings with misaligned array size."""
|
||||
key = "measurement_load1_mr"
|
||||
key = "load1_mr"
|
||||
start_datetime = measurement_eos.min_datetime
|
||||
end_datetime = measurement_eos.max_datetime
|
||||
interval = duration(hours=1)
|
||||
@@ -134,7 +134,7 @@ def test_energy_from_meter_readings_misaligned_array(measurement_eos):
|
||||
|
||||
def test_energy_from_meter_readings_partial_data(measurement_eos, caplog):
|
||||
"""Test _energy_from_meter_readings with partial data (misaligned but empty array)."""
|
||||
key = "measurement_load2_mr"
|
||||
key = "load2_mr"
|
||||
start_datetime = datetime(2023, 1, 1, 0)
|
||||
end_datetime = datetime(2023, 1, 1, 5)
|
||||
interval = duration(hours=1)
|
||||
@@ -153,7 +153,7 @@ def test_energy_from_meter_readings_partial_data(measurement_eos, caplog):
|
||||
|
||||
def test_energy_from_meter_readings_negative_interval(measurement_eos):
|
||||
"""Test _energy_from_meter_readings with a negative interval."""
|
||||
key = "measurement_load3_mr"
|
||||
key = "load3_mr"
|
||||
start_datetime = datetime(2023, 1, 1, 0)
|
||||
end_datetime = datetime(2023, 1, 1, 5)
|
||||
interval = duration(hours=-1)
|
||||
@@ -191,23 +191,23 @@ def test_name_to_key(measurement_eos):
|
||||
"""Test name_to_key functionality."""
|
||||
settings = SettingsEOS(
|
||||
measurement=MeasurementCommonSettings(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
load0_name="Household",
|
||||
load1_name="Heat Pump",
|
||||
)
|
||||
)
|
||||
measurement_eos.config.merge_settings(settings)
|
||||
|
||||
assert measurement_eos.name_to_key("Household", "measurement_load") == "measurement_load0_mr"
|
||||
assert measurement_eos.name_to_key("Heat Pump", "measurement_load") == "measurement_load1_mr"
|
||||
assert measurement_eos.name_to_key("Unknown", "measurement_load") is None
|
||||
assert measurement_eos.name_to_key("Household", "load") == "load0_mr"
|
||||
assert measurement_eos.name_to_key("Heat Pump", "load") == "load1_mr"
|
||||
assert measurement_eos.name_to_key("Unknown", "load") is None
|
||||
|
||||
|
||||
def test_name_to_key_invalid_topic(measurement_eos):
|
||||
"""Test name_to_key with an invalid topic."""
|
||||
settings = SettingsEOS(
|
||||
MeasurementCommonSettings(
|
||||
measurement_load0_name="Household",
|
||||
measurement_load1_name="Heat Pump",
|
||||
load0_name="Household",
|
||||
load1_name="Heat Pump",
|
||||
)
|
||||
)
|
||||
measurement_eos.config.merge_settings(settings)
|
||||
|
@@ -17,25 +17,6 @@ from akkudoktoreos.prediction.weatherclearoutside import WeatherClearOutside
|
||||
from akkudoktoreos.prediction.weatherimport import WeatherImport
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_settings(config_eos):
|
||||
"""Fixture that adds settings data to the global config."""
|
||||
settings = {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
"weather_provider": None,
|
||||
"pvforecast_provider": None,
|
||||
"load_provider": None,
|
||||
"elecprice_provider": None,
|
||||
}
|
||||
|
||||
# Merge settings to config
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
return config_eos
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prediction():
|
||||
"""All EOS predictions."""
|
||||
@@ -59,7 +40,7 @@ def forecast_providers():
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prediction_hours, prediction_historic_hours, latitude, longitude, expected_timezone",
|
||||
"hours, historic_hours, latitude, longitude, expected_timezone",
|
||||
[
|
||||
(48, 24, 40.7128, -74.0060, "America/New_York"), # Valid latitude/longitude
|
||||
(0, 0, None, None, None), # No location
|
||||
@@ -67,17 +48,17 @@ def forecast_providers():
|
||||
],
|
||||
)
|
||||
def test_prediction_common_settings_valid(
|
||||
prediction_hours, prediction_historic_hours, latitude, longitude, expected_timezone
|
||||
hours, historic_hours, latitude, longitude, expected_timezone
|
||||
):
|
||||
"""Test valid settings for PredictionCommonSettings."""
|
||||
settings = PredictionCommonSettings(
|
||||
prediction_hours=prediction_hours,
|
||||
prediction_historic_hours=prediction_historic_hours,
|
||||
hours=hours,
|
||||
historic_hours=historic_hours,
|
||||
latitude=latitude,
|
||||
longitude=longitude,
|
||||
)
|
||||
assert settings.prediction_hours == prediction_hours
|
||||
assert settings.prediction_historic_hours == prediction_historic_hours
|
||||
assert settings.hours == hours
|
||||
assert settings.historic_hours == historic_hours
|
||||
assert settings.latitude == latitude
|
||||
assert settings.longitude == longitude
|
||||
assert settings.timezone == expected_timezone
|
||||
@@ -86,8 +67,8 @@ def test_prediction_common_settings_valid(
|
||||
@pytest.mark.parametrize(
|
||||
"field_name, invalid_value, expected_error",
|
||||
[
|
||||
("prediction_hours", -1, "Input should be greater than or equal to 0"),
|
||||
("prediction_historic_hours", -5, "Input should be greater than or equal to 0"),
|
||||
("hours", -1, "Input should be greater than or equal to 0"),
|
||||
("historic_hours", -5, "Input should be greater than or equal to 0"),
|
||||
("latitude", -91.0, "Input should be greater than or equal to -90"),
|
||||
("latitude", 91.0, "Input should be less than or equal to 90"),
|
||||
("longitude", -181.0, "Input should be greater than or equal to -180"),
|
||||
@@ -97,11 +78,12 @@ def test_prediction_common_settings_valid(
|
||||
def test_prediction_common_settings_invalid(field_name, invalid_value, expected_error):
|
||||
"""Test invalid settings for PredictionCommonSettings."""
|
||||
valid_data = {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"hours": 48,
|
||||
"historic_hours": 24,
|
||||
"latitude": 40.7128,
|
||||
"longitude": -74.0060,
|
||||
}
|
||||
assert PredictionCommonSettings(**valid_data) is not None
|
||||
valid_data[field_name] = invalid_value
|
||||
|
||||
with pytest.raises(ValidationError, match=expected_error):
|
||||
@@ -110,16 +92,14 @@ def test_prediction_common_settings_invalid(field_name, invalid_value, expected_
|
||||
|
||||
def test_prediction_common_settings_no_location():
|
||||
"""Test that timezone is None when latitude and longitude are not provided."""
|
||||
settings = PredictionCommonSettings(
|
||||
prediction_hours=48, prediction_historic_hours=24, latitude=None, longitude=None
|
||||
)
|
||||
settings = PredictionCommonSettings(hours=48, historic_hours=24, latitude=None, longitude=None)
|
||||
assert settings.timezone is None
|
||||
|
||||
|
||||
def test_prediction_common_settings_with_location():
|
||||
"""Test that timezone is correctly computed when latitude and longitude are provided."""
|
||||
settings = PredictionCommonSettings(
|
||||
prediction_hours=48, prediction_historic_hours=24, latitude=34.0522, longitude=-118.2437
|
||||
hours=48, historic_hours=24, latitude=34.0522, longitude=-118.2437
|
||||
)
|
||||
assert settings.timezone == "America/Los_Angeles"
|
||||
|
||||
|
@@ -101,14 +101,14 @@ class TestPredictionBase:
|
||||
assert base.config.prediction.latitude == 2.5
|
||||
|
||||
def test_config_value_from_field_default(self, base, monkeypatch):
|
||||
assert base.config.prediction.model_fields["prediction_hours"].default == 48
|
||||
assert base.config.prediction.prediction_hours == 48
|
||||
monkeypatch.setenv("EOS_PREDICTION__PREDICTION_HOURS", "128")
|
||||
assert base.config.prediction.model_fields["hours"].default == 48
|
||||
assert base.config.prediction.hours == 48
|
||||
monkeypatch.setenv("EOS_PREDICTION__HOURS", "128")
|
||||
base.config.reset_settings()
|
||||
assert base.config.prediction.prediction_hours == 128
|
||||
monkeypatch.delenv("EOS_PREDICTION__PREDICTION_HOURS")
|
||||
assert base.config.prediction.hours == 128
|
||||
monkeypatch.delenv("EOS_PREDICTION__HOURS")
|
||||
base.config.reset_settings()
|
||||
assert base.config.prediction.prediction_hours == 48
|
||||
assert base.config.prediction.hours == 48
|
||||
|
||||
def test_get_config_value_key_error(self, base):
|
||||
with pytest.raises(AttributeError):
|
||||
@@ -159,14 +159,14 @@ class TestPredictionProvider:
|
||||
"""Test that computed fields `end_datetime` and `keep_datetime` are correctly calculated."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(sample_start_datetime)
|
||||
provider.config.prediction.prediction_hours = 24 # 24 hours into the future
|
||||
provider.config.prediction.prediction_historic_hours = 48 # 48 hours into the past
|
||||
provider.config.prediction.hours = 24 # 24 hours into the future
|
||||
provider.config.prediction.historic_hours = 48 # 48 hours into the past
|
||||
|
||||
expected_end_datetime = sample_start_datetime + to_duration(
|
||||
provider.config.prediction.prediction_hours * 3600
|
||||
provider.config.prediction.hours * 3600
|
||||
)
|
||||
expected_keep_datetime = sample_start_datetime - to_duration(
|
||||
provider.config.prediction.prediction_historic_hours * 3600
|
||||
provider.config.prediction.historic_hours * 3600
|
||||
)
|
||||
|
||||
assert (
|
||||
@@ -183,8 +183,8 @@ class TestPredictionProvider:
|
||||
# EOS config supersedes
|
||||
ems_eos = get_ems()
|
||||
# The following values are currently not set in EOS config, we can override
|
||||
monkeypatch.setenv("EOS_PREDICTION__PREDICTION_HISTORIC_HOURS", "2")
|
||||
assert os.getenv("EOS_PREDICTION__PREDICTION_HISTORIC_HOURS") == "2"
|
||||
monkeypatch.setenv("EOS_PREDICTION__HISTORIC_HOURS", "2")
|
||||
assert os.getenv("EOS_PREDICTION__HISTORIC_HOURS") == "2"
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "37.7749")
|
||||
assert os.getenv("EOS_PREDICTION__LATITUDE") == "37.7749"
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "-122.4194")
|
||||
@@ -194,13 +194,13 @@ class TestPredictionProvider:
|
||||
ems_eos.set_start_datetime(sample_start_datetime)
|
||||
provider.update_data()
|
||||
|
||||
assert provider.config.prediction.prediction_hours == config_eos.prediction.prediction_hours
|
||||
assert provider.config.prediction.prediction_historic_hours == 2
|
||||
assert provider.config.prediction.hours == config_eos.prediction.hours
|
||||
assert provider.config.prediction.historic_hours == 2
|
||||
assert provider.config.prediction.latitude == 37.7749
|
||||
assert provider.config.prediction.longitude == -122.4194
|
||||
assert provider.start_datetime == sample_start_datetime
|
||||
assert provider.end_datetime == sample_start_datetime + to_duration(
|
||||
f"{provider.config.prediction.prediction_hours} hours"
|
||||
f"{provider.config.prediction.hours} hours"
|
||||
)
|
||||
assert provider.keep_datetime == sample_start_datetime - to_duration("2 hours")
|
||||
|
||||
@@ -290,7 +290,7 @@ class TestPredictionContainer:
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction": {
|
||||
"prediction_hours": hours,
|
||||
"hours": hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
@@ -320,7 +320,7 @@ class TestPredictionContainer:
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction": {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
"historic_hours": historic_hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
@@ -328,7 +328,7 @@ class TestPredictionContainer:
|
||||
assert compare_datetimes(container.keep_datetime, expected).equal
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"start, prediction_hours, expected_hours",
|
||||
"start, hours, expected_hours",
|
||||
[
|
||||
("2024-11-10 00:00:00", 24, 24), # No DST in Germany
|
||||
("2024-08-10 00:00:00", 24, 24), # DST in Germany
|
||||
@@ -336,13 +336,13 @@ class TestPredictionContainer:
|
||||
("2024-10-27 00:00:00", 24, 25), # DST change in Germany (25 hours/ day)
|
||||
],
|
||||
)
|
||||
def test_total_hours(self, container, start, prediction_hours, expected_hours):
|
||||
def test_total_hours(self, container, start, hours, expected_hours):
|
||||
"""Test the `total_hours` property."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction": {
|
||||
"prediction_hours": prediction_hours,
|
||||
"hours": hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
@@ -363,7 +363,7 @@ class TestPredictionContainer:
|
||||
ems_eos.set_start_datetime(to_datetime(start, in_timezone="Europe/Berlin"))
|
||||
settings = {
|
||||
"prediction": {
|
||||
"prediction_historic_hours": historic_hours,
|
||||
"historic_hours": historic_hours,
|
||||
}
|
||||
}
|
||||
container.config.merge_settings_from_dict(settings)
|
||||
|
@@ -26,13 +26,13 @@ def sample_settings(config_eos):
|
||||
"""Fixture that adds settings data to the global config."""
|
||||
settings = {
|
||||
"prediction": {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"hours": 48,
|
||||
"historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
},
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": -10,
|
||||
"pvforecast0_surface_tilt": 7,
|
||||
@@ -59,7 +59,7 @@ def sample_settings(config_eos):
|
||||
|
||||
# Merge settings to config
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert config_eos.pvforecast.pvforecast_provider == "PVForecastAkkudoktor"
|
||||
assert config_eos.pvforecast.provider == "PVForecastAkkudoktor"
|
||||
return config_eos
|
||||
|
||||
|
||||
@@ -147,13 +147,13 @@ sample_value = AkkudoktorForecastValue(
|
||||
)
|
||||
sample_config_data = {
|
||||
"prediction": {
|
||||
"prediction_hours": 48,
|
||||
"prediction_historic_hours": 24,
|
||||
"hours": 48,
|
||||
"historic_hours": 24,
|
||||
"latitude": 52.52,
|
||||
"longitude": 13.405,
|
||||
},
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastAkkudoktor",
|
||||
"provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": 180,
|
||||
"pvforecast0_surface_tilt": 30,
|
||||
|
@@ -13,11 +13,11 @@ FILE_TESTDATA_PVFORECASTIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.js
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pvforecast_provider(sample_import_1_json, config_eos):
|
||||
def provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a PVForecastProvider instance."""
|
||||
settings = {
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "PVForecastImport",
|
||||
"provider": "PVForecastImport",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"pvforecastimport_json": json.dumps(sample_import_1_json),
|
||||
@@ -43,24 +43,24 @@ def sample_import_1_json():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(pvforecast_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that PVForecastForecast behaves as a singleton."""
|
||||
another_instance = PVForecastImport()
|
||||
assert pvforecast_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(pvforecast_provider, config_eos):
|
||||
"""Test requesting an unsupported pvforecast_provider."""
|
||||
def test_invalid_provider(provider, config_eos):
|
||||
"""Test requesting an unsupported provider."""
|
||||
settings = {
|
||||
"pvforecast": {
|
||||
"pvforecast_provider": "<invalid>",
|
||||
"provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not pvforecast_provider.enabled()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -81,7 +81,7 @@ def test_invalid_provider(pvforecast_provider, config_eos):
|
||||
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
|
||||
],
|
||||
)
|
||||
def test_import(pvforecast_provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
def test_import(provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
"""Test fetching forecast from import."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
@@ -91,25 +91,23 @@ def test_import(pvforecast_provider, sample_import_1_json, start_datetime, from_
|
||||
else:
|
||||
config_eos.pvforecast.provider_settings.pvforecastimport_file_path = None
|
||||
assert config_eos.pvforecast.provider_settings.pvforecastimport_file_path is None
|
||||
pvforecast_provider.clear()
|
||||
provider.clear()
|
||||
|
||||
# Call the method
|
||||
pvforecast_provider.update_data()
|
||||
provider.update_data()
|
||||
|
||||
# Assert: Verify the result is as expected
|
||||
assert pvforecast_provider.start_datetime is not None
|
||||
assert pvforecast_provider.total_hours is not None
|
||||
assert compare_datetimes(pvforecast_provider.start_datetime, ems_eos.start_datetime).equal
|
||||
assert provider.start_datetime is not None
|
||||
assert provider.total_hours is not None
|
||||
assert compare_datetimes(provider.start_datetime, ems_eos.start_datetime).equal
|
||||
values = sample_import_1_json["pvforecast_ac_power"]
|
||||
value_datetime_mapping = pvforecast_provider.import_datetimes(
|
||||
ems_eos.start_datetime, len(values)
|
||||
)
|
||||
value_datetime_mapping = provider.import_datetimes(ems_eos.start_datetime, len(values))
|
||||
for i, mapping in enumerate(value_datetime_mapping):
|
||||
assert i < len(pvforecast_provider.records)
|
||||
assert i < len(provider.records)
|
||||
expected_datetime, expected_value_index = mapping
|
||||
expected_value = values[expected_value_index]
|
||||
result_datetime = pvforecast_provider.records[i].date_time
|
||||
result_value = pvforecast_provider.records[i]["pvforecast_ac_power"]
|
||||
result_datetime = provider.records[i].date_time
|
||||
result_value = provider.records[i]["pvforecast_ac_power"]
|
||||
|
||||
# print(f"{i}: Expected: {expected_datetime}:{expected_value}")
|
||||
# print(f"{i}: Result: {result_datetime}:{result_value}")
|
||||
|
@@ -17,7 +17,7 @@ FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON = DIR_TESTDATA.joinpath("weatherforecast_b
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def weather_provider(monkeypatch):
|
||||
def provider(monkeypatch):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
monkeypatch.setenv("EOS_WEATHER__WEATHER_PROVIDER", "BrightSky")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "50.0")
|
||||
@@ -52,27 +52,27 @@ def cache_store():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(weather_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that WeatherForecast behaves as a singleton."""
|
||||
another_instance = WeatherBrightSky()
|
||||
assert weather_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(weather_provider, monkeypatch):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
def test_invalid_provider(provider, monkeypatch):
|
||||
"""Test requesting an unsupported provider."""
|
||||
monkeypatch.setenv("EOS_WEATHER__WEATHER_PROVIDER", "<invalid>")
|
||||
weather_provider.config.reset_settings()
|
||||
assert not weather_provider.enabled()
|
||||
provider.config.reset_settings()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
def test_invalid_coordinates(weather_provider, monkeypatch):
|
||||
def test_invalid_coordinates(provider, monkeypatch):
|
||||
"""Test invalid coordinates raise ValueError."""
|
||||
monkeypatch.setenv("EOS_PREDICTION__LATITUDE", "1000")
|
||||
monkeypatch.setenv("EOS_PREDICTION__LONGITUDE", "1000")
|
||||
with pytest.raises(
|
||||
ValueError, # match="Latitude '1000' and/ or longitude `1000` out of valid range."
|
||||
):
|
||||
weather_provider.config.reset_settings()
|
||||
provider.config.reset_settings()
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -80,15 +80,13 @@ def test_invalid_coordinates(weather_provider, monkeypatch):
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_irridiance_estimate_from_cloud_cover(weather_provider):
|
||||
def test_irridiance_estimate_from_cloud_cover(provider):
|
||||
"""Test cloud cover to irradiance estimation."""
|
||||
cloud_cover_data = pd.Series(
|
||||
data=[20, 50, 80], index=pd.date_range("2023-10-22", periods=3, freq="h")
|
||||
)
|
||||
|
||||
ghi, dni, dhi = weather_provider.estimate_irradiance_from_cloud_cover(
|
||||
50.0, 10.0, cloud_cover_data
|
||||
)
|
||||
ghi, dni, dhi = provider.estimate_irradiance_from_cloud_cover(50.0, 10.0, cloud_cover_data)
|
||||
|
||||
assert ghi == [0, 0, 0]
|
||||
assert dhi == [0, 0, 0]
|
||||
@@ -101,7 +99,7 @@ def test_irridiance_estimate_from_cloud_cover(weather_provider):
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_request_forecast(mock_get, weather_provider, sample_brightsky_1_json):
|
||||
def test_request_forecast(mock_get, provider, sample_brightsky_1_json):
|
||||
"""Test requesting forecast from BrightSky."""
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
@@ -110,10 +108,10 @@ def test_request_forecast(mock_get, weather_provider, sample_brightsky_1_json):
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
# Preset, as this is usually done by update()
|
||||
weather_provider.config.update()
|
||||
provider.config.update()
|
||||
|
||||
# Test function
|
||||
brightsky_data = weather_provider._request_forecast()
|
||||
brightsky_data = provider._request_forecast()
|
||||
|
||||
assert isinstance(brightsky_data, dict)
|
||||
assert brightsky_data["weather"][0] == {
|
||||
@@ -150,7 +148,7 @@ def test_request_forecast(mock_get, weather_provider, sample_brightsky_1_json):
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_update_data(mock_get, weather_provider, sample_brightsky_1_json, cache_store):
|
||||
def test_update_data(mock_get, provider, sample_brightsky_1_json, cache_store):
|
||||
"""Test fetching forecast from BrightSky."""
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
@@ -163,14 +161,14 @@ def test_update_data(mock_get, weather_provider, sample_brightsky_1_json, cache_
|
||||
# Call the method
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime("2024-10-26 00:00:00", in_timezone="Europe/Berlin"))
|
||||
weather_provider.update_data(force_enable=True, force_update=True)
|
||||
provider.update_data(force_enable=True, force_update=True)
|
||||
|
||||
# Assert: Verify the result is as expected
|
||||
mock_get.assert_called_once()
|
||||
assert len(weather_provider) == 338
|
||||
assert len(provider) == 338
|
||||
|
||||
# with open(FILE_TESTDATA_WEATHERBRIGHTSKY_2_JSON, "w") as f_out:
|
||||
# f_out.write(weather_provider.to_json())
|
||||
# f_out.write(provider.to_json())
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -179,14 +177,14 @@ def test_update_data(mock_get, weather_provider, sample_brightsky_1_json, cache_
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="For development only")
|
||||
def test_brightsky_development_forecast_data(weather_provider):
|
||||
def test_brightsky_development_forecast_data(provider):
|
||||
"""Fetch data from real BrightSky server."""
|
||||
# Preset, as this is usually done by update_data()
|
||||
weather_provider.start_datetime = to_datetime("2024-10-26 00:00:00")
|
||||
weather_provider.latitude = 50.0
|
||||
weather_provider.longitude = 10.0
|
||||
provider.start_datetime = to_datetime("2024-10-26 00:00:00")
|
||||
provider.latitude = 50.0
|
||||
provider.longitude = 10.0
|
||||
|
||||
brightsky_data = weather_provider._request_forecast()
|
||||
brightsky_data = provider._request_forecast()
|
||||
|
||||
with open(FILE_TESTDATA_WEATHERBRIGHTSKY_1_JSON, "w") as f_out:
|
||||
json.dump(brightsky_data, f_out, indent=4)
|
||||
|
@@ -21,11 +21,11 @@ FILE_TESTDATA_WEATHERCLEAROUTSIDE_1_DATA = DIR_TESTDATA.joinpath("weatherforecas
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def weather_provider(config_eos):
|
||||
def provider(config_eos):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
settings = {
|
||||
"weather": {
|
||||
"weather_provider": "ClearOutside",
|
||||
"provider": "ClearOutside",
|
||||
},
|
||||
"prediction": {
|
||||
"latitude": 50.0,
|
||||
@@ -64,28 +64,28 @@ def cache_store():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(weather_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that WeatherForecast behaves as a singleton."""
|
||||
another_instance = WeatherClearOutside()
|
||||
assert weather_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(weather_provider, config_eos):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
def test_invalid_provider(provider, config_eos):
|
||||
"""Test requesting an unsupported provider."""
|
||||
settings = {
|
||||
"weather": {
|
||||
"weather_provider": "<invalid>",
|
||||
"provider": "<invalid>",
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert not weather_provider.enabled()
|
||||
assert not provider.enabled()
|
||||
|
||||
|
||||
def test_invalid_coordinates(weather_provider, config_eos):
|
||||
def test_invalid_coordinates(provider, config_eos):
|
||||
"""Test invalid coordinates raise ValueError."""
|
||||
settings = {
|
||||
"weather": {
|
||||
"weather_provider": "ClearOutside",
|
||||
"provider": "ClearOutside",
|
||||
},
|
||||
"prediction": {
|
||||
"latitude": 1000.0,
|
||||
@@ -103,15 +103,13 @@ def test_invalid_coordinates(weather_provider, config_eos):
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_irridiance_estimate_from_cloud_cover(weather_provider):
|
||||
def test_irridiance_estimate_from_cloud_cover(provider):
|
||||
"""Test cloud cover to irradiance estimation."""
|
||||
cloud_cover_data = pd.Series(
|
||||
data=[20, 50, 80], index=pd.date_range("2023-10-22", periods=3, freq="h")
|
||||
)
|
||||
|
||||
ghi, dni, dhi = weather_provider.estimate_irradiance_from_cloud_cover(
|
||||
50.0, 10.0, cloud_cover_data
|
||||
)
|
||||
ghi, dni, dhi = provider.estimate_irradiance_from_cloud_cover(50.0, 10.0, cloud_cover_data)
|
||||
|
||||
assert ghi == [0, 0, 0]
|
||||
assert dhi == [0, 0, 0]
|
||||
@@ -124,7 +122,7 @@ def test_irridiance_estimate_from_cloud_cover(weather_provider):
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_request_forecast(mock_get, weather_provider, sample_clearout_1_html, config_eos):
|
||||
def test_request_forecast(mock_get, provider, sample_clearout_1_html, config_eos):
|
||||
"""Test fetching forecast from ClearOutside."""
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
@@ -136,14 +134,14 @@ def test_request_forecast(mock_get, weather_provider, sample_clearout_1_html, co
|
||||
config_eos.update()
|
||||
|
||||
# Test function
|
||||
response = weather_provider._request_forecast()
|
||||
response = provider._request_forecast()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.content == sample_clearout_1_html
|
||||
|
||||
|
||||
@patch("requests.get")
|
||||
def test_update_data(mock_get, weather_provider, sample_clearout_1_html, sample_clearout_1_data):
|
||||
def test_update_data(mock_get, provider, sample_clearout_1_html, sample_clearout_1_data):
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
@@ -157,17 +155,17 @@ def test_update_data(mock_get, weather_provider, sample_clearout_1_html, sample_
|
||||
# Call the method
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(expected_start)
|
||||
weather_provider.update_data()
|
||||
provider.update_data()
|
||||
|
||||
# Check for correct prediction time window
|
||||
assert weather_provider.config.prediction.prediction_hours == 48
|
||||
assert weather_provider.config.prediction.prediction_historic_hours == 48
|
||||
assert compare_datetimes(weather_provider.start_datetime, expected_start).equal
|
||||
assert compare_datetimes(weather_provider.end_datetime, expected_end).equal
|
||||
assert compare_datetimes(weather_provider.keep_datetime, expected_keep).equal
|
||||
assert provider.config.prediction.hours == 48
|
||||
assert provider.config.prediction.historic_hours == 48
|
||||
assert compare_datetimes(provider.start_datetime, expected_start).equal
|
||||
assert compare_datetimes(provider.end_datetime, expected_end).equal
|
||||
assert compare_datetimes(provider.keep_datetime, expected_keep).equal
|
||||
|
||||
# Verify the data
|
||||
assert len(weather_provider) == 165 # 6 days, 24 hours per day - 7th day 21 hours
|
||||
assert len(provider) == 165 # 6 days, 24 hours per day - 7th day 21 hours
|
||||
|
||||
# Check that specific values match the expected output
|
||||
# for i, record in enumerate(weather_data.records):
|
||||
@@ -179,7 +177,7 @@ def test_update_data(mock_get, weather_provider, sample_clearout_1_html, sample_
|
||||
|
||||
@pytest.mark.skip(reason="Test fixture to be improved")
|
||||
@patch("requests.get")
|
||||
def test_cache_forecast(mock_get, weather_provider, sample_clearout_1_html, cache_store):
|
||||
def test_cache_forecast(mock_get, provider, sample_clearout_1_html, cache_store):
|
||||
"""Test that ClearOutside forecast data is cached with TTL.
|
||||
|
||||
This can not be tested with mock_get. Mock objects are not pickable and therefor can not be
|
||||
@@ -193,12 +191,12 @@ def test_cache_forecast(mock_get, weather_provider, sample_clearout_1_html, cach
|
||||
|
||||
cache_store.clear(clear_all=True)
|
||||
|
||||
weather_provider.update_data()
|
||||
provider.update_data()
|
||||
mock_get.assert_called_once()
|
||||
forecast_data_first = weather_provider.to_json()
|
||||
forecast_data_first = provider.to_json()
|
||||
|
||||
weather_provider.update_data()
|
||||
forecast_data_second = weather_provider.to_json()
|
||||
provider.update_data()
|
||||
forecast_data_second = provider.to_json()
|
||||
# Verify that cache returns the same object without calling the method again
|
||||
assert forecast_data_first == forecast_data_second
|
||||
# A mock object is not pickable and therefor can not be chached to file
|
||||
@@ -212,7 +210,7 @@ def test_cache_forecast(mock_get, weather_provider, sample_clearout_1_html, cach
|
||||
|
||||
@pytest.mark.skip(reason="For development only")
|
||||
@patch("requests.get")
|
||||
def test_development_forecast_data(mock_get, weather_provider, sample_clearout_1_html):
|
||||
def test_development_forecast_data(mock_get, provider, sample_clearout_1_html):
|
||||
# Mock response object
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
@@ -220,14 +218,14 @@ def test_development_forecast_data(mock_get, weather_provider, sample_clearout_1
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
# Fill the instance
|
||||
weather_provider.update_data(force_enable=True)
|
||||
provider.update_data(force_enable=True)
|
||||
|
||||
with open(FILE_TESTDATA_WEATHERCLEAROUTSIDE_1_DATA, "w", encoding="utf8") as f_out:
|
||||
f_out.write(weather_provider.to_json())
|
||||
f_out.write(provider.to_json())
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="For development only")
|
||||
def test_clearoutsides_development_scraper(weather_provider, sample_clearout_1_html):
|
||||
def test_clearoutsides_development_scraper(provider, sample_clearout_1_html):
|
||||
"""Test scraping from ClearOutside."""
|
||||
soup = BeautifulSoup(sample_clearout_1_html, "html.parser")
|
||||
|
||||
|
@@ -13,14 +13,14 @@ FILE_TESTDATA_WEATHERIMPORT_1_JSON = DIR_TESTDATA.joinpath("import_input_1.json"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def weather_provider(sample_import_1_json, config_eos):
|
||||
def provider(sample_import_1_json, config_eos):
|
||||
"""Fixture to create a WeatherProvider instance."""
|
||||
settings = {
|
||||
"weather": {
|
||||
"weather_provider": "WeatherImport",
|
||||
"provider": "WeatherImport",
|
||||
"provider_settings": {
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"weatherimport_json": json.dumps(sample_import_1_json),
|
||||
"import_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"import_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -43,24 +43,24 @@ def sample_import_1_json():
|
||||
# ------------------------------------------------
|
||||
|
||||
|
||||
def test_singleton_instance(weather_provider):
|
||||
def test_singleton_instance(provider):
|
||||
"""Test that WeatherForecast behaves as a singleton."""
|
||||
another_instance = WeatherImport()
|
||||
assert weather_provider is another_instance
|
||||
assert provider is another_instance
|
||||
|
||||
|
||||
def test_invalid_provider(weather_provider, config_eos, monkeypatch):
|
||||
"""Test requesting an unsupported weather_provider."""
|
||||
def test_invalid_provider(provider, config_eos, monkeypatch):
|
||||
"""Test requesting an unsupported provider."""
|
||||
settings = {
|
||||
"weather": {
|
||||
"weather_provider": "<invalid>",
|
||||
"provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"weatherimport_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
"import_file_path": str(FILE_TESTDATA_WEATHERIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
config_eos.merge_settings_from_dict(settings)
|
||||
assert weather_provider.enabled() == False
|
||||
assert provider.enabled() == False
|
||||
|
||||
|
||||
# ------------------------------------------------
|
||||
@@ -81,33 +81,33 @@ def test_invalid_provider(weather_provider, config_eos, monkeypatch):
|
||||
("2024-10-27 00:00:00", False), # DST change in Germany (25 hours/ day)
|
||||
],
|
||||
)
|
||||
def test_import(weather_provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
def test_import(provider, sample_import_1_json, start_datetime, from_file, config_eos):
|
||||
"""Test fetching forecast from Import."""
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.weather.provider_settings.weatherimport_json = None
|
||||
assert config_eos.weather.provider_settings.weatherimport_json is None
|
||||
config_eos.weather.provider_settings.import_json = None
|
||||
assert config_eos.weather.provider_settings.import_json is None
|
||||
else:
|
||||
config_eos.weather.provider_settings.weatherimport_file_path = None
|
||||
assert config_eos.weather.provider_settings.weatherimport_file_path is None
|
||||
weather_provider.clear()
|
||||
config_eos.weather.provider_settings.import_file_path = None
|
||||
assert config_eos.weather.provider_settings.import_file_path is None
|
||||
provider.clear()
|
||||
|
||||
# Call the method
|
||||
weather_provider.update_data()
|
||||
provider.update_data()
|
||||
|
||||
# Assert: Verify the result is as expected
|
||||
assert weather_provider.start_datetime is not None
|
||||
assert weather_provider.total_hours is not None
|
||||
assert compare_datetimes(weather_provider.start_datetime, ems_eos.start_datetime).equal
|
||||
assert provider.start_datetime is not None
|
||||
assert provider.total_hours is not None
|
||||
assert compare_datetimes(provider.start_datetime, ems_eos.start_datetime).equal
|
||||
values = sample_import_1_json["weather_temp_air"]
|
||||
value_datetime_mapping = weather_provider.import_datetimes(ems_eos.start_datetime, len(values))
|
||||
value_datetime_mapping = provider.import_datetimes(ems_eos.start_datetime, len(values))
|
||||
for i, mapping in enumerate(value_datetime_mapping):
|
||||
assert i < len(weather_provider.records)
|
||||
assert i < len(provider.records)
|
||||
expected_datetime, expected_value_index = mapping
|
||||
expected_value = values[expected_value_index]
|
||||
result_datetime = weather_provider.records[i].date_time
|
||||
result_value = weather_provider.records[i]["weather_temp_air"]
|
||||
result_datetime = provider.records[i].date_time
|
||||
result_value = provider.records[i]["weather_temp_air"]
|
||||
|
||||
# print(f"{i}: Expected: {expected_datetime}:{expected_value}")
|
||||
# print(f"{i}: Result: {result_datetime}:{result_value}")
|
||||
|
2
tests/testdata/optimize_input_1.json
vendored
2
tests/testdata/optimize_input_1.json
vendored
@@ -35,7 +35,7 @@
|
||||
"inverter": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_wh": 10000,
|
||||
"battery": "battery1"
|
||||
"battery_id": "battery1"
|
||||
},
|
||||
"eauto": {
|
||||
"device_id": "ev1",
|
||||
|
2
tests/testdata/optimize_input_2.json
vendored
2
tests/testdata/optimize_input_2.json
vendored
@@ -173,7 +173,7 @@
|
||||
"inverter": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_wh": 10000,
|
||||
"battery": "battery1"
|
||||
"battery_id": "battery1"
|
||||
},
|
||||
"dishwasher": {
|
||||
"device_id": "dishwasher1",
|
||||
|
Reference in New Issue
Block a user