Run the full three-year dispatch and fix issues it surfaced
CI / test (push) Successful in 14s

Executed battery-dispatch run over the full 2018-2020 Attachment 2
series (225.9s, 1096 rolling-horizon windows) and committed the
results: outputs/ (dispatch.csv, annual/monthly summaries, summary.json,
four figures), README.md (setup, run instructions, assumptions, the
one-paragraph approach summary) and RESULTS.md (headline numbers,
per-year table, charts, commentary).

The full run surfaced three real issues, all fixed rather than papered
over:

- validate_schedule's soc_within_bounds check used a fixed 1e-6 MWh
  tolerance for the "state of charge below zero" case, while its
  sibling soc_matches_power_flows check already scales its tolerance
  with sqrt(n) for the same reason (CBC's own solver precision
  accumulating over a long cumulative sum). Over 52,608 half-hours this
  false-failed on a 5.1e-6 MWh solver-noise dip, not a real violation.
  Scaled it the same way.
- cli.py's summary printed net_of_opex_gbp under the label "Less fixed
  opex", so the terminal output showed the post-opex profit (£193k)
  where a reader would expect the opex figure itself (£15k). Split into
  two correctly-labelled lines.
- plots.py's monthly revenue chart stacked Market 1 (always negative
  here) and Market 2 (always positive) with a single running `bottom`,
  which is only correct for same-signed series: Market 2's bar
  completely overlapped and painted over Market 1's, hiding the
  -£297k Market 1 loss entirely. Now accumulates positive and negative
  contributions on separate baselines and marks the net.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 16:29:40 +01:00
co-authored by Claude Sonnet 5
parent afa02864a7
commit c70c9731a6
13 changed files with 52976 additions and 9 deletions
+64
View File
@@ -0,0 +1,64 @@
{
"solver": {
"commit_hours": 24,
"degradation_cost_gbp_per_mwh": 0.0,
"milp_fallbacks": 1096,
"mode": "auto",
"window_hours": 48,
"windows": 1096
},
"summary": {
"average_captured_spread_gbp_per_mwh": 15.816025,
"capacity_end_mwh": 3.861279,
"capacity_retained_fraction": 0.96532,
"capacity_start_mwh": 4.0,
"charged_market_1_mwh": 11297.137749,
"charged_market_2_mwh": 3305.034144,
"cycle_budget": 5000.0,
"cycle_budget_used_fraction": 0.693603,
"days": 1096.0,
"discharged_market_1_mwh": 1952.341071,
"discharged_market_2_mwh": 11226.119067,
"end": "2020-12-31 23:30:00",
"energy_charged_from_grid_mwh": 14602.171893,
"energy_discharged_to_grid_mwh": 13178.460138,
"equivalent_full_cycles": 3468.015826,
"equivalent_full_cycles_per_year": 1155.741588,
"fixed_opex_gbp": 15003.422313,
"gross_profit_per_year_gbp": 69461.10309,
"gross_trading_profit_gbp": 208430.852803,
"half_hours": 52608,
"implied_life_years": 4.326227,
"net_of_opex_gbp": 193427.430489,
"profit_market_1_gbp": -297418.057114,
"profit_market_2_gbp": 505848.909916,
"profit_share_market_1": -1.426939,
"profit_share_market_2": 2.426939,
"round_trip_efficiency": 0.9025,
"start": "2018-01-01 00:00:00"
},
"validation": {
"checks": {
"charge_within_limit": true,
"discharge_within_limit": true,
"hourly_commitment_constant": true,
"no_simultaneous_charge_discharge": true,
"revenue_matches_prices_and_powers": true,
"soc_matches_power_flows": true,
"soc_within_bounds": true
},
"details": {
"hours_checked": 26304.0,
"max_hourly_commitment_gap_mw": 0.0,
"max_revenue_error_gbp": 0.0,
"max_soc_above_capacity_mwh": 0.000413531614,
"max_soc_below_zero_mwh": 5.101628e-06,
"max_soc_drift_mwh": 5.14322e-06,
"max_total_charge_mw": 2.00000005,
"max_total_discharge_mw": 2.0000001,
"simultaneous_half_hours": 0.0
},
"failures": [],
"ok": true
}
}