55
66from contextlib import AsyncExitStack
77
8+ import pytest
89from frequenz .quantities import Power
910from pytest_mock import MockerFixture
1011
@@ -96,7 +97,44 @@ async def test_no_producer_power(self, mocker: MockerFixture) -> None:
9697 0.0
9798 )
9899
99- async def test_producer_fallback_formula (self , mocker : MockerFixture ) -> None :
100+ @pytest .mark .parametrize (
101+ "meter_power,pv_inverter_power,chp_power,expected_power" ,
102+ [
103+ # Add power from meters and chp
104+ ([- 1.0 , - 2.0 ], [None , - 200.0 ], [300 ], Power .from_watts (297.0 )),
105+ ([- 1.0 , - 10 ], [- 100.0 , - 200.0 ], [400 ], Power .from_watts (389.0 )),
106+ # Case 2: The first meter is unavailable (None)
107+ ([None , - 2.0 ], [- 100 , - 200.0 ], [400 ], None ),
108+ # Case 3: First meter is unavailable (None). Fallback inverter provides value
109+ ([None , - 2.0 ], [- 100 , - 200.0 ], [400 ], Power .from_watts (298.0 )),
110+ ([None , - 2.0 ], [- 50 , - 200.0 ], [300 ], Power .from_watts (248.0 )),
111+ # Case 4: Both first meter and its fallback inverter are unavailable
112+ ([None , - 2.0 ], [None , - 200.0 ], [300 ], Power .from_watts (298.0 )),
113+ ([None , - 10.0 ], [- 20.0 , - 200.0 ], [300 ], Power .from_watts (270.0 )),
114+ # Case 5: CHP is unavailable
115+ ([None , - 10.0 ], [- 20.0 , - 200.0 ], [None ], Power .from_watts (- 30.0 )),
116+ # Case 6: Both meters are unavailable (None)
117+ ([None , None ], [- 20.0 , - 200.0 ], [None ], None ),
118+ ([None , None ], [- 20.0 , - 200.0 ], [None ], Power .from_watts (- 220.0 )),
119+ ([None , None ], [None , - 200.0 ], [None ], Power .from_watts (- 200.0 )),
120+ # Case 7: All components are unavailable (None)
121+ ([None , None ], [None , None ], [None ], Power .from_watts (0 )),
122+ ([None , None ], [None , None ], [None ], Power .from_watts (0 )),
123+ ([None , None ], [None , None ], [300.0 ], Power .from_watts (300.0 )),
124+ ([- 200.0 , None ], [None , - 100.0 ], [50.0 ], Power .from_watts (- 250.0 )),
125+ ([- 200.0 , - 200.0 ], [- 10.0 , - 20.0 ], [50.0 ], Power .from_watts (- 350.0 )),
126+ # Case 8: Meter is unavailable but we already subscribed for inverter
127+ ([None , - 200.0 ], [- 10.0 , - 100.0 ], [50.0 ], Power .from_watts (- 160.0 )),
128+ ],
129+ )
130+ async def test_producer_fallback_formula (
131+ self ,
132+ mocker : MockerFixture ,
133+ meter_power : list [float | None ],
134+ pv_inverter_power : list [float | None ],
135+ chp_power : list [float | None ],
136+ expected_power : Power | None ,
137+ ) -> None :
100138 """Test the producer power formula with fallback formulas."""
101139 mockgrid = MockMicrogrid (grid_meter = False , mocker = mocker )
102140 mockgrid .add_solar_inverters (2 )
@@ -108,70 +146,16 @@ async def test_producer_fallback_formula(self, mocker: MockerFixture) -> None:
108146 stack .push_async_callback (producer .stop )
109147 producer_power_receiver = producer .power .new_receiver ()
110148
111- # Note: ProducerPowerFormula has a "nones-are-zero" rule, that says:
112- # * if the meter value is None, it should be treated as None.
113- # * for other components None is treated as 0.
114-
115- # fmt: off
116- expected_input_output : list [
117- tuple [list [float | None ], list [float | None ], list [float | None ], Power | None ]
118- ] = [
119- # ([pv_meter_power], [pv_inverter_power], [chp_power], expected_power)
120- # Add power from meters and chp
121- ([- 1.0 , - 2.0 ], [None , - 200.0 ], [300 ], Power .from_watts (297.0 )),
122- ([- 1.0 , - 10 ], [- 100.0 , - 200.0 ], [400 ], Power .from_watts (389.0 )),
123- # Case 2: The first meter is unavailable (None).
124- # Subscribe to the fallback inverter, but return None as the result,
125- # according to the "nones-are-zero" rule
126- ([None , - 2.0 ], [- 100 , - 200.0 ], [400 ], None ),
127- # Case 3: First meter is unavailable (None). Fallback inverter provides
128- # a value.
129- # Add second meter, first inverter and chp power
130- ([None , - 2.0 ], [- 100 , - 200.0 ], [400 ], Power .from_watts (298.0 )),
131- ([None , - 2.0 ], [- 50 , - 200.0 ], [300 ], Power .from_watts (248.0 )),
132- # Case 4: Both first meter and its fallback inverter are unavailable
133- # (None). Return 0 from failing component according to the
134- # "nones-are-zero" rule.
135- ([None , - 2.0 ], [None , - 200.0 ], [300 ], Power .from_watts (298.0 )),
136- ([None , - 10.0 ], [- 20.0 , - 200.0 ], [300 ], Power .from_watts (270.0 )),
137- # Case 5: CHP is unavailable. Return 0 from failing component
138- # according to the "nones-are-zero" rule.
139- ([None , - 10.0 ], [- 20.0 , - 200.0 ], [None ], Power .from_watts (- 30.0 )),
140- # Case 6: Both meters are unavailable (None). Subscribe for fallback inverter
141- ([None , None ], [- 20.0 , - 200.0 ], [None ], None ),
142- ([None , None ], [- 20.0 , - 200.0 ], [None ], Power .from_watts (- 220.0 )),
143- ([None , None ], [None , - 200.0 ], [None ], Power .from_watts (- 200.0 )),
144- # Case 7: All components are unavailable (None). Return 0 according to the
145- # "nones-are-zero" rule.
146- ([None , None ], [None , None ], [None ], Power .from_watts (0 )),
147- ([None , None ], [None , None ], [None ], Power .from_watts (0 )),
148- ([None , None ], [None , None ], [300.0 ], Power .from_watts (300.0 )),
149- ([- 200.0 , None ], [None , - 100.0 ], [50.0 ], Power .from_watts (- 250.0 )),
150- ([- 200.0 , - 200.0 ], [- 10.0 , - 20.0 ], [50.0 ], Power .from_watts (- 350.0 )),
151- # Case 8: Meter is unavailable but we already subscribed for inverter
152- # So don't return None in this case. Just proper formula result.
153- ([None , - 200.0 ], [- 10.0 , - 100.0 ], [50.0 ], Power .from_watts (- 160.0 )),
154-
155- ]
156- # fmt: on
157-
158- for idx , (
159- meter_power ,
160- pv_inverter_power ,
161- chp_power ,
162- expected_power ,
163- ) in enumerate (expected_input_output ):
164- await mockgrid .mock_resampler .send_chp_power (chp_power )
165- await mockgrid .mock_resampler .send_meter_power (meter_power )
166- await mockgrid .mock_resampler .send_pv_inverter_power (pv_inverter_power )
167- mockgrid .mock_resampler .next_ts ()
168-
169- result = await producer_power_receiver .receive ()
170- assert result .value == expected_power , (
171- f"Test case { idx } failed:"
172- + f" meter_power: { meter_power } "
173- + f" pv_inverter_power { pv_inverter_power } "
174- + f" chp_power { chp_power } "
175- + f" expected_power: { expected_power } "
176- + f" actual_power: { result .value } "
177- )
149+ await mockgrid .mock_resampler .send_chp_power (chp_power )
150+ await mockgrid .mock_resampler .send_meter_power (meter_power )
151+ await mockgrid .mock_resampler .send_pv_inverter_power (pv_inverter_power )
152+ mockgrid .mock_resampler .next_ts ()
153+
154+ result = await producer_power_receiver .receive ()
155+ assert result .value == expected_power , (
156+ f"meter_power: { meter_power } "
157+ + f" pv_inverter_power { pv_inverter_power } "
158+ + f" chp_power { chp_power } "
159+ + f" expected_power: { expected_power } "
160+ + f" actual_power: { result .value } "
161+ )
0 commit comments