BRAGA

01 Work01-04

Energy procurement risk analyser

Full spot, indexed, fixed or partly hedged: four ways to buy electricity for one Styrian manufacturer. The data warehouse is built; the answer in euros is not yet.

EnergyIn buildPublished 24 Jul 20264 min readRepository
4
Sources ingested
4
Procurement strategies, not yet scored
50 GWh
Annual load, constructed
Contents
  1. 01Question
  2. 02Data
  3. 03Method
  4. 04Result
  5. 05Limitations
  6. 06Decision
  7. 07Build log
Summary

The ingest from four sources and a DuckDB warehouse modelled in dbt are built and tested; strategy results do not exist yet. No strategy is recommended, and by the project’s own rule no result is quoted until it appears in the auto-generated numeric source-of-truth file that arrives with the strategy simulator.

Question

After the 2021–2023 European energy crisis, electricity procurement became a board-level risk for Austrian industry. What did buying electricity the wrong way cost a Styrian manufacturer using 50 GWh a year in 2021–2025, and what is the P95 cost exposure for the next twelve months under each procurement strategy? The project compares four strategies on the Austrian day-ahead market (full spot, an indexed contract, a fixed annual price and a partial hedge) for one constructed 50 GWh industrial load. The unit of comparison is the energy-price cost of that one load, in euros, under each strategy: realised on the prices of 2021–2025, and as the P95 and CVaR95 of the next twelve months on resampled prices.

Data

The market data is real, and no market price is synthetic. Day-ahead prices for Austria and Germany–Luxembourg and Austrian load and generation come from the ENTSO-E Transparency Platform; the monthly price index ÖSPI comes from the Austrian Energy Agency, transcribed by hand with double entry; daily mean temperature for Graz comes from GeoSphere Austria; and a calendar holds the Austrian and Styrian public holidays. The window runs from January 2019 to the latest complete month.

Two inputs are assumptions and are tagged as such. The consumer’s load shape is constructed, not measured, and ÖSPI stands in for contract and forward prices. Grid fees, taxes and levies are out of scope, so the analysis isolates the energy-price lever.

Inputs
Day-ahead prices
Austria and Germany–Luxembourg · ENTSO-E Transparency Platform
Load and generation
Austria · ENTSO-E Transparency Platform
Price index
ÖSPI, monthly · Austrian Energy Agency · transcribed by hand with double entry
Temperature
daily mean, Graz · GeoSphere Austria
Calendar
Austrian and Styrian public holidays
Window
January 2019 to the latest complete month
Load shape
constructed, 50 GWh a year · assumption
Contract and forward prices
ÖSPI as the stand-in · assumption
Scope
the energy price only · grid fees, taxes and levies excluded

Method

The pipeline is built from the sources to a tested warehouse. In order:

  1. Ingest. All four sources, fetched with retries and caching, into raw Parquet.
  2. Validation gates. Part of the ingest, so that a plausible-looking file cannot pass silently. They check hour coverage, including the 23- and 25-hour daylight-saving days, price and load plausibility, the Austria–Germany join and the presence of negative prices.
  3. Warehouse. DuckDB, modelled in dbt from staging to marts. The dbt tests check the mart schema contract, daylight-saving adjacency, a price reconciliation for August 2022 and gaps in the month spine.
  4. Continuous integration. CI runs lint, the tests and a network-free dbt build against a fixture warehouse.

Still to build: the load profile, market analytics, four strategy cost engines with a seeded block bootstrap for forward risk (P95 and CVaR95), and the reporting layer. No price forecast is planned: strategies are scored on realised prices and on resampling them.

P95 and CVaR95

P95 is the twelve-month cost that 95% of the resampled outcomes stay at or below. CVaR95 is the mean cost of the outcomes beyond P95: it reads how heavy the tail is, not only where it starts.

Result

None yet. The foundation stage is in place; the parts that would produce a result are not.

Component State
ENTSO-E ingest Built
Weather, index and calendar sources Built
Validation gates and dbt tests Built
DuckDB warehouse, dbt from staging to marts Built
Continuous integration Built
Load profile Not built
Market analytics Not built
Strategy simulator: four cost engines, block bootstrap Not built
Reporting Not built

Until the last four are built, their commands fail loudly and name the milestone they belong to.

By the project’s own rule, every published number must first appear, with its epistemic tag, in an auto-generated numeric source-of-truth file, and that file arrives with the strategy simulator. Until then this page quotes no result: no strategy cost for 2021–2025, no P95 and no CVaR95.

Result · foundation stage

Ingest and warehouse are built; strategy results do not exist yet.

Limitations

