Skip to content

Code-quality gate + decomposition sweep (5 modules) - #104

Merged
marota merged 7 commits into
mainfrom
claude/improve-code-quality-Uzrhu
Apr 21, 2026
Merged

marota merged 7 commits into
mainfrom
claude/improve-code-quality-Uzrhu

Conversation

@marota

@marota marota commented Apr 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds continuous code-quality reporting and a CI gate, then uses the gate's metrics to drive five rounds of decomposition on the largest files in the codebase. Every change is behaviour-preserving — public method signatures are unchanged, existing tests continue to pass, and zero new regressions were introduced (verified against a pre-refactor baseline after each pass).

Continuous quality tooling

Surface File Purpose
Reporter scripts/code_quality_report.py Offline AST scan → JSON + Markdown metrics (per-module LoC, longest functions, smell counts, any/@ts-ignore counts)
Gate scripts/check_code_quality.py CI gate. Exits non-zero on regression — 0 print(), 0 traceback.print_exc, 0 silent except, ≤1200 LoC/backend module, ≤1500 LoC/frontend component, ≤2000 LoC/frontend util, 0 any, 0 @ts-ignore
Reporter tests scripts/test_code_quality_report.py 7 unit tests on the AST smell walker
CI wiring .github/workflows/code-quality.yml + .circleci/config.yml Runs ruff, the gate, the reporter tests, generates reports, publishes Markdown to $GITHUB_STEP_SUMMARY, uploads as a 30-day artifact
Ruff config pyproject.toml ([tool.ruff]) Narrow ruleset (E9, F) for real bugs only — stylistic rules deliberately off
Layer-4 invariants scripts/check_invariants.py File-hint paths updated to point at the new extracted modules

Decomposition sweep — 5 passes

Every pass: extract pure helpers into a sibling package, slim the orchestrator to variant-handling + delegation, add focused unit tests, verify no regressions.

Pass 1 — simulate_manual_action + compute_superposition

Function Before After
simulate_manual_action 599 146
compute_superposition 285 108
  • New module expert_backend/services/simulation_helpers.py (14 stateless helpers: setpoint math, PST parsing, classification, care-mask, metrics, topology extraction, result serialisation, combined description, rho superposition)
  • 12 private methods on SimulationMixin for the stateful pieces (obs cache, action dict, target MW/tap updates, …)
  • 66 new unit tests in tests/test_simulation_helpers.py
  • Bug surfaced: RecommenderService._build_action_entry_from_topology self-reference (F821 NameError in simulation_mixin.py) — fixed by switching to self.*

Pass 2 — frontend/src/utils/svgUtils.ts

1807-line omnibus → 60-line barrel + 8 focused modules under frontend/src/utils/svg/:

Module Lines
idMap.ts 29
metadataIndex.ts 40
svgBoost.ts 122
fitRect.ts 125
deltaVisuals.ts 137
actionPinData.ts 344
highlights.ts 422
actionPinRender.ts 537
  • New public helpers promoted from inline closures: formatPinLabel, formatPinTitle, fanOutColocatedPins, curveMidpoint, computePinScale
  • svgUtils.ts re-exports everything — no caller changed
  • 61 new unit tests across 6 co-located *.test.ts files
  • Existing 144 svgUtils.test.ts + 1000-test frontend suite still green
  • Largest frontend file is now App.tsx (1370, exempt — state orchestration hub)
  • Updated scripts/check_invariants.py to point Layer-4 hints at the new modules (3 invariants: pin_severity_uses_monitoringFactor, combined_pairs_filter_estimated, pin_resolver_is_topology_first)

Pass 3 — analysis_mixin.py

1,116 → 509 lines. Split into 4 modules under expert_backend/services/analysis/:

Module Lines Role
pdf_watcher.py 43 Overflow PDF glob + mtime filter
analysis_runner.py 193 Legacy AC→DC worker + PDF-polling event stream
mw_start_scoring.py 341 MW-at-start dispatcher + per-type math (LS / curtail / disco / PST / open coupling)
action_enrichment.py 389 Load-shedding / curtailment / PST details, rho scaling, topology extraction
  • Dependency injection preserves @patch compatibility: get_virtual_line_flow and run_analysis are threaded into helpers as optional callables; the mixin reads them from its module namespace at call time so legacy @patch('expert_backend.services.analysis_mixin.*') tests keep working unchanged
  • Legacy instance-method wrappers (_compute_*_details, _mw_start_*, _is_renewable_gen, _get_pst_tap_start, _get_action_mw_start) kept as delegators
  • 68 new unit tests in tests/test_analysis_helpers.py

