--- name: healthcare-professionals-register description: Query MEDRE's public JSON API for registered healthcare professionals and download its occupation, speciality, and pharmacist open-data files. module: medre.mjs execution: post --- # Healthcare Professionals Register (MEDRE) ## Vetted module (use this first) `medre.mjs` ships in https://kodanikukratt.ee/kratt-agent.tar.gz (plain ESM, Node 22+) and is re-verified against the live source. **Read `## Limits` first if the question names a person.** This register lists named private individuals. ```js import { show } from "./kratt.mjs"; import { healthcareProfessionals, healthcareProfessional, healthcareOccupations } from "./medre.mjs"; show(await healthcareOccupations()); // the only values `occupation`/`speciality` will match const r = await healthcareProfessionals({ lastName: "…", firstName: "…" }); show({ kokku: r.totals, note: r.note }); show(r.rows); const p = await healthcareProfessional(r.rows[0].id); show({ nimi: p.name, kutsed: p.occupations, erialad: p.specialities, töökohad: p.employments }); ``` - **MEDRE ignores a filter name it does not know and answers with the WHOLE register.** Measured 2026-08-01: `{lastName}` returned 12 people, while `{name}`, `{fullName}`, `{searchTerm}`, `{occupationId}` and `{specialityCode}` each returned all 29,668 — no error, nothing saying the filter was dropped. So the module enforces an allowlist and throws instead: `firstName`, `lastName`, `occupationCode`, `occupation`, `speciality`. `totals.register` is returned alongside `totals.matched` so "did my filter apply" is answerable from the result. - `occupation` matches the occupation **name**, exactly and case-sensitively ("Arst" works, "arst" returns 0). `speciality` matches the **code**, not the name ("E170", not "kardioloogia"). Either mistake returns 0, which reads exactly like "nobody is registered for that" — hence `healthcareOccupations()`. - Paging is 1-based here and 0-based in the API; the module translates it. - **Personal identification codes are stripped on the way out and must not be reconstructed.** The public endpoints do not return one today, but the register's own frontend types a person with an `idCode` field, so the redaction runs over everything the module returns. - **Registration is a qualification, not employment, not a performance record, and not whether someone is taking patients.** `employments` names the activity-licence holder a person is registered under; an open `endDate` means the registration has not been closed, not that they are on shift. - Absence is meaningful in one direction only: someone missing is not registered in that occupation, but a person being listed says nothing about complaints or disputes — that is Terviseamet's, and it is not public. The endpoints below are the fallback if a module call throws. ## Access - Frontend: `https://medre.tehik.ee/home` - API base: `https://medre.tehik.ee/api-common` - Public JSON search and XML downloads; no login is required for the endpoints below. ## Retrieve POST JSON to `/public/persons/filter`. Paging fields are `page` (zero-based), `size`, and optional `sort`. For an unfiltered page: ```json {"page": 0, "size": 10} ``` The response has `content`, `page`, `size`, `totalElements`, and `totalPages`. Each person includes `id`, `firstName`, `lastName`, `occupationCodes`, `specialities`, and `specialistCodes`. Read occupation and speciality IDs from: - `GET /public/persons/occupations` - `GET /public/persons/specialities` Bulk/classifier downloads: - `/public/persons/pharmacists/open-data` -> `od_apteekrid.xml` - `/public/persons/occupations/open-data` -> `od_kutsed.xml` - `/public/persons/specialities/open-data` -> `od_erialad.xml` The frontend's public search form is authoritative for additional filter names; preserve the exact POST payload used. ## Return - Preserve the public person ID, name, registration code, occupation and speciality codes/names, registration dates, source URL, query, and retrieval time. - Keep multiple occupations and specialities as arrays rather than flattening them into a single label. - Report `totalElements` and all paging parameters. ## Limits - **Searching people is a POST**: the filter goes in a JSON request body, and the same URL asked as a GET comes back empty rather than saying no — an empty body reads exactly like "nobody matched" (measured 2026-08-02). So we cannot offer the person search to an agent that can issue GET requests and nothing else. The classifier lists and the open-data files below are GETs and stay available to one. - The API exposes professional registration, not employment history or private personnel records. - Unfiltered search is paginated; do not mistake one page for the complete register. - The dedicated bulk person file currently covers pharmacists; other professionals are available through paged JSON search. ## Verify - Require `/public/persons/filter` to return JSON with nonempty `content` for `{"page":0,"size":2}` and the documented person fields. - Require classifier endpoints to return nonempty JSON or XML. Reject the single-page frontend shell as register data. ## Module API healthcareProfessionals({firstName, lastName, occupation, occupationCode, speciality, page, size}) -> {source, page, filter, totals, rows, note} healthcareProfessional(id) -> {source, page, id, name, occupations, specialities, employments, note}