This release closes the v0.3.x feedback that "the range of forecast lines
was fairly limited" by exposing every detailed-forecast table in the EFO
Aggregates and Economy workbooks. v0.4.x exposed 4. v0.5.0 exposes all
39: 35 via standard layout parsers, 2 via bespoke layout parsers
(subsector_matrix for Table 6.4, quarterly_indented for Table 6.10),
and 2 via cross-reference auto-follow to the previous EFO vintage
(Tables 6.11 and 6.15, which OBR redirects to Tables 6.2 and 6.5 of the
November 2025 EFO).
obr_efo_catalogue() returns a data frame describing every detailed-
forecast table the package can fetch: table_id, file, section,
title, layout, default metric_type, default unit. Use this to
discover what's available.get_efo_table(table_id, vintage, refresh) is the generic dispatcher.
Pass any catalogue id (e.g. "6.13", "1.19", "6.1") and get the
parsed contents back in the standard v0.4.0 schema (period,
period_type, series, metric_type, value, unit).get_efo_fiscal() and get_efo_economy() are now thin wrappers over
the dispatcher and continue to work unchanged. Series names for the
single-series Economy tables (output gap, nominal GDP, electricity
price) are preserved via internal overrides so v0.4.x scripts still
match.
The dispatcher routes each table to one of:
quarterly_wide periods in col 2 (Q1/Q2 strings), series acrossquarterly_single periods in col 2, single value column to the rightannual_year_wide calendar years as column headers, series in col 2annual_period_wide calendar years in col 2 (rows), series acrossfiscal_year_wide fiscal years as column headers, series in col 2subsector_matrix single fiscal year, series in col 2, sub-sector in column headers (used for Table 6.4); adds a sub_sector extra columnquarterly_indented "Q1 2016" format periods in col 3, value in col 4, with Outturn/Forecast section markers in col 2 (used for Table 6.10)cross_reference sheet redirects to a previous EFO; the dispatcher follows the redirect, fetches the named vintage, and returns the linked table with provenance pointing at the previous vintageTightened classify_metric_type() so the v0.5.0 expanded coverage tags
each row correctly:
Index-linked gilts (and similar) is no longer classified as
metric_type = "index". The "index" pattern now requires Index at
the end of the series name or an explicit (2015=100) / (2010=100)
base-year tag. Net-debt composition (Table 6.13) now correctly tags
every row as pct.change no longer triggers yoy_pct because OBR uses "change in
X" for level differences too (e.g. Adjustment for the change in pension entitlements, in £bn). Explicit YoY signals (growth, inflation,
y/y, yoy, year on year, % change, annual %) still trigger.This release standardises the columns returned by the data-fetching
functions backing the Public Finances Databank (PFD), Economic and Fiscal
Outlook (EFO), Historical Forecasts Database (HFD), Welfare Trends Report
(WTR), and Fiscal Risks and Sustainability Report (FSR) so they can be
rbind()'d, joined, plotted, and reasoned about the same way regardless
of which OBR publication produced them. Driven by feedback from Ben
Northcott (Office for Budget Responsibility) on the v0.3.x release.
The Forecast Revisions Database (get_forecast_revisions()) and Policy
Measures Database (get_policy_measures()) keep their existing
multi-dimensional schemas in v0.4.0; migrating them to the standard
schema is queued for a later release.
All long-format outputs from the EFO / PFD / HFD / WTR / FSR functions now share the columns:
period - the time period as a character stringperiod_type - one of "fiscal_year", "quarter", "calendar_year"series - the variable namemetric_type - one of "level", "yoy_pct", "index", "pct", "pct_pts"value - the numeric valueunit - one of "gbp_bn", "pct", "pct_pts", "index", "count_k", etc.get_forecasts() adds forecast_date as a leading column.
get_pension_projections() adds scenario_type as a trailing column.
| Old column | New column |
|------------|-----------|
| year, fiscal_year | period (with period_type to disambiguate) |
| value_bn, psnb_bn, psnd_bn, tme_bn | value (with unit = "gbp_bn") |
| pct_gdp (FSR pension projections) | value (with unit = "pct") |
| scenario (FSR) | series |
get_psnb(), get_psnd(), get_expenditure() now return the standard
long schema and tag rows with series = "PSNB", "PSND", "TME"
respectively rather than collapsing the value into a series-named column.
In v0.3.x, calling get_efo_economy("inflation") returned CPI Index values
(~135) and CPI YoY growth values (~2.1) in the same value column with
no machine-readable distinction between them. v0.4.0 tags every row with
a metric_type ("index" for index levels, "yoy_pct" for growth
rates, "pct" for shares, etc.) and a matching unit, so callers can
filter or facet on metric type directly.
This is the v0.4.0 fix for the bug Ben Northcott raised on 2026-04-29: "If I pull e.g. the CPI forecast the index value and the YoY growth rate appear in the same 'value' column. Probably an additional column in long format indicating YoY or Index would be useful."
classify_metric_type() (internal): heuristic classifier for series
names. Returns "index", "yoy_pct", "pct", "pct_pts", or
"level". Used by every parser to populate metric_type from raw
source labels.default_unit_for_metric() (internal): maps a metric_type to its
default unit. For "level" returns NA (caller must supply since a
level can be gbp_bn, count_mn, etc.).obr_long() (internal): canonical constructor for the v0.4.0 schema.
All parsers use this to build their tidy long output.OBR_PERIOD_TYPES, OBR_METRIC_TYPES, OBR_UNITS: controlled
vocabularies for the schema metadata columns. Internal but documented
in the package source.obr_compare_vintages(vintage_a, vintage_b, what) joins the same EFO
table from two vintages on the standard schema and returns a tidy diff
with value_a, value_b, and revision = value_b - value_a. Supports
what = "fiscal" (default), "inflation", "labour", or
"output_gap".obr_actual_vs_forecast(series) joins the long-format Historical
Forecasts Database against PFD outturn for the same series, returning
one row per (forecast vintage, fiscal year) with the realised forecast
error. Supports series = "PSNB", "PSND", or "expenditure".Both helpers feed naturally into the kind of forecast-evaluation tables the OBR's own Forecast Evaluation Report uses.
vignette("efo-forecasts") covers the v0.4.0 schema: how to read the
Index vs YoY split, how to combine EFO with PFD outturn via the shared
schema, and how to compare two vintages. The existing vignette("vintages")
is updated for the renamed period column.obr_tbl object: a data.frame
with attached provenance recording the source URL, OBR publication code
("PFD", "HFD", "EFO", "WTR", "FSR"), publication vintage (e.g. "March 2026"),
retrieval timestamp, file MD5 fingerprint, and obr package version. This
lets users audit which OBR publication produced any number, and reproduce
analyses across new package or OBR releases.summary() returns the full provenance card.obr_provenance() extracts the metadata as a list.[ preserves the class and attributes; as.data.frame()
strips them.obr_efo_vintages() returns a structured table of every EFO published
since the OBR was created in June 2010, with publication dates and URL
slugs.obr_as_of(date) returns the EFO that was current on a given calendar
date. Useful for reproducing analyses as they would have looked at a
given point in time.obr_pin(vintage) sets a session-wide EFO vintage that flows into
get_efo_fiscal() and get_efo_economy(). obr_unpin() clears it and
obr_pinned() returns the current pin.get_efo_fiscal() and get_efo_economy() now accept a vintage =
argument that overrides any pin and downloads the file for that specific
EFO. Cached files are vintage-tagged so different vintages do not
overwrite each other.get_policy_measures() provides programmatic access to the OBR's Policy
Measures Database, with one row per fiscal-event-scored measure since
1970 (tax) or 2010 (spending). Columns include the fiscal event, plain-
English description, Treasury head, fiscal year, and Exchequer effect
in GBP million.type ("tax", "spending", or both), search (regex
on description / head, case-insensitive), since (fiscal-year cut-off).policy_measures_summary() aggregates the long format to net Exchequer
effect by event and fiscal year.obr_fiscal_rules() returns a structured table of the three numerical
rules in the current Charter for Budget Responsibility (Autumn 2024,
updated Autumn 2025): the stability rule (current budget surplus), the
investment rule (PSNFL/GDP falling), and the welfare cap. The metric,
pass direction, target-year mechanics, and source Charter / Act are
shipped as data. Numerical headroom is not shipped as a constant
because it changes at every fiscal event; users should derive it from
current EFO output via [get_efo_fiscal()] or read the EFO press release.get_forecast_revisions() provides programmatic access to the OBR's
Forecast Revisions Database, which decomposes each EFO-to-EFO change
in the headline PSNB forecast into policy, classifications and one-offs,
and underlying (economic determinants) components. Available in both
GBP billion and per cent of GDP.obr_forecast_panel() pivots the long-format Historical Forecasts
Database to a wide real-time panel: rows are forecast vintages, columns
are fiscal years, cells are forecast values. Mirrors how the OBR's own
Forecast Evaluation Report lays out forecast performance and lets users
read the h-step-ahead forecast for any vintage off the diagonal.obr_tbl class and provenance attributes.cli::cli_warn() instead of failing silently. The returned data frame
records source = "fallback" so callers can detect this state.obr_resolve_url() now uses HEAD requests for faster candidate probing
and captures the redirect target so the Public Finances Databank vintage
can be recovered from the stable slug.parse_efo_output_gap() (sheet 1.14) now scans for the value column with
the most aligned numeric entries instead of hardcoding column 3.parse_pension_projections() (FSR sheet C1.2) now detects sections via
fuzzy matching on "demographic" and "triple lock" rather than exact strings.match.arg() is now used for series and measure arguments.inst/CITATION reads the version from DESCRIPTION so it stays in sync.tempdir() instead of the user's home directory,
fixing CRAN policy compliance for \donttest examples.options(obr.cache_dir = ...).get_efo_fiscal() for five-year fiscal projections (net borrowing components) from the latest Budgetget_efo_economy(measure) for quarterly economic projections: "inflation" (CPI, CPIH, RPI, RPIX), "labour" (employment, unemployment, participation), or "output_gap"list_efo_economy_measures() lists available economy measuresget_welfare_spending() returns working-age welfare spending split by incapacity and non-incapacity (% of GDP, from 1978-79)get_incapacity_spending() returns incapacity benefits spending by benefit type (ESA, IB, Invalidity Benefit, Sickness Benefit, SDA) as % of GDPget_incapacity_caseloads() returns combined incapacity benefit caseloads and prevalence since 2008-09get_pension_projections() returns 50-year state pension spending projections (% of GDP) under alternative demographic and triple-lock scenariosget_psnb(), get_psnd(), get_expenditure(), get_receipts() for Public Finances Databank aggregatesget_public_finances() for all Databank series in tidy long formatget_forecasts() and list_forecast_series() for Historical Official Forecasts Databaseclear_cache() to remove locally cached files