Spaces:
Sleeping
Sleeping
| # Copyright (c) 2025-2026, RTE (https://www.rte-france.com) | |
| # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. | |
| # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, | |
| # you can obtain one at http://mozilla.org/MPL/2.0/. | |
| # SPDX-License-Identifier: MPL-2.0 | |
| """Tests for ``RecommenderService._apply_contingency``. | |
| Regression guard. ``pypowsybl.network.disconnect()`` silently returns | |
| ``False`` on some grids (small test grids, certain RTE xiidm exports) | |
| when the equipment's terminals don't expose the breaker model the | |
| high-level API expects. The previous ``try / except Exception`` in | |
| ``_get_contingency_variant`` caught only raised exceptions, missed the False | |
| return, and left the N-1 variant identical to N — every overload- | |
| dependent UI element (sidebar N-1 overloads, orange halos on the N-1 | |
| diagram) then reported "no overloads" even when step1 (which runs | |
| through grid2op's own network) correctly detected them. | |
| The helper must fall back to ``update_lines`` / | |
| ``update_2_windings_transformers`` so the variant reflects the | |
| contingency regardless of which pypowsybl API path works. | |
| """ | |
| from __future__ import annotations | |
| from unittest.mock import MagicMock | |
| import pandas as pd | |
| from expert_backend.services.recommender_service import RecommenderService | |
| def _make_net(disconnect_return, line_ids=("LINE_A",), tfo_ids=()): | |
| net = MagicMock(name="net") | |
| net.disconnect = MagicMock(return_value=disconnect_return) | |
| net.update_lines = MagicMock() | |
| net.update_2_windings_transformers = MagicMock() | |
| net.get_lines = MagicMock(return_value=pd.DataFrame(index=list(line_ids))) | |
| net.get_2_windings_transformers = MagicMock( | |
| return_value=pd.DataFrame(index=list(tfo_ids)) | |
| ) | |
| return net | |
| def test_disconnect_success_skips_fallback(): | |
| service = RecommenderService() | |
| net = _make_net(disconnect_return=True) | |
| assert service._apply_contingency_element(net, "LINE_A") is True | |
| net.disconnect.assert_called_once_with("LINE_A") | |
| net.update_lines.assert_not_called() | |
| net.update_2_windings_transformers.assert_not_called() | |
| def test_falls_back_to_update_lines_when_disconnect_returns_false(): | |
| """Real-world regression: pypowsybl returned False silently for | |
| P.SAOL31RONCI on the bare_env_small_grid_test grid, leaving the | |
| N-1 variant identical to N and blanking the sidebar overloads + | |
| orange halos.""" | |
| service = RecommenderService() | |
| net = _make_net(disconnect_return=False) | |
| assert service._apply_contingency_element(net, "LINE_A") is True | |
| net.update_lines.assert_called_once_with( | |
| id="LINE_A", connected1=False, connected2=False | |
| ) | |
| net.update_2_windings_transformers.assert_not_called() | |
| def test_falls_back_to_update_2wt_for_transformer(): | |
| service = RecommenderService() | |
| net = _make_net(disconnect_return=False, line_ids=(), tfo_ids=("TRAFO_1",)) | |
| assert service._apply_contingency_element(net, "TRAFO_1") is True | |
| net.update_2_windings_transformers.assert_called_once_with( | |
| id="TRAFO_1", connected1=False, connected2=False | |
| ) | |
| net.update_lines.assert_not_called() | |
| def test_disconnect_exception_also_triggers_fallback(): | |
| service = RecommenderService() | |
| net = _make_net(disconnect_return=False) | |
| net.disconnect = MagicMock(side_effect=RuntimeError("boom")) | |
| assert service._apply_contingency_element(net, "LINE_A") is True | |
| net.update_lines.assert_called_once() | |
| def test_returns_false_when_element_not_found(): | |
| """Unknown equipment id — no fallback available; log + return | |
| False so the caller can surface the issue.""" | |
| service = RecommenderService() | |
| net = _make_net(disconnect_return=False, line_ids=(), tfo_ids=()) | |
| assert service._apply_contingency_element(net, "MYSTERY_X") is False | |
| net.update_lines.assert_not_called() | |
| net.update_2_windings_transformers.assert_not_called() | |