Pass 4 — diagram_mixin.py

974 → 469 lines. Split into 7 modules under expert_backend/services/diagram/:

Module Lines Role
sld_render.py 36 SLD SVG + metadata with fallbacks
nad_params.py 41 Perf-tuned NadParameters factory
layout_cache.py 63 (path, mtime)-keyed grid_layout.json loader
flows.py 67 Branch + asset flow extractors (vectorised)
nad_render.py 89 NAD generation + NaN element stripping
overloads.py 131 Overload filtering + element-currents
deltas.py 241 Terminal-aware flow-delta math (pure)
  • 6 new private orchestrator helpers: _require_action, _lf_status_for_variant, _snapshot_n1_state, _attach_flow_deltas_vs_base, _attach_convergence_from_obs, _diff_switches
  • Bug fixed: get_action_variant_sld had a hidden ordering dependency — changed_switches needed to be captured before flow extraction so mock networks with missing flows still returned the switch diff. The orchestrator now splits switch-diff and delta-math into independent try/except blocks
  • 39 new unit tests in tests/test_diagram_helpers.py

Pass 5 — quick wins & housekeeping

  • network_service.py::get_load_voltage_levels_bulk — was incomplete (returned {} without populating the dict). Fixed to mirror get_generator_types_bulk.
  • main.py — CORS origins configurable via CORS_ALLOWED_ORIGINS env var; unused GZipMiddleware import removed; 2 print() → logger.warning(); 12 traceback.print_exc() → logger.exception(); 1 bare except: pass → logged suppression.
  • sanitize.py — added Any → Any type hints.
  • SettingsModal.tsx — empty .catch(() => {}) → .catch(err => console.error(...)).
  • Frontend deps — removed unused framer-motion + lucide-react from package.json.
  • 9 auto-fixed f-string placeholders via ruff --fix.

Added / refreshed surfaces

  • CONTRIBUTING.md — setup, test commands, quality-gate thresholds, commit conventions
  • .env.example — backend env var template (CORS, fast-mode, log level)
  • .editorconfig — 4-space Python, 2-space TS/JSON, LF endings
  • .gitignore — reports/, .env
  • CLAUDE.md (root) — refreshed (stale test_*.py references removed, scripts/ section added, quality-gate section added)
  • docs/architecture/code-quality-analysis.md — 5 delta sections (§9–§13), updated §6 metrics, §6b continuous-quality tooling, §10–§13 per-decomposition deltas

Metrics — before vs after

Backend (expert_backend/)
  Source files (non-test):     8    → 20  (5 new packages)
  Total lines:                 ~5100 → ~5500  (helpers + tests, orchestrators shrunk)
  Largest module:              1116 → 741  (recommender_service.py)
  print() calls in source:     2 → 0
  traceback.print_exc() calls: 12 → 0
  Bare except: pass:           1 → 0

Frontend (frontend/src/)
  Largest non-exempt file:     1807 → 1370  (App.tsx, state hub — exempt)
  any types:                   0 → 0
  @ts-ignore:                  0 → 0

Top-5 longest backend functions
  before                           after
  simulate_manual_action    599    update_config                 166
  compute_superposition     285    simulate_manual_action        146
  run_analysis              169    compute_superposition         112
  update_config             166    compute_action_metrics         87
  _enrich_actions           125    _augment_superposition_result  81

Test results

  • Backend: 530 passing, 19 pre-existing test-pollution failures (same set before and after — verified via git stash diff on every pass). The 19 failures only happen when the full suite runs together; each fails individually passes in isolation (fixture ordering issue, unrelated to this PR).
  • Frontend: 1000 tests passing, tsc --noEmit clean, ESLint zero warnings.
  • Layer-4 invariants: 10/10 satisfied after updating file hints.
  • Quality gate + reporter tests: green.
  • Ruff: green.

CI reports

