Co-Study4Grid / CLAUDE.md
github-actions[bot]
Deploy 7688ef1
13d4e44
|
Raw
History Blame Contribute Delete
45.5 kB

CLAUDE.md - Co-Study4Grid

Project Overview

Co-Study4Grid is a full-stack web application for power grid contingency analysis and N-1 planning. It provides an interface to the expert_op4grid_recommender library, allowing operators to simulate element disconnections, visualize network overflow graphs, and receive prioritized remedial action recommendations.

Architecture

Monorepo with two main components plus a standalone HTML mirror:

Co-Study4Grid/
├── CLAUDE.md                  # This file — project overview + standalone parity audit
├── README.md                  # User-facing project description + quick start
├── CHANGELOG.md               # Per-release changelog (current: 0.9.0)
├── CONTRIBUTING.md            # Contributor setup, code-quality gate
├── pyproject.toml             # Python project metadata + ruff config (E9/F ruleset)
├── pytest.ini                 # Pytest config (testpaths = expert_backend/tests)
├── expert_backend/            # Python FastAPI backend
│   ├── CLAUDE.md              # Backend-scoped guide (singletons, mixins, lifecycle)
│   ├── main.py                # FastAPI app: endpoints, CORS, gzip helpers, NDJSON streaming
│   ├── test_backend.py        # Ad-hoc integration script (not part of pytest)
│   ├── recommenders/          # Pluggable recommendation-model registry: registry.py,
│   │   │                      # random_basic / random_overflow canonical examples,
│   │   │                      # overflow_path_filter + network_existence sampling
│   │   │                      # filters, synthetic_actions builders. Models are
│   │   │                      # registered at package import; the service integration
│   │   │                      # is EXPLICIT composition (RecommenderService inherits
│   │   │                      # ModelSelectionMixin; AnalysisMixin.run_analysis_step2
│   │   │                      # consumes the registry — no import-time patching).
│   │   │                      # Full reference: docs/backend/recommender_models.md
│   ├── services/
│   │   ├── network_service.py     # pypowsybl Network singleton + metadata queries
│   │   ├── recommender_service.py # Analysis orchestrator (composes the 4 mixins below)
│   │   ├── diagram_mixin.py       # NAD/SLD orchestrator — delegates to services/diagram/
│   │   ├── analysis_mixin.py      # Two-step analysis orchestrator (model-aware step-2
│   │   │                          # dispatch through the recommenders registry) —
│   │   │                          # delegates to services/analysis/
│   │   ├── simulation_mixin.py    # Manual-action + superposition orchestrator
│   │   ├── model_selection_mixin.py # Active recommender model + overflow-graph toggle
│   │   ├── simulation_helpers.py  # Stateless helpers extracted from simulation_mixin (PR #104)
│   │   ├── overflow_overlay.py    # Pin / filter overlay injector for the interactive
│   │   │                          # HTML overflow viewer (PR #116, 0.7.0)
│   │   ├── sanitize.py            # NumPy → native-Python recursive coercion
│   │   ├── analysis/              # PR #104 decomposition — action_enrichment,
│   │   │                          # mw_start_scoring, analysis_runner, pdf_watcher,
│   │   │                          # overflow_geo_transform (PR #116 — geo-layout SVG
│   │   │                          # transform for /api/regenerate-overflow-graph)
│   │   └── diagram/               # PR #104 decomposition — layout_cache, nad_params,
│   │                              # nad_render, sld_render, overloads, flows, deltas,
│   │                              # obs_prewarm (post-contingency obs cache prewarm
│   │                              # that lets run_analysis_step1 skip the LF),
│   │                              # action_patch (extracted action-variant patch
│   │                              # pipeline — keeps diagram_mixin under the LoC
│   │                              # ceiling)
│   └── tests/                 # pytest suite — see tests/CLAUDE.md for the mock layer
├── frontend/                  # React 19 + TypeScript 5.9 + Vite 7 frontend
│   ├── CLAUDE.md              # Frontend-scoped guide (App.tsx hub, hooks, SVG levers)
│   ├── package.json, vite.config.ts, vite.config.standalone.ts,
│   │                          # eslint.config.js, tsconfig*.json
│   └── src/
│       ├── App.tsx                # State orchestration hub (~1400 lines)
│       ├── api.ts                 # Axios HTTP client (base URL: 127.0.0.1:8000)
│       ├── types.ts               # All TypeScript interfaces (one file)
│       ├── styles/                # Design-token palette (PR #120, 0.7.0):
│       │                          # tokens.css (CSS custom properties) +
│       │                          # tokens.ts (typed colors / space / text /
│       │                          # radius / pinColors* constants). Single
│       │                          # source of truth for the entire UI; the
│       │                          # code-quality gate enforces zero hex
│       │                          # literals outside these two files.
│       ├── hooks/                 # useSettings / useActions / useAnalysis / useDiagrams /
│       │                          # useSession / useDetachedTabs / useTiedTabsSync /
│       │                          # usePanZoom / useSldOverlay / useContingencyFetch (svgPatch fast-
│       │                          # path + full fallback) / useDiagramHighlights (per-tab
│       │                          # highlight pipeline + Flow/Impacts view-mode state) /
│       │                          # useOverflowIframe (PR #116 — iframe lifecycle, layer
│       │                          # toggles, postMessage bridge, pin overlay payload) /
│       │                          # useSldTopologyEdit (interactive SLD switch-edit →
│       │                          # manual action, 0.8.0) / useTheme (light/dark theme
│       │                          # toggle + persistence, 0.8.0)
│       ├── components/            # Header, ActionFeed, ActionCard, ActionCardPopover,
│       │                          # ActionSearchDropdown (editable Δ MW column
│       │                          # for redispatch in score table),
│       │                          # ActionTypeFilterChips,
│       │                          # ActionFilterRings (shared severity + action-type +
│       │                          # Max-loading ring strip, 0.8.0), ActionTypeIcon /
│       │                          # SeverityIcon (action-type + severity pictograms),
│       │                          # AdditionalLinesPicker,
│       │                          # ActionOverviewDiagram, AppSidebar (collapsible
│       │                          # shell — readability-feed PR), SidebarSummary
│       │                          # (sticky strip — hosts Clear button + overload
│       │                          # info bubble that absorbed the legacy
│       │                          # OverloadPanel affordances),
│       │                          # NotificationHost (typed toast store —
│       │                          # severity/dismiss/aria-live, PR D5),
│       │                          # VisualizationPanel, OverloadPanel
│       │                          # (kept on disk for unit-test backwards-compat;
│       │                          # no longer rendered from App.tsx),
│       │                          # CombinedActionsModal, ComputedPairsTable,
│       │                          # ExplorePairsTab, SldOverlay, SldEditPanel
│       │                          # (interactive maneuver list, 0.8.0),
│       │                          # SldInjectionPopover (SLD load/gen active-
│       │                          # power editor bubble), DetachableTabHost,
│       │                          # MemoizedSvgContainer, ErrorBoundary, NoticesPanel
│       │                          # (PR #122 tier system), DiagramLegend (PR #122),
│       │                          # InspectSearchField + DetachedPlaceholder (PR #116
│       │                          # — extracted from VisualizationPanel)
│       │                          # + modals/ (SettingsModal, ReloadSessionModal,
│       │                          #            ConfirmationDialog)
│       ├── game/                  # Timed, scored Game Mode (0.8.0; active only
│       │                          # with ?game=1) — GameShell / useGameSession /
│       │                          # gameBridge / GameConfigScreen / GameHud /
│       │                          # GameResults / scoring / gameLog / presets /
│       │                          # types. See docs/features/game-mode-codabench.md
│       └── utils/                 # svgUtils (barrel re-exporting utils/svg/*),
│                                  # svgPatch (DOM-recycling patch applier),
│                                  # overloadHighlights, sessionUtils, interactionLogger,
│                                  # popoverPlacement, mergeAnalysisResult, actionTypes
│                                  # (classifyActionType + DEFAULT_ACTION_OVERVIEW_FILTERS),
│                                  # inspectables (filterInspectables — match an element
│                                  # by its displayed name, not just its raw id),
│                                  # ndjsonStream (single NDJSON reader, D5),
│                                  # notifications (typed toast store, D5),
│                                  # fileRegistry (structure regression guard)
│           └── svg/               # PR #104 decomposition — idMap, metadataIndex,
│                                  # svgBoost, fitRect, deltaVisuals, actionPinData,
│                                  # actionPinRender, highlights, vlInteractions (VL-disk
│                                  # hover name / click-to-inspect / dbl-click SLD),
│                                  # overflowPinPayload
│                                  # + overflowOverlayRender + pinGlyph (PR #116 —
│                                  # iframe-overlay pin pipeline)
├── standalone_interface_legacy.html  # DECOMMISSIONED 2026-04-20 — hand-maintained
│                              # single-file mirror frozen at its last version and
│                              # tracked here for reference only. Replaced by the
│                              # auto-generated `frontend/dist-standalone/standalone.html`
│                              # (`npm run build:standalone`). New UI changes land ONLY
│                              # in `frontend/src/` — do NOT edit this file further.
├── docs/                      # Design docs — organized into features/, performance/
│                              # (+ history/), architecture/, proposals/, data/.
│                              # See `docs/README.md` for the index.
├── data/                      # Sample grids: bare_env_small_grid_test, pypsa_eur_fr400,
│                              # pypsa_eur_fr225_400 (France 225/400 kV — its large
│                              # network ships compressed as network.xiidm.zip,
│                              # auto-decompressed by network_service on load)
├── benchmarks/                # Perf scripts (bench_load_study, _bench_common)
├── Overflow_Graph/            # Generated PDFs (created at runtime)
├── overrides.txt              # Pinned versions for transitive Python deps
├── requirements_py310.txt     # Python 3.10-pinned requirements superset
├── scripts/                   # Integration / parity / build helpers —
│                              # `check_standalone_parity.py`,
│                              # `check_session_fidelity.py`,
│                              # `check_gesture_sequence.py`,
│                              # `check_invariants.py`, `check_code_quality.py`,
│                              # `check_openapi_contract.py` (D2 — diffs the live
│                              # app.openapi() against expert_backend/openapi.snapshot.json),
│                              # `code_quality_report.py`, `profile_diagram_perf.py`,
│                              # `test_code_quality_report.py`,
│                              # `test_estimation_vs_simulation_small_grid.py`,
│                              # `pypsa_eur/` (full PyPSA-EUR → XIIDM pipeline
│                              # with its own pytest coverage; `build_pipeline.py`
│                              # writes a per-bundle provenance.json, D8), and
│                              # `game_mode/` (`e2e_game_session.py` — real-backend
│                              # Game Mode replay; `scoring_program/score.py` —
│                              # in-repo Codabench scorer twin of scoring.ts,
│                              # pinned to `scoring_golden.json` by test_score.py
│                              # + scoring.test.ts for cross-language parity, D8)
├── Dockerfile                 # Single-container HuggingFace Docker Space image —
│                              # same-origin SPA + FastAPI on :7860, game mode on
├── .dockerignore
├── deploy/                    # HuggingFace Space README + step-by-step SETUP.md
├── config.default.json        # Bundled first-run settings (fr225_400 grid +
│                              # per-action-type recommender minima)
├── .editorconfig              # Cross-editor indent / EOL defaults
├── .env.example               # Template for backend env vars (CORS, …)
├── .gitattributes             # Git LFS tracking for *.zip / *.png / *.jpg(eg)
│                              # (HuggingFace Space git endpoint requires LFS)
└── .gitignore                 # Excludes __pycache__/, *.pyc, *.pyo, node_modules/

