From f40a46b2c1e9747f0e1311b8baa2fd19fc68b400 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:06:43 +0100 Subject: [PATCH 1/6] Cleanup PV power formula tests Formula generation and evaluation are implemented separately and tested elsewhere. So here we only need to test that the formulas apply the correct formula. Signed-off-by: Sahas Subramanian --- tests/timeseries/test_logical_meter.py | 34 +------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/tests/timeseries/test_logical_meter.py b/tests/timeseries/test_logical_meter.py index 95245f1d4..1ac0b8d49 100644 --- a/tests/timeseries/test_logical_meter.py +++ b/tests/timeseries/test_logical_meter.py @@ -6,7 +6,6 @@ from contextlib import AsyncExitStack -import pytest from frequenz.quantities import Power from pytest_mock import MockerFixture @@ -41,10 +40,6 @@ async def test_chp_power(self, mocker: MockerFixture) -> None: await mockgrid.mock_resampler.send_chp_power([-12.0]) assert (await chp_power_receiver.receive()).value == Power.from_watts(-12.0) - @pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" - ) async def test_pv_power(self, mocker: MockerFixture) -> None: """Test the pv power formula.""" mockgrid = MockMicrogrid(grid_meter=False, mocker=mocker) @@ -55,32 +50,5 @@ async def test_pv_power(self, mocker: MockerFixture) -> None: stack.push_async_callback(pv_pool.stop) pv_power_receiver = pv_pool.power.new_receiver() - await mockgrid.mock_resampler.send_meter_power([-1.0, -2.0]) - await mockgrid.mock_resampler.send_pv_inverter_power([-10.0, -20.0]) + await mockgrid.mock_resampler.send_meter_power([-10.0, -20.0]) assert (await pv_power_receiver.receive()).value == Power.from_watts(-30.0) - - async def test_pv_power_no_meter(self, mocker: MockerFixture) -> None: - """Test the pv power formula.""" - mockgrid = MockMicrogrid(grid_meter=False, mocker=mocker) - mockgrid.add_solar_inverters(2, no_meter=True) - - async with mockgrid, AsyncExitStack() as stack: - pv_pool = microgrid.new_pv_pool(priority=5) - stack.push_async_callback(pv_pool.stop) - pv_power_receiver = pv_pool.power.new_receiver() - - await mockgrid.mock_resampler.send_pv_inverter_power([-1.0, -2.0]) - assert (await pv_power_receiver.receive()).value == Power.from_watts(-3.0) - - async def test_pv_power_no_pv_components(self, mocker: MockerFixture) -> None: - """Test the pv power formula without having any pv components.""" - async with ( - MockMicrogrid(grid_meter=True, mocker=mocker) as mockgrid, - AsyncExitStack() as stack, - ): - pv_pool = microgrid.new_pv_pool(priority=5) - stack.push_async_callback(pv_pool.stop) - pv_power_receiver = pv_pool.power.new_receiver() - - await mockgrid.mock_resampler.send_non_existing_component_value() - assert (await pv_power_receiver.receive()).value == Power.zero() From 8ad739c2dceeeb6ff050267f1a3e709ecbe94515 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:13:05 +0100 Subject: [PATCH 2/6] Cleanup battery power formula tests Formula generation and evaluation are implemented separately and tested elsewhere. So here we only need to test that the formulas apply the correct formula. Signed-off-by: Sahas Subramanian --- .../_battery_pool/test_battery_pool.py | 174 +----------------- 1 file changed, 2 insertions(+), 172 deletions(-) diff --git a/tests/timeseries/_battery_pool/test_battery_pool.py b/tests/timeseries/_battery_pool/test_battery_pool.py index b39b2c663..783fedb6c 100644 --- a/tests/timeseries/_battery_pool/test_battery_pool.py +++ b/tests/timeseries/_battery_pool/test_battery_pool.py @@ -21,16 +21,7 @@ import time_machine from frequenz.channels import Receiver, Sender from frequenz.client.common.microgrid.components import ComponentId -from frequenz.client.microgrid.component import ( - Battery, - Component, - ComponentCategory, - InverterType, -) -from frequenz.microgrid_component_graph import ( - FormulaGenerationError, - InvalidGraphError, -) +from frequenz.client.microgrid.component import Battery, Component from frequenz.quantities import Energy, Percentage, Power, Temperature from pytest_mock import MockerFixture @@ -46,7 +37,6 @@ from frequenz.sdk.timeseries import Bounds, ResamplerConfig2, Sample from frequenz.sdk.timeseries._base_types import SystemBounds from frequenz.sdk.timeseries.battery_pool import BatteryPool -from tests.utils.graph_generator import GraphGenerator from ...timeseries.mock_microgrid import MockMicrogrid from ...utils.component_data_streamer import MockComponentDataStreamer @@ -503,10 +493,6 @@ async def run_test_battery_status_channel( compare_messages(msg, all_pool_result) -@pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" -) async def test_battery_pool_power(mocker: MockerFixture) -> None: """Test `BatteryPool.power` method.""" mockgrid = MockMicrogrid(grid_meter=True, mocker=mocker) @@ -518,165 +504,9 @@ async def test_battery_pool_power(mocker: MockerFixture) -> None: power_receiver = battery_pool.power.new_receiver() # send meter power [grid_meter, battery1_meter, battery2_meter] - await mockgrid.mock_resampler.send_meter_power([100.0, 2.0, 3.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, 30.0]) + await mockgrid.mock_resampler.send_meter_power([100.0, 20.0, 30.0]) assert (await power_receiver.receive()).value == Power.from_watts(50.0) - await mockgrid.mock_resampler.send_meter_power([100.0, -2.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([-20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-70.0) - - await mockgrid.mock_resampler.send_meter_power([100.0, 2.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-30.0) - - -@pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" -) -async def test_battery_pool_power_two_inverters_per_battery( - mocker: MockerFixture, -) -> None: - """Test power method with two inverters per battery.""" - gen = GraphGenerator() - bat = gen.component(ComponentCategory.BATTERY) - mockgrid = MockMicrogrid( - graph=gen.to_graph( - (ComponentCategory.METER, gen.battery_with_inverter(bat, 2)) - ), - mocker=mocker, - ) - async with mockgrid, AsyncExitStack() as stack: - battery_pool = microgrid.new_battery_pool(priority=5) - stack.push_async_callback(battery_pool.stop) - power_receiver = battery_pool.power.new_receiver() - - # send meter power [grid_meter, battery1_meter] - # Fallback formula - use only meter power, inverter and batteries are not used. - await mockgrid.mock_resampler.send_meter_power([100.0, 2.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, 30.0]) - assert (await power_receiver.receive()).value == Power.from_watts(50.0) - - await mockgrid.mock_resampler.send_meter_power([100.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([-20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-70.0) - - await mockgrid.mock_resampler.send_meter_power([100.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-30.0) - - -@pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" -) -async def test_batter_pool_power_two_batteries_per_inverter( - mocker: MockerFixture, -) -> None: - """Test power method with two batteries per inverter.""" - gen = GraphGenerator() - mockgrid = MockMicrogrid( - graph=gen.to_graph( - [ - ( - ComponentCategory.METER, - ( - ComponentCategory.INVERTER, - [ComponentCategory.BATTERY, ComponentCategory.BATTERY], - ), - ), - ( - ComponentCategory.METER, - ( - ComponentCategory.INVERTER, - [ComponentCategory.BATTERY, ComponentCategory.BATTERY], - ), - ), - ] - ), - mocker=mocker, - ) - - async with mockgrid, AsyncExitStack() as stack: - battery_pool = microgrid.new_battery_pool(priority=5) - stack.push_async_callback(battery_pool.stop) - power_receiver = battery_pool.power.new_receiver() - - # send meter power [battery1_meter, battery2_meter] - # Fallback formula - use only meter power, inverter and batteries are not used. - await mockgrid.mock_resampler.send_meter_power([100.0, 3.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, 30.0]) - assert (await power_receiver.receive()).value == Power.from_watts(50.0) - - await mockgrid.mock_resampler.send_meter_power([100.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([-20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-70.0) - - await mockgrid.mock_resampler.send_meter_power([3.0, -5.0]) - await mockgrid.mock_resampler.send_bat_inverter_power([20.0, -50.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-30.0) - - -async def test_batter_pool_power_no_batteries(mocker: MockerFixture) -> None: - """Test power method with no batteries.""" - graph_gen = GraphGenerator() - mockgrid = MockMicrogrid( - graph=graph_gen.to_graph( - ( - ComponentCategory.METER, - [ - graph_gen.component(ComponentCategory.INVERTER, InverterType.SOLAR), - graph_gen.component(ComponentCategory.INVERTER, InverterType.SOLAR), - ], - ) - ) - ) - await mockgrid.start(mocker) - battery_pool = microgrid.new_battery_pool(priority=5) - power_receiver = battery_pool.power.new_receiver() - - await mockgrid.mock_resampler.send_non_existing_component_value() - assert (await power_receiver.receive()).value == Power.from_watts(0) - - -async def test_battery_pool_power_with_no_inverters(mocker: MockerFixture) -> None: - """Test power method with no inverters.""" - with pytest.raises(InvalidGraphError): - mockgrid = MockMicrogrid( - graph=GraphGenerator().to_graph( - (ComponentCategory.METER, ComponentCategory.BATTERY) - ) - ) - await mockgrid.start(mocker) - - -async def test_battery_pool_power_incomplete_bat_request(mocker: MockerFixture) -> None: - """Test power method when not all requested ids are behind the same inverter.""" - gen = GraphGenerator() - bats = gen.components( - ComponentCategory.BATTERY, ComponentCategory.BATTERY, ComponentCategory.BATTERY - ) - - mockgrid = MockMicrogrid( - graph=gen.to_graph( - ( - ComponentCategory.METER, - gen.batteries_with_inverter(bats, 2), - ) - ) - ) - await mockgrid.start(mocker) - - with pytest.raises(FormulaGenerationError): - # Request only two of the three batteries behind the inverters - battery_pool = microgrid.new_battery_pool( - priority=5, component_ids=set([bats[1].id, bats[0].id]) - ) - power_receiver = battery_pool.power.new_receiver() - await mockgrid.mock_resampler.send_bat_inverter_power([2.0]) - assert (await power_receiver.receive()).value == Power.from_watts(2.0) - async def run_capacity_test( # pylint: disable=too-many-locals fake_time: time_machine.Coordinates, setup_args: SetupArgs From 74450172ebfdeb72c78bbaf726c6ca9e6ce4ec46 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:15:37 +0100 Subject: [PATCH 3/6] Cleanup EV charger power formula tests Formula generation and evaluation are implemented separately and tested elsewhere. So here we only need to test that the formulas apply the correct formula. Signed-off-by: Sahas Subramanian --- .../_ev_charger_pool/test_ev_charger_pool.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/timeseries/_ev_charger_pool/test_ev_charger_pool.py b/tests/timeseries/_ev_charger_pool/test_ev_charger_pool.py index a2b4a73fb..113908923 100644 --- a/tests/timeseries/_ev_charger_pool/test_ev_charger_pool.py +++ b/tests/timeseries/_ev_charger_pool/test_ev_charger_pool.py @@ -4,7 +4,6 @@ """Tests for the `EVChargerPool`.""" -import pytest from frequenz.quantities import Power from pytest_mock import MockerFixture @@ -15,10 +14,6 @@ class TestEVChargerPool: """Tests for the `EVChargerPool`.""" - @pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" - ) async def test_ev_power( # pylint: disable=too-many-locals self, mocker: MockerFixture, @@ -31,10 +26,5 @@ async def test_ev_power( # pylint: disable=too-many-locals ev_pool = microgrid.new_ev_charger_pool(priority=5) power_receiver = ev_pool.power.new_receiver() - await mockgrid.mock_resampler.send_meter_power([None]) - await mockgrid.mock_resampler.send_evc_power([2.0, 4.0, 10.0]) + await mockgrid.mock_resampler.send_meter_power([16.0]) assert (await power_receiver.receive()).value == Power.from_watts(16.0) - - await mockgrid.mock_resampler.send_meter_power([None]) - await mockgrid.mock_resampler.send_evc_power([2.0, 4.0, -10.0]) - assert (await power_receiver.receive()).value == Power.from_watts(-4.0) From 9b245f43f899abe25c61296d040dfb68c90f2157 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:20:12 +0100 Subject: [PATCH 4/6] Update formula composition tests to work with latest formulas These tests should ideally use custom formulas and not depend on the formula generator, but that's a bigger change because that would affect most of the tests in this repo. Signed-off-by: Sahas Subramanian --- tests/timeseries/_formulas/test_formula_composition.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/timeseries/_formulas/test_formula_composition.py b/tests/timeseries/_formulas/test_formula_composition.py index d490bc8f7..cb72f222a 100644 --- a/tests/timeseries/_formulas/test_formula_composition.py +++ b/tests/timeseries/_formulas/test_formula_composition.py @@ -206,10 +206,6 @@ async def test_formula_composition_missing_bat(self, mocker: MockerFixture) -> N assert count == 10 - @pytest.mark.skip( - reason="Needs to be adapted to the new component graph behavior, see " - "https://github.com/frequenz-floss/frequenz-sdk-python/issues/1345" - ) async def test_formula_composition_min_max(self, mocker: MockerFixture) -> None: """Test the composition of formulas with the min and max.""" mockgrid = MockMicrogrid(grid_meter=True, mocker=mocker, num_namespaces=2) @@ -237,13 +233,13 @@ async def test_formula_composition_min_max(self, mocker: MockerFixture) -> None: assert ( str(formula_min) == "[grid_power_min](" - + "MIN([grid_power](COALESCE(#4, #7)), [chp_power](COALESCE(#5, #7, 0.0)))" + + "MIN([grid_power](#4), [chp_power](COALESCE(#7, #5, 0.0)))" + ")" ) assert ( str(formula_max) == "[grid_power_max](" - + "MAX([grid_power](COALESCE(#4, #7)), [chp_power](COALESCE(#5, #7, 0.0)))" + + "MAX([grid_power](#4), [chp_power](COALESCE(#7, #5, 0.0)))" + ")" ) From 22bb7145c61acb5d383f043ece6c4631cebfa484 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:27:46 +0100 Subject: [PATCH 5/6] Bump frequenz-microgrid-component-graph to >=0.3.4 Signed-off-by: Sahas Subramanian --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 70a9c9b63..c3b49f107 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ # changing the version # (plugins.mkdocstrings.handlers.python.import) "frequenz-client-microgrid >= 0.18.1, < 0.19.0", - "frequenz-microgrid-component-graph >= 0.3.2, < 0.4", + "frequenz-microgrid-component-graph >= 0.3.4, < 0.4", "frequenz-client-common >= 0.3.6, < 0.4.0", "frequenz-channels >= 1.6.1, < 2.0.0", "frequenz-quantities[marshmallow] >= 1.0.0, < 2.0.0", From 187621c954d1301074f429ed79c65b183bdaa94a Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 9 Feb 2026 10:34:17 +0100 Subject: [PATCH 6/6] Update release notes Signed-off-by: Sahas Subramanian --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 34cef0f7a..4241c66de 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,7 +6,7 @@ ## Upgrading - +- The minimum required version of `frequenz-microgrid-component-graph` is now `0.3.4`. ## New Features