# CMTEB Bucharest district-heating status > The operational status of the Bucharest district-heating network — every heating point (substation, thermal module or similar) in the city, and whether it is running normally, has a known deficiency, or is in fault — from the status map published by Compania Municipală Termoenergetica București (CMTEB). CMTEB shows only the current moment; this dataset turns hourly snapshots of it into a queryable outage history that does not otherwise exist. - Dataset id: `cmteb-heating-bucharest` - Update cadence: hourly - Upstream source: https://www.cmteb.ro/harta_stare_sistem_termoficare_bucuresti.php - Tags: heating, district-heating, bucharest, romania, infrastructure, utilities, cmteb, energy, outages - Current version: ingest `01M54NPAGTW44D5PTTS910A2HW`, snapshot 2026-10-17, generated 2026-10-17T10:14:43.587Z ## Licence and attribution - Licence: Romanian official acts and public-interest information — outside copyright (`ro-official-acts-public-domain`) - Licence text: https://legislatie.just.ro/Public/DetaliiDocument/7816 - Required attribution: Date despre starea rețelei de termoficare publicate de CMTEB — cmteb.ro - Notes: Legea 8/1996 privind dreptul de autor, art. 9, excludes from copyright both official texts of an administrative nature and simple facts and data. The heating-network status snapshots republished here are bare facts — the operational state of a public utility's facilities at a point in time — and the facility register itself is the official record CMTEB, a municipal company, maintains for that network. They are also public-interest information under Legea 544/2001 and re-usable public-sector information under Legea 179/2022, which transposes the EU Open Data Directive. HOWEVER, and a reuser must weigh this: checked cmteb.ro for a licence, terms or legal page and found none: the only footer text anywhere on the site is "Compania Municipală Termoenergetica București S.A. • 2019 - 2026", with no © notice and no link to any terms of use. This dataset therefore rests on the legal position above rather than on any grant from CMTEB, and anyone republishing it should make their own assessment. What it publishes is a DERIVED work: only facility_name, status (both the normalised value and the source's own Romanian `stare` text), tip, remediere, latitude/longitude and the snapshot timestamp are republished — no map imagery, no page markup, no other content from cmteb.ro. robots.txt (checked 2026-09-02) disallows exactly three paths: `/_incluse/`, `/_notes/` and `/files/CVs/istoric/`; the page this connector reads, `/harta_stare_sistem_termoficare_bucuresti.php`, is not covered by any of them. Finally: the snapshot history accumulated by this connector — the whole point of the dataset — is created here, run by run; CMTEB's own page shows only the current moment and keeps no archive, so a past snapshot in this table cannot be corroborated against the source after the fact. 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/cmteb-heating-bucharest/manifest.json - All datasets: https://data-directory.fsn1.your-objectstorage.com/public/index.json - Human page: https://data.bunnytech.app/datasets/cmteb-heating-bucharest 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 heating_points AS SELECT * FROM read_parquet('https://data-directory.fsn1.your-objectstorage.com/public/cmteb-heating-bucharest/v/01M54NPAGTW44D5PTTS910A2HW/heating_points.parquet'); ``` Example queries: -- The network right now: Counts by status in the most recent snapshot. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); WITH latest AS ( SELECT max(snapshot_date) AS d, max(snapshot_hour) AS h FROM datadirectory.cmteb_heating_bucharest.heating_points WHERE snapshot_date = (SELECT max(snapshot_date) FROM datadirectory.cmteb_heating_bucharest.heating_points) ) SELECT status, count(*) AS points FROM datadirectory.cmteb_heating_bucharest.heating_points, latest WHERE snapshot_date = latest.d AND snapshot_hour = latest.h GROUP BY status ORDER BY points DESC ``` -- Outage history of one facility: facility_name_lc is searched, never lower(facility_name) — a function on a column cannot use parquet statistics. ```sql ATTACH IF NOT EXISTS 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/catalog.ducklake' AS "datadirectory" (READ_ONLY); SELECT snapshot_date, snapshot_hour, status, stare, remediere FROM datadirectory.cmteb_heating_bucharest.heating_points WHERE facility_name_lc LIKE '%barbu vacarescu%' ORDER BY snapshot_date, snapshot_hour ``` ## 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/cmteb-heating-bucharest/catalog.ducklake' AS cmteb_heating_bucharest (READ_ONLY); SELECT * FROM cmteb_heating_bucharest.main.heating_points LIMIT 5; USE cmteb_heating_bucharest; ``` - Latest catalog: https://data-directory.fsn1.your-objectstorage.com/catalog/cmteb-heating-bucharest/catalog.ducklake - This exact version: https://data-directory.fsn1.your-objectstorage.com/catalog/cmteb-heating-bucharest/v/01M54NPAGTW44D5PTTS910A2HW/catalog.ducklake (sha256 9b4cad6198eeb312e66ecc756e49919dd6d0bf7639ace2573f14d1597ec0014c) - Written by DuckDB v1.5.5 - Time travel: this ingest is snapshot 32 — e.g. `SELECT * FROM cmteb_heating_bucharest.heating_points AT (VERSION => 32)` ## 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/cmteb-heating-bucharest/v/01M54NPAGTW44D5PTTS910A2HW/**/*.parquet'); ``` ## Schema ### heating_points One row per heating point (substation, thermal module or similar) in the Bucharest district-heating network per hourly snapshot, as CMTEB's own status map shows it at that moment. CMTEB publishes only the current state; this table is what turns repeated snapshots of it into a queryable outage history. - 27.6K rows · 80 KB · write mode `snapshot_history` - Physically sorted by: `snapshot_date`, `snapshot_hour`, `status`, `facility_name` - Primary key: `snapshot_date`, `snapshot_hour`, `facility_name`, `facility_seq` - Text search columns: `facility_name` - Files: 1 - https://data-directory.fsn1.your-objectstorage.com/public/cmteb-heating-bucharest/v/01M54NPAGTW44D5PTTS910A2HW/heating_points.parquet (27.6K rows) Columns: - `snapshot_date` DATE NOT NULL — The calendar day (UTC) this snapshot was taken. Leading sort column and, together with snapshot_hour, the reload unit: re-ingesting the same (date, hour) pair replaces exactly those rows and nothing else. (range: "2026-09-02" … "2026-10-17") (~5 distinct) - `snapshot_hour` INTEGER NOT NULL [hour of day (UTC)] — The hour of day (0-23, UTC) this snapshot belongs to — the connector's own poll slot, not a value CMTEB publishes. Combined with snapshot_date it names the reload unit: this connector runs hourly, and keying on the hour (rather than the date alone) is what stops a later run in the same day from erasing an earlier one's outages before they are ever queried. See the file-level comment on this table for the full argument. (range: 0 … 23) (~20 distinct) - `snapshot_at` TIMESTAMPTZ NOT NULL — The exact instant (UTC) this snapshot was fetched — ctx.runAt, unrounded. snapshot_date/snapshot_hour bucket a run into its reload unit; this column keeps the precise minute for anyone who needs it. - `facility_name` VARCHAR NOT NULL — The facility's name, exactly as CMTEB's `denumire` field gives it, trimmed of leading and trailing whitespace only (the source pads a number of names with a trailing space, e.g. "VSNord "). This is the practical key for finding one facility, but the source does NOT guarantee it is unique: two of the 951 points captured on 2026-09-02 are both named "P.D." at different coordinates. Use facility_seq (and, where available, latitude/longitude) to tell such rows apart; do not assume one name is one place. (range: "1 Alexandria" … "Școala Gimnazială Regina Maria nr. 198") (~842 distinct) - `facility_name_lc` VARCHAR NOT NULL — facility_name, lowercased and accent-folded, for case- and accent-insensitive search. Query this, not lower(facility_name) — a function on a column cannot use parquet statistics. - `facility_seq` INTEGER NOT NULL — Disambiguates a facility_name that repeats within the same snapshot — 0 for the (normal) case of a unique name. When a name is shared by more than one physical point in one fetch, every point sharing it is sorted deterministically by (longitude, latitude, tip, stare) as CMTEB reported them that hour, ascending, and numbered 0, 1, 2, … in that order — never by array position, which the source does not promise is stable. Because a physical heating point's coordinates do not move, the same point keeps the same facility_seq across snapshots as long as CMTEB keeps reporting a coordinate for it, which is what makes (facility_name, facility_seq) usable as a stable per-point key alongside the primary key's snapshot columns. (~2 distinct) - `status` VARCHAR NOT NULL — The facility's status, decoded from which of the three arrays the source page listed it in, never from its raw text: "functional" (from passedFeatures_verde, colour #008217 on the source map — running normally), "deficient" (passedFeatures_galben, #ffe53e — a known deficiency but still in service) or "fault" (passedFeatures_rosu, #e0002b — stopped / under repair). The `stare` column carries the source's own free text for the same row; this column is what is safe to filter and aggregate on. (range: "deficient" … "functional") - `stare` VARCHAR — The source's own Romanian status text, verbatim (CMTEB's `stare` field) — anything from the fixed phrase "Functionare normala" for every functional point to a free-text description of a specific fault or works order. Not normalised and not guaranteed to take any fixed set of values; read `status` for that. - `tip` VARCHAR — The kind of issue CMTEB records for this point (`tip`), e.g. "Oprire ACC" (hot-water supply stopped) or "Deficienta ACC" (hot-water supply deficiency). CMTEB writes the literal string "-" for a functional point with nothing to report, which is kept verbatim rather than converted to NULL: it is the source's own value for "nothing wrong", distinct from a genuinely missing field. - `remediere` VARCHAR — The source's own remediation / expected-fix note (`remediere`), verbatim. Present only on some deficient/fault rows in practice (never observed on a functional one) and, when present, usually but not reliably formatted "DD.MM.YYYY HH:mm" — kept as text rather than parsed into a date/time column because that format is CMTEB's convention, not a documented guarantee. - `latitude` DOUBLE [degrees] — The facility's latitude (WGS 84). NULL when CMTEB's coordinate for this row is missing, non-numeric, or falls outside a generous Bucharest-area bounding box (44.30-44.60°N, 25.90-26.30°E) — treated as a bad coordinate and discarded rather than published, because every one of the 951 points in the 2026-09-02 capture already falls well inside it (44.37-44.49°N observed). The row itself is always kept; only the coordinate pair is nulled. describeRun logs how many rows this affected on each run. - `longitude` DOUBLE [degrees] — The facility's longitude (WGS 84), validated against the same bounding box as latitude (see its description) and nulled together with it — a coordinate pair is accepted or rejected as a pair, never independently. ## 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. - `heating_points` prunes on `snapshot_date` → `snapshot_hour` → `status` → `facility_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.cmteb.ro/harta_stare_sistem_termoficare_bucuresti.php) 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.