55
66from  contextlib  import  AsyncExitStack 
77
8- import  pytest 
98from  frequenz .quantities  import  Power 
109from  pytest_mock  import  MockerFixture 
1110
@@ -97,44 +96,7 @@ async def test_no_producer_power(self, mocker: MockerFixture) -> None:
9796                0.0 
9897            )
9998
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 :
99+     async  def  test_producer_fallback_formula (self , mocker : MockerFixture ) ->  None :
138100        """Test the producer power formula with fallback formulas.""" 
139101        mockgrid  =  MockMicrogrid (grid_meter = False , mocker = mocker )
140102        mockgrid .add_solar_inverters (2 )
@@ -146,16 +108,70 @@ async def test_producer_fallback_formula(
146108            stack .push_async_callback (producer .stop )
147109            producer_power_receiver  =  producer .power .new_receiver ()
148110
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-             )
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+                 )
0 commit comments