The Code Quality / gate job publishes the full Markdown metrics report to $GITHUB_STEP_SUMMARY on every run — visible at the top of the workflow run page, no artifact download needed. The JSON + Markdown artifacts are also uploaded with 30-day retention for programmatic consumption.

https://claude.ai/code/session_01QXXXKMPZSJk8RRtE7Ur7tr

claude added 7 commits April 20, 2026 16:57
Adds automated metric reporting and a CI gate that locks in the
reductions documented in docs/architecture/code-quality-analysis.md,
and closes remaining housekeeping issues called out in the audit.

Tooling
- scripts/code_quality_report.py: AST-walks backend sources + globs
  frontend for per-file LoC, longest-function report, and smell
  counts (print / traceback.print_exc / silent except / any /
  @ts-ignore / weak casts). JSON + Markdown output.
- scripts/check_code_quality.py: PR gate. Non-zero exit when a
  threshold is exceeded (see CONTRIBUTING.md).
- scripts/test_code_quality_report.py: 7 unit tests on the AST
  smell-walker — strings containing `print(` are not flagged,
  logged except is not silent, etc.
- pyproject.toml: [quality] extra (ruff + radon) and a narrow ruff
  ruleset (F, E9) scoped to real bugs.
- .github/workflows/code-quality.yml + .circleci/config.yml
  code-quality job: run ruff, the gate, the reporter unit tests,
  and upload reports/ as a CI artifact.

Bugs surfaced by the new tooling
- simulation_mixin.py: self-reference called static method via
  RecommenderService.* without importing it (F821 NameError if
  branch ever taken). Switched to self.*.
- network_service.py: get_load_voltage_levels_bulk fell through
  without a loop body. Implemented to mirror get_generator_types_bulk.
- analysis_mixin.py: traceback.print_exc() replaced with
  logger.exception(); stray `except Exception: pass` logged via
  logger.debug; redundant shadowed imports removed.
- main.py: unused GZipMiddleware import + stray print() calls
  replaced with logger.warning; consolidated imports; CORS now
  honours CORS_ALLOWED_ORIGINS env var (with credentials-on-wildcard
  guard).
- 9 auto-fixed f-strings without placeholders via `ruff --fix`.

Frontend
- Dropped unused deps: framer-motion, lucide-react.
- SettingsModal.tsx: empty .catch(() => {}) handlers log via
  console.error.

Housekeeping
- CONTRIBUTING.md, .editorconfig, .env.example added; referenced
  from root CLAUDE.md.
- .gitignore: exclude reports/ and .env.
- Root CLAUDE.md refreshed — dropped stale test_*.py references,
  added scripts/ + code-quality sections.

Docs
- docs/architecture/code-quality-analysis.md updated to 2026-04-20:
  new Metrics Summary from the reporter, Continuous-Quality Tooling
  section (6b), Delta section (9), and each resolved row marked.
…rposition

The two longest functions flagged by the continuous reporter:

  simulate_manual_action:  599 → 146 lines
  compute_superposition:   285 → 108 lines

Both are now thin orchestrators that delegate each phase to a named
helper — the data flow is explicit in argument lists instead of hidden
as closures over locals.

New stateless module: expert_backend/services/simulation_helpers.py

  canonicalize_action_id        parse_pst_tap_id / clamp_tap
  classify_action_content       is_pst_action
  pst_fallback_line_idxs        compute_reduction_setpoint
  build_care_mask               resolve_lines_overloaded
  compute_action_metrics        extract_action_topology
  serialize_action_result       normalise_non_convergence
  build_combined_description    compute_combined_rho

New private methods on SimulationMixin (need self state):

  _inject_action_content_entries / _fetch_n_and_n1_observations
  _create_dynamic_{curtailment,load_shedding,pst} / _create_dynamic_actions_if_needed
  _promote_recent_actions_to_dict / _apply_target_mw_updates / _apply_target_tap_updates
  _build_combined_action_object / _resolve_action_description_and_content
  _register_action_result
  _ensure_pair_simulated / _identify_elements_with_pst_fallback
  _superposition_lines_overloaded / _augment_superposition_result
  _log_dict_action_snapshot / _log_per_line_rho