Per-subtree docs

File Scope
CLAUDE.md (this file) Project overview, API table, conventions, parity-audit pointer
frontend/PARITY_AUDIT.md Full standalone-parity audit: feature inventory, mirror-status table, Layer 1–4 conformity findings, gap-priority list, deltas. Split out of this file 2026-04-20.
expert_backend/CLAUDE.md Backend internals: singletons, mixin composition, state lifecycle, NDJSON streaming, gzip helpers, layout cache invariants, NAD prefetch
expert_backend/tests/CLAUDE.md Test conventions, the conftest.py mock layer for pypowsybl / expert_op4grid_recommender, frontend Vitest patterns
frontend/CLAUDE.md Frontend internals: hook split, data flow, state reset, SVG performance levers, detached/tied tabs, interaction logger contract
docs/README.md Index of design/feature/perf/architecture/proposal docs. Start here for any docs/** lookup.
docs/features/save-results.md Save / reload session contract (JSON schema, reload flow, regression-guard matrix)
docs/features/adding-action-type.md Cross-cutting checklist for adding/upgrading a remedial-action type (lib → backend → frontend → save/log/reload triad → regression specs)
docs/features/interaction-logging.md Replay-ready event log contract
docs/features/sld-topology-edit.md Interactive SLD topology edit → manual action card
docs/features/sld-diagram-feeder-labels.md SLD feeders relabelled by far-end VL name (+ parallel index), overload-halo friendly-name↔IIDM-id bridge, and the charging-current annotation explaining the "after" loading of a line opened at one end
docs/features/vl-disk-interactions.md Interactive VL disks on the NAD (hover name / click → Inspect / double-click → SLD) + delegation performance contract
docs/features/game-mode-codabench.md Timed, scored Game Mode (?game=1) + Codabench benchmark bundle
deploy/huggingface/ HuggingFace Docker Space deployment (Space README + SETUP.md)

Tech Stack

Backend

  • Python with FastAPI + Uvicorn
  • pypowsybl - Power system network loading, load flow, and diagram generation
  • expert_op4grid_recommender - Domain-specific grid optimization recommendations
  • grid2op / pandapower / lightsim2grid - Grid simulation backends

Frontend

  • React 19 with TypeScript 5.9
  • Vite 7 - Build tool and dev server
  • axios - HTTP client
  • react-select - Searchable dropdown for branch selection
  • vite-plugin-singlefile - Auto-generated single-file standalone bundle
  • Vitest + React Testing Library - Unit / integration tests

Development Workflow

Running the Backend

# From the project root:
python -m expert_backend.main
# Or:
uvicorn expert_backend.main:app --host 0.0.0.0 --port 8000

The backend serves on http://localhost:8000. It expects pypowsybl and expert_op4grid_recommender to be available in the Python environment.

Running the Frontend

cd frontend
npm install
npm run dev      # Start Vite dev server with HMR

The frontend dev server proxies API calls to http://localhost:8000 (hardcoded in frontend/src/api.ts).

Build & Lint

cd frontend
npm run build    # TypeScript compilation (tsc -b) + Vite production build
npm run lint     # ESLint
npm run preview  # Preview production build

Running Tests

Backend unit tests use pytest and run against the in-repo mock layer (no live pypowsybl required):

pytest                                   # Full backend suite
pytest expert_backend/tests/test_foo.py  # Single file

Ad-hoc integration scripts live in scripts/ (and scripts/pypsa_eur/ for the PyPSA-EUR → XIIDM pipeline). The pipeline scripts carry their own pytest coverage (scripts/pypsa_eur/test_*.py) alongside the backend suite; the rest require a running backend with real data:

pytest scripts/pypsa_eur                           # Pipeline unit tests
python scripts/pypsa_eur/test_pipeline.py          # End-to-end smoke test
python scripts/pypsa_eur/test_n1_calibration.py    # N-1 flow calibration check
python scripts/pypsa_eur/test_grid_layout.py       # Layout loading sanity check
python scripts/profile_diagram_perf.py             # NAD rendering profiler

Frontend unit tests use Vitest:

cd frontend
npm run test         # Run Vitest test suite

Code-Quality Checks (continuous reporting)

# Generate a full JSON + Markdown report (backend + frontend metrics)
python scripts/code_quality_report.py --output reports/code-quality.json \
                                      --markdown reports/code-quality.md

# Gate a pull request: non-zero exit on threshold violation
python scripts/check_code_quality.py

Both scripts run in CI (.github/workflows/code-quality.yml). The gate guards the reductions documented in docs/architecture/code-quality-analysis.md (no new print() / bare except, module-size ceilings, no any / @ts-ignore in frontend source).

API Endpoints

Method Path Description
GET /api/user-config Read persisted user configuration (paths, recommender params)
POST /api/user-config Persist user configuration
GET /api/config-file-path Get the current user-config file path
POST /api/config-file-path Set a custom user-config file path
POST /api/config Set network path, action file path, and all recommender parameters (incl. model and compute_overflow_graph)
GET /api/models List registered recommendation models with their params_spec() and capability flags
POST /api/recommender-model Lightweight swap of the active recommender model (no network reload) — fired by the model dropdowns in Settings and above Analyze & Suggest
GET /api/branches List disconnectable elements (lines + 2-winding transformers)
GET /api/voltage-levels List voltage levels in the network
GET /api/nominal-voltages Map voltage level IDs to nominal voltages (kV)
GET /api/element-voltage-levels Resolve equipment ID to its voltage level IDs
GET /api/voltage-level-substations Map voltage level IDs to their parent substation IDs (used by the SLD overlay and overflow pin pipeline)
POST /api/run-analysis Run full N-1 contingency analysis (streaming NDJSON, legacy)
POST /api/run-analysis-step1 Two-step analysis Part 1: detect overloads
POST /api/run-analysis-step2 Two-step analysis Part 2: resolve with actions (streaming NDJSON)
GET /api/network-diagram Get N-state network SVG diagram (NAD)
POST /api/contingency-diagram Get post-contingency N-1 diagram with flow deltas
POST /api/contingency-diagram-patch SVG-less per-branch delta for DOM-recycling fast path (PR #108)
POST /api/action-variant-diagram Get network state after applying a remedial action
POST /api/action-variant-diagram-patch Per-branch delta + VL-subtree splice for action DOM recycling
POST /api/focused-diagram Generate NAD sub-diagram focused on a specific element
POST /api/action-variant-focused-diagram Focused NAD for specific VL in post-action state
POST /api/n-sld Single Line Diagram for voltage level in N state. Response includes switch_states (per-switch open/closed map), injections (per-load/generator active-power baseline) used by the interactive SLD-edit feature, and feeder_labels (per-branch {name, other_vl, label}label = far-end VL name + parallel index, used to relabel feeders and bridge friendly-named overloads to the SLD cell).
POST /api/contingency-sld Single Line Diagram in N-1 state (with flow deltas + switch_states + injections + feeder_labels).
POST /api/action-variant-sld SLD in post-action state (with flow deltas, changed_switches, switch_states, injections, feeder_labels).
POST /api/sld-topology-preview Target-topology preview SLD for the interactive SLD-edit feature: applies staged switch overrides on a throwaway variant and re-renders with topological colouring (no load flow; stale_flows: true).
GET /api/actions Return all available action IDs and descriptions
POST /api/regenerate-overflow-graph Regenerate (or serve from cache) the overflow graph in hierarchical / geo layout — drives the toggle on the Overflow Analysis tab
POST /api/simulate-manual-action Simulate a specific action against a contingency. Accepts an optional voltage_level_id field used to auto-name switch-only user actions (interactive SLD-edit feature).
POST /api/simulate-and-variant-diagram NDJSON stream: {type:"metrics"} then {type:"diagram"} so sidebar updates ahead of the SVG
POST /api/compute-superposition Compute combined effect of two actions (superposition theorem)
POST /api/save-session Save session folder with JSON snapshot + PDF copy
GET /api/list-sessions List available session folders in a directory
POST /api/load-session Load session JSON and restore PDFs
POST /api/restore-analysis-context Restore analysis context from saved session
GET /api/pick-path Open native OS file/directory picker (tkinter subprocess)
GET /results/pdf/{filename} Serve generated overflow-graph files from Overflow_Graph/ — HTML (interactive viewer, current default via config.VISUALIZATION_FORMAT="html") or PDF (legacy sessions). URL path kept for backward compatibility.

Key Patterns & Conventions

Backend

  • Singleton services: network_service and recommender_service are module-level singleton instances
  • Streaming responses: Analysis uses StreamingResponse with NDJSON (application/x-ndjson), yielding {"type": "pdf", ...} then {"type": "result", ...} events
  • AC/DC fallback: Analysis first tries AC load flow; falls back to DC if AC does not converge
  • Threaded analysis: run_analysis runs the computation in a background thread and polls for PDF generation
  • JSON sanitization: NumPy types are recursively converted to native Python types via sanitize_for_json()
  • Unified error contract (D2, 2026-07): every error is {"detail", "code"} produced in one place (services/api_errors.py, install_error_handlers(app)). Raise a plain HTTPException (code derived from status: 400/404/409/422/500) or AppHTTPException(status, detail, code) when the frontend branches on the failure (e.g. ACTION_RESULT_UNAVAILABLE, STUDY_BUSY). Uncaught exceptions → generic logged 500 (never str(e) — it leaks paths). The frontend reads it via frontend/src/utils/apiError.ts.
  • Machine-checked API contract (D2, 2026-07): app.openapi() is snapshotted to expert_backend/openapi.snapshot.json and diffed in CI (scripts/check_openapi_contract.py, test_openapi_contract.py). Regenerate on any deliberate endpoint / model / status change: python scripts/check_openapi_contract.py --write.
  • Concurrency ownership (D3, 2026-07): a service-level re-entrant lock (services/service_lock.py, @with_network_lock[_stream]) serializes the variant-switching entry points on the shared Network; overlapping study mutations (config load / analysis) get HTTP 409. See docs/architecture/shared-network-concurrency.md.
  • Mixin → helper-package decomposition (PR #104 / #106): DiagramMixin, AnalysisMixin and SimulationMixin are thin orchestrators. Pure numerics live in services/diagram/, services/analysis/ and services/simulation_helpers.py respectively — dependency-injected so existing @patch tests keep working.
  • SVG DOM recycling (PR #108): patch endpoints (/api/contingency-diagram-patch, /api/action-variant-diagram-patch) return per-branch deltas + optional VL-subtree splices so the frontend can clone the already-mounted N-state SVG instead of re-downloading the full NAD (~80 % faster tab switches on large grids).
  • Interactive overflow viewer (PR #116, 0.7.0): services/overflow_overlay.py injects a Co-Study4Grid pin / filter overlay (<style> + <script> block) into the upstream expert_op4grid_recommender HTML viewer before serving it from /results/pdf/{filename}. services/analysis/overflow_geo_transform.py is a pure lxml transform that rewrites the hierarchical-layout SVG to geographic coordinates for the /api/regenerate-overflow-graph toggle; the geo cache is per-study and cleared on reset().
  • Shared diagram helpers: RecommenderService uses _load_network(), _load_layout(), _default_nad_parameters(), and _generate_diagram() to deduplicate diagram generation logic across endpoints
  • Focused diagrams: The /api/focused-diagram endpoint resolves an element to its voltage levels and generates a sub-diagram with configurable depth, useful for inspecting specific parts of large grids
  • Ruff-gated: pyproject.toml configures a narrow E9 + F ruleset (real bugs only); stylistic rules deliberately off

Frontend

  • Strict TypeScript: strict: true, noUnusedLocals, noUnusedParameters, noFallthroughCasesInSwitch
  • Functional components with React hooks; no external state management library
  • Inline styles: Components use inline style objects rather than CSS modules or utility classes
  • Design tokens (PR #120, 0.7.0): every colour / spacing / typography / radius value lives in frontend/src/styles/tokens.{css,ts}. Inline style objects import the typed colors / space / text / radius constants from tokens.ts; stylesheet rules use var(--…) from tokens.css. Raw SVG attribute setters (element.setAttribute('fill', …)) import the hex-valued pinColors / pinChrome constants because browsers don't reliably resolve var(--…) inside SVG presentation attributes. The code-quality gate enforces zero hex literals outside the two token files.
  • Light / dark theme (0.8.0): theming is a token swap, not per-component overrides — the useTheme hook flips a theme attribute that re-points the tokens.css custom properties, with a tiny pre-mount script to avoid the first-paint flash. A legibility pass covers the pypowsybl NAD / SLD chrome and the injected overflow-viewer overlay. See docs/features/dark-mode.md.
  • Component architecture (Phase 2 hook extraction, PR #109):
    • App.tsx (~1400 lines) is the state orchestration hub — it wires all hooks together and handles cross-hook logic (e.g., handleApplySettings). It should NOT contain large JSX blocks.
    • Presentational components live in components/ and components/modals/. They receive data and callbacks via typed props; all business logic stays in App.tsx or in hooks.
    • hooks/useContingencyFetch.ts owns the N-1 diagram fetch pipeline (svgPatch fast-path + /api/contingency-diagram fallback + contingency-change confirm routing).
    • hooks/useDiagramHighlights.ts owns the per-tab SVG highlight pipeline (overload halos, contingency highlight, action targets, delta visuals) + per-tab Flow/Impacts view-mode state.
    • hooks/useOverflowIframe.ts (PR #116, 0.7.0) owns the interactive overflow viewer — iframe lifecycle, layer-toggle state, hierarchical ↔ geo layout switch, postMessage bridge to the host, and the action-pin overlay payload computation.
    • hooks/useSldTopologyEdit.ts (0.8.0) owns the interactive SLD edit flow — editMode (implicit while the SLD is open — no toggle button; read-only on close), staged pendingStates (switch toggles) + pendingInjections (load / generator active-power retunes), toggle / removeSwitch(es) / setInjection / removeInjection / focusedSwitchId — that turns clicked breakers AND injection retunes into one manual action card (SldInjectionPopover is the active-power editor bubble). See docs/features/sld-topology-edit.md.
    • useSettings.ts exposes SettingsState (all settings values + setters), which is passed wholesale to SettingsModal to avoid 30+ prop-drilling.
  • SVG DOM recycling (PR #108): utils/svgPatch.ts clones the already-mounted N-state SVGSVGElement and patches only per-branch deltas on N-1 / action tab switches, saving a 12–28 MB SVG re-download and re-parse.
  • Props-based data flow: State lifted to App.tsx, passed down via props
  • ESLint: Flat config (v9+) with typescript-eslint, react-hooks, and react-refresh plugins
  • Unit tests use Vitest + React Testing Library. Isolated component tests (no backend mocking needed) live alongside their components as *.test.tsx files.

Data Flow

  1. User sets network path + action file path -> POST /api/config loads the network
  2. Frontend fetches disconnectable branches -> GET /api/branches
  3. User selects a contingency branch -> N-1 diagram fetched with overload highlighting
  4. User runs analysis (two-step flow):
    • Step 1: POST /api/run-analysis-step1 detects overloads in N-1 state
    • User selects which overloads to resolve
    • Step 2: POST /api/run-analysis-step2 streams PDF event + action results
  5. Frontend displays overflow PDF and action cards in ActionFeed panel
  6. User can star/reject actions, manually simulate others, compute combined pairs
  7. Action selection triggers POST /api/action-variant-diagram -> post-action diagram
  8. Session save captures full snapshot to session.json + overflow PDF copy

Session Save/Load

  • Save: buildSessionResult() in sessionUtils.ts serializes all state (config, contingency, actions with status tags, combined pairs) -> POST /api/save-session writes to disk
  • Load: POST /api/load-session reads session.json -> frontend restores all state without re-simulating actions
  • Output folder: <output_folder>/costudy4grid_session_<contingency>_<timestamp>/ contains session.json + overflow PDF
  • Interaction logging: Every user interaction is logged as a timestamped, replay-ready event via interactionLogger. Saved as interaction_log.json alongside session.json.
  • See docs/features/save-results.md for session save/load, docs/features/interaction-logging.md for the replay contract, and docs/features/action-overview-diagram.md for the Remedial Action overview (pin overlay on N-1 network)

SVG Visualization

Both the React frontend and the auto-generated frontend/dist-standalone/standalone.html render pypowsybl NAD/SLD payloads:

  • Dynamic text scaling (utils/svgUtils.ts:boostSvgForLargeGrid / standalone boostSvgForLargeGrid): font sizes for node labels, edge info, and legends scale proportionally to diagram size via sqrt(diagramSize / referenceSize), so text is readable when zoomed in and naturally invisible at full zoom-out. Engaged for grids ≥ 500 voltage levels.
  • Bus / transformer scaling: circle radii for bus nodes and transformer windings are boosted proportionally.
  • Edge-info scaling: flow values and arrow glyphs are scaled via transform groups so they remain proportional to the line on which they sit.
  • ViewBox zoom: auto-centers on selected contingency targets with adjustable padding.
  • Pan/zoom: the usePanZoom hook writes the SVG viewBox directly (rAF-batched, cached CTM) — no pan/zoom library — in both the React dev build and the auto-generated standalone (they share the same source tree).

Dependencies

Backend (pyproject.toml + overrides.txt)

  • fastapi, uvicorn, python-multipart
  • pypowsybl, expert_op4grid_recommender (expected in venv)
  • pandas>=2.2.2, numpy>=2.0.0, grid2op>=1.12.2, pandapower>=2.14.0
  • lightsim2grid>=0.12.0, matplotlib>=3.10.6, scipy>=1.16.0
  • lxml>=6.0.0, contourpy>=1.2.0, tqdm>=4.65.0

Frontend (frontend/package.json)

  • See dependencies and devDependencies in frontend/package.json

File Conventions

  • Network data files: .xiidm format (loaded by pypowsybl)
  • Action definitions: .json files with action IDs mapping to descriptions
  • Generated outputs: PDF files in Overflow_Graph/ directory
  • Network layouts: grid_layout.json (node ID -> [x, y] coordinates)

Notes for AI Assistants

  • The backend API base URL defaults to http://127.0.0.1:8000 in frontend/src/api.ts (API_BASE_URL), overridable at build time via VITE_API_BASE_URL — set it to "" for same-origin hosting where the backend serves the SPA (the HuggingFace Docker Space), so requests become relative /api/...
  • CORS defaults to the local Vite dev/preview origins on loopback (localhost/127.0.0.1 on :5173 / :4173); a wildcard is explicit opt-in and any other set is configurable via the CORS_ALLOWED_ORIGINS env var (see .env.example)
  • Frontend architecture (Phase 2 hook extraction, PR #109): App.tsx is the state orchestration hub; it must NOT contain large inline JSX blocks. Extracted presentational components live in components/ and components/modals/; cross-cutting state pipelines live in hooks/ (notably useContingencyFetch and useDiagramHighlights). When adding new UI sections, create a new component file (or hook for stateful pipelines) and wire it in App.tsx.
  • useSettings hook: Exposes a SettingsState object with all settings fields + setters. This is passed wholesale to SettingsModal to avoid excessive prop drilling. Adding a new setting means: (1) add to useSettings.ts, (2) add to SettingsModal.tsx. No manual standalone mirror is required — the legacy hand-maintained file has been decommissioned and the auto-generated bundle inherits from the React source automatically.
  • Standalone bundle (auto-generated): npm run build:standalone in frontend/ produces frontend/dist-standalone/standalone.html — a single-file HTML with React + CSS inlined via vite-plugin-singlefile. This is the canonical distribution artifact replacing the former standalone_interface.html. The legacy file remains on disk as standalone_interface_legacy.html (tracked as a frozen snapshot — do NOT edit).
  • Online deployment (HuggingFace Docker Space, 0.8.0): Dockerfile builds the SPA with VITE_API_BASE_URL="" + VITE_GAME_MODE=1 and serves it same-origin with the FastAPI backend on port 7860. main.py optionally mounts the built SPA via COSTUDY4GRID_FRONTEND_DIST (mounted LAST, after every /api/* and /results/* route; inert when the dist is absent, so local dev is unaffected). One Space instance serves one player (module-level singletons). See deploy/huggingface/.
  • Deployment lockdown (D7, 2026-07): the Dockerfile sets COSTUDY4GRID_LOCKDOWN=1, which disables the desktop-era filesystem RPCs (custom config-file path, session save/list/load, native file picker) with a 403 {code: LOCKED_DOWN} — they assume a local operator and would otherwise expose the container filesystem to an anonymous Space visitor. The read-only app config stays available so the SPA boots; unset locally so dev is unaffected. The HF deploy is test-gated (workflow_run on a green Tests run) and tags each deploy on origin (space-deploy-*) as the rollback pointer. See docs/architecture/deployment-trust.md.
  • Game Mode (0.8.0): a timed, scored session shell in frontend/src/game/, additive and inert unless ?game=1main.tsx mounts GameShell instead of App; App integration is three gameBridge.isGameMode()-guarded touch points. See docs/features/game-mode-codabench.md.
  • Binary assets via Git LFS + transparent network decompression (0.8.0): .gitattributes tracks *.zip / *.png / *.jpg via Git LFS (the HuggingFace Space git endpoint rejects non-LFS binaries); the large France 225/400 kV grid ships as network.xiidm.zip and network_service._resolve_network_file / _extract_network_zip decompress it transparently on load.
  • CI pipelines: GitHub Actions only (.github/workflows/: code-quality.yml, parity.yml, test.yml, canary.yml, deploy-huggingface.yml) run the code-quality gate, ruff, the pytest + Vitest suites, the data-pipeline + scorer-parity slice, and the parity scripts. CircleCI was removed (QW23 — GitHub Actions is a strict superset). The backend test + the Dockerfile install expert_op4grid_recommender from recommender-pin.txt — one pinned, exact version (QW8) so an upstream release can't silently break a PR or a rebuild; the weekly canary.yml floats to the latest release and flags regressions, so upgrades are a deliberate bump of that file.
  • Root .gitignore excludes __pycache__/, *.pyc, *.pyo; frontend/.gitignore handles frontend build artifacts
  • Integration helpers and parity scripts live under scripts/. They are NOT part of the pytest suite — invoke them directly. The PyPSA-EUR pipeline scripts under scripts/pypsa_eur/ DO carry pytest coverage (test_build_pipeline.py, test_calibrate_thermal_limits.py, test_generate_n1_overloads.py, test_regenerate_grid_layout.py).
  • overrides.txt contains pinned versions for transitive Python dependencies that need to be forced to specific versions
  • Frontend unit tests use Vitest + React Testing Library. Isolated component tests live as *.test.tsx files next to their component. Run with cd frontend && npm run test. No backend mocking is needed for component tests since they only use mocked props.
  • The two-step analysis flow (step1: detect overloads, step2: resolve) is the primary user workflow; the single-step /api/run-analysis is a legacy alternative
  • Session save/load is documented in docs/features/save-results.md
  • grid_layout.json coordinate scale (2026-05-08): the on-disk layout MUST be in raw Mercator metres (span ≈ 1.4–1.6 M for the French grid). pypowsybl emits VL outer circles at a fixed r = 27.5 user-space units, so any layout squashed below ~500 000 units forces overlap on dense regions (Paris/Lyon). scripts/pypsa_eur/regenerate_grid_layout.py defaults to raw metres; the legacy --target-width 8000 flag is preserved but warns. Full rationale + operator-vs-PyPSA comparison in docs/data/grid-layout-coordinate-scale.md.

Contributing & Pull Requests

  • Upstream is ainetus; marota is the working fork. Development branches are pushed to marota/Co-Study4Grid, but pull requests are opened directly against the upstream ainetus/Co-Study4Grid (base = its default branch, head = marota:<branch>) — not against marota. The sibling library Expert_op4grid_recommender follows the same rule against ainetus/Expert_op4grid_recommender.

  • Load ainetus as an initial source. A cross-fork PR into ainetus can only be created from a session/tool context that has the ainetus repo in scope, so a new working session should be started with ainetus/Co-Study4Grid and ainetus/Expert_op4grid_recommender as the initial sources (they should always be auto-loaded). A session rooted only at marota cannot target ainetus (cross-tier adds are blocked) and the PR step fails with an access-denied error.

  • Sync marota with ainetus before starting new work. PRs merge into ainetus/main, but development happens on marota, so marota/main drifts behind ainetus/main after every merged PR (this applies to both repos — Co-Study4Grid and Expert_op4grid_recommender). At the start of a dev session, bring marota/main up to date with ainetus/main — GitHub "Sync fork", or locally git fetch ainetus main && git merge --ff-only ainetus/main then push marota/main — and branch from there. Skipping this makes a new branch collide with the already-merged revisions when it is PR'd into ainetus. If the sync was missed and the PR already shows conflicts, merge ainetus/main into the branch (or rebase onto it) and resolve, then force-with-lease push.

  • DCO sign-off is required on every commit. The ainetus repos enforce the Developer Certificate of Origin: every commit must carry a Signed-off-by: <Name> <amarot91@gmail.com> trailer, and because the DCO check matches the sign-off against the commit author, the commit must also be authored under that same identity (author email = amarot91@gmail.com):

    git config user.name  "<Name>"
    git config user.email "amarot91@gmail.com"
    git commit -s -m "..."          # -s appends the Signed-off-by trailer
    

    To sign off commits already made under a different identity, re-author and add the trailer (git rebase --exec 'git commit --amend --no-edit --reset-author \ -s' <base>), then force-with-lease push.


Standalone Interface Parity Audit

The detailed audit — feature inventory, mirror-status table, Layer 1–4 conformity findings, regression-guard matrix, gap-priority list and delta-vs-previous commits — lives in frontend/PARITY_AUDIT.md. That document is the working record of the parity project and is updated as fixes land.

Quick status summary (2026-05-05):

  • Canonical distribution is now the auto-generated frontend/dist-standalone/standalone.html (npm run build:standalone). The hand-maintained standalone_interface.html has been decommissioned and renamed to standalone_interface_legacy.html — committed as a frozen snapshot of its last version (commit 5d2b9d1 content), do NOT edit further. Regenerate UI from frontend/src/ via npm run build:standalone instead. The standalone versioned snapshot was bumped to v0.7 on adae7ac to include references to the new /api/*-diagram-patch endpoints.
  • Four parity layers run against the React source + the standalone of choice:
    • Layer 1 — static parity (scripts/check_standalone_parity.py)
    • Layer 2 — session-reload fidelity (scripts/check_session_fidelity.py)
    • Layer 3a — gesture-sequence static proxy (scripts/check_gesture_sequence.py)
    • Layer 3b — behavioural E2E (scripts/parity_e2e/e2e_parity.spec.ts)
    • Layer 4 — user-observable invariants (scripts/check_invariants.py)
  • All parity scripts accept COSTUDY4GRID_STANDALONE_PATH to re-target any artifact; they default to the auto-gen bundle and fall back to the legacy file when the auto-gen is not built.

See frontend/PARITY_AUDIT.md for the full gap list, the session-fidelity regression record, the honest-gap report of what each layer catches and misses, and the 2026-04-20 delta that documents the /api/restore-analysis-context one-way API drift (now resolved) and the auto-generated-standalone viability confirmation.