# Romanian electricity production and consumption history (Transelectrica) > The measured history of Romania's national power system, out of the workbooks Transelectrica publishes on its own website: national production from 2015-01-01 to 2024-12-31 and national consumption from 2015-01-01 to 2025-12-31, both at quarter-hour resolution since February 2021 and hourly before it, in gross and net values. Beside them sits the archived day-ahead production forecast for 2010-09-01 to 2019-12-31, kept in a table of its own because a forecast is not a measurement. The files are landed as real tables with their own columns and types rather than as links to themselves; the annual network performance reports, being Word documents, remain a catalogue that states why they could not be turned into tables. - Dataset id: `transelectrica-history` - Update cadence: monthly - Upstream source: https://www.transelectrica.ro/ro/web/tel/productie - Tags: energy, electricity, grid, romania, transelectrica, consumption, production, forecast, time-series, history - Current version: ingest `01M1NQPZZZG54BBX89G44D1W2W`, snapshot 2026-09-04, generated 2026-09-04T08:14:35.557Z ## Licence and attribution - Licence: Transelectrica public operational data (no explicit re-use licence) (`LicenseRef-Transelectrica-Public-Data`) - Licence text: https://www.transelectrica.ro/ - Required attribution: Transelectrica - Notes: No re-use licence is published by Transelectrica; only a bare copyright notice ("Copyright © Transelectrica") exists in the site footer, and these files are published openly on its public website without authentication and without any stated restriction on access. There is no licence page, no terms-of-use page and no open-data statement on the site; the GDPR and cookie policies concern personal data and say nothing about this data. So the position is: publicly published operational data, no re-use licence stated by the publisher, only a bare copyright notice. This dataset is a derived work — the numeric measurements, restructured, documented and re-typed, with no page, image or other creative material copied — and it is republished with attribution to Transelectrica. Anyone redistributing it commercially, or relying on it where the terms matter, should seek permission from Transelectrica first. It is also operational reporting, not a settlement record: the values are the publisher's own measurements and forecasts, they are not reconciled afterwards, and they must not be used where money or safety depends on the number. Reproduce the attribution line verbatim anywhere you republish this data. ## Manifest - Start here for how this directory works: https://data.bunnytech.app/llms.txt - This dataset: https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/manifest.json - All datasets: https://data-directory.fsn1.your-objectstorage.com/public/index.json - Human page: https://data.bunnytech.app/datasets/transelectrica-history The manifest is the contract: file URLs, row counts, column types, min/max statistics and example queries all live there. Re-read it to discover a new version. ## Fastest path — Parquet exports Immutable, physically sorted snapshot exports. No catalog fetch, best row-group pruning, safe to cache forever. Register one view per table: ```sql INSTALL httpfs; LOAD httpfs; CREATE OR REPLACE VIEW production_intervals AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/production_intervals.parquet'); CREATE OR REPLACE VIEW consumption_intervals AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/consumption_intervals.parquet'); CREATE OR REPLACE VIEW production_forecast_hourly AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/production_forecast_hourly.parquet'); CREATE OR REPLACE VIEW performance_reports AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/performance_reports.parquet'); ``` Example queries: -- The national load curve for one day: day comes first because it is the leading sort column, so this reads a handful of row groups out of half a million rows. interval_minutes tells you whether the day is hourly or quarter-hourly. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); SELECT interval_start_local, interval_minutes, power_mw FROM datadirectory.transelectrica_history.consumption_intervals WHERE day = DATE '2024-01-15' AND measure = 'gross' ORDER BY interval ``` -- Monthly average production and consumption, gross: The two measured tables share their sort order on purpose, so this join needs no re-sort on either side. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); SELECT date_trunc('month', p.day) AS month, avg(p.power_mw) AS avg_production_mw, avg(c.power_mw) AS avg_consumption_mw FROM datadirectory.transelectrica_history.production_intervals p JOIN datadirectory.transelectrica_history.consumption_intervals c ON c.day = p.day AND c.interval = p.interval AND c.measure = p.measure WHERE p.measure = 'gross' AND p.day >= DATE '2021-02-01' GROUP BY 1 ORDER BY 1 ``` ## Live lake — DuckLake catalog with time travel Attach the published catalog read-only over plain https. This works in native DuckDB AND in duckdb-wasm in the browser — the reader catalog resolves its data files through public https URLs, so no credentials and no backend are involved. ```sql INSTALL ducklake; LOAD ducklake; ATTACH 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/transelectrica-history/catalog.ducklake' AS transelectrica_history (READ_ONLY); SELECT * FROM transelectrica_history.main.production_intervals LIMIT 5; USE transelectrica_history; ``` - Latest catalog: https://data-directory.fsn1.your-objectstorage.com/catalog/transelectrica-history/catalog.ducklake - This exact version: https://data-directory.fsn1.your-objectstorage.com/catalog/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/catalog.ducklake (sha256 010e7e90d3df700e77c26d213a67bf98404577c27f7d2022e0149fa09eb52580) - Written by DuckDB v1.5.5 - Time travel: this ingest is snapshot 7 — e.g. `SELECT * FROM transelectrica_history.production_intervals AT (VERSION => 7)` ## Wholesale — S3 access For bulk downloads, read the bucket prefix directly over S3 with httpfs: ```sql INSTALL httpfs; LOAD httpfs; CREATE SECRET hetzner ( TYPE s3, ENDPOINT 'fsn1.your-objectstorage.com', URL_STYLE 'path', KEY_ID '', SECRET '' ); SELECT * FROM read_parquet('s3://data-directory/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/**/*.parquet'); ``` ## Schema ### production_intervals MEASURED electricity production for the whole Romanian national grid, one row per day, per interval, per side of the meter, from the "weekly production" workbook Transelectrica publishes, covering 2015-01-01 to 2024-12-31. **The series is hourly (interval_minutes = 60, 24 intervals a day) through 2021-01-31 and quarter-hourly (interval_minutes = 15, 96 intervals) from 2021-02-01 onward — interval_minutes records which applies to each row, so read it rather than assuming either resolution.** These are measurements, not forecasts — the forecast lives in production_forecast_hourly and must not be mixed in. Gross and net are both present; filter on measure or every day is counted twice. - 381.2K rows · 2.2 MB · write mode `snapshot_history` - Physically sorted by: `day`, `measure`, `interval` - Primary key: `day`, `measure`, `interval` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/production_intervals.parquet (381.2K rows) Columns: - `day` DATE NOT NULL — The delivery day these measurements belong to — the Europe/Bucharest calendar day, read from the workbook's own "Data" column, an Excel date serial in the file, decoded to a real date here. This is the identity of the day, not the day the sheet was written; that is update_day. (range: "2015-01-01" … "2024-12-31") (~4093 distinct) - `measure` VARCHAR NOT NULL — "gross" or "net": which side of the power stations' own consumption the figure is measured on. Gross is power at the generator terminals; net is what is left after station services, and runs about 6-8% lower. The workbook publishes both as two sheets and both are landed here, so a query that does not filter on this column counts every quarter-hour twice. (range: "gross" … "net") (~2 distinct) - `interval` INTEGER NOT NULL — The source's own interval number inside the day, starting at 1. Read it together with interval_minutes: 1-24 while the workbook was hourly, 1-96 since it went quarter-hourly. It is part of the primary key because it, and not a clock reading, is what the source actually publishes. (range: 1 … 96) (~96 distinct) - `interval_minutes` INTEGER [minutes] — How long one interval is. Transelectrica changed this workbook from hourly to quarter-hourly resolution in the middle of the file: rows up to 2021-01-31 carry 60 and rows from 2021-02-01 carry 15. Averaging or summing across that boundary without reading this column mixes two different things. - `interval_start_local` TIMESTAMP — The wall-clock start of the interval in Romanian civil time (Europe/Bucharest), computed as midnight plus (interval - 1) slots of interval_minutes. It carries no time zone on purpose: the source numbers intervals and never states an offset, so on the two days a year the clock moves this column and the real wall clock part company. Use it to plot; use day plus interval to identify. - `update_day` DATE — The workbook's "Update day" column for this row — a Europe/Bucharest calendar day, like day: the day its author stamped on the line, normally the day before day. NULL where the sheet leaves the cell empty, which the older monthly sheets always do. - `power_mw` DOUBLE [MW] — Average power produced across the whole of Romania during this interval, in megawatts, exactly as the workbook prints it — no rounding, no rescaling. NULL means the cell was empty in the source, which is not the same as zero. - `source_file` VARCHAR — The workbook inside the downloaded archive this row was read from, e.g. "weekly production 2015-2024.xls". Worth keeping because the archive's own filename disagrees with the workbook inside it, in both directions, on this source. - `source_sheet` VARCHAR — The sheet inside that workbook, verbatim — e.g. "valori brute (gross values)". It is where measure was read from, so it is the audit trail for that column. ### consumption_intervals MEASURED electricity consumption for the whole Romanian national grid, one row per day, per interval, per side of the meter, from the "weekly consumption" workbook Transelectrica publishes, covering 2015-01-01 to 2025-12-31 — eleven years, despite the archive being published under the name "weekly consumption_2015-2019.zip". **The series is hourly (interval_minutes = 60) through 2021-01-31 and quarter-hourly (interval_minutes = 15) from 2021-02-01 onward — interval_minutes records which applies to each row.** Gross and net are both present; filter on measure or every day is counted twice. Join it to production_intervals on day and interval to get the national balance. - 451.3K rows · 2.4 MB · write mode `snapshot_history` - Physically sorted by: `day`, `measure`, `interval` - Primary key: `day`, `measure`, `interval` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/consumption_intervals.parquet (451.3K rows) Columns: - `day` DATE NOT NULL — The delivery day these measurements belong to — the Europe/Bucharest calendar day, read from the workbook's own "Data" column, an Excel date serial in the file, decoded to a real date here. This is the identity of the day, not the day the sheet was written; that is update_day. (range: "2015-01-01" … "2025-12-31") (~4494 distinct) - `measure` VARCHAR NOT NULL — "gross" or "net": which side of the power stations' own consumption the figure is measured on. Gross is power at the generator terminals; net is what is left after station services, and runs about 6-8% lower. The workbook publishes both as two sheets and both are landed here, so a query that does not filter on this column counts every quarter-hour twice. (range: "gross" … "net") (~2 distinct) - `interval` INTEGER NOT NULL — The source's own interval number inside the day, starting at 1. Read it together with interval_minutes: 1-24 while the workbook was hourly, 1-96 since it went quarter-hourly. It is part of the primary key because it, and not a clock reading, is what the source actually publishes. (range: 1 … 96) (~96 distinct) - `interval_minutes` INTEGER [minutes] — How long one interval is. Transelectrica changed this workbook from hourly to quarter-hourly resolution in the middle of the file: rows up to 2021-01-31 carry 60 and rows from 2021-02-01 carry 15. Averaging or summing across that boundary without reading this column mixes two different things. - `interval_start_local` TIMESTAMP — The wall-clock start of the interval in Romanian civil time (Europe/Bucharest), computed as midnight plus (interval - 1) slots of interval_minutes. It carries no time zone on purpose: the source numbers intervals and never states an offset, so on the two days a year the clock moves this column and the real wall clock part company. Use it to plot; use day plus interval to identify. - `update_day` DATE — The workbook's "Update day" column for this row — a Europe/Bucharest calendar day, like day: the day its author stamped on the line, normally the day before day. NULL where the sheet leaves the cell empty, which the older monthly sheets always do. - `power_mw` DOUBLE [MW] — Average power consumed across the whole of Romania during this interval, in megawatts, exactly as the workbook prints it — no rounding, no rescaling. NULL means the cell was empty in the source, which is not the same as zero. - `source_file` VARCHAR — The workbook inside the downloaded archive this row was read from, e.g. "weekly production 2015-2024.xls". Worth keeping because the archive's own filename disagrees with the workbook inside it, in both directions, on this source. - `source_sheet` VARCHAR — The sheet inside that workbook, verbatim — e.g. "valori brute (gross values)". It is where measure was read from, so it is the audit trail for that column. ### production_forecast_hourly THE DAY-AHEAD PRODUCTION FORECAST — "prognoza" — NOT a measurement. Every number in this table is what Transelectrica expected the national grid to produce, published before the day happened; nothing here was ever metered. It is kept as its own table, and never merged into production_intervals, precisely so the two cannot be confused. One row per day, per hour, per side of the meter, from the eight yearly workbooks in the archived "ARHIVA - 7Productie3" bundle, covering 2010-09-01 to 2019-12-31 at hourly resolution. Gross and net forecasts are both present; filter on measure. To score the forecast, join it to production_intervals on day for the overlapping years 2015-2019. - 157.7K rows · 1,012 KB · write mode `snapshot_history` - Physically sorted by: `day`, `measure`, `hour` - Primary key: `day`, `measure`, `hour` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/production_forecast_hourly.parquet (157.7K rows) Columns: - `day` DATE NOT NULL — The day the forecast is FOR — the Europe/Bucharest calendar day — read from the workbook's own "Data" column (an Excel date serial, decoded here). The forecast was published before this day, not on it. (range: "2010-09-01" … "2019-12-31") (~3161 distinct) - `measure` VARCHAR NOT NULL — "gross" or "net": which side of the power stations' own consumption the forecast is stated on. Each monthly sheet stacks a gross block above a net block; both are landed. The value is read from the ROMANIAN heading ("Valori brute" / "Valori nete") because the English half of several of those headings says "Gross" above net values — a mistake in the source that would silently mislabel the data if it were trusted. (range: "gross" … "net") (~2 distinct) - `hour` INTEGER NOT NULL — The source's own hour number inside the day, 1 to 24. It is what the workbook publishes (columns int1..int24), so it is what identifies the row. (range: 1 … 25) (~26 distinct) - `hour_start_local` TIMESTAMP — The wall-clock start of the hour in Romanian civil time (Europe/Bucharest), computed as midnight plus (hour - 1) hours. Zone-free on purpose: the source numbers hours and never states an offset, so on the two days a year the clock moves this column and the real wall clock part company. Use it to plot; use day plus hour to identify. - `update_day` DATE — The workbook's "Update day" column for this row — a Europe/Bucharest calendar day, when it carries one. These monthly forecast sheets almost always leave it blank, so this column is usually NULL — which is also why the forecast's publication date cannot be recovered from this archive. - `power_mw` DOUBLE [MW] — Forecast average power for the whole Romanian grid during this hour, in megawatts, exactly as the workbook prints it. NULL means the cell was empty, not zero. - `source_file` VARCHAR — The workbook inside the archive this row came from, e.g. "monthly production 2013.xls". The bundle mixes real BIFF .xls files with real .xlsx files, so this is also the record of which format was actually read. - `source_sheet` VARCHAR — The monthly sheet the row was read from, verbatim — e.g. "Ianuarie 2013". The archive reprints the tail of the previous month at the top of each sheet, so a day can appear in two sheets; when it does, the row kept is the one from the sheet named after that day's own month, and this column says which sheet won. ### performance_reports A CATALOGUE, not data: Transelectrica's annual network performance reports ("Indicatori de performanta"), one row per yearly report, 2009 onwards. These are Word documents — prose with embedded tables, not a machine-readable file format — so nothing is materialised from them and each row says so in materialize_skipped_reason and points at the document instead. This table is a companion to the three data tables, never a substitute for one: it exists so a reader can see the whole of what the source publishes, including the part no query can reach. - 17 rows · 4.5 KB · write mode `replace` - Physically sorted by: `year`, `file_name` - Primary key: `year`, `file_name` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/transelectrica-history/v/01M1NQPZZZG54BBX89G44D1W2W/performance_reports.parquet (17 rows) Columns: - `year` INTEGER NOT NULL — The reporting year the document covers, taken from the digits in its own filename. This is the identity of the row. (range: 2009 … 2025) (~20 distinct) - `file_name` VARCHAR NOT NULL — The document's filename exactly as the publisher wrote it, e.g. "Indicatori de performanta 2024_final.doc". Part of the key because a year can be published twice (a draft and a final). (range: "Indicatori de performanta 2009.doc" … "Indicatori de performanta 2025.doc") (~17 distinct) - `title` VARCHAR — The report title. The source gives no title other than the filename, so this is that filename with the extension removed — said plainly rather than dressed up as metadata the publisher did not provide. - `file_format` VARCHAR — The document format, lower-case, from the filename extension: "doc" for every report published so far. - `file_bytes` BIGINT [bytes] — The uncompressed size of the document as the ZIP's own directory records it. NULL for a report published on its own rather than inside the archive, where no size is stated. - `modified_at` TIMESTAMP — The timestamp the ZIP directory carries for the document — in practice when the publisher last saved it. NULL for a standalone report. A DOS timestamp inside a ZIP records no real offset, so this column stores the archive's raw date/time fields under a UTC label as a storage convention (see workbook.ts's dosDateToUtc), not a claim that the publisher's own clock was actually set to UTC — the true zone it kept is unrecoverable from the archive. - `archive_url` VARCHAR — The ZIP the document was found inside, when it came from one. NULL for a report the listing page publishes on its own. - `source_url` VARCHAR — A direct link to the document, when the listing page publishes one. NULL for a document that exists only inside the archive, which has no per-file URL — use archive_url then. - `materialize_skipped_reason` TEXT — Why no table was built from this document. It is filled in for every row here, and the reason is always the same in substance: the report is a Word document, a page-layout format holding prose and hand-formatted tables with no stable machine-readable structure, so extracting numbers from it would mean guessing at them. The document is linked instead. ## Performance notes - There are no indexes in a lakehouse. The physical sort order plus Parquet row-group min/max statistics ARE the index. Predicates on the leading sort columns prune whole row groups; everything else is a full column scan. - `production_intervals` prunes on `day` → `measure` → `interval`. - `consumption_intervals` prunes on `day` → `measure` → `interval`. - `production_forecast_hourly` prunes on `day` → `measure` → `hour`. - `performance_reports` prunes on `year` → `file_name`. - `ILIKE '%needle%'` cannot prune: it decompresses that whole column. Prefer prefix predicates on the lowercased search companion columns where they exist. - Every file URL under `/v//` is immutable. Cache it as aggressively as you like. - Files are never rewritten in place — a new ingest writes a new version path. Re-read the manifest to pick up updates rather than polling the file URLs. - Pull only the columns you need. Parquet is columnar and the reader range-GETs what it reads. ## Do not - Do not scrape https://data.bunnytech.app HTML. Every number on those pages comes from the manifest above. - Do not hit the upstream source (https://www.transelectrica.ro/ro/web/tel/productie) in bulk. This mirror exists so you do not have to. - Do not read DuckLake internal data files with a plain Parquet reader. They come with positional delete files, and ignoring those silently returns deleted rows. Use the ducklake extension, or use the published exports above. - Do not republish without the attribution line, and do not imply endorsement.