Spaces:
Sleeping
Pluggable Recommendation Models
Co-Study4Grid ships with a small registry of recommendation models and
binds them through the FastAPI backend and the React frontend. The
library-side contract (RecommenderModel ABC, RecommenderInputs /
RecommenderOutput DTOs, reusable reassessment phase) lives in
marota/expert_op4grid_recommender.
This document is the app-side reference: registry, built-in random examples, the three-layer filter chain, backend / frontend wiring, and the step-by-step guide for shipping a new model. See also the broader backend overview.
1. The registry
Lives in expert_backend/recommenders/registry.py. Tiny by design:
from expert_backend.recommenders.registry import (
DEFAULT_MODEL, # "expert"
register, # decorator / function: add a model class
unregister, # remove a model by name
build_recommender, # instantiate by name (falls back to DEFAULT_MODEL on empty / None)
get_model_class, # lookup; returns None on miss
list_models, # JSON-ready descriptors for the UI
)
Three models are registered at startup (expert_backend/recommenders/__init__.py):
| name | label | requires_overflow_graph | params_spec |
|---|---|---|---|
expert |
Expert system | True |
Every legacy knob (n_prioritized_actions, min_line_*, the per-action-type minima min_load_shedding / min_renewable_curtailment_actions / min_redispatch / min_pst, ignore_reconnections, ...). |
random |
Random | False |
Just n_prioritized_actions. |
random_overflow |
Random (post overflow analysis) | True |
Just n_prioritized_actions. |
Third-party packages can extend the registry by decorating their
RecommenderModel subclass with @register at import time. The
library (expert_op4grid_recommender) only owns the contract; the
registry sits here so this app stays in control of which models are
offered to operators.
2. Built-in random models
Intended as canonical examples of how to plug a model in, plus baselines against the expert system.
RandomRecommender (requires_overflow_graph=False)
File: expert_backend/recommenders/random_basic.py.
Samples uniformly from the operator's action dictionary, augmented at
runtime with simple reconnection / load-shedding / curtailment actions
derived from the post-fault observation (see synthetic_actions.py).
Skips the expensive step-2 overflow-graph build by default.
RandomOverflowRecommender (requires_overflow_graph=True)
File: expert_backend/recommenders/random_overflow.py.
Samples uniformly from actions inside the reduced action space the expert sees: actions retained by the expert rule filter AND touching the overflow-graph paths AND existing on the loaded network. Three filter layers stacked before sampling — see next section.
Returns {} (not a fallback to the full dict) when any layer empties
the pool: that's the correct semantic for "no overflow-relevant
actions for this contingency".
3. The three-layer filter chain
Applied by RandomOverflowRecommender before sampling. The expert
pipeline gets layers 1 and 2 implicitly via ActionDiscoverer's
per-type mixins.
Layer 1 — Expert rule filter
- Where:
_run_expert_action_filter(context)in the library, invoked byrun_analysis_step2_discoverywhenever the overflow graph is available in the context (= the chosen model required it OR the operator opted in viacompute_overflow_graph=True). - What: runs path analysis +
ActionRuleValidator.categorize_actions, which removes broadly invalid actions (wrong shape, lines already open, missing devices, ...). - Output: writes
context["filtered_candidate_actions"], forwarded to the recommender viainputs.filtered_candidate_actions. - Idempotent — free no-op when already populated. The expert model invokes it internally too.
- Note: this filter does NOT restrict to overflow-relevant actions — that targeting is layer 2.
Layer 2 — Overflow-graph path filter
- Where:
expert_backend/recommenders/overflow_path_filter.py(restrict_to_overflow_paths). - What: extracts the same path lists the expert orchestrator uses
from
g_distribution_graph:- dispatch path lines + constrained path lines →
relevant_lines, - dispatch loop nodes + blue path nodes + hub substations →
relevant_subs. Keeps an action when ANY of these references matches:
entry["VoltageLevelId"](orvoltage_level_id) inrelevant_subs,content.set_bus.lines_or_id/lines_ex_id/pst_tapkeys inrelevant_lines,- action-id suffix for
disco_<LINE>/reco_<LINE>entries, - any
_-split segment inrelevant_subs(UUID-prefixed coupling shape<uuid>_<VL>_..._coupling).
- dispatch path lines + constrained path lines →
- Robustness:
_resolve_node_to_namehandles both shapes the distribution graph returns — integer indices intoobs.name_sub(legacy build) and substation-name strings (current build, includingnumpy.str_). Conservative on extraction failure (returns the input list unchanged so a buggy graph never silently empties the pool).
Layer 3 — Network existence filter
- Where:
expert_backend/recommenders/network_existence.py(filter_to_existing_network_elements). - What: drops actions whose
VoltageLevelIdorset_bus.lines_*_idreferences an element that doesn't exist on the loaded pypowsybl network. Catches the case where a dict shipped for a larger grid is used against a smaller subset (the original AUBE P4 / small_grid bug). - Robustness: returns the input list unchanged when the network introspection itself fails — never silently empties the pool.
Layer ordering rationale
dict_action (potentially 24k entries)
|
v
Layer 1: ActionRuleValidator → ~few hundred candidates
|
v
Layer 2: overflow_path_filter → ~few dozen candidates on paths
|
v
Layer 3: network_existence_filter → candidates known to the grid
|
v
env.action_space(content) → final pool, drop on materialise error
|
v
random.sample(pool, min(n, len(pool)))
Each layer is conservative on internal failure (returns input unchanged) so a bug in any single filter cannot silently empty the pool. Layers can be independently disabled by mocking the inputs they read.
4. Backend wiring
ConfigRequest extension
expert_backend/main.py adds two fields to the existing config:
class ConfigRequest(BaseModel):
# ... existing fields ...
model: str = "expert"
compute_overflow_graph: bool = True
Defaults match the legacy expert behaviour, so existing clients keep working.
Restricting the proposed action families (allowed_action_types)
POST /api/config also accepts an allowed_action_types list, driven
by the "Restrict to action types" control in Settings →
Recommender. It is plumbed through to config.ALLOWED_ACTION_TYPES:
- Empty / unset — the recommender proposes every action family (the previous, unrestricted behaviour).
- A non-empty subset (any of
reco/close/open/disco/pst/ls/rc/redispatch) — the recommender proposes only actions of those families.
This narrows the candidate pool the active model sees, independently of
the per-family min_* minima above.
GET /api/models
Lists every registered model with its label,
requires_overflow_graph, is_default flag, and params (the
params_spec descriptors). The frontend reads this once on mount to
populate the dropdown and render only the parameter inputs the active
model actually consumes.
POST /api/recommender-model
Lightweight model swap on the running RecommenderService. Body:
{ model: str, compute_overflow_graph?: bool }. Calls
_apply_model_settings(req) and echoes back
{ status, active_model, compute_overflow_graph }.
Unlike POST /api/config, this does not reload the network or
rebuild the action dictionary — it only updates the two
ModelSelectionMixin attributes. The frontend fires it from
useSettings whenever recommenderModel / computeOverflowGraph
change, so a model picked in either the Settings → Recommender
tab or the dropdown above the Analyze & Suggest button takes
effect on the very next POST /api/run-analysis-step2 — without an
expensive study reload. The Step-2 graph cache (see below) is
deliberately left intact across a model swap: the overflow graph
doesn't depend on the model, so a swap re-runs only the discovery
step.
RecommenderService integration
Explicit composition (2026-07 D1 revision — the former
_service_integration.py module that grafted all of this onto the
class at import time was removed):
- State + getters via
ModelSelectionMixin, a regular base class ofRecommenderService—_recommender_model_nameand_compute_overflow_graph(with defaults"expert"andTrue). Public gettersget_active_model_name()andget_compute_overflow_graph()are echoed back by/api/config. update_config/resetcall_apply_model_settings(settings)/_reset_model_settings()explicitly, so the two ConfigRequest fields are captured every time the operator applies settings and cleared on every study reload.AnalysisMixin.run_analysis_step2is the single, model-aware generator (no shadowed legacy copy). It:- builds the recommender from the registry (lazy import, so the
mock-layer test sandbox can still import
analysis_mixin), - conditionally skips the overflow-graph step
(
needs_graph = requires_overflow_graph OR get_compute_overflow_graph()— a model that requires the graph can never be skipped, even via direct API call), - threads the recommender all the way through to
run_analysis_step2_discovery, - echoes
active_modelandcompute_overflow_graphon theresultevent so the frontend can persist them in the saved session (analysis.active_model).
- builds the recommender from the registry (lazy import, so the
mock-layer test sandbox can still import
The patches are applied as a side-effect of importing
expert_backend.recommenders. expert_backend/main.py only needs
that single import to enable everything.
Step-2 overflow-graph cache (model-swap fast path)
run_analysis_step2 keys the overflow graph on a signature of its
inputs: (disconnected_elements, selected_overloads, all_overloads, monitor_deselected, additional_lines_to_cut). The signature + the
enriched context are stored on _last_step2_signature /
_last_step2_context, and the produced HTML viewer path on
_overflow_layout_cache["hierarchical"].
When a re-run posts an identical signature, the orchestrator skips
_narrow_context_to_selected_overloads + run_analysis_step2_graph +
the PDF mtime poll, yields the cached pdf event with cached: true,
and jumps straight to run_analysis_step2_discovery. Because the
overflow graph depends only on topology — never on the recommender —
this makes the common "swap model and re-run" loop near-instant: only
the discovery step actually re-executes. Any change to the
contingency or the additional-lines hypothesis changes the signature
and forces a full rebuild.
_last_step2_signature and _last_step2_context are per-study caches
— both are cleared by RecommenderService.reset() so a freshly
loaded study never reuses the previous study's graph.
5. Frontend wiring
useSettings hook
frontend/src/hooks/useSettings.ts:
- New state:
recommenderModel: string(default"expert"),computeOverflowGraph: boolean(defaulttrue). availableModels: ModelDescriptor[]fetched on mount viaapi.getModels().useEffectforcescomputeOverflowGraph = truewhenever the active model declaresrequires_overflow_graph = true. Keeps persisted user config in sync with what the backend will actually run.- A second
useEffectpushes the model to the running backend viaapi.setRecommenderModel()(→POST /api/recommender-model) wheneverrecommenderModel/computeOverflowGraphchange. AlastPushedModelRefguard skips redundant pushes. This is what makes a mid-session model swap (from either dropdown) actually reach the backend without an Apply Settings round-trip. buildConfigRequest()carriesmodelandcompute_overflow_graphthrough every/api/configcall.
ActionFeed — model selector + active-model reminder + Clear
frontend/src/components/ActionFeed.tsx:
- Model dropdown above "Analyze & Suggest" — a mirror of the
Settings → Recommender selector, populated from
availableModels. Lets the operator swap model and re-run without opening Settings. Every change emits arecommender_model_changedinteraction event withsource: 'action_feed'. - Active-model reminder — once a run has produced suggestions, an
italic "Suggestions produced by <model label>" line sits
just below the Suggested Actions tab header. The label is resolved
from
result.active_modelagainstavailableModels. - Clear button — a danger-coloured button on that reminder line.
It opens the shared
<ConfirmationDialog/>(type: 'clearSuggested'); on confirm,App.performClearSuggestedwipes the recommender suggestions the operator has NOT triaged (un-starred, un-rejected, not manually added) and emitssuggested_actions_cleared. It does not re-run the analysis — the operator clears, optionally swaps the model, then presses Analyze & Suggest. The analysis-trigger slot is gated onprioritizedEntries.length === 0, so it reappears the moment the Suggested feed empties out.
ActionCard — origin / "Source" row
frontend/src/components/ActionCard.tsx:
Every action carries an origin field (ActionDetail.origin)
recording its provenance — set once at creation, never changed by
starring or re-simulating:
"user"— the operator simulated it themselves (manual search dropdown / "Make a first guess"). Set byuseActions.handleManualActionAdded(default).<model id>— produced by a recommender. Set by the step-2 result loop inuseAnalysisfrom theactive_modelechoed on the stream'sresultevent. The unsimulated-pin path (App.handleSimulateUnsimulatedAction) also stamps the model id, not"user", because that pin was scored by the model — the operator only triggered its materialisation.
origin is distinct from the is_manual flag, which is overloaded
UI state (also true when the operator merely stars a recommender
suggestion). The unfolded action card renders an origin-derived
"Source" row — ActionCard resolves a model id to its label via
availableModels, falling back to the raw id. The field is persisted
in session.json (SavedActionEntry.origin) and restored verbatim;
legacy dumps get a derived origin on reload. See
docs/features/save-results.md.
SettingsModal — Recommender tab
frontend/src/components/modals/SettingsModal.tsx:
- Top of the tab: a model dropdown populated from
availableModels. - Below the dropdown: the
Compute Overflow Graph (step 1)checkbox with three states:- Locked + checked with the suffix "required by this model"
when
activeModel.requires_overflow_graphis true. - Editable with the suffix "optional for this model" when the model doesn't require the graph (useful when the operator still wants to inspect the overflow analysis tab alongside a graph-agnostic recommender).
- Hidden entirely while
availableModelsis loading (then falls back to showing all the legacy expert fields).
- Locked + checked with the suffix "required by this model"
when
- Below the toggle: the recommender parameters. Each expert-specific
input is rendered only when the active model declares it in
params_spec. SoRandomonly showsN Prioritized Actions;Expertshows the full legacy list.
ActionCard — VL chip
frontend/src/components/ActionCard.tsx:
renderBadges() reads details.action_topology.voltage_level_id as
the highest-priority signal for nodal / coupling / switch-based
actions (pypowsybl UUID-prefixed ..._VL_..._coupling). The chip is
clickable (zoom to VL) and double-clickable (open SLD) — matching the
behaviour of the existing load-shedding / curtailment VL chips. The
backend surfaces this field from dict_action[id]["VoltageLevelId"]
via extract_action_topology.
6. How to add a new recommendation model
Three files; nothing else needs to change in the app.
Step 1 — Write the model class
Anywhere in your package (or a new file under
expert_backend/recommenders/). The class follows the library
contract:
from expert_op4grid_recommender.models.base import (
RecommenderModel, RecommenderInputs, RecommenderOutput, ParamSpec,
)
class MyMLPolicy(RecommenderModel):
name = "ml_policy"
label = "ML policy v3"
requires_overflow_graph = True # we want the overflow analysis features
@classmethod
def params_spec(cls):
return [
ParamSpec("n_prioritized_actions", "N Actions", "int",
default=5, min=1, max=20),
ParamSpec("temperature", "Sampling temperature", "float",
default=0.7, min=0.0, max=2.0),
]
def recommend(self, inputs: RecommenderInputs, params: dict) -> RecommenderOutput:
# Use any combination of:
# inputs.obs / inputs.network (N state)
# inputs.obs_defaut / inputs.network_defaut (N-K state)
# inputs.lines_overloaded_names / _ids / _rho
# inputs.dict_action
# inputs.filtered_candidate_actions (your model gets the same reduced
# action space the expert sees)
# inputs.distribution_graph / hubs (overflow path info)
# inputs.env (to materialise actions via env.action_space(content))
my_picks = pick_actions_with_ml(...)
return RecommenderOutput(prioritized_actions=my_picks)
Step 2 — Register it
Decorate with @register (or call it as a function) at import time:
from expert_backend.recommenders.registry import register
@register
class MyMLPolicy(RecommenderModel):
...
For models shipped as a third-party package: import the registry from
that package, decorate your class. The registration runs on import,
so your package needs to be imported by the backend before
/api/models is queried (typical pattern: import it from
expert_backend/recommenders/__init__.py or from your own startup
hook).
Step 3 — No further wiring needed
The frontend picks up the new model automatically:
GET /api/modelsincludes it,- the Settings → Recommender dropdown lists it,
- the parameter inputs are rendered dynamically from
params_spec(), - the
Compute Overflow Graphtoggle is locked/checked or editable based onrequires_overflow_graph, - the analysis pipeline calls your
recommend()viarun_analysis_step2_discovery, - saved sessions persist the active model under
analysis.active_model(seedocs/features/save-results.md).
If your model needs the same reduced action space as the expert
(filtered_candidate_actions), declare requires_overflow_graph=True
and the pipeline runs the expert rule filter for you. For models
that need the path-relevant subset, additionally apply
restrict_to_overflow_paths (and optionally
filter_to_existing_network_elements) inside recommend() — see
RandomOverflowRecommender for the canonical pattern.
7. Testing
App-side tests live in tests/. Mock-based; no live pypowsybl /
grid2op needed.
test_recommenders_registry.py— register / unregister, build with empty / None, fallback to default,list_models()shape and per-model flags, canonical three models.test_random_recommenders.py— metadata, sampling cardinality, the three-layer filter chain for RandomOverflow, None-vs-[]fallback semantics forfiltered_candidate_actions, drop-on-unknown-VL regression (AUBE P4 case).test_overflow_path_filter.py—_resolve_node_to_namecovering int / numpy.int64 / str / numpy.str_ / bytes, thenumpy.str_regression for the legacyidx < n_subscrash, end-to-end with numpy nodes.test_network_existence.py—filter_to_existing_network_elements, short-circuit on first unknown line, conservative fallback on introspection failure, transformer ids accepted as branches.test_action_enrichment.py—extract_action_topologycovering numpy-array attribute tolerance, four-way set_bus backfill,voltage_level_idsurfacing (upper- and lower-case), switches fallback, combined pypowsybl switch-based shape.test_model_selection_mixin.py— default state,_apply_model_settingswith explicit / empty / whitespace / non-string values, missing attrs use defaults.test_model_composition.py— the explicit composition: mixin inherited,update_config/resetdelegate to it, single model-awarerun_analysis_step2, unknown model emits an error event, overflow-graph cache fast path,antenna_metapass-through.test_models_api.py—ConfigRequestdefaults / accepts custom model / round-trips through JSON;GET /api/modelsshape and canonical content.
Run the suite: pytest expert_backend/tests (or plain pytest) from the repo root — the files above live in the canonical suite and run in CI.
8. Troubleshooting
"RandomOverflowRecommender: filtered_candidate_actions is None"
The expert rule filter is supposed to populate it. If the warning fires, either:
- the step-2 graph wasn't built (check the
Compute Overflow Graphtoggle in the Settings → Recommender tab — should be locked-on forrandom_overflow), or g_distribution_graphisn't in the context (look for an earlier warning fromrun_analysis_step2_graph).
Pins clustered on the overload, all showing the same %
Diagnosis: the resolveActionAnchor in
frontend/src/utils/svg/actionPinData.ts is falling back to
max_rho_line. Root causes (in priority order):
action_topology.voltage_level_idis missing from the backend payload → checkextract_action_topologyis surfacing theVoltageLevelIdkey fromdict_action.- Action targets reference elements outside the SVG metadata index
(NAD doesn't cover them) → expected for filtered topologies; the
action should still be filtered out by
filter_to_existing_network_elements.
Suggestions spread across the whole grid for random_overflow
Check the backend logs for overflow-path-filter: could not extract path targets — a numpy.str_ regression used to disable the filter
silently. Fixed in overflow_path_filter._resolve_node_to_name.
If the message still appears, file a bug with the exception details.
"Compute Overflow Graph" toggle does nothing for the active model
Intended behaviour for models with requires_overflow_graph=True —
the checkbox is locked-on with the "required by this model" suffix.
The backend enforces the same guarantee
(needs_graph = requires_overflow_graph OR get_compute_overflow_graph())
so direct API calls cannot bypass it.
Execution-time breakdown
Every two-step analysis run now reports a per-stage execution-time
breakdown. The values travel on the streaming NDJSON events from
POST /api/run-analysis-step2 and are persisted in analysis.* on
session save (see Save Results § analysis).
What each stage measures
| Stage | Where it's measured | Covers |
|---|---|---|
step1_time |
expert_backend/services/analysis_mixin.py (wrapper) |
Contingency simulation + overload detection (run_analysis_step1). Near-zero when the obs is pre-warmed (see below). |
overflow_graph_time |
expert_backend/services/analysis_mixin.py |
_narrow_context_to_selected_overloads + run_analysis_step2_graph + the PDF mtime poll. null when the active model doesn't consume the overflow graph; 0.0 on a cached re-run. |
action_prediction_time |
analysis_mixin.py (upstream-reported) |
recommender.recommend(inputs, params) — the model's intrinsic selection step. For Expert-style models, includes the internal candidate simulation used to score topology actions. |
assessment_time |
analysis_mixin.py (upstream-reported) |
reassess_prioritized_actions + propagate_non_convergence_to_scores + compute_combined_pairs. Each prioritized action is re-simulated to compute its final rho_before / rho_after. Scales linearly with the number of prioritized actions. |
enrichment_time |
analysis_mixin.py |
Co-Study4Grid post-processing: _enrich_actions + _augment_combined_actions_with_target_max_rho + _compute_mw_start_for_scores. UI-facing decoration only. |
wall_clock_time |
frontend/src/hooks/useAnalysis.ts |
performance.now() from the "Analyze & Suggest" click until the result NDJSON event arrives. Includes every backend stage + network round-trip + NDJSON streaming overhead. |
The headline number in the ActionFeed reminder (Suggestions produced by <model> in <X>s ⓘ) is wall_clock_time. The native <title>
tooltip lists each stage plus the residual Other (network / streaming) = wall_clock_time − Σ(stage_times). See
frontend/src/components/ActionFeed.tsx
for the rendering and
docs/features/save-results.md
for the saved-JSON schema.
How the breakdown is wired
Backend NDJSON events:
- The
pdfevent (sent before theresultevent so the iframe can render the overflow graph early) carriesoverflow_graph_timeso the iframe's<h1>subtitle (Total execution time: <X>s) can appear as soon as the file is ready. - The
resultevent carries all six fields. The Co-Study4Grid frontend stampswall_clock_timeitself; the other five come from the backend.
Frontend persistence:
frontend/src/utils/sessionUtils.ts(buildSessionResult) writes each field intoanalysis.*, defaulting tonullwhen the liveresultdoesn't have it. The JSON shape stays stable across runs.frontend/src/hooks/useSession.ts(handleRestoreSession) re-attaches each field onto the restoredAnalysisResult. Saved sessions from before the breakdown landed simply restore with these fieldsundefined, and the ActionFeed reminder'sshowBreakdownshort-circuit hides the headline entirely.
Pre-warming the post-contingency observation
Selecting a contingency triggers /api/n1-diagram (or
/api/n1-diagram-patch), which creates a pypowsybl variant, runs the
AC load flow, and returns the diagram. Before this optimisation,
run_analysis_step1 blindly re-ran the same load flow when the
operator clicked "Analyze & Suggest" — the LF on the French grid is
~1-3 s.
DiagramMixin._cache_obs_for_variant (a thin wrapper around
services/diagram/obs_prewarm.py:build_prewarmed_obs) now builds a
PypowsyblObservation off the already-converged variant and stores it
on _cached_obs_n1 / _cached_obs_n1_id / _cached_obs_n1_elements.
The stateless helper lives in services/diagram/ so the mixin stays
under the function-LoC ceiling guarded by the code-quality gate.
AnalysisMixin.run_analysis_step1 validates the cache against the
contingency variant ID + element list and, on a hit, forwards the
observation to the upstream library through the new
prebuilt_obs_simu_defaut kwarg. The upstream then skips
simulate_contingency_pypowsybl entirely.
Safety gate. The diagram path applies the contingency only — not
any maintenance reconnections. When the operator opts into them via
DO_RECO_MAINTENANCE=True, the cached obs would be physically wrong,
so run_analysis_step1 disables the reuse path regardless of variant
match. The default config (DO_RECO_MAINTENANCE=False) keeps the
fast path enabled.
Backward compatibility. Older expert_op4grid_recommender
releases don't accept the prebuilt_obs_simu_defaut kwarg.
AnalysisMixin._upstream_step1_supports_prebuilt_obs() introspects
the upstream signature with inspect.signature and only forwards the
kwarg when the parameter exists. On older libraries the wrapper logs
a one-line notice and falls back to the slow path — no crash, no
synced-pull requirement for operators.
Action-discovery seam (run_analysis_step2_discovery)
The model-aware step-2 generator (AnalysisMixin.run_analysis_step2
in expert_backend/services/analysis_mixin.py) drives action
discovery through the upstream run_analysis_step2_discovery
wrapper rather than calling its sub-primitives
(build_recommender_inputs, reassess_prioritized_actions,
compute_combined_pairs) directly. This keeps the long-standing test
seam at
@patch('expert_backend.services.analysis_mixin.run_analysis_step2_discovery')
intact — test_overload_filtering.py, test_superposition_service.py
and friends short-circuit the discovery step at that boundary.
Per-stage timings (prediction_time, assessment_time) are read out
of the upstream return dict when present (≥ 0.2.2.post1). Against
older releases the wrapper falls back to a single total surfaced as
action_prediction_time (and assessment_time = 0.0) so the React UI
shows something useful instead of a misleading split.
get_maintenance_timestep_pypowsybl fast-exit
The upstream expert_op4grid_recommender.utils.helpers_pypowsybl.get_maintenance_timestep_pypowsybl
function fast-exits when do_reco_maintenance=False. Previously, it
unconditionally scanned every disconnected line in the network and
formatted a multi-line print listing them — informational only, since
the returned lines_in_maintenance is never consumed downstream when
the flag is off. The scan + print add ~150-300 ms per analysis run on
large grids with many pre-disconnected lines.
Cache lifecycle
| Event | Effect on _cached_obs_n1* |
|---|---|
/api/n1-diagram or /api/n1-diagram-patch for a converged contingency |
Cache (re)populated for the new contingency. |
| Operator picks a different contingency | The diagram refetch overwrites the cache with the new contingency's observation. |
RecommenderService.reset() (Apply Settings / Load Study) |
All three fields cleared to None. |
simulate_manual_action / compute_superposition |
The action variants clone from the contingency variant; the cache is not modified. |
| LF did not converge | Prewarm is skipped; analysis falls back to the full path. |
Test coverage
| File | What it asserts |
|---|---|
expert_backend/tests/test_obs_prewarm_for_step1.py |
_cache_obs_for_variant uses the right env; reset clears the cache; cache hit forwards the obs; variant mismatch / maintenance-flag-on disables reuse; signature-introspection fallback. |
expert_backend/tests/test_action_patch_module.py |
Covers the action-patch extraction (services/diagram/action_patch.py): public-import surface, _extract_convergence_status shapes, _capture_action_snapshots isolation + copy discipline, _unpatchable_response payload, extract_vl_subtrees_with_edges with the injected generate_diagram callable, build_action_patch_payload early-return contract. |
Expert_op4grid_recommender/tests/test_helpers_pypowsybl_maintenance.py |
Fast-exit returns an empty action without scanning when do_reco_maintenance=False; the print is suppressed; the full path runs unchanged when the flag is on. |
Expert_op4grid_recommender/tests/test_run_analysis_step1_prebuilt_obs.py |
run_analysis_step1 accepts the prebuilt_obs_simu_defaut kwarg with default=None (signature contract for Co-Study4Grid's introspection). |
frontend/src/utils/sessionUtils.test.ts |
All six timing fields are persisted; missing fields written as null (stable JSON shape); null overflow_graph_time round-trips. |
frontend/src/hooks/useSession.test.ts |
All six fields are restored onto the live AnalysisResult; legacy sessions without timings restore as undefined; null overflow_graph_time round-trips. |
frontend/src/components/ActionFeed.test.tsx |
The "in <X>s ⓘ" line shows the wall-clock total; tooltip lists every reported stage; hides when no timings; the overflow line is omitted when overflowGraphTime is null. |
frontend/src/hooks/useOverflowIframe.test.tsx |
cs4g:overflow-meta is broadcast to the iframe whenever overflowGraphTime changes (after the handshake). |
expert_backend/tests/test_overflow_overlay.py |
The injected iframe overlay exposes renderOverflowMeta + the cs4g:overflow-meta listener and the #cs4g-overflow-meta DOM hook with its "Total execution time:" label. |
Related docs
- Backend overview (this folder).
- Save Results — session JSON shape
including
analysis.active_modelandconfiguration.model. - Interaction Logging —
config_loaded/settings_applieddetails include the recommender selection. - Library-side contract:
marota/expert_op4grid_recommender— docs/recommender_models.md. - Performance history (overflow-graph caching, NAD prefetch, SVG DOM
recycling):
docs/performance/history/.