{
  "manifestVersion": 1,
  "dataset": {
    "id": "infoaer-bucharest-air",
    "name": "Bucharest air quality (InfoAer)",
    "description": "Current readings from InfoAer, the municipal air-quality sensor network Primăria Municipiului București operates across Bucharest. The source publishes each sensor's current pollutant readings as a small HTML fragment; this connector reshapes that into long format — one row per (sensor, reading_time, pollutant) — so pollutants can be filtered, aggregated and compared directly.",
    "descriptions": {
      "ro": "Citiri curente de la rețeaua municipală de senzori de calitate a aerului InfoAer, operată de Primăria Municipiului București, pentru orașul București. Datele publicate de sursă sub formă de fragment HTML, câte unul pentru fiecare senzor, au fost restructurate într-un format lung — câte un rând pentru fiecare combinație senzor-moment-poluant — astfel încât poluanții pot fi filtrați, agregați și comparați direct.",
      "en": "Current readings from InfoAer, the municipal air-quality sensor network Primăria Municipiului București operates across Bucharest. The source publishes each sensor's current pollutant readings as a small HTML fragment; this connector reshapes that into long format — one row per (sensor, reading_time, pollutant) — so pollutants can be filtered, aggregated and compared directly."
    },
    "names": {
      "ro": "Calitatea aerului în București (InfoAer)",
      "en": "Bucharest air quality (InfoAer)"
    },
    "sourceUrl": "https://infoaer.pmb.ro/",
    "license": {
      "id": "ro-official-acts-public-domain",
      "name": "Romanian official acts and public-interest information — outside copyright",
      "url": "https://legislatie.just.ro/Public/DetaliiDocument/7816",
      "attribution": "Date despre calitatea aerului publicate de Primăria Municipiului București — InfoAer — infoaer.pmb.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 sensor readings republished here are bare factual measurements — pollutant concentrations captured by municipal monitoring hardware — and the sensor catalogue is the official record of that public monitoring network's own devices. 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: no explicit reuse licence is published anywhere on infoaer.pmb.ro — checked directly on 2026-09-01/02: the site carries no footer copyright line, no terms page and no legal link, and its \"Legislație\" page lists only national/EU air-quality law, not site terms of use. This dataset therefore rests on the legal position above rather than on any grant from Primăria Municipiului București, and anyone republishing it should make their own assessment. This is a DERIVED work, not a republication of the site: `sensors` carries only each device's id, name, network type, coordinates and icon path, and `readings` carries only the pollutant readings reshaped out of the per-sensor popup HTML into long format — no page markup, images, or any other part of the site is republished. `robots.txt` on this domain has an empty `Disallow` — nothing on the site is off-limits to automated access. Finally, and most important for how this dataset should be used: these are municipal INDICATIVE sensors (a mix of Clarity, low-cost \"CalitateAer\", Bettair and MQTT devices), NOT the national regulatory reference network (RNMCA / calitateaer.ro). Do not present a value from this dataset as an official air-quality compliance measurement, and do not use it in place of a certified RNMCA reference station where the distinction matters (e.g. a legal exceedance finding)."
    },
    "cadence": "hourly",
    "tags": [
      "air-quality",
      "environment",
      "sensors",
      "pollution",
      "romania",
      "bucharest",
      "infoaer",
      "pmb"
    ],
    "sources": []
  },
  "ingest": {
    "id": "01M5J7K5G4Y8JED8SXP6633GF4",
    "startedAt": "2026-10-22T16:37:32.292Z",
    "snapshotDate": "2026-10-22",
    "catalogSnapshotId": 154,
    "params": {}
  },
  "baseUrl": "https://data-directory.fsn1.your-objectstorage.com/public/infoaer-bucharest-air",
  "catalog": {
    "url": "https://data-directory.fsn1.your-objectstorage.com/catalog/infoaer-bucharest-air/v/01M5J7K5G4Y8JED8SXP6633GF4/catalog.ducklake",
    "latestUrl": "https://data-directory.fsn1.your-objectstorage.com/catalog/infoaer-bucharest-air/catalog.ducklake",
    "sha256": "7606a15e6fbc937096d4dcec1824bd9b1232d9c2df8784c41bf057b456f4b812",
    "duckdbVersion": "v1.5.5",
    "attachExample": "ATTACH 'ducklake:https://data-directory.fsn1.your-objectstorage.com/catalog/infoaer-bucharest-air/catalog.ducklake' AS lake (READ_ONLY); SELECT * FROM lake.main.sensors LIMIT 5;"
  },
  "queryHints": {
    "examples": [
      {
        "title": "Current PM2.5 reading at every sensor, most recent first",
        "sql": "SELECT s.name, r.reading_time, r.value, r.unit, r.quality_label\nFROM readings r\nJOIN sensors s USING (sensor_id)\nWHERE r.pollutant = 'pm_2_5'\nORDER BY r.reading_time DESC\nLIMIT 20",
        "description": "pollutant leads the sort, so this filters to one pollutant's row groups before ordering by time."
      },
      {
        "title": "City-wide average NO2 over the last 24 hours",
        "sql": "SELECT date_trunc('hour', reading_time) AS hour, avg(value) AS avg_no2\nFROM readings\nWHERE pollutant = 'no2'\n  AND reading_time >= now() - INTERVAL 24 HOUR\nGROUP BY 1\nORDER BY 1"
      },
      {
        "title": "Every sensor whose name mentions a school",
        "sql": "SELECT sensor_id, name, sensor_type, latitude, longitude\nFROM sensors\nWHERE name_lc LIKE '%scoala%'\nORDER BY name_lc",
        "description": "Search name_lc, never lower(name): a function on a column cannot prune."
      }
    ]
  },
  "tables": [
    {
      "name": "sensors",
      "description": "One row per air-quality sensor InfoAer lists for Bucharest, with its location and network identity. Upserted: first_seen_date/last_seen_date widen over time, everything else reflects the most recent poll.",
      "descriptions": {
        "ro": "Câte un rând pentru fiecare senzor de calitate a aerului listat de InfoAer pentru București, cu locația și identitatea sa în rețea. Actualizat prin upsert: first_seen_date/last_seen_date se extind în timp, restul coloanelor reflectă cea mai recentă interogare.",
        "en": "One row per air-quality sensor InfoAer lists for Bucharest, with its location and network identity. Upserted: first_seen_date/last_seen_date widen over time, everything else reflects the most recent poll."
      },
      "rowCount": 114,
      "lastRunRows": 70,
      "totalBytes": 12413,
      "sortBy": [
        "sensor_id"
      ],
      "searchColumns": [
        "name"
      ],
      "primaryKey": [
        "sensor_id"
      ],
      "writeMode": "upsert",
      "storage": "lake",
      "columns": [
        {
          "name": "sensor_id",
          "type": "BIGINT",
          "nullable": false,
          "description": "InfoAer's own numeric sensor id (\"Id\" in the listing response). Stable for the life of the device; joins to readings.sensor_id.",
          "descriptions": {
            "ro": "Identificatorul numeric propriu al senzorului în InfoAer (\"Id\" în răspunsul listei). Rămâne stabil pe toată durata de viață a dispozitivului; se leagă de readings.sensor_id.",
            "en": "InfoAer's own numeric sensor id (\"Id\" in the listing response). Stable for the life of the device; joins to readings.sensor_id."
          },
          "min": "1",
          "max": "99",
          "distinctApprox": 115
        },
        {
          "name": "name",
          "type": "VARCHAR",
          "nullable": true,
          "description": "Sensor location name as published by InfoAer (e.g. a school, hospital or landmark near the installation). Null when the source sent nothing usable.",
          "descriptions": {
            "ro": "Numele locației senzorului, așa cum este publicat de InfoAer (de regulă o școală, un spital sau un reper aflat lângă instalație). Este null atunci când sursa nu a trimis nimic utilizabil.",
            "en": "Sensor location name as published by InfoAer (e.g. a school, hospital or landmark near the installation). Null when the source sent nothing usable."
          }
        },
        {
          "name": "name_lc",
          "type": "VARCHAR",
          "nullable": true,
          "description": "name, lowercased and accent-folded. The prefix-search column — see searchColumns.",
          "descriptions": {
            "ro": "name, cu litere mici și fără diacritice. Coloana folosită pentru căutare.",
            "en": "name, lowercased and accent-folded. The prefix-search column — see searchColumns."
          }
        },
        {
          "name": "sensor_type",
          "type": "VARCHAR",
          "nullable": true,
          "description": "The sensor hardware/network family InfoAer reports this device under (observed values: \"Clarity\", \"CalitateAer\", \"Bettair\", \"MQTT\") — which vendor or protocol powers this installation, not an air-quality classification. Also selects which pin icon is shown and is required to look up this sensor's own popup/readings endpoint.",
          "descriptions": {
            "ro": "Familia de senzori/rețea sub care InfoAer raportează acest dispozitiv (valori observate: \"Clarity\", \"CalitateAer\", \"Bettair\", \"MQTT\") — ce producător sau protocol alimentează această instalație, nu o clasificare a calității aerului. Determină și iconița afișată pe hartă și este necesară pentru a interoga propriul punct final de citiri al acestui senzor.",
            "en": "The sensor hardware/network family InfoAer reports this device under (observed values: \"Clarity\", \"CalitateAer\", \"Bettair\", \"MQTT\") — which vendor or protocol powers this installation, not an air-quality classification. Also selects which pin icon is shown and is required to look up this sensor's own popup/readings endpoint."
          }
        },
        {
          "name": "latitude",
          "type": "DOUBLE",
          "nullable": true,
          "description": "Sensor location, WGS 84 latitude. Null when InfoAer's own coordinate for this sensor fell outside a generous Bucharest bounding box (see BUCHAREST_BBOX in feed.ts) — a small number of devices report an unset-GPS sentinel of (0, 0). The row itself is still published; only the untrustworthy coordinate pair is nulled.",
          "descriptions": {
            "ro": "Locația senzorului, latitudine WGS 84. Este null atunci când coordonata proprie raportată de InfoAer pentru acest senzor a picat în afara unei zone generoase din jurul Bucureștiului (vezi BUCHAREST_BBOX în feed.ts) — un număr mic de dispozitive raportează o valoare-santinelă de GPS nesetat, (0, 0). Rândul rămâne publicat; doar perechea de coordonate nesigură este anulată.",
            "en": "Sensor location, WGS 84 latitude. Null when InfoAer's own coordinate for this sensor fell outside a generous Bucharest bounding box (see BUCHAREST_BBOX in feed.ts) — a small number of devices report an unset-GPS sentinel of (0, 0). The row itself is still published; only the untrustworthy coordinate pair is nulled."
          },
          "unit": "degrees"
        },
        {
          "name": "longitude",
          "type": "DOUBLE",
          "nullable": true,
          "description": "Sensor location, WGS 84 longitude. Nulled under the same bounding-box check as latitude — always null exactly when latitude is null.",
          "descriptions": {
            "ro": "Locația senzorului, longitudine WGS 84. Anulată sub aceeași verificare a zonei ca și latitude — este null exact atunci când latitude este null.",
            "en": "Sensor location, WGS 84 longitude. Nulled under the same bounding-box check as latitude — always null exactly when latitude is null."
          },
          "unit": "degrees"
        },
        {
          "name": "icon_path",
          "type": "VARCHAR",
          "nullable": true,
          "description": "Map pin icon path as published by InfoAer, relative to https://infoaer.pmb.ro (e.g. \"/modules/infoaer/images/pins/Clarity-2.svg\"). Cosmetic; not resolved to an absolute URL.",
          "descriptions": {
            "ro": "Calea către iconița afișată pe hartă, așa cum este publicată de InfoAer, relativă la https://infoaer.pmb.ro (de ex. \"/modules/infoaer/images/pins/Clarity-2.svg\"). Cosmetică; nu este rezolvată într-un URL absolut.",
            "en": "Map pin icon path as published by InfoAer, relative to https://infoaer.pmb.ro (e.g. \"/modules/infoaer/images/pins/Clarity-2.svg\"). Cosmetic; not resolved to an absolute URL."
          }
        },
        {
          "name": "first_seen_date",
          "type": "DATE",
          "nullable": false,
          "description": "The first run day (UTC) on which this connector saw this sensor. Maintained automatically by the upsert loader — never overwritten, only widened.",
          "descriptions": {
            "ro": "Prima zi de rulare (UTC) în care acest conector a observat acest senzor. Întreținută automat de mecanismul de upsert — nu este niciodată suprascrisă, doar extinsă.",
            "en": "The first run day (UTC) on which this connector saw this sensor. Maintained automatically by the upsert loader — never overwritten, only widened."
          }
        },
        {
          "name": "last_seen_date",
          "type": "DATE",
          "nullable": false,
          "description": "The most recent run day (UTC) on which InfoAer still listed this sensor.",
          "descriptions": {
            "ro": "Cea mai recentă zi de rulare (UTC) în care InfoAer mai lista acest senzor.",
            "en": "The most recent run day (UTC) on which InfoAer still listed this sensor."
          }
        }
      ],
      "files": [
        {
          "url": "https://data-directory.fsn1.your-objectstorage.com/public/infoaer-bucharest-air/v/01M5J7K5G4Y8JED8SXP6633GF4/sensors.parquet",
          "sizeBytes": 12413,
          "rowCount": 114,
          "etag": "\"5efac1d8c08568b6d05961e5d145f507\"",
          "stats": {
            "sensor_id": {
              "min": "1",
              "max": "99"
            }
          }
        }
      ],
      "example": [
        {
          "sensor_id": 1,
          "name": "Scoala 6",
          "name_lc": "scoala 6",
          "sensor_type": "Clarity",
          "latitude": 44.4952285669381,
          "longitude": 26.0743409201351,
          "icon_path": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTUzIiB3aWR0aD0iMzIiIGhlaWdodD0iNDkiPgogIDxwYXRoIGQ9Ik01MCw4IEMyMiw4IDQsMjggNCw1MiBDNCw4MiA1MCwxNDggNTAsMTQ4IEM1MCwxNDggOTYsODIgOTYsNTIgQzk2LDI4IDc4LDggNTAsOCBaIiBmaWxsPSIjODdDRUVCIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utb3BhY2l0eT0iMSIgc3Ryb2tlLXdpZHRoPSIxMCIgcGFpbnQtb3JkZXI9InN0cm9rZSBmaWxsIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MiIgcj0iMjEiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuODgiLz4KPC9zdmc+",
          "first_seen_date": "2026-09-02T00:00:00.000Z",
          "last_seen_date": "2026-09-04T00:00:00.000Z"
        },
        {
          "sensor_id": 2,
          "name": "AUTOLABORATOR PMB",
          "name_lc": "autolaborator pmb",
          "sensor_type": "Clarity",
          "latitude": 44.39822207470184,
          "longitude": 26.04437075794886,
          "icon_path": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAwnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMgDPvnFDsCiQOE49Ctk3aDHX8B0qpss4TzMDIhYX+/nuHWwSRBUtFcc44GqVK5WaJxog2mKIMHsktWL/1wCmwtWMQs1QU6+nQazNAsSxcjvbuwrUIV99cvI54BfaKeP9youhF4CuQGrflXqpbrF7Y9rtB5QifRdeyfutj2HsneAfMOQjQGZA6AfhDQLCnGjGQXI3Tk0ZhQ3MwW8m9PB8IH5QlZF0m9N3EAAAGEaUNDUElDQyBwcm9maWxlAAB4nH2RPUjDQBzFX1NLRSqCdlBxyFCd7KIijqWKRbBQ2gqtOphc+gVNGpIUF0fBteDgx2LVwcVZVwdXQRD8AHF1cVJ0kRL/lxRaxHhw3I939x537wChWWWq2RMDVM0y0om4mMuvisFXBDGCAAYRlZipJzOLWXiOr3v4+HoX5Vne5/4c/UrBZIBPJI4x3bCIN4hnNy2d8z5xmJUlhficeNKgCxI/cl12+Y1zyWGBZ4aNbHqeOEwslrpY7mJWNlTiGeKIomqUL+RcVjhvcVardda+J39hqKCtZLhOcwwJLCGJFETIqKOCKixEadVIMZGm/biHf9Txp8glk6sCRo4F1KBCcvzgf/C7W7M4PeUmheJA4MW2P8aB4C7Qatj297Ftt04A/zNwpXX8tSYw90l6o6NFjoCBbeDiuqPJe8DlDjD8pEuG5Eh+mkKxCLyf0TflgaFboG/N7a29j9MHIEtdLd8AB4fARImy1z3e3dvd279n2v39ALUPcsEyBxnIAAANdmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDozMjJlOGJlNS1mNzAxLTRkZGItYWRmNy00NjU3Y2U3MjZiYTUiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTM0NGZkYzYtNmRkYy00NjQ2LWFmZmMtZWUyNzhjMmQ4YTFhIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MzAwMTQxNGItN2RmMy00YjA2LWIxODgtMGI1NDcwNTlkYWJkIgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE2OTAyNjI0MjIwMzMyNzAiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zNCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjM6MDc6MjVUMDg6MjA6MTcrMDM6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDIzOjA3OjI1VDA4OjIwOjE3KzAzOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6N2E4ZGNhMzUtMzRhZS00NWY0LWEwMGEtN2FiYTI3OThkMjFkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDIzLTA3LTI1VDA4OjIwOjIyIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/Pr7WuSoAAAMAUExURUdwTAAAAAAAAAAAAAAAAAsHBxIJCQ4FBR8QERYMDAAAAAAAAAEAAAAAAAAAAAEAAAcCAgcHBwkCAoGIjC0tLQICAgsDAxsbGyEhIfIPDxUWFwsKCissLTY2NhkZGSMjIzg4ODMzMz0/QA4ODi8vLxgYGGJobG8EBDo7PDI1OGIDA4EFBXB3fHQGBhYWFlJTVD5AQk1PUPMKCmxydkREREtMTfgKCuAJCbhAQlZWVmxtbuw/P25wcFpcXllbW77AwGZrbXd8f98JCWducp2foYqKi6ChokxNTrQHB6kGBrK0tZGUloaKjUNDQ5ZcXZWZmz09PZdSUtgJCfEKCu8KCu4KCvAKCqipqTrePewsOQ+I/zk5OXJ6fzo6Ot7e3j09PWNjY1JSUkNDQz8/P1tbXGlpaVVVVWBgYFdXV2tsbUtLS0VFRXFxcUlJSXZ2dl1dXllZWa2wskdHRzw8PE1NTW5ub6KmqXN0dGZnZ/8LC09PT5ubm3h4eZ6jpYGBgdrb2/Hx8bCztfb29rW4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",
          "first_seen_date": "2026-09-02T00:00:00.000Z",
          "last_seen_date": "2026-09-04T00:00:00.000Z"
        }
      ]
    },
    {
      "name": "readings",
      "description": "One row per (sensor, reading_time, pollutant): a current pollutant reading InfoAer reports for one sensor, as of that sensor's own last-updated time. Reshaped from the per-sensor popup endpoint's HTML reading list into long format so pollutants can be filtered, grouped and compared directly instead of one column family per pollutant. A reading is never revised once published — the next poll of the same sensor arrives with a later reading_time (or the identical one, if the sensor has not updated), never a correction.",
      "descriptions": {
        "ro": "Câte un rând pentru fiecare combinație (senzor, moment_citire, poluant): o citire curentă a unui poluant raportată de InfoAer pentru un senzor, la momentul propriei sale ultime actualizări. Restructurat din lista de citiri HTML a punctului final de tip \"popup\" al fiecărui senzor într-un format lung, astfel încât poluanții pot fi filtrați, grupați și comparați direct. O citire nu este niciodată modificată după ce a fost publicată — următoarea interogare a aceluiași senzor sosește cu un moment_citire mai recent (sau identic, dacă senzorul nu s-a actualizat), niciodată ca o corecție.",
        "en": "One row per (sensor, reading_time, pollutant): a current pollutant reading InfoAer reports for one sensor, as of that sensor's own last-updated time. Reshaped from the per-sensor popup endpoint's HTML reading list into long format so pollutants can be filtered, grouped and compared directly instead of one column family per pollutant. A reading is never revised once published — the next poll of the same sensor arrives with a later reading_time (or the identical one, if the sensor has not updated), never a correction."
      },
      "rowCount": 44685,
      "lastRunRows": 407,
      "totalBytes": 125405,
      "sortBy": [
        "pollutant",
        "reading_time",
        "sensor_id"
      ],
      "searchColumns": [],
      "primaryKey": [
        "sensor_id",
        "reading_time",
        "pollutant"
      ],
      "writeMode": "append",
      "storage": "lake",
      "columns": [
        {
          "name": "sensor_id",
          "type": "BIGINT",
          "nullable": false,
          "description": "The InfoAer sensor id this reading came from. Joins to sensors.sensor_id.",
          "descriptions": {
            "ro": "Identificatorul senzorului InfoAer de la care provine această citire. Se leagă de sensors.sensor_id.",
            "en": "The InfoAer sensor id this reading came from. Joins to sensors.sensor_id."
          },
          "min": "1",
          "max": "99",
          "distinctApprox": 115
        },
        {
          "name": "reading_time",
          "type": "TIMESTAMPTZ",
          "nullable": false,
          "description": "The instant this reading is for — the timestamp InfoAer's own popup published alongside it (Europe/Bucharest local time on the source, converted to UTC here), not the time this connector happened to poll the API. Two sensors polled in the same run can carry different reading_time values when one of them has not updated in a while.",
          "descriptions": {
            "ro": "Momentul la care se referă această citire — marcajul de timp publicat de propriul punct final \"popup\" al InfoAer (ora locală România, convertită aici în UTC), nu momentul la care acest conector a interogat efectiv API-ul. Doi senzori interogați în aceeași rulare pot avea valori diferite ale reading_time atunci când unul dintre ei nu s-a mai actualizat de ceva vreme.",
            "en": "The instant this reading is for — the timestamp InfoAer's own popup published alongside it (Europe/Bucharest local time on the source, converted to UTC here), not the time this connector happened to poll the API. Two sensors polled in the same run can carry different reading_time values when one of them has not updated in a while."
          },
          "min": "2023-11-14 14:00:00+00",
          "max": "2026-10-22 16:30:00+00",
          "distinctApprox": 724
        },
        {
          "name": "pollutant",
          "type": "VARCHAR",
          "nullable": false,
          "description": "Canonical pollutant code, slugified from the source's own display label (e.g. \"PM 2.5\" -> \"pm_2_5\", \"Index VOC\" -> \"index_voc\") — never a hand-maintained lookup table, so a label InfoAer adds tomorrow gets a code automatically. See pollutant_label for the label exactly as published.",
          "descriptions": {
            "ro": "Codul canonic al poluantului, obținut prin slugificarea etichetei proprii a sursei (de ex. \"PM 2.5\" -> \"pm_2_5\", \"Index VOC\" -> \"index_voc\") — niciodată dintr-un tabel de corespondență întreținut manual, astfel încât o etichetă adăugată de InfoAer mâine primește automat un cod. Vezi pollutant_label pentru eticheta exact așa cum este publicată.",
            "en": "Canonical pollutant code, slugified from the source's own display label (e.g. \"PM 2.5\" -> \"pm_2_5\", \"Index VOC\" -> \"index_voc\") — never a hand-maintained lookup table, so a label InfoAer adds tomorrow gets a code automatically. See pollutant_label for the label exactly as published."
          },
          "min": "h2s",
          "max": "zgomot",
          "distinctApprox": 14
        },
        {
          "name": "pollutant_label",
          "type": "VARCHAR",
          "nullable": true,
          "description": "Pollutant name exactly as InfoAer's popup displays it (e.g. \"PM 2.5\", \"NO2\", \"Temperatura\", \"Index VOC\") — verbatim, not a description of it.",
          "descriptions": {
            "ro": "Numele poluantului exact așa cum îl afișează InfoAer (de ex. \"PM 2.5\", \"NO2\", \"Temperatura\", \"Index VOC\") — verbatim, nu o descriere a acestuia.",
            "en": "Pollutant name exactly as InfoAer's popup displays it (e.g. \"PM 2.5\", \"NO2\", \"Temperatura\", \"Index VOC\") — verbatim, not a description of it."
          }
        },
        {
          "name": "unit",
          "type": "VARCHAR",
          "nullable": true,
          "description": "Unit of value, parsed out of the same text as the number (e.g. \"µg/m³\", \"°C\", \"%\", \"hPa\", \"dBA\"). Null for a handful of unitless index readings InfoAer itself publishes with no unit (e.g. \"Index VOC\", \"Index NOx\").",
          "descriptions": {
            "ro": "Unitatea de măsură a valorii, extrasă din același text ca și numărul (de ex. \"µg/m³\", \"°C\", \"%\", \"hPa\", \"dBA\"). Este null pentru câteva citiri de tip index fără unitate, publicate ca atare chiar de InfoAer (de ex. \"Index VOC\", \"Index NOx\").",
            "en": "Unit of value, parsed out of the same text as the number (e.g. \"µg/m³\", \"°C\", \"%\", \"hPa\", \"dBA\"). Null for a handful of unitless index readings InfoAer itself publishes with no unit (e.g. \"Index VOC\", \"Index NOx\")."
          }
        },
        {
          "name": "value",
          "type": "DECIMAL(10,4)",
          "nullable": true,
          "description": "The reading's numeric value, in `unit`. Nullable in principle like every non-identity column, but in practice always populated — a pollutant line whose number could not be parsed is not published as a row at all (see transform.ts), the same way a wholly unreadable popup is skipped rather than published with nulls.",
          "descriptions": {
            "ro": "Valoarea numerică a citirii, în unitatea unit. Este nullable în principiu, ca orice coloană care nu este de identitate, dar în practică este întotdeauna populată — o linie de poluant al cărei număr nu a putut fi interpretat nu este publicată deloc ca rând (vezi transform.ts), la fel cum un popup complet ilizibil este omis, nu publicat cu valori null.",
            "en": "The reading's numeric value, in `unit`. Nullable in principle like every non-identity column, but in practice always populated — a pollutant line whose number could not be parsed is not published as a row at all (see transform.ts), the same way a wholly unreadable popup is skipped rather than published with nulls."
          }
        },
        {
          "name": "quality_label",
          "type": "VARCHAR",
          "nullable": true,
          "description": "The Romanian air-quality category badge InfoAer shows next to this specific reading (observed values: \"Bun\", \"Acceptabil\", \"Moderat\", \"Rău\"), when it published one. Only the pollutants InfoAer itself classifies get a badge — most auxiliary readings (temperature, humidity, pressure, noise, VOC/NOx indices) do not, and this is null for those.",
          "descriptions": {
            "ro": "Eticheta categoriei de calitate a aerului afișată de InfoAer lângă această citire specifică (valori observate: \"Bun\", \"Acceptabil\", \"Moderat\", \"Rău\"), atunci când a fost publicată una. Doar poluanții pe care InfoAer îi clasifică efectiv primesc o astfel de etichetă — majoritatea citirilor auxiliare (temperatură, umiditate, presiune, zgomot, indici VOC/NOx) nu au una, iar pentru acestea coloana este null.",
            "en": "The Romanian air-quality category badge InfoAer shows next to this specific reading (observed values: \"Bun\", \"Acceptabil\", \"Moderat\", \"Rău\"), when it published one. Only the pollutants InfoAer itself classifies get a badge — most auxiliary readings (temperature, humidity, pressure, noise, VOC/NOx indices) do not, and this is null for those."
          }
        }
      ],
      "files": [
        {
          "url": "https://data-directory.fsn1.your-objectstorage.com/public/infoaer-bucharest-air/v/01M5J7K5G4Y8JED8SXP6633GF4/readings.parquet",
          "sizeBytes": 125405,
          "rowCount": 44685,
          "etag": "\"0e04e7bedf79145e4eaf7b57cf65b3a4\"",
          "stats": {
            "pollutant": {
              "min": "h2s",
              "max": "zgomot"
            },
            "reading_time": {
              "min": "2023-11-14 14:00:00+00",
              "max": "2026-10-22 16:30:00+00"
            },
            "sensor_id": {
              "min": "1",
              "max": "99"
            }
          }
        }
      ],
      "example": [
        {
          "sensor_id": 2,
          "reading_time": "2026-08-18T14:00:00.000Z",
          "pollutant": "h2s",
          "pollutant_label": "H2S",
          "unit": "µg/m³",
          "value": 4.8,
          "quality_label": null
        },
        {
          "sensor_id": 78,
          "reading_time": "2026-09-02T02:50:00.000Z",
          "pollutant": "h2s",
          "pollutant_label": "H2S",
          "unit": "µg/m³",
          "value": 0.1,
          "quality_label": null
        }
      ]
    }
  ],
  "generatedAt": "2026-10-22T16:38:52.566Z"
}
