Skip to content

Commit 5cd8d9a

Browse files
Fix power failure report (#763)
The succeeded power in `PartialFailure` result should exclude any failed power. There was no unit tests covering `PartialFailure` results so this patch adds a test to cover the case where the microgrid, for any reason, fails to set power for one of the batteries in the power request. Fixes #751
2 parents fdd18a9 + 2cb41c4 commit 5cd8d9a

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ This version ships an experimental version of the **Power Manager**, adds prelim
7272
- The `__getitem__` magic of the `MovingWindow` is fixed to support the same functionality that the `window` method provides.
7373
- Fixes incorrect implementation of single element access in `__getitem__` magic of `MovingWindow`.
7474
- Fix incorrect grid current calculations in locations where the calculations depended on current measurements from an inverter.
75+
- Fix power failure report to exclude any failed power from the succeeded power.

src/frequenz/sdk/actor/power_distributing/power_distributing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ async def _run(self) -> None: # pylint: disable=too-many-locals
350350
succeed_batteries = set(battery_distribution.keys()) - failed_batteries
351351
response = PartialFailure(
352352
request=request,
353-
succeeded_power=Power.from_watts(distributed_power_value),
353+
succeeded_power=Power.from_watts(
354+
distributed_power_value - failed_power
355+
),
354356
succeeded_batteries=succeed_batteries,
355357
failed_power=Power.from_watts(failed_power),
356358
failed_batteries=failed_batteries,

tests/actor/power_distributing/test_power_distributing.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from frequenz.sdk.actor.power_distributing.result import (
2525
Error,
2626
OutOfBounds,
27+
PartialFailure,
2728
PowerBounds,
2829
Result,
2930
Success,
@@ -1249,3 +1250,65 @@ async def test_not_all_batteries_are_working(self, mocker: MockerFixture) -> Non
12491250
assert result.request == request
12501251

12511252
await mockgrid.cleanup()
1253+
1254+
async def test_partial_failure_result(self, mocker: MockerFixture) -> None:
1255+
"""Test power results when the microgrid failed to set power for one of the batteries."""
1256+
mockgrid = MockMicrogrid(grid_meter=False)
1257+
mockgrid.add_batteries(3)
1258+
await mockgrid.start(mocker)
1259+
await self.init_component_data(mockgrid)
1260+
1261+
mocker.patch("asyncio.sleep", new_callable=AsyncMock)
1262+
1263+
batteries = {9, 19, 29}
1264+
failed_batteries = {9}
1265+
failed_power = 500.0
1266+
1267+
attrs = {"get_working_batteries.return_value": batteries}
1268+
mocker.patch(
1269+
"frequenz.sdk.actor.power_distributing.power_distributing.BatteryPoolStatus",
1270+
return_value=MagicMock(
1271+
spec=BatteryPoolStatus,
1272+
**attrs,
1273+
),
1274+
)
1275+
1276+
mocker.patch(
1277+
"frequenz.sdk.actor.power_distributing.PowerDistributingActor._parse_result",
1278+
return_value=(failed_power, failed_batteries),
1279+
)
1280+
1281+
requests_channel = Broadcast[Request]("power_distributor requests")
1282+
results_channel = Broadcast[Result]("power_distributor results")
1283+
1284+
battery_status_channel = Broadcast[BatteryStatus]("battery_status")
1285+
async with PowerDistributingActor(
1286+
requests_receiver=requests_channel.new_receiver(),
1287+
results_sender=results_channel.new_sender(),
1288+
battery_status_sender=battery_status_channel.new_sender(),
1289+
):
1290+
request = Request(
1291+
power=Power.from_kilowatts(1.70),
1292+
batteries=batteries,
1293+
request_timeout=SAFETY_TIMEOUT,
1294+
)
1295+
1296+
await requests_channel.new_sender().send(request)
1297+
result_rx = results_channel.new_receiver()
1298+
1299+
done, pending = await asyncio.wait(
1300+
[asyncio.create_task(result_rx.receive())],
1301+
timeout=SAFETY_TIMEOUT.total_seconds(),
1302+
)
1303+
assert len(pending) == 0
1304+
assert len(done) == 1
1305+
result = done.pop().result()
1306+
assert isinstance(result, PartialFailure)
1307+
assert result.succeeded_batteries == batteries - failed_batteries
1308+
assert result.failed_batteries == failed_batteries
1309+
assert result.succeeded_power.isclose(Power.from_watts(1000.0))
1310+
assert result.failed_power.isclose(Power.from_watts(failed_power))
1311+
assert result.excess_power.isclose(Power.from_watts(200.0))
1312+
assert result.request == request
1313+
1314+
await mockgrid.cleanup()

0 commit comments

Comments
 (0)