What this does not establish:

  • Any strategy result. No realised cost for 2021–2025, no P95 and no CVaR95 exists yet; this page quotes none.
  • A measured load. The load shape is constructed, not measured, and it is one industrial load of 50 GWh a year.
  • Contract and forward prices. ÖSPI stands in for them; that is an assumption, tagged as one.
  • The full cost of electricity. Grid fees, taxes and levies are out of scope; the analysis isolates the energy-price lever.
  • A price forecast. None is planned: strategies are scored on realised prices and on resampling them.

Decision

No strategy is recommended yet. The project leads from electricity-price records to a cost-risk decision between the four strategies, and that decision waits for the strategy simulator and the source-of-truth file that arrives with it.

In build: the foundation stage is implemented, and the load profile, analytics, simulator and reporting are not. The repository holds the charter, the specifications and the pipeline so far.

Build log

175 commits and 14 merged pull requests on 14 days between 19 Jul 2026 and 27 Oct 2026. Merge commits and bots are left out; times are Vienna time.

Fig. 01-04-B1 · CalendarThe busiest day was 21 Jul 2026, with 49 commits.
JulSepOctMonWedFriSun19 Jul 2026 · 220 Jul 2026 · 121 Jul 2026 · 4922 Jul 2026 · 2523 Jul 2026 · 3124 Jul 2026 · 3725 Jul 2026 · 026 Jul 2026 · 027 Jul 2026 · 028 Jul 2026 · 029 Jul 2026 · 030 Jul 2026 · 031 Jul 2026 · 01 Aug 2026 · 02 Aug 2026 · 03 Aug 2026 · 04 Aug 2026 · 05 Aug 2026 · 06 Aug 2026 · 07 Aug 2026 · 08 Aug 2026 · 09 Aug 2026 · 010 Aug 2026 · 011 Aug 2026 · 012 Aug 2026 · 013 Aug 2026 · 014 Aug 2026 · 015 Aug 2026 · 016 Aug 2026 · 017 Aug 2026 · 018 Aug 2026 · 019 Aug 2026 · 020 Aug 2026 · 021 Aug 2026 · 022 Aug 2026 · 023 Aug 2026 · 024 Aug 2026 · 025 Aug 2026 · 026 Aug 2026 · 027 Aug 2026 · 028 Aug 2026 · 029 Aug 2026 · 030 Aug 2026 · 031 Aug 2026 · 01 Sep 2026 · 22 Sep 2026 · 03 Sep 2026 · 04 Sep 2026 · 05 Sep 2026 · 06 Sep 2026 · 07 Sep 2026 · 08 Sep 2026 · 09 Sep 2026 · 010 Sep 2026 · 011 Sep 2026 · 012 Sep 2026 · 013 Sep 2026 · 014 Sep 2026 · 015 Sep 2026 · 016 Sep 2026 · 117 Sep 2026 · 318 Sep 2026 · 019 Sep 2026 · 020 Sep 2026 · 021 Sep 2026 · 122 Sep 2026 · 023 Sep 2026 · 024 Sep 2026 · 025 Sep 2026 · 026 Sep 2026 · 027 Sep 2026 · 028 Sep 2026 · 029 Sep 2026 · 030 Sep 2026 · 61 Oct 2026 · 02 Oct 2026 · 03 Oct 2026 · 04 Oct 2026 · 05 Oct 2026 · 06 Oct 2026 · 07 Oct 2026 · 08 Oct 2026 · 09 Oct 2026 · 010 Oct 2026 · 011 Oct 2026 · 012 Oct 2026 · 013 Oct 2026 · 014 Oct 2026 · 815 Oct 2026 · 016 Oct 2026 · 017 Oct 2026 · 018 Oct 2026 · 019 Oct 2026 · 020 Oct 2026 · 021 Oct 2026 · 022 Oct 2026 · 023 Oct 2026 · 024 Oct 2026 · 025 Oct 2026 · 026 Oct 2026 · 227 Oct 2026 · 701–234–56–910–1617–2829–49

n = 175 commits. Colour: commits per day, seven bins from 1 to 49; an empty cell had none. Hover a cell for its count.
Source: git history of RafaelBraga-Kribitz/energy-procurement-risk-analyzer, default branch, snapshot 27 Oct 2026.

