FORCE: Audit & Visualization Scripts¶
Copyright (c) 2025-2026 EDF (Electricité De France). Author: Yann Allioux
This folder contains standalone, reproducible scripts that render the project’s scientific audit figures from the assembled dataset. Each script is independent and can be run on its own; together they form a visual QA suite for the pipeline output.
How to run¶
All scripts read the assembled dataset Outputs/dataset_30min_full.csv (produced by
energy-assemble) and require a headless matplotlib backend on Windows:
# from the repository root, with the project environment active
set MPLBACKEND=Agg # Windows (PowerShell: $env:MPLBACKEND="Agg")
python scripts/audit/gen_availability_heatmap.py
Outputs land either in Outputs/audit/ (diagnostic figures) or paper/images/
(figures referenced by the JOSS paper), as noted per script below.
1. Data completeness & coverage¶
gen_availability_heatmap.py to Outputs/audit/availability_heatmap.png¶
Weekly fill-rate (fraction of non-null timestamps) for every numeric column, grouped
by source (Enedis / Météo / RTE / Calendar) and rendered as a red to green heatmap. Also
exports Outputs/audit/availability_stats.csv (per-column global and in-window
availability).
Look for: the Enedis block turning green only from ~2020 (smart-meter rollout); the
post-2023 gap in the radiation rows (PVGIS coverage ends 2023); any unexpected white
(missing) stripes in RTE/Météo.
gen_scope_timeline.py to paper/images/scope_timeline.png¶
Horizontal Gantt of each data stream’s active period, annotated with its fill rate and grouped by provider (TSO / DSO / Météo-France / ODRÉ / Etalab). Look for: the 2012 start of RTE vs the 2020 onset of Enedis DSO observability.
2. Cross-source validation¶
gen_validation_stacked.py to paper/images/validation_stacked.png¶
Two panels: (A) the multi-barycenter effect, solar-weighted vs population-weighted national temperature, whose daytime divergence confirms PV assets sit in climatically distinct areas; (B) TSO vs DSO load harmonization, RTE national load vs Enedis distribution load, the gap representing transmission-connected industry + grid losses. Look for: tight temporal alignment of the two load curves; a consistent (not erratic) TSO-DSO gap.
3. Spatial decomposition¶
gen_spatial_breakdown.py to paper/images/spatial_breakdown_{wind,solar}.png¶
Stacked area of regional generation (wind and solar) summing to the national signal (dashed line), illustrating the portfolio effect. Look for: colored bands summing to the national line (bottom-up consistency); the north/west concentration for wind vs the southern dominance for solar.
gen_radiation_regional.py to Outputs/audit/radiation_regional.png (new)¶
Monthly PVGIS radiation climatology for each region (thin lines) against the national solar-weighted reference (bold). Applies the notebook’s “find national reference” idea to the new radiation feature. Look for: southern regions (Occitanie, PACA) sitting above the national curve; all regions sharing the summer-peaked seasonal shape.
4. Data maturity & observability¶
gen_maturity_forensics.py to paper/images/maturity_forensics.png¶
The Golden Record lifecycle for national load: (A) the full timeline coloured by quality level (Definitive to Consolidated to Real-Time); (B) a zoom on the Definitive to Consolidated handoff showing the inter-stream delta. Look for: no discontinuity in the synthesized signal at the maturity cutoffs.
gen_enedis_observability.py to paper/images/enedis_observability_*.png¶
Smart-meter rollout dashboards (composite + per sector: residential / professional / entreprise): (A) telemetered vs profiled load with a rising telemetry-rate line; (B) the average diurnal cycle. Look for: the green (“telemetered”) area expanding over time; the residential nocturnal water-heating / EV peak absent from the generalized profile.
5. Physical & driver relationships (new)¶
gen_generation_mix.py to Outputs/audit/generation_mix.png (new)¶
Stacked area of the full RTE generation mix (nuclear, hydro, gas, coal, bioenergy, wind, solar) against the national load (dashed) for a representative winter week. Look for: nuclear baseload, the daily solar bumps, and dispatchable gas tracking the load peaks.
gen_thermosensitivity.py to Outputs/audit/thermosensitivity.png (new)¶
Scatter of national load vs population-weighted temperature, coloured by month, the classic thermosensitivity relationship. Look for: the “hockey-stick”, load rising steeply below ~15 °C (electric heating) with a milder summer cooling uptick; winter months (cold colours) on the high-load arm.
gen_solar_clock.py to Outputs/audit/solar_clock.png (new)¶
Heatmap of mean solar generation across hour-of-day × month, the solar production envelope. Look for: the bright midday core widening from winter to summer (longer days, higher sun); strict zeros at night confirming the physical night constraint.
6. Per-dataset sweep: gen_all_audits.py (new)¶
Runs a generic, column-gated audit over every CSV dataset, both the assembled ones
in Outputs/ and the imputed ones in Outputs/Imputed/, and writes one folder of
figures per dataset:
Outputs/audit/<dataset_name>/
missingness.png # % missing per column (bar)
availability_heatmap.png # weekly fill rate, all numeric columns
national_overview.png # daily-mean time series of key national variables
correlation.png # correlation matrix of key national variables
distributions.png # sampled value histograms
generation_mix.png # only if RTE generation columns are present
thermosensitivity.png # only if load + national temperature are present
solar_clock.png # only if rte_solar_france is present
Each figure is emitted only when the dataset actually contains the required columns, so
the rich *_full datasets get the complete set while the thematic shards
(*_france_load, *_france_solar, *_france_wind) get the applicable subset.
Because it runs over both Outputs/ and Outputs/Imputed/, you get non-imputed vs
imputed versions side by side, most informative on missingness.png and
availability_heatmap.png, where the imputed folders should show the structural gaps
filled (except the by-design Enedis pre-2020 and post-2023 PVGIS-radiation windows).
Run (generates all per-dataset folders in one pass):
python scripts/audit/gen_all_audits.py
Reads are column-selective via DuckDB, so even the multi-GB *_full datasets are handled by
streaming aggregation rather than full in-memory loads.
7. Faithful notebook reproduction: gen_notebook_audit.py (new)¶
This is a direct port of audit.ipynb’s interactive engine (plot_analysis +
generate_hybrid_config), turned into a batch generator that saves every figure the
notebook produces for every CSV dataset (assembled + imputed). Unlike
gen_all_audits.py (a small fixed set), this reproduces the full auto-generated config:
Calendar line-stacks, normalized step plots per calendar theme (vacation zones, day types, seasons, technical).
Physical balance checks, Enedis conservation (In = Out) with a delta panel (global balance, load-by-sector, production-by-technology).
Regional decompositions, for every regional variable group (temperature, nebulosity, wind speed, air density, humidity, wind direction/gusts, dew point, precip, pressure, radiation, RTE load/solar/wind/nuclear/hydro/bioenergy/pump/exchange): two figures each, (a) spatial consistency (sum/mean of regions vs the national reference + delta), (b) regional breakdown (every region overlaid with the national line).
Expert pairwise comparisons, a comprehensive, themed list (every defined pair is plotted when both columns exist, no dedup-skip):
Theme |
Pairs (driver to output, and cross-validations) |
|---|---|
Solar & Radiation |
PVGIS POA vs RTE solar gen; PVGIS POA vs Enedis solar gen; PVGIS POA vs Enedis pseudo-radiation (real vs proxy); PVGIS POA vs ERA5 GHI (when available); PVGIS POA vs nebulosity; PVGIS POA vs site temperature; pseudo-radiation vs solar gen; nebulosity vs solar gen; RTE vs Enedis solar; Enedis solar profiled vs total |
Wind |
speed vs RTE wind gen; speed vs Enedis wind gen; RTE vs Enedis wind; total vs onshore; speed vs air density; mean speed vs gusts; speed vs direction; air density vs gusts; load- vs wind-weighted speed; pressure vs wind speed |
Load & Demand |
load vs temperature (thermosensitivity); RTE vs Enedis load; load vs nebulosity; load vs solar (net load); actual vs day-ahead / intraday forecast; intraday vs day-ahead forecast; residential vs professional vs enterprise; industrial vs total; losses vs total; telemetered vs profiled (×4 sectors) |
Data maturity |
best vs definitive / consolidated / real-time; definitive vs real-time (deviation) |
Generation mix & system |
solar vs wind (VRE complementarity); nuclear vs load; nuclear vs coal; pumping vs hydro; hydro lake vs run-of-river; gas vs bioenergy; gas CCGT vs TAC; gas vs CO₂ rate; fuel vs coal; hydro vs precipitation; exchange vs Enedis flow; exchange vs load; Enedis production vs TSO export |
Temperature & meteo |
load- vs solar-weighted temp; Meteo-France vs Enedis temp; Enedis real vs normal temp; temp vs dew point; humidity vs dew point; nebulosity load vs solar weight; precip 1h vs humidity; precip 1h vs 3h; pressure MSL vs station; pressure vs temp |
Calendar |
TOY vs TOD; bank holiday vs bridge day; summer vs Christmas |
Orphan auto-pairing, any remaining columns are paired so nothing goes unplotted.
Radiation note: the “radiation vs radiation” check available today is PVGIS POA vs the Enedis nebulosity-pseudo proxy (
enedis_meteo_radiation_pseudo_france). The PVGIS vs ERA5 pair is defined but only renders once ERA5 is collected and wired into the assembled dataset (currently ERA5 lives in a separatesynop_radiation_era5table, see METEO.md Table D).
Output per dataset: Outputs/audit/<dataset_name>/NNN_<type>_<title>.png
(NNN = config index; <type> ∈ calendar / balance / spatial / regional / pair).
A rich *_full dataset yields ~80+ figures; thematic shards yield their applicable subset.
python scripts/audit/gen_notebook_audit.py
Each dataset is loaded for a representative window (2024, long_term rolling = 7 days) via
DuckDB so even the multi-GB *_full files load only the needed slice.
gen_all_audits.py(§6) andgen_notebook_audit.py(§7) both write into the same per-dataset folders and are complementary: §6 gives a compact fixed overview, §7 gives the exhaustive notebook reproduction.
8. PVGIS vs ERA5 radiation comparison: gen_pvgis_vs_era5.py (new)¶
A dedicated comparison that reads both raw radiation tables directly from
raw_meteo.duckdb, synop_radiation (PVGIS, plane-of-array 35°) and
synop_radiation_era5 (ERA5, GHI), joined on (date, station_id), independent of the
assembled CSVs. Output: Outputs/audit/comparison/pvgis_vs_era5.png, a 4-panel figure:
(A) monthly climatology, (B) monthly-mean time series over the overlap, (C) POA-vs-GHI
scatter with correlation / regression / ratio, (D) per-station mean bias.
It runs safely today and prints a clear message if ERA5 isn’t collected yet, so it is “ready and waiting”:
python scripts/audit/gen_pvgis_vs_era5.py
PVGIS POA (35° tilt) and ERA5 GHI (horizontal) are different physical quantities, so the comparison reports correlation, bias and the POA/GHI ratio, not equality.
9. Hierarchy witnesses: gen_hierarchies.py (new)¶
The visual companion to documentation/HIERARCHIES.md:
it renders the verified structural relationships between columns from a single DuckDB scan of
dataset_30min_full.csv. Three figures. (Definitional relationships, segment
total = telemetered + profiled, calendar encodings, are excluded; only non-trivial ones are
shown.)
hierarchy_balances.png to Outputs/audit/comparison/hierarchy_balances.png¶
The energy inflow/outflow balance on both grids, side by side. (A) distribution (Enedis)
over a representative week, import + production (inflow) vs load + losses + export_rte + export_dso (outflow), the two lines coinciding; (B) transmission (RTE) over the same week,
Σ generation + net imports + pump (supply) vs load, with the shaded gap; (C) the Enedis
balance scatter pinned to y = x (mean residual ≈ 0.002 MW); (D) the RTE balance scatter, also
on y = x (≈ 28 MW / 0.05% residual).
Look for: both balances closing, the distribution one to machine precision, the
transmission one to ~0.05% (Corsica + overseas + rounding/maturity-lag). pump carries the
real pumped-storage consumption (≈ −800 MW) after the clip-sign fix.
hierarchy_regional.png to Outputs/audit/comparison/hierarchy_regional.png¶
RTE national vs Σ 12 metropolitan regions for all eight families (load, nuclear, wind, solar,
hydro, bioenergy, pump, exchange_net) as a 2×4 scatter grid, each annotated with its rel|err|.
Look for: all families hugging the diagonal (national ≈ Σ regions to <0.4%) without closing
exactly (Corsica + overseas + rounding/lag); pump collapsing to a near-zero blob.
hierarchy_aggregations.png to Outputs/audit/comparison/hierarchy_aggregations.png¶
(A) Enedis load decomposition, national load (published) vs Σ 4 segment totals (closes
exactly); (B) Enedis production total vs Σ 5 streams (small systematic gap); (C) RTE family
totals as horizontal bars of the exposed sub-streams plus the hatched unlisted remainder
(STEP turbining, cogeneration, biomass, offshore wind); (D) the RTE golden record, monthly
composition of rte_load_france by source nature.
Look for: the hatched remainder dominating the bioenergy bar (biogas is only ~26%); the
golden record shifting from Definitive to Consolidated/Real-Time at the recent edge.
python scripts/audit/gen_hierarchies.py
10. Imputation engine comparison: gen_imputation_compare.py (new)¶
gen_imputation_compare.py to Outputs/audit/comparison/imputation_tsicl_vs_xgboost.png¶
Overlays, on real gaps, the original series (with the gap shaded), the TS-ICL fill and
the XGBoost fill, with observed datapoints before and after each gap for context. It
auto-locates interior gaps in the non-imputed dataset_30min_full.csv and reads both engine
outputs (Outputs/Imputed/tsicl/ and Outputs/Imputed/xgboost/), so it requires both engines
to have been run.
Look for: TS-ICL tracking the local rhythm/amplitude smoothly vs XGBoost’s jaggier
over/undershoot; e.g. the negative pumped-storage spikes recovered on rte_pump_grand_est.
python scripts/audit/gen_imputation_compare.py
Notes¶
Scripts are read-only on the data and safe to run while other pipeline stages execute (they only read the CSV / warehouse).
Radiation columns follow the convention
radiation_{plane}_{source}_wm2(e.g.meteo_radiation_poa_pvgis_wm2_france_solar).For a quick driver check,
gen_radiation_regional.pyandgen_solar_clock.pyread only the columns they need (via DuckDB) and run in seconds even on the full dataset.