Repository navigation
Add PST tap position re-simulation support - #78
Merged
Merged
Conversation
Allow users to change tap values for PST actions within the available tap range and re-simulate, similar to the existing MW adjustment for curtailment and load shedding actions. Backend: - Add target_tap parameter to ManualActionRequest and simulate_manual_action - Add _compute_pst_details() returning tap_position, low_tap, high_tap per PST - Include pst_details in analysis enrichment and manual simulation responses Frontend: - Add PstDetail type and wire pst_details through API, types, and session utils - Add purple tap adjustment UI (input + Re-simulate) in ActionFeed action cards - Mirror all changes in standalone_interface.html https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
For PST action types in the scored actions table:
- Replace "MW Start" header with "Tap Start" showing current tap position
and valid range [low..high] from get_pst_tap_info
- Add "Target Tap" editable column synchronized with action card cardEditTap
- Row clicks trigger handleResimulateTap for computed PST actions with
a valid target, or pass targetTap to handleAddAction for new simulations
Backend:
- Add _get_pst_tap_start() returning {pst_name, tap, low_tap, high_tap}
- Add tap_start dict to action scores for PST types in _compute_mw_start_for_scores
Frontend:
- Add tap_start to AnalysisResult action_scores type
- Extend handleAddAction with optional targetTap parameter
- Add isPstType / hasEditableColumn logic to score table rendering
- Mirror all changes in standalone_interface.html
https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
- Fix "Tap Start" showing N/A by falling back to pst_details from computed actions when tap_start scores are not yet available - Fix "Target Tap" not syncing from action card to score table by defaulting the input value to tapInfo.tap instead of empty string - Fix default suggested tap for uncomputed actions by using effectiveTap variable that falls back to the start tap position https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
_get_pst_tap_start now returns the current tap position from the base network (via get_pst_tap_info) instead of the action's target tap value. Frontend score table prioritizes tapStartMap (N-state) over computedPst (post-simulation) for the Tap Start display. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
_get_pst_tap_start now reads the tap position directly from the pypowsybl base network's N-state variant via get_phase_tap_changers(), ensuring the original tap value is returned regardless of any simulation that may have modified the working variant. Falls back to the simulation environment's get_pst_tap_info only as a last resort. Frontend score table prioritizes tapStartMap (N-state from scores) for Tap Start display, using computedPst only as a fallback. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
Three-tier lookup for the N-state PST tap position: 1. Action's "parameters" -> "previous tap" field (from action JSON file) 2. _initial_pst_taps cache captured at network load time via get_phase_tap_changers() before any simulation modifies state 3. Simulation environment get_pst_tap_info as last resort This ensures Tap Start always shows the original base network tap, not the action's target tap or post-simulation value. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
The "previous tap" value in action_scores[type].params[actionId] is the true N-state tap from the recommender library. Use it as the primary source for the Tap Start column, falling back to tapStartMap and computedPst only when not available. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
Test suite (16 tests): - Verifies Tap Start reads from params "previous tap" (N-state) - Verifies N/A is not shown when params exist for unsimulated actions - Verifies Tap Start stays stable after simulation/re-simulation - Verifies Target Tap defaults to previous tap value - Verifies fallback chain: params > tap_start > computedPst > N/A - Tests key variants: "previous tap", "previous_tap", "previousTap" - Tests re-simulation API call with target_tap parameter Robust key lookup: tries "previous tap", "previous_tap", "previousTap", and falls back to fuzzy key matching for any key containing both "previous" and "tap". Debug console.log added to help diagnose actual params structure. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
Target Tap in score table now defaults to the simulated tap from pst_details (e.g. 29) for computed actions, falling back to start tap (previous tap) only for unsimulated actions. Priority chain: user edit > simulated tap > start tap. Info bubble's selected_pst_tap field now reflects the current effective tap value instead of the static backend value. New tests (5): - Target Tap defaults to simulated tap when action is computed - Target Tap defaults to start tap when action is NOT simulated - Target Tap updates after re-simulation with a different tap - Tap Start stays stable at 27 while Target Tap shows 29 - User editing Target Tap overrides the simulated default Removed debug console.log statements. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
After a PST tap or MW re-simulation, all combined pairs containing the re-simulated action now have their estimations refreshed via computeSuperposition. This keeps the Computed Pairs tab in the Combined Actions modal consistent with the latest simulation state. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
Temporary diagnostics to trace why combined pair estimations aren't updating after PST tap re-simulation. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
…tion _identify_action_elements from the library returns empty for PST tap actions because they don't change topology (no line/bus switches). Add a fallback that identifies the PST transformer's line index from the action content's pst_tap field, allowing superposition estimation to work correctly for PST actions after re-simulation. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
…sition The library's compute_combined_pair_superposition detects PST actions as "No-op" because PST tap changes don't produce topology changes (no line/bus switches). When this no-op error occurs for a pair involving a PST action, fall back to additive superposition (betas=[1,1]) using the stored observations directly. This allows combined pair estimations to update after PST tap re-simulation. The additive formula: rho_combined = obs_act1 + obs_act2 - obs_start is a first-order approximation that captures the independent effects of both actions. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
When simulate_manual_action re-simulates an action (e.g. after a PST tap change), selectively merge updated fields (observation, action, action_topology, content) into the existing _dict_action entry instead of replacing it entirely. This preserves the original library-format structure that _identify_action_elements needs to identify PST transformer elements for superposition estimation. Also removes the incorrect additive superposition fallback (betas=[1,1]) that was added as a workaround — the library's compute_combined_pair_ superposition handles PST actions correctly when the entry structure is intact. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
…al_action Adds detailed logging to diagnose why compute_combined_pair_superposition returns "No-op" for PST actions after re-simulation: - _dict_action entry structure (keys, content, pst_tap) - _identify_action_elements results - rho and p_or deltas at PST line indexes (obs_start vs obs_act) - Library function inputs and result - simulate_manual_action merge diagnostics https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
The library's compute_combined_pair_superposition has act1_is_pst and act2_is_pst parameters that control no-op detection. Without these flags (defaulting to False), PST tap actions are checked for line-status changes which never occur for PST actions — resulting in false "No-op" errors. With the flags set to True, the library correctly checks for power flow changes (abs(p_or delta) > 0.1 MW) instead. Detect PST actions using the same logic the library uses internally: action_type == "pst"/"pst_tap" or "pst_tap"/"pst_" in action_id. Also fixes pre-existing test failures in test_superposition_service.py by providing proper numpy-based mock observations and real config values instead of MagicMock objects that caused numpy comparison errors. https://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC
The compute_superposition estimation was excluding pre-existing overloaded lines (like .BIESL61PRAGN) from the eligible_mask when they weren't worsened beyond the threshold. This caused the estimated max_rho_line to differ from simulate_manual_action, which force-includes lines_overloaded_ids in its care_mask. Changes: - Force-include lines_overloaded_ids in eligible_mask (matching simulate_manual_action behavior) - Prefer _analysis_context["lines_overloaded"] for lines_overloaded_ids (consistent with simulate_manual_action priority) - Add diagnostic logging for eligible line counts and max_rho result https://claude.ai/code/session_01RVqqPvcxFpaqdYprGKygdX
The previous fix force-included unfiltered lines_overloaded_ids (any line with rho >= monitoring_factor) into the eligible_mask, bypassing the branches_with_limits filter. Lines without permanent thermal limits (e.g. CIVAUY712) produced wildly inaccurate rho estimates (441%). Changes: - Move lines_overloaded_ids computation after lines_we_care_about and branches_with_limits are available - Fallback now filters by both sets + pre-existing exclusion (matching simulate_manual_action's vectorized overload detection) - Restructure care_mask to match simulate_manual_action exactly: care + limits -> rho_combined -> pre-existing exclusion -> force-include - Use vectorized np.isin for limits_mask instead of Python loop - Reuse name_line_list/num_lines instead of recomputing https://claude.ai/code/session_01RVqqPvcxFpaqdYprGKygdX
…vs simulation Adds TOP 5 lines logging and .BIESL61PRAGN-specific checks in both compute_superposition (estimation) and simulate_manual_action (simulation) to identify why the estimated max line differs from the simulated max line. Logs show: in_care_mask, in_limits, in_lwca, estimated rho, N-1 rho, N rho https://claude.ai/code/session_01RVqqPvcxFpaqdYprGKygdX
Tests (11 new in test_superposition_monitoring_consistency.py): - Lines without thermal limits excluded from max_rho (CIVAUY712 regression) - Fallback lines_overloaded_ids filtered by care + limits - N-1 overloaded lines force-included despite pre-existing N-state overloads - Analysis context priority over recomputation - Pre-existing overload exclusion/inclusion based on worsening - Global max scan without caching - PST + switching monitoring consistency - is_rho_reduction on overloaded lines - Monitoring factor scaling Docs (curtailment-actions.md → curtailment-loadshedding-pst-actions.md): - Added sections 7-10: PST actions, PST in superposition, monitoring consistency, superposition accuracy limitations - Documented is_pst flag, element identification fallback, tap re-simulation, combined pair refresh flow - Added real-data example showing estimation vs simulation discrepancy https://claude.ai/code/session_01RVqqPvcxFpaqdYprGKygdX
Add diagnostic logging and improve line filtering logic in superposition
marota
pushed a commit
that referenced
this pull request
Apr 14, 2026
Session reload no longer loses data introduced by PRs #73/#78/#83/#88: - handleRestoreSession now restores lines_overloaded_after, load_shedding_details, curtailment_details and pst_details on each ActionDetail. Previously these were dropped on reload, so the PST / load-shedding / curtailment editor cards rendered empty and the Remedial Action tab lost its post-action overload halos until the user re-ran analysis. - buildSessionResult persists the sticky-header rho arrays (n_overloads_rho / n1_overloads_rho) alongside the overload name lists, guarded on matching length so misaligned legacy data is omitted instead of saved. - committedNetworkPathRef is now updated on session restore so the "Change Network?" confirmation dialog no longer misfires (or silently drops the study) after a reload. Interaction logging now captures every user gesture the replay contract needs to faithfully reproduce a session: - config_loaded and settings_applied include the full settings payload (all paths, every recommender threshold including min_load_shedding and min_renewable_curtailment_actions, monitoring, pre-existing overload threshold, ignore_reconnections, pypowsybl_fast_mode). - settings_tab_changed emits { from_tab, to_tab } and skips no-op clicks on the already-active tab. - New event types action_mw_resimulated and pst_tap_resimulated are logged from ActionFeed.handleResimulate / handleResimulateTap with the raw user-entered target_mw / target_tap. useActions no longer logs manual_action_simulated from handleActionResimulated, which conflated the two flows and made replay impossible. docs/interaction-logging.md is rewritten to reflect all of the above: documented tab_detached / tab_reattached / tab_tied / tab_untied visualisation events (previously in types.ts but undocumented), corrected the details shape for view_mode_changed / asset_clicked / inspect_query_changed / sld_overlay_* / session_* to match the actual emitted payloads, documented the applySettings / loadStudy / changeNetwork cases on contingency_confirmed, and added a new "Session reload fidelity" section listing exactly which fields are persisted / restored and which are intentionally ephemeral. Tests: 695 frontend tests still pass; sessionUtils.test.ts gains coverage for rho persistence guards and useActions.test.ts now asserts that handleActionResimulated does not log from the hook. https://claude.ai/code/session_013qJjLFQWMR91ZfPTRCLFiu
marota
pushed a commit
that referenced
this pull request
Apr 14, 2026
Adds 20 new tests across three files, guarding every fix from the
previous commit.
frontend/src/hooks/useSession.test.ts (+10 tests)
New describe block "handleRestoreSession" with a reusable
makeCtx() / makeSession() fixture pair. Covers:
- Full configuration restore (every field, including new
min_load_shedding / min_renewable_curtailment_actions
thresholds from PRs #73 / #78).
- Legacy session fallback to 0.0 when the two new thresholds
are absent from older JSON dumps.
- committedNetworkPathRef.current update on success — the
regression for the "Change Network?" dialog misfire fix.
- api.updateConfig payload shape, including the new thresholds.
- session_reloaded interaction event emission.
- Empty outputFolderPath short-circuit (no API call, ref
untouched).
- Backend error surfacing via ctx.setError with no ref mutation.
- Enrichment field round-trip: captures the setResult updater
via a captureRestoredResult() helper and asserts
load_shedding_details, curtailment_details, pst_details and
lines_overloaded_after all land on the restored ActionDetail.
- Action status flag restoration into selected / suggested /
rejected / manually-simulated sets.
- Legacy action shape (enrichment fields absent) doesn't crash.
- Estimation-only combined entries are filtered out of top-level
actions but survive under combined_actions.
frontend/src/components/ActionFeed.test.tsx (+4 tests)
New "Re-simulation interaction logging" describe block:
- action_mw_resimulated is recorded with target_mw equal to
parseFloat(user input) when re-simulating a load-shedding
card.
- manual_action_simulated is NOT emitted on LS re-simulation
(regression guard for the mistyped event in useActions).
- pst_tap_resimulated is recorded with target_tap equal to the
user-entered integer on PST re-simulation, and neither
manual_action_simulated nor action_mw_resimulated leak into
the log.
- target_mw is parsed as a float even when the user types
trailing zeros (5.400 → 5.4).
frontend/src/components/modals/SettingsModal.test.tsx (+5 tests)
New "settings_tab_changed interaction log shape" describe block:
- paths → recommender logs { from_tab: 'paths',
to_tab: 'recommender' }.
- paths → configurations logs the matching transition.
- from_tab tracks the currently-active tab, not the initial one
(rendering the modal already on 'recommender' and clicking
'configurations' yields from_tab: 'recommender').
- Clicking the already-active tab does NOT log a
settings_tab_changed entry (no-op skip).
- setSettingsTab is still called unconditionally on no-op
clicks — pins the "setter always, logger only on transition"
split behaviour.
Results: 39 test files, 715 tests passing (was 695). tsc -b and
eslint both clean.
https://claude.ai/code/session_013qJjLFQWMR91ZfPTRCLFiu
marota
pushed a commit
that referenced
this pull request
Apr 30, 2026
Reconciliation of section 2 (0.5.0): - Drop misattributed PRs that were actually pre-rebrand: save/reload (#49/#52), MW Start (#62), interaction-logging (#64), SLD highlights (#63), load shedding initial integration (#61). All now properly cited in section 1.5–1.6. - Disambiguate App.tsx refactor history: PR #56 (hooks, 2100 → 800, pre-rebrand) vs PR #74 (components, 1000 → 650, 0.5.0) vs PR #75 (memoization Phase 2, same LoC). - Add accurate 0.5.0 PRs: #66 (vectorization w/ benchmark table), #69/#70/#71 (UI polish), #72 (curtailment), #73 (loads_p/gens_p format + configurable MW), #74/#75 (App.tsx decomposition), #78 (PST tap re-simulation), #84/#86/#87/#90 (detachable tabs). - Add a recap table summarizing what's truly new in 0.5.0. Diagrams added (Mermaid, GitHub-rendered): - Gantt timeline of all 4 phases (top of doc). - High-level architecture (frontend / backend / data). - Two-step analysis sequence diagram (section 1.6). - App.tsx LoC evolution flow (section 2.4). - Backend mixin decomposition before/after PR #104/#106 (section 3). - PyPSA-EUR pipeline flowchart (section 4). https://claude.ai/code/session_01Pg7fuCUG2edfm5PyHS6SbN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds support for re-simulating Phase Shift Transformer (PST) actions with different tap positions. Users can now edit the tap position of PST actions and re-simulate them to see the impact on the grid, similar to the existing MW adjustment feature for load shedding/curtailment actions.
Key Changes
Backend (expert_backend)
_compute_pst_details()inrecommender_service.py: Extracts PST tap information from actions and retrieves tap bounds (low_tap, high_tap) from the network managersimulate_manual_action(): Addedtarget_tapparameter to support PST tap re-simulation with automatic clamping to valid boundsManualActionRequestnow accepts optionaltarget_tapparameterFrontend (React/TypeScript)
cardEditTapstate to track per-action editable tap positionshandleResimulateTap(): Processes PST tap re-simulation requests and updates action resultsPstDetailinterface with pst_name, tap_position, low_tap, and high_tap fieldssimulateManualAction()to accept optionaltargetTapparameterStandalone Interface
Implementation Details
get_pst_tap_info()to ensure valid rangeshttps://claude.ai/code/session_01DuZKBaZ2wDnQFMd9bY3QWC