Fig. 01-04-B2 · WeekThe busiest hour of the week: Friday, 10:00–11:00, 16 commits.
0003060912151821MonTueWedThuFriSatSunMon 00:00 · 0Mon 01:00 · 0Mon 02:00 · 0Mon 03:00 · 0Mon 04:00 · 0Mon 05:00 · 0Mon 06:00 · 0Mon 07:00 · 0Mon 08:00 · 0Mon 09:00 · 0Mon 10:00 · 0Mon 11:00 · 0Mon 12:00 · 0Mon 13:00 · 0Mon 14:00 · 0Mon 15:00 · 1Mon 16:00 · 0Mon 17:00 · 0Mon 18:00 · 0Mon 19:00 · 1Mon 20:00 · 1Mon 21:00 · 1Mon 22:00 · 0Mon 23:00 · 0Tue 00:00 · 4Tue 01:00 · 3Tue 02:00 · 0Tue 03:00 · 0Tue 04:00 · 0Tue 05:00 · 0Tue 06:00 · 0Tue 07:00 · 0Tue 08:00 · 0Tue 09:00 · 0Tue 10:00 · 0Tue 11:00 · 0Tue 12:00 · 0Tue 13:00 · 0Tue 14:00 · 0Tue 15:00 · 9Tue 16:00 · 10Tue 17:00 · 9Tue 18:00 · 2Tue 19:00 · 0Tue 20:00 · 0Tue 21:00 · 7Tue 22:00 · 14Tue 23:00 · 0Wed 00:00 · 0Wed 01:00 · 0Wed 02:00 · 0Wed 03:00 · 0Wed 04:00 · 0Wed 05:00 · 8Wed 06:00 · 2Wed 07:00 · 1Wed 08:00 · 0Wed 09:00 · 0Wed 10:00 · 0Wed 11:00 · 0Wed 12:00 · 0Wed 13:00 · 0Wed 14:00 · 4Wed 15:00 · 3Wed 16:00 · 0Wed 17:00 · 0Wed 18:00 · 3Wed 19:00 · 7Wed 20:00 · 0Wed 21:00 · 1Wed 22:00 · 6Wed 23:00 · 5Thu 00:00 · 10Thu 01:00 · 0Thu 02:00 · 0Thu 03:00 · 0Thu 04:00 · 0Thu 05:00 · 0Thu 06:00 · 0Thu 07:00 · 0Thu 08:00 · 0Thu 09:00 · 0Thu 10:00 · 0Thu 11:00 · 11Thu 12:00 · 0Thu 13:00 · 0Thu 14:00 · 0Thu 15:00 · 0Thu 16:00 · 2Thu 17:00 · 0Thu 18:00 · 0Thu 19:00 · 1Thu 20:00 · 7Thu 21:00 · 3Thu 22:00 · 0Thu 23:00 · 0Fri 00:00 · 0Fri 01:00 · 4Fri 02:00 · 0Fri 03:00 · 0Fri 04:00 · 0Fri 05:00 · 0Fri 06:00 · 0Fri 07:00 · 0Fri 08:00 · 0Fri 09:00 · 0Fri 10:00 · 16Fri 11:00 · 15Fri 12:00 · 2Fri 13:00 · 0Fri 14:00 · 0Fri 15:00 · 0Fri 16:00 · 0Fri 17:00 · 0Fri 18:00 · 0Fri 19:00 · 0Fri 20:00 · 0Fri 21:00 · 0Fri 22:00 · 0Fri 23:00 · 0Sat 00:00 · 0Sat 01:00 · 0Sat 02:00 · 0Sat 03:00 · 0Sat 04:00 · 0Sat 05:00 · 0Sat 06:00 · 0Sat 07:00 · 0Sat 08:00 · 0Sat 09:00 · 0Sat 10:00 · 0Sat 11:00 · 0Sat 12:00 · 0Sat 13:00 · 0Sat 14:00 · 0Sat 15:00 · 0Sat 16:00 · 0Sat 17:00 · 0Sat 18:00 · 0Sat 19:00 · 0Sat 20:00 · 0Sat 21:00 · 0Sat 22:00 · 0Sat 23:00 · 0Sun 00:00 · 0Sun 01:00 · 0Sun 02:00 · 0Sun 03:00 · 0Sun 04:00 · 0Sun 05:00 · 0Sun 06:00 · 0Sun 07:00 · 1Sun 08:00 · 0Sun 09:00 · 0Sun 10:00 · 0Sun 11:00 · 0Sun 12:00 · 1Sun 13:00 · 0Sun 14:00 · 0Sun 15:00 · 0Sun 16:00 · 0Sun 17:00 · 0Sun 18:00 · 0Sun 19:00 · 0Sun 20:00 · 0Sun 21:00 · 0Sun 22:00 · 0Sun 23:00 · 001234–56–78–1112–16

n = 175 commits. Colour: commits per hour, seven bins from 1 to 16; an empty cell had none. Hover a cell for its count.
Source: git history of RafaelBraga-Kribitz/energy-procurement-risk-analyzer, default branch, snapshot 27 Oct 2026.

Fig. 01-04-B3 · Lines written

59 lines of tests for every 100 lines of code.

Code15,803
Tests9,333
Docs24,341
CI & setup568
Lines added, n = 50,045. Data, generated files, figures, notebooks and lockfiles are left out.
Source: git history of RafaelBraga-Kribitz/energy-procurement-risk-analyzer, default branch, snapshot 27 Oct 2026.

#energy-procurement#risk-analysis#data-warehouse

Questions about this project, or a role where this kind of work fits?

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close