Data pipeline
Users call get_*(), which reads tidy_data_sources.yml for the current version and URL, downloads the pre-built parquet if not already cached, and caches it to ~/.cache/R/nhstt/tidy/{dataset}.parquet. Subsequent calls return the cached file instantly.
Developers run build_tidy_data(dataset) (just build-data for all), which downloads raw source files, applies the tidy pipeline for every period, and writes a combined parquet to data-raw/. Parquets are published as GitHub Release assets and are directly usable from Python, Julia, or any language that reads parquet.
Available datasets
| Function | Frequency | First period | Last period | Periods | Version |
|---|---|---|---|---|---|
get_measures_annual() |
annual | 2012-13 | 2025-26 | 14 | 0.5.0 |
get_proms_annual() |
annual | 2019-20 | 2025-26 | 7 | 0.3.0 |
get_therapy_position_annual() |
annual | 2019-20 | 2025-26 | 7 | 0.2.0 |
get_measures_monthly() |
monthly | 2021-01 | 2026-07 | 67 | 0.6.0 |
get_measures_quarterly() |
quarterly | 2022-23-q2 | 2026-27-q1 | 16 | 0.2.0 |
get_metadata_measures_annual() |
annual | 2024-25 | 2025-26 | 2 | 0.2.0 |
get_metadata_variables_annual() |
annual | 2024-25 | 2025-26 | 2 | 0.2.0 |
get_metadata_monthly() |
monthly | 2026-07 | 2026-07 | 1 | 0.2.0 |
get_metadata_providers() |
live | current | current | 1 | 0.2.0 |
Approximate build times
One period timed per dataset, multiplied by the number of periods. These numbers are hardcoded (last measured 2026-07-13) — they are not re-measured on render because timing a build of every dataset takes ages. To update them, run the build-times chunk in DEVELOPERS.Rmd interactively (it has eval = FALSE) and paste the table it prints over the one below.
| Dataset | Timed period | Periods | Time for one (s) | Est. full build (min) |
|---|---|---|---|---|
measures_annual |
2024-25 | 8 | 62.0 | 8.3 |
proms_annual |
2024-25 | 6 | 212.2 | 21.2 |
therapy_position_annual |
2024-25 | 6 | 0.6 | 0.1 |
measures_monthly |
2026-05 | 65 | 6.0 | 6.6 |
Annual measures before 2017-18
The annual reports from 2012-13 to 2016-17 use a different format from later years. Up to 2014-15 they are XLSX workbooks. 2015-16 and 2016-17 are ZIP packs of CSVs. The layout changes every year. For these years measures_annual has England totals only. It has no breakdowns and no organisations below England.
The measures_annual entry in raw_annual_data_config.yml has legacy_archive: "annual_legacy". This tells build_tidy_data() to add these rows with tidy_annual_legacy_totals() in R/raw-annual-legacy.R. It also adds these years to available_nhstt_reports(). The annual_legacy archive says where each measure is in each year’s file. The rows then go through the same tidy config as later years.
The build stops if a published percentage does not match its count. This catches a column read from the wrong position. Measures without a percentage have no such check, and neither does any of 2012-13. We checked their column positions by hand against the source headers.
Some measures are missing in early years.
- 2013-14 and 2012-13 do not publish the number not at caseness.
- 2012-13 was the first year of the dataset, so only referrals received in that year count. Its outcomes are grouped differently. We include only the recovery and reliable recovery counts, with no percentages.
Publishing new data
Each dataset is released independently — updating monthly data does not affect annual dataset versions or caches.
Update raw config (e.g.
raw_monthly_data_config.yml) with new sourcesAdd the new version (
version+url) at the top of the dataset’sversionslist ininst/config/tidy_data_sources.yml— the first entry is the default. Copy theurlfrom the previous entry and update the version;just releasecreates the tag{dataset-with-dashes}-v{version}, so the URL ishttps://github.com/bennettoxford/nhstt/releases/download/{tag}/{dataset}.parquetjust build-data— rebuilds all parquets and writes todata-raw/-
Push the branch, then create a GitHub Release with
just release. It tags the current branch, so you can run it from a pull request branch before merging: Check the
urlintidy_data_sources.ymlmatches the parquet asset URL on the release page (copy-paste it if not)Run
just docsto check the vignettes with the released data. The pkgdown check on the pull request downloads the same release
The monthly metadata file (metadata_measures_monthly) is replaced by NHS Digital every month: they upload a newly dated xlsx and delete the old one, so the URL in raw_metadata_config.yml breaks each time. When the build fails downloading it, find the current link on the NHS Talking Therapies data set reports page, then update the period, URL, and cell range in raw_metadata_config.yml, recreate the raw fixture with create_raw_fixture(), and update the period in the tests and the source link in the get_metadata_monthly() docs.
Provider metadata is published the same way, see Provider metadata.
Provider metadata
get_metadata_providers() downloads a published parquet like every other getter. build_tidy_data("metadata_providers") builds that parquet with build_metadata_providers() in four steps.
- Take the provider codes from the published annual measures
- Look up each provider in the live NHS Organisation Data Service (ODS) API with
get_ods_data() - Add latitude and longitude for each postcode from postcodes.io with
get_postcode_coordinates() - Add the last period each provider reported, and the latest period, in the published annual, quarterly, and monthly data with
add_provider_reporting()
Steps 1 and 4 read the published datasets. Rebuild provider metadata after releasing new annual, quarterly, or monthly data, so the reporting columns include the new period. ODS changes over time, so the retrieved_date column records the day of the build.
just build-data-metadata
just release metadata_providers 0.2.0 "Provider organisation metadata from ODS"Providers vignette
vignettes/metadata-providers.qmd draws the providers map and table. Three files sit in vignettes/assets/.
-
metadata-providers.jshas the map interactions, passed tohtmlwidgets::onRender() -
metadata-providers.csshas the page styles -
nhs-england-regions.geojsonhas the NHS England region boundaries
The vignette reads the JavaScript and CSS into the page when it renders, so the pkgdown site needs no extra files.
The region boundaries come from the Office for National Statistics (ONS) Open Geography Portal. update_region_boundaries() downloads them and renames the fields to region_code and region_name, so the vignette does not depend on the boundary year. To update them, change the default url and the year in attribution, then run just update-region-boundaries. The region names must match the region_name values from ODS. If they do not, clicking a region on the map hides every provider.
Configuration files
All in inst/config/:
-
tidy_data_sources.yml— version and GitHub Release URL per released dataset -
raw_*_config.yml— raw source archives, URLs, and periods -
tidy_*_config.yml— tidy transformations (filters, derivations, columns)
After changing raw config column structure, run just update-schemas to update inst/schemas/.
Dev utilities
Internal functions in R/dev-utils.R (load with devtools::load_all()):
-
list_archives_periods()— all archives and available periods -
list_archive_files("annual_main", "2024-25")— CSV files inside an archive -
read_archive_file("annual_main", "2024-25", "main")— read a specific file -
compare_schemas("main")— column changes across periods -
build_tidy_data("dataset_name")— build combined parquet for one dataset -
update_region_boundaries()— download the region boundaries for the providers map