Coverage
- 66 new unit tests in expert_backend/tests/test_simulation_helpers.py —
  one class per helper, exercising islanding, PST-tap-only topologies,
  heuristic power reduction, pre-existing overload exclusion,
  combined-pair description, sign-preserving rho superposition.
- 130 existing simulation + superposition tests continue to pass
  (same mocks, same call order, same public contract). Pre-existing
  test-pollution failures (19) are unchanged — same before and after
  the refactor.

Docs
- Updated §6 metrics in code-quality-analysis.md with the new
  longest-function table.
- Added §10 delta documenting the decomposition + next candidates
  (run_analysis 169, update_config 166, _enrich_actions 125).
The largest frontend source file, flagged by the continuous quality
reporter. Split into 8 modules under `frontend/src/utils/svg/`, each
with a single responsibility. `svgUtils.ts` is now a 60-line barrel
that re-exports every symbol — every caller keeps working unchanged.

Module              Lines   Responsibility
─────────────────── ─────── ─────────────────────────────────────────
idMap.ts              29    Cached DOM-id map for an SVG container
svgBoost.ts          122    Dynamic font/node scaling + processSvg
metadataIndex.ts      40    Build MetadataIndex from pypowsybl meta
highlights.ts        422    Overloaded / action-target / contingency
                            halos; line & VL target resolution
deltaVisuals.ts      137    Delta flow coloring + text replacement
actionPinData.ts     344    Pin descriptors + severity palette +
                            anchor resolution (pure, no DOM)
actionPinRender.ts   537    DOM injection for pins + highlights +
                            click semantics + scale math
fitRect.ts           125    Padded viewBox computation

Largest frontend file is now App.tsx at 1370 lines — the state
orchestration hub, exempt from the component-size ceiling by design.

New helpers promoted to the public API (formerly inline closures):

- formatPinLabel(details)       — percentage / DIV / ISL / em-dash
- formatPinTitle(idLabel, det)  — hover tooltip string
- fanOutColocatedPins(pins)     — circular fan-out of colocated pins
- curveMidpoint(p1, p2)         — quadratic Bezier midpoint + ctrl
- computePinScale(...)          — pin scale math for rescaler
- rectFromBounds (internal)     — shared MIN_SPAN + padding rules

Coverage
- 61 new unit tests across 5 co-located test files (idMap,
  svgBoost, metadataIndex, actionPinData, actionPinRender,
  fitRect).
- Existing svgUtils.test.ts (144 tests) unchanged and all green.
- Full frontend suite: 1000 tests passing.
- TypeScript strict build clean; ESLint zero warnings.

Docs
- Added §11 Delta in docs/architecture/code-quality-analysis.md
  with the module table and test coverage list.
