--- name: health-statistics description: Query the National Institute for Health Development PxWeb API for official Estonian health indicators and machine-readable multidimensional tables. module: tervisestat.mjs execution: post --- # Estonia Health Statistics Tervise Arengu Instituut publishes Estonia's official health statistics — births, deaths, disease, treatment, health behaviour, medicines — as **2,406 PxWeb tables** at `https://statistika.tai.ee/api/v1/et/Andmebaas`. Public, no authentication. ## Retrieve **Use the module below.** Two calls answer almost everything: find the table, then read it. ```js const f = await findHealthTable("surmad"); // ONE request, the database's own search show(f.rows.slice(0, 5)); // { tableId, title, path, updated } const r = await healthTable("SR001", { query: { Elulisus: ["1"] } }); show(r.totals); // { rows, series, first, last, firstValue, lastValue, changePct } show(r.mandatoryNotes); // the caveats TAI marks as must-show show(r.rows.slice(0, 5)); ``` **A dimension you do not name comes back in full.** Leaving `Aasta` out of the query returns all 34 years of SR001 in one call. So a ten-year question is one call, not ten — and `totals.first`/`last`/`changePct` are computed here over the whole series rather than by picking two printed rows and subtracting. Get the exact codes from `healthTableMeta(tableId)` when a query is rejected: they are Estonian words (`Aasta`, `Elulisus`, `Maakond`, `Vanuserühm`) and must be sent exactly as returned. ## Methodology notes are part of the answer Each table ships its own `note` array, and `extension.noteMandatory` flags the ones TAI requires to be shown. The module surfaces those as `mandatoryNotes`, keeping the methodology page's URL rather than stripping it. **That is where a changed definition, denominator or coverage break is written down** — two years either side of such a break are not comparable, and saying so is part of answering. ## Limits - **Reading a table is a form POST**: the selection goes in a JSON request body, and a plain GET of the table URL returns its dimension list only — the codes, never the numbers. Table *search* is a GET. So we cannot offer this source to an agent that can issue GET requests and nothing else; it can find a table here but not read one. - Public aggregates only; no patient-level records. - A blank cell is suppressed or not collected, **never a zero**. `rows` carries only cells that have a value, so a missing year is missing on purpose. - Five table ids exist in two folders each (`RV31`, `PH5`, `KP15`, `TTO60`, `RV301`). The module throws naming both paths instead of picking one. - The API rate-limits: `429 - Too many requests in too short timeframe` on concurrent requests. Do not fan out. - `?query=` is the whole search contract — adding PxWeb's documented `&filter=*` returns `[]` with HTTP 200 here, which reads exactly like "no such subject". ## Verify `class: "dataset"`, a value count matching the product of `size`, and the table's `source` and `updated`. A search that matches nothing returns an empty list with HTTP 200 — `totals.tables: 0` is the module saying that out loud. ## Module API healthTable(tableId, {query, lang}) -> {source, page, tableId, title, producer, updated, totals, rows, mandatoryNotes, notes, note} findHealthTable(term) -> {source, page, term, totals, rows, note}