Spaces:
Sleeping
Sleeping
| # State Reset & Confirmation Dialogs | |
| ## Overview | |
| Co-Study4Grid manages complex analysis state across contingency selections, N-1 simulations, manual action simulations, and prioritized action results. To prevent stale data from interfering with new analyses, the application implements two complementary mechanisms: | |
| 1. **Confirmation dialogs** when switching contingency or reloading a study with existing analysis state | |
| 2. **Full state reset** when loading a new study or applying new settings | |
| --- | |
| ## Confirmation Dialogs | |
| ### When They Trigger | |
| A confirmation dialog appears when the user attempts to: | |
| - **Change contingency** (select a different branch in the datalist) while analysis results, manual simulations, or action selections exist | |
| - **Reload study** (click "Load Study") while analysis state exists | |
| ### What They Warn About | |
| The dialog informs the user that all previous analysis results will be cleared: | |
| - Analysis results and pending results | |
| - Manual simulation results | |
| - Action selections (favorited, rejected) | |
| - Action diagrams and N-1 diagrams | |
| - Overload selections | |
| - VL overlay (SLD popups) | |
| ### User Options | |
| - **Confirm**: clears all analysis state and proceeds with the action | |
| - **Cancel**: reverts to the previous state (for contingency change, the input reverts to the previous branch) | |
| ### Implementation Details | |
| #### React Frontend (`App.tsx`) | |
| The confirmation logic is implemented at the **N-1 fetch `useEffect` level**, not at the input `onChange` level. This is because: | |
| - The contingency input is a `<input list="...">` (datalist) that fires `onChange` on every keystroke | |
| - Intercepting keystrokes would interfere with typing partial branch names | |
| - The useEffect only fires when `selectedBranch` matches a valid branch in the `branches` array | |
| Key state: | |
| - `confirmDialog`: `{ type: 'contingency' | 'loadStudy', pendingBranch?: string } | null` | |
| - `committedBranchRef`: tracks the last confirmed branch (prevents re-prompting for the same branch) | |
| Flow for contingency change: | |
| 1. User types/selects a new branch name | |
| 2. `selectedBranch` state updates freely (allows typing) | |
| 3. N-1 useEffect fires when the value matches a valid branch | |
| 4. If `selectedBranch !== committedBranchRef.current` AND `hasAnalysisState()` is true: | |
| - Dialog is shown | |
| - Input reverts to `committedBranchRef.current` | |
| 5. On confirm: `clearContingencyState()` is called, `committedBranchRef` is updated, branch is committed | |
| 6. On cancel: dialog closes, input stays at committed branch | |
| #### Standalone Interface | |
| No separate standalone mirror is required: the single-file distribution is now | |
| `frontend/dist-standalone/standalone.html`, auto-generated from this same React | |
| source via `npm run build:standalone` (PR #101). It therefore renders the exact | |
| same `ConfirmationDialog` component — the legacy hand-maintained | |
| `standalone_interface.html` that used `window.confirm()` has been decommissioned | |
| and frozen as `standalone_interface_legacy.html`. | |
| --- | |
| ## Full State Reset | |
| ### When It Happens | |
| A complete state reset occurs when: | |
| 1. **Load Study** is clicked (or confirmed via dialog) | |
| 2. **Apply Settings** is clicked in the Settings modal | |
| ### What Gets Reset | |
| #### Frontend State | |
| | Category | State Variables | Reset Value | | |
| |----------|----------------|-------------| | |
| | **Diagrams** | `nDiagram`, `n1Diagram`, `actionDiagram`, `originalViewBox` | `null` | | |
| | **Analysis** | `result`, `pendingAnalysisResult`, `selectedActionId` | `null` | | |
| | **Action tracking** | `selectedActionIds`, `manuallyAddedIds`, `rejectedActionIds` | `new Set()` | | |
| | **Loading flags** | `analysisLoading`, `n1Loading`, `actionDiagramLoading` | `false` | | |
| | **Analysis flow** | `selectedOverloads`, `monitorDeselected` | `new Set()` / `false` | | |
| | **Visualization** | `activeTab`, `actionViewMode`, `vlOverlay` | `'n'` / `'network'` / `null` | | |
| | **Branch** | `selectedBranch`, `committedBranchRef`, `inspectQuery` | `''` | | |
| | **Messages** | `error`, `infoMessage`, `showMonitoringWarning` | `''` / `false` | | |
| | **Refs** | `lastZoomState`, `actionSyncSourceRef` | Reset to defaults | | |
| #### What is NOT reset (preserved across reloads) | |
| - `networkPath` and `actionPath` (user inputs) | |
| - All settings values (`minLineReconnections`, `minCloseCoupling`, etc.) | |
| - `linesMonitoringPath` | |
| - `monitoringFactor`, `preExistingOverloadThreshold` | |
| - `ignoreReconnections`, `pypowsyblFastMode` | |
| #### Backend State | |
| The `/api/config` endpoint now **always reloads the network** and **resets the recommender service caches**, even if the network path hasn't changed. This prevents: | |
| - Stale in-memory network modifications from previous N-1 simulations | |
| - Cached simulation environments (`_simulation_env`) from a previous analysis | |
| - Leftover `_base_network`, `_last_disconnected_element`, `_dict_action`, `_analysis_context` | |
| The `RecommenderService.reset()` method clears, in order: | |
| 0. `_reset_model_settings()` — model selection (active model + | |
| `compute_overflow_graph` toggle) back to defaults (`ModelSelectionMixin`). | |
| 1. Bumps `_prefetch_generation` (so any in-flight NAD-prefetch worker | |
| discards its result under the service lock instead of writing into | |
| the next study's cache), then `_drain_pending_base_nad_prefetch()` — | |
| a no-op when the concurrency lock is present (lock ownership already | |
| excludes the worker), a `join` fallback for lock-less test hosts. | |
| See [docs/architecture/shared-network-concurrency.md](../architecture/shared-network-concurrency.md). | |
| 2. Per-study analysis state: `_last_result`, `_is_running`, | |
| `_generator`, `_base_network`, `_simulation_env`, | |
| `_last_disconnected_element`, `_dict_action`, `_analysis_context`, | |
| `_saved_computed_pairs`. | |
| 3. Fast-path caches: `_cached_obs_n`, `_cached_obs_n_id`, | |
| `_cached_obs_n1`, `_cached_obs_n1_id`, `_cached_obs_n1_elements`, | |
| `_cached_env_context`, `_initial_pst_taps`, `_lf_status_by_variant`, | |
| `_last_step1_time`. | |
| - `_cached_obs_n1_elements` is the exact contingency element list | |
| that produced `_cached_obs_n1` (defense-in-depth on top of the | |
| variant-ID check, used by `run_analysis_step1` to decide whether | |
| to reuse the pre-warmed obs and skip the contingency LF). See | |
| [docs/backend/recommender_models.md § Execution-time breakdown](../backend/recommender_models.md#execution-time-breakdown). | |
| - `_last_step1_time` stashes the wall-clock of the most recent | |
| `run_analysis_step1` so the step-2 result event can echo it to | |
| the React UI for the "Suggestions produced by …" breakdown. | |
| 4. `_layout_cache` — the cached `grid_layout.json` DataFrame used as | |
| `fixed_positions` for NAD generation. Must be cleared so a new | |
| study loaded from a different grid does not reuse the previous | |
| grid's substation coordinates. | |
| 5. NAD-prefetch state: `_prefetched_base_nad`, | |
| `_prefetched_base_nad_error`, `_prefetched_base_nad_event`, | |
| `_prefetched_base_nad_thread`. | |
| 6. Overflow-graph toggle state: | |
| - `_overflow_layout_mode` — reset to `"hierarchical"` so a new | |
| study's Overflow Analysis tab always opens in the default layout. | |
| - `_overflow_layout_cache` — cleared so file paths produced for | |
| the previous contingency cannot be served for the new one. | |
| - `_last_step2_context` — the preserved enriched Step-2 context | |
| used by `/api/regenerate-overflow-graph` and by the model-swap | |
| fast path; stale context must not be reused after a study reload. | |
| - `_last_step2_signature` — the input signature | |
| `(contingency, selected_overloads, all_overloads, | |
| monitor_deselected, additional_lines_to_cut)` that gates the | |
| Step-2 overflow-graph cache reuse. Cleared so a freshly loaded | |
| study never reuses the previous study's overflow graph. | |
| 7. Concurrency lifecycle (D3): `_contingency_variant_lru` — emptied | |
| because the contingency variants it tracks die with the Network | |
| being dropped. The `_network_lock` / `_study_gate` themselves are | |
| NOT cleared — they guard the reset itself and persist for the | |
| process. See | |
| [docs/architecture/shared-network-concurrency.md](../architecture/shared-network-concurrency.md). | |
| Adding a new per-study cache? It MUST be listed in | |
| `RecommenderService.reset()` (see | |
| `expert_backend/services/recommender_service.py:99`) and, if it holds | |
| a thread / future / event, drained first via a `_drain_pending_*` | |
| helper — otherwise it WILL leak across studies (regression history: | |
| the `_layout_cache` fix on `claude/fix-grid-layout-reset-8TYEV`). | |
| ### Why Force Reload? | |
| Previously, the backend cached the network path and skipped reloading if it hadn't changed (`if config.network_path != last_network_path`). This caused issues because: | |
| 1. N-1 simulations modify the in-memory network (disconnect elements, run load flow) | |
| 2. The `RecommenderService` caches simulation environments tied to specific contingencies | |
| 3. Action simulations may leave residual state in the recommender's internal caches | |
| By always reloading and resetting, we guarantee a clean slate for every new study load. | |
| --- | |
| ## `hasAnalysisState()` — What Counts as "Analysis State" | |
| The function returns `true` if ANY of these are non-null/non-empty: | |
| - `result` — analysis results from `run-analysis` | |
| - `pendingAnalysisResult` — results waiting to be displayed | |
| - `selectedActionId` — an action is being viewed | |
| - `actionDiagram` — an action variant diagram is loaded | |
| - `manuallyAddedIds.size > 0` — manual simulations were added | |
| - `selectedActionIds.size > 0` — actions were favorited | |
| - `rejectedActionIds.size > 0` — actions were rejected | |
| Note: the mere presence of an N-1 diagram does NOT count as analysis state. Simply selecting a contingency and viewing the N-1 diagram is considered a lightweight operation that doesn't warrant a confirmation dialog. | |
| --- | |
| ## Testing | |
| Tests are in `frontend/src/App.test.tsx` and cover: | |
| ### Contingency Change Confirmation (5 tests) | |
| 1. No dialog when switching branch without analysis state | |
| 2. Dialog shown when switching branch after running analysis | |
| 3. State cleared and branch switched on confirm | |
| 4. Input reverts to old branch on cancel | |
| 5. No dialog triggered for partial/invalid branch text | |
| ### Load Study Confirmation (4 tests) | |
| 6. Loads directly when no analysis state exists | |
| 7. Dialog shown when clicking Load Study after analysis | |
| 8. Study reloads on confirm | |
| 9. State preserved on cancel | |
| Run tests with: | |
| ```bash | |
| cd frontend | |
| npm run test | |
| ``` | |