Rename settings variables (remove prefixes)

This commit is contained in:
Dominique Lasserre
2025-01-18 14:26:34 +01:00
parent 1e1bac9fdb
commit 3257dac92b
58 changed files with 867 additions and 918 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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}")

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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)

View File

@@ -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,

View File

@@ -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}")

View File

@@ -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)

View File

@@ -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")

View File

@@ -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}")

View File

@@ -35,7 +35,7 @@
"inverter": {
"device_id": "inverter1",
"max_power_wh": 10000,
"battery": "battery1"
"battery_id": "battery1"
},
"eauto": {
"device_id": "ev1",

View File

@@ -173,7 +173,7 @@
"inverter": {
"device_id": "inverter1",
"max_power_wh": 10000,
"battery": "battery1"
"battery_id": "battery1"
},
"dishwasher": {
"device_id": "dishwasher1",