After the svgUtils.ts → utils/svg/* decomposition (a26ce0e), the
following three Layer 4 invariant regexes no longer matched their
scoped file_hint (the barrel file) and reported FAIL:

  - pin_severity_uses_monitoringFactor    (computeActionSeverity)
  - combined_pairs_filter_estimated       (buildCombinedActionPins)
  - pin_resolver_is_topology_first        (resolveActionAnchor)

All three contracts live on pure pin descriptors / severity / anchor
resolution and now have a focused home in
`frontend/src/utils/svg/actionPinData.ts`. Update each file_hint to
point there — the regex bodies stay unchanged.

Verified: `python scripts/check_invariants.py` reports 10/10
satisfied.
Adds a step that pipes `reports/code-quality.md` into
`$GITHUB_STEP_SUMMARY` so the metrics show up directly on the
workflow run page under the "Summary" tab — no artifact download
needed. `if: always()` ensures the summary is published even when
earlier steps (ruff, gate) fail, so a regression still surfaces
the full metrics view.

The upload-artifact step stays for machine-readable consumption
(code-quality.json) and 30-day history.
analysis_mixin.py was the largest backend file (1,116 lines). Split
into four focused modules under a new `services/analysis/` package:

  pdf_watcher.py         43    Overflow PDF glob + mtime search
  action_enrichment.py   389   LS / curtailment / PST details, rho
                               scaling, topology extraction,
                               non-convergence normalisation — pure
  mw_start_scoring.py    341   MW-at-start dispatcher + per-type math
                               (LS, curtailment, line disco, PST,
                               open coupling) — pure
  analysis_runner.py     193   Legacy AC→DC worker + PDF-polling
                               generator + derive_analysis_message

analysis_mixin.py shrank 1,116 → 509 lines. The three public entry
points (run_analysis_step1 / run_analysis_step2 / run_analysis) are
now thin orchestrators + `_enrich_actions` /
`_compute_mw_start_for_scores` iterators that delegate to the
stateless helpers.

Dependency injection preserves @patch compatibility

Two library functions that tests traditionally @patch
(`get_virtual_line_flow`, `run_analysis`) are now threaded into the
helpers as optional callables. The mixin reads the module-level
name at call time and forwards it — so existing
`@patch('expert_backend.services.analysis_mixin.*')` tests keep
working unchanged.

Legacy instance-method wrappers kept
- `_compute_load_shedding_details`, `_compute_curtailment_details`,
  `_compute_pst_details`, `_is_renewable_gen`
- `_mw_start_load_shedding`, `_mw_start_curtailment`,
  `_mw_start_open_coupling`, `_get_action_mw_start`,
  `_get_pst_tap_start`

They now delegate to the module-level helpers. Tests that patched
these methods on the instance (test_recommender_regressions) keep
passing without modification.

Coverage
- 68 new unit tests in tests/test_analysis_helpers.py covering
  every helper in isolation (pdf_watcher mtime filter, non-convergence
  normalisation, lines-overloaded reconstruction, load-shedding /
  curtailment / PST details, MW-start dispatcher, analysis-message
  derivation, …).
- Diffed full-suite failure set against pre-refactor baseline —
  identical 19 pre-existing test-pollution failures on both sides,
  zero new regressions.
- Quality gate green, ruff (E9/F) clean.

Top-5 longest functions after the pass
  update_config             166
  simulate_manual_action    146
  compute_superposition     112
  get_action_variant_sld     97
  _compute_deltas            92

`run_analysis` (was 169 lines) and `_enrich_actions` (was 125 lines)
both fell out of the top-5.

Docs
- Added §12 Delta to docs/architecture/code-quality-analysis.md
  with the module table, DI strategy, and next candidates.
diagram_mixin.py was the second-largest backend file after the
analysis sweep. Split into seven focused modules under a new
services/diagram/ package:

  layout_cache.py    63   (path, mtime)-keyed grid_layout.json loader
  nad_params.py      41   Default NadParameters factory (perf-tuned)
  nad_render.py      89   generate_diagram + NaN element stripping
  sld_render.py      36   SLD SVG + metadata extraction with fallbacks
  overloads.py      131   Overload filtering + per-element currents
  flows.py           67   Branch + asset flow extractors (vectorised)
  deltas.py         241   Terminal-aware flow-delta math (pure)

diagram_mixin.py shrank 974 → 469 lines. Every public method
(get_network_diagram, get_n1_diagram, get_action_variant_diagram,
get_n_sld, get_n1_sld, get_action_variant_sld) is a short
orchestrator that switches the right variant, calls the stateless
helpers, and stashes results.

Five new private helpers factor out patterns that previously
repeated across methods:

  _require_action                  validate + fetch the action entry
  _lf_status_for_variant           load-flow status with variant cache
  _snapshot_n1_state               N-1 flows + assets with variant restore
  _attach_flow_deltas_vs_base      populate flow/asset deltas on diagram
  _attach_convergence_from_obs     copy lf_converged from observation
  _diff_switches                   switch-state diff between variants

Subtle bug fixed by the refactor

test_sld_highlight.py uncovered an ordering dependency on the SLD
manual-action path: `changed_switches` must be captured BEFORE
attempting flow extraction so that mock/malformed networks with
missing flows still return the switch diff. The new orchestrator
splits the try/except into separate blocks — one for the switch
diff, one for the delta math — so each can fail independently.

Coverage
- 39 new unit tests in tests/test_diagram_helpers.py covering every
  helper in isolation (layout cache eviction, NaN stripping,
  overload filtering with N-state exclusion, terminal-aware delta
  math with direction-flip, vectorised equivalent against scalar
  reference, asset-delta categorisation, …).
- Full-suite diff against pre-refactor baseline: identical 19
  pre-existing test-pollution failures, zero new regressions.
- Quality gate green, ruff (E9/F) clean.

Top-5 longest functions after the pass
  update_config                 166
  simulate_manual_action        146
  compute_superposition         112
  compute_action_metrics         87
  _augment_superposition_result  81

Every diagram-related function fell out of the top-5.

Docs
- Added §13 Delta to docs/architecture/code-quality-analysis.md.
@marota marota changed the title Add continuous code-quality reporting and CI gate Code-quality gate + decomposition sweep (5 modules) Apr 21, 2026
@marota
marota merged commit 4996312 into main Apr 21, 2026
8 checks passed
marota pushed a commit that referenced this pull request Apr 22, 2026
… dynamic fix

Captures the post-0.6.0 work: svgPatch DOM-recycling (PR #108),
Action Overview filters + unsimulated pins (PR #105, #107),
code-quality gate + 5 decomposition passes (PR #104, #106),
docs reorganisation (PR #103), App.tsx hook extraction
(PR #109), and the dynamic reco_ reconnection-action fix on
the current branch.

https://claude.ai/code/session_01Tzp2fdUas3Y9vNZy6dxxuC
marota pushed a commit that referenced this pull request Apr 24, 2026
Follow-up to the root/frontend/backend CLAUDE.md + README refresh.
Audits the remaining project docs against PRs merged 2026-04-21 →
2026-04-23 and updates the eight that had actually drifted.

frontend/README.md (HIGH)
  Replaced the stock Vite-template boilerplate with a real frontend
  orientation: npm scripts, source-tree map (hooks, components,
  utils incl. the utils/svg/* split from PR #104), pointer to the
  auto-generated standalone bundle, testing + lint commands, and
  cross-refs to CLAUDE.md / PARITY_AUDIT.md / docs/README.md.

expert_backend/tests/CLAUDE.md (MEDIUM)
  Full rewrite of the test-file inventory: added the PR #104
  decomposition suites (test_simulation_helpers.py,
  test_analysis_helpers.py, test_diagram_helpers.py), the patch-
  endpoint coverage (test_diagram_patch_helpers.py,
  test_n1_diagram_fast_path.py) from PR #108, the regression
  guards (test_resimulate_regression.py,
  test_second_contingency_reset.py,
  test_get_n1_variant_clones_from_n_state.py,
  test_configurable_mw.py) that were added since 2026-04-11, and
  the dynamic reco_* reconnection path from PR #110. Replaced the
  frontend inventory with a structural summary matching the
  current ~60-file Vitest layout, listed the scripts/pypsa_eur
  pytest coverage, and documented the tests removed by PR #103 /
  #104 (test_ui_regressions.py, standaloneInterface.test.ts,
  cssRegression.test.ts).

docs/features/combined-actions.md (MEDIUM)
  Added a "Recent updates (PR #114, release 0.6.5)" section
  covering LS/curtailment in combined pairs, Simulate Combined
  moved out of the card + clickable sub-action badges,
  target_max_rho on the user-selected overload set, and Explore-
  Pairs re-estimation aligned with the pre-computed betas.
  Rewrote the "Standalone Interface" section to describe the
  auto-generated bundle, and dropped the checklist line that
  required manual standalone_interface.html mirroring. Added a
  row to the key-files table for the ActionCard sub-action badges
  and the ComputedPairsTable / ExplorePairsTab split.

docs/features/state-reset-and-confirmation-dialogs.md (MEDIUM)
  Replaced the standalone_interface.html section with a pointer
  to the auto-generated bundle, and expanded the "What reset()
  clears" list to match the current RecommenderService.reset()
  implementation (drain order + fast-path caches +
  _layout_cache + NAD-prefetch state, with the
  add-a-new-cache-goes-here guardrail).

docs/features/detachable-viz-tabs.md (MEDIUM)
  Removed the "standalone_interface.html mirror" row and the
  follow-up caveat that said the single-file interface doesn't
  support detaching — both are obsolete now that the bundle is
  auto-generated from the React tree.

docs/architecture/app-refactoring-plan.md (MEDIUM)
  Added a "Status: SHIPPED" banner at the top summarising both
  refactor waves (Phase 1 via PR #74, Phase 2 hook extraction
  via PR #109) and pointing readers at the current source of
  truth (frontend/CLAUDE.md + CHANGELOG.md 0.6.5). Preserved the
  original plan below the banner for historical value.

docs/architecture/phase2-state-management-optimization.md (LOW)
  Added a "Status: Partially shipped" banner — memoization +
  React.memo pass shipped under PR #75, superseded in part by
  PR #109 (useN1Fetch / useDiagramHighlights extractions),
  orchestrator hooks still deferred.

docs/features/frontend-ui-improvements.md (LOW)
  Scope header now names `frontend/dist-standalone/standalone.html`
  (auto-generated, PR #101) instead of the decommissioned
  hand-maintained file.

https://claude.ai/code/session_01RsvHjjFbAauF3NvD9BNT5V
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
marota pushed a commit that referenced this pull request May 17, 2026
``diagram_mixin.py`` hit 1220 lines after the prewarm method was
added inline, tripping the 1200-line ceiling enforced by
``scripts/check_code_quality.py``.

Move the heavy lifting into a focused stateless helper
(``services/diagram/obs_prewarm.py:build_prewarmed_obs``) following
the PR #104 / PR #106 mixin → helper-package convention: the helper
takes its dependencies (cached env context, simulation-env factory)
as keyword arguments and returns the (obs, variant_id, elements)
tuple; the mixin keeps the writes to ``_cached_obs_n1*`` so the
cache invariants stay in one place.

``DiagramMixin._cache_obs_for_variant`` becomes a thin wrapper.
Behaviour is unchanged — the existing
``test_obs_prewarm_for_step1.py`` suite (cache hit / miss /
fallback / DO_RECO_MAINTENANCE gate / signature-introspection
fallback) still passes.

File size: 1220 → 1198 (under the ceiling).

Updates:

* ``expert_backend/services/diagram/obs_prewarm.py`` — new module.
* ``expert_backend/services/diagram_mixin.py`` — slim wrapper + import.
* ``expert_backend/CLAUDE.md`` + root ``CLAUDE.md`` — mention the new
  helper in the diagram-package index.
* ``docs/backend/recommender_models.md`` § Execution-time breakdown —
  reference the helper module.
marota pushed a commit that referenced this pull request May 17, 2026
…odule

``diagram_mixin.py`` had drifted near the 1200-line ceiling for the
third time in this branch (1198/1200 after the previous prewarm
extraction). The single biggest contributor was
``get_action_variant_diagram_patch`` at 279 lines — by far the heaviest
method in the file, plus three patch-specific helpers totalling ~200
more lines.

Move the whole action-patch pipeline into a focused module
``services/diagram/action_patch.py`` (following the PR #104 /
PR #106 mixin → helper-package convention):

* ``compute_vl_topology_diff`` — bus-count diff per VL.
* ``get_disconnected_branches_from_snapshot`` — branch state from
  pre-captured connectivity frames.
* ``extract_vl_subtrees_with_edges`` — per-VL focused-NAD subtrees
  for the SVG splice. Takes ``generate_diagram`` as a callable so the
  NAD-generation seam stays the mixin's.
* ``build_action_patch_payload(service, action_id)`` — public entry
  point the mixin wraps in two lines. Reads the dozen-odd service
  collaborators it needs through the passed-in service handle.

Three private orchestrator helpers keep the entry point under the
function-LoC ceiling: ``_extract_convergence_status``,
``_capture_action_snapshots`` (fold the five try/except snapshot
blocks into one routine), ``_unpatchable_response`` (the standard
fallback payload).

Test compatibility: ``tests/test_diagram_patch_helpers.py`` calls
``DiagramMixin._compute_vl_topology_diff`` and
``DiagramMixin._get_disconnected_branches_from_snapshot`` as static
methods — both are re-exported as thin static-method wrappers on the
mixin so the tests keep working unchanged (all 22 pass).

File sizes:
* ``diagram_mixin.py``: 1198 → 769 lines (-429, 36% reduction).
* ``services/diagram/action_patch.py``: new, 531 lines.

Comfortable 431-line buffer below the 1200 ceiling means we can land
several rounds of new diagram features before hitting the wall again.
Per the user's request, this is the targeted "option 1" extraction
discussed in the earlier review.

CLAUDE.md indexes (root + ``expert_backend/``) updated to mention the
new module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants