RecallRx: a static, country-aware search app for medicine recalls from official public sources.
Open the live recall search app
RecallRx turns official medicine recall notices into normalized JSON datasets that can be searched from a static website. The generated app can be hosted on GitHub Pages without a backend, database server, account system, or cloud runtime.
The active datasets are currently Spain / AEMPS, Portugal / INFARMED, and France / ANSM. The app, schema, record IDs, and data paths are country-aware, and the live interface already exposes a planned country slot for Andorra / Ministeri de Salut. Additional authorities are added through separate source adapters without changing the browser search contract.
RecallRx is not a safety certification system. A missing search result only means no matching recall was found in the indexed records. The public app must not claim that a medicine is safe.
Documentation: https://mrueda.github.io/recallrx/
Live Search App: https://mrueda.github.io/recallrx/app/
Quick Start: https://mrueda.github.io/recallrx/docs/usage/quickstart
Data Schema: https://mrueda.github.io/recallrx/docs/technical-details/data-schema
GitHub Repository: https://github.com/mrueda/recallrx
Install locally from the repository root:
python3 -m pip install -e ".[test]"Validate the bundled seed dataset:
python3 -m recallrx validate dataCollect recent recall data and merge it into the retained dataset:
python3 -m recallrx build --output data --mode incrementalRun an explicit historical backfill from the configured start year:
python3 -m recallrx build --output data --mode fullBuild the static deploy directory:
python3 -m recallrx distServe the static app locally:
python3 -m http.server 8000 --directory distGenerated data is written as static JSON:
data/
metadata.json
countries/
es/
metadata.json
recalls-summary.json
recalls/
by-code/
by-year/
build-report.json
pt/
fr/
ad/
Records use country-scoped identifiers such as ES_AEMPS_R_21_2026.
Product codes are represented as structured entries, so each country can keep
its own code system:
{"system": "CN", "value": "755215"}Spain currently uses CN, Portugal uses INFARMED registration numbers as
AIM, and France uses CIP without changing the JSON field shape.
Each country source is implemented as an adapter. Adapters use Python HTTP
requests, retries, pagination, structured APIs or HTML parsing where available,
and transient PDF fallback parsing when useful. They do not rely on wget, and
they do not commit downloaded PDFs.
Collection has two modes. incremental is the daily path: it checks recent or
current-year authority listings and merges updated records into the retained
dataset. full is an operator-triggered historical backfill: it scans from
backfill_start_year and replaces the selected countries only after collection
finishes.
The current Spain / AEMPS source flow is:
- Discover candidates through the public AEMPS WordPress posts API, with the AEMPS search endpoint as fallback.
- Scan the current year during incremental updates and use the configurable start year for full backfills.
- Parse AEMPS detail pages as the primary source.
- Accept human-medicine recall IDs such as
R_21/2026. - Reject veterinary alerts, cosmetics, product-safety notices, and general news.
- Write parser warnings and rejected candidates to
build-report.json.
The current Portugal / INFARMED source flow is:
- Use the compact paginated Alertas feed for daily incremental discovery.
- Use INFARMED's human-medicine search for historical backfills, retaining only content pages and deduplicating their attached PDF search results.
- Retain an explicitly marked record from the official search summary when an older Liferay detail route is unavailable; INFARMED's year-organized alert archive provides the corresponding circular-PDF corroboration path.
- Accept medicine recall and withdrawal notices marked as
Tipo de alerta: med. - Reject cosmetic and device alerts even when their titles mention withdrawal.
- Parse circular IDs, dates, medicine names, INFARMED registration numbers, lots, expiry dates, manufacturers, reasons, and actions from the detail page.
- Write parser warnings and rejected candidates to
build-report.json.
The current France / ANSM source flow is:
- Query ANSM's official year and product filters for
RAPPEL DE PRODUITandMédicaments, using the current year incrementally and all configured years during a backfill. - Reject device, diagnostic, cosmetic, and other non-medicine recalls.
- Parse publication dates, medicine names, manufacturers, CIP codes, lots, expiry dates, reasons, and recall-level text from the detail page.
- Write parser warnings and rejected candidates to
build-report.json.
Daily GitHub Pages deployments collect recent entries, merge them into retained history, validate the result, and publish the static app. Substantive record changes are committed by the workflow so entries remain available after they leave an authority's recent feed. A manual workflow run can select either collection mode. The app marks exports as delayed after 48 hours and stale after 72 hours, while the workflow summary reports collection and retention counts for each country.
Run the Python test suite:
python3 -m pip install -e ".[test]"
pytestRun bytecode and frontend checks:
python3 -m py_compile src/recallrx/*.py src/recallrx/adapters/*.py
node --check site/app.jsValidate static data:
python3 -m recallrx validate dataRun the docs checks:
cd docs-site
npm ci
npm run typecheck
npm run buildNo formal citation is available yet. For now, cite the GitHub repository:
RecallRx: static, country-extensible medicine recall search. https://github.com/mrueda/recallrx
Written by Manuel Rueda.
Repository: https://github.com/mrueda/recallrx
Copyright (C) 2026 Manuel Rueda.
This project is distributed under the MIT License. See LICENSE for details.