METHODOLOGY · HOW LIFEMAP IS BUILT

Show our working.

Lifemap is a thin layer over public data. The site is editorial; the data is somebody else’s. This page documents every transformation that happens between a UK postcode and a number on the screen, so any figure can be reproduced from first principles.

Last updated · 26 April 2026 Pipeline source · build_dataset.py Dataset · uk_la_data.json
§ 01 · The shape

One JSON file per build, 380 council records.

The pipeline produces uk_la_data.json: a flat object keyed by area slug. Each record carries life expectancy, healthy life expectancy, and five lifestyle indicators. The slug is the lowercased admin_district field that postcodes.io returns for any UK postcode, with a small set of known aliases, so a successful postcode lookup is always one direct dictionary read away.

Country aggregates (England, Wales, Scotland, NI, UK) live under _meta.country_totals — available for editorial reference, kept out of the per-area lookup.

§ 02 · Indicator resolution by name, not ID

The pipeline survives Fingertips renumbering.

OHID Fingertips occasionally retires an indicator ID and replaces it with a fresh one. We’ve had at least two confirmed deaths in this dataset’s lifetime: 93798 and 91414. The pipeline therefore resolves indicators by name search rather than hardcoded ID.

  1. Hit /api/indicator_search?search_text=… with the natural-language name.
  2. Validate the returned ID against /api/indicator_metadata/by_indicator_id?indicator_ids=… to confirm the description still matches the keywords we expect.
  3. Only then do we use the ID in the data fetch. The resolved IDs are written back into the dataset under _meta.resolved_indicators so drift is auditable.

This is the difference between a pipeline that silently produces blanks and one that fails loudly when the upstream changes shape.

§ 03 · Dual UTLA + LTLA fetch

Districts get their own number, not the county’s.

For two-tier areas of England (Kent + Canterbury, West Sussex + Adur, Norfolk + Norwich, …), we fetch Fingertips lifestyle indicators at both upper-tier (area_type_id=502) and lower-tier (area_type_id=501) level. This way Canterbury’s record carries Canterbury’s own smoking, obesity, activity and 5-a-day figures, not Kent’s.

One indicator—higher-risk drinking, Fingertips 92778—does not publish at LTLA. For that field, English districts have null in the data file and the frontend surfaces the parent county’s value as a clearly labelled adjacency block, never as the district’s own number.

§ 04 · The screenshot trap

We never copy a parent county’s value into a district row.

Editorial principle (load-bearing)
A Canterbury row in our dataset that contained Kent’s 22% drinking figure would be the screenshot trap that breaks journalist trust. A reader cites “Canterbury 22%”, an alert local-government contact emails the journalist with the actual Kent figure—same number, but published as the county not the district—and the correction lands. Our data is now wrong on the public record. We avoid this by keeping inheritance strictly UI-side, with explicit “belongs to Kent, not Canterbury” framing.

In the JSON, the district’s parent_utla_slug field points at the county. The frontend uses this pointer to render the parent’s HLE and drinking values in a separate, dashed-border block, not in the main stat tiles. The district row stays clean.

§ 05 · Country-by-country sources

Different countries, different agencies, same column.

England’s lifestyle indicators come from OHID Fingertips. Scotland’s from Scottish Government surveys (SSCQ for smoking; SHeS council-area Shiny dashboard for the other four). Wales’s from the Welsh Health Survey 2014–15 via InfoBaseCymru. Northern Ireland publishes adult lifestyle prevalence at HSC Trust level (5 trusts) only, never at LGD2014 council level (11 districts), so the five lifestyle fields stay null for NI districts.

Per-country source URLs and the exact period each indicator covers are documented on /sources/ and machine-readable under uk_la_data.json._meta.lifestyle_sources.

§ 06 · Definition harmonisation

What does “obesity” even mean?

England (Fingertips 93088) reports “Overweight including obesity” at BMI ≥ 25. Scotland (SHeS) and Wales (WHS) report obesity at BMI ≥ 30. The two definitions are not interchangeable, so cross-country obesity comparisons must be read with the threshold in mind. We disclose the mismatch on every page that surfaces obesity, and in _meta.lifestyle_sources.

For drinking and physical activity we have closer alignment after the Scotland refresh: Scotland uses the same >14 units/week and CMO 150 min/wk MVPA thresholds Fingertips uses. Wales is still on its pre-2016 daily-units and 5×30 min/wk definitions and is flagged as not directly comparable.

§ 07 · Slug normalisation

The postcodes.io ↔ ONS handshake.

Postcodes.io returns admin_district for every UK postcode. ONS publishes data keyed by GSS area code (E06000023, S12000026, …). The pipeline normalises both to a slug (lowercase, accents stripped, “, City of” suffixes dropped, non-alphanumerics collapsed to single spaces) and joins on slug. A small alias list (data.js → LM_SLUG_ALIASES) handles the few cases where the two providers disagree on naming — e.g. Bristol, City of vs Bristol.

§ 08 · Failure modes

What we’ve already accounted for.

  • ONS XLSX URLs of the form /current/….xlsx rotate filenames between releases. The pipeline scrapes the dataset landing page each run rather than hardcoding a path.
  • Fingertips’ older bulk endpoint (/api/all_data/csv/by_indicator_id) returns HTTP 500 and looks deprecated. We use the documented /api/all_data/csv/for_one_indicator instead.
  • Fingertips area-type IDs change after boundary reorganisations. area_type_id=502 is the post-April-2023 UTLA type; =501 is post-April-2023 LTLA; older =102 and =101 are dead.
  • Boundary geography drifts: Cumbria split into Cumberland and Westmorland & Furness; North Yorkshire and Somerset reorganised; South Yorkshire renumbered Barnsley and Sheffield. Map joins use a slug fallback when codes don’t match.
  • Postcode FY1 1AA is terminated (withdrawn May 2010). Tests use FY1 2AA instead.
§ 09 · Reproducibility

Rebuild the dataset yourself.

The pipeline is a single Python script at build_dataset.py. To rebuild from scratch:

  • git clone https://github.com/visata/lifemap.git && cd lifemap
  • python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt
  • python3 build_dataset.py

First run downloads roughly 5 MB of ONS XLSX files into cache/ and makes a handful of Fingertips API calls. Subsequent runs reuse the cache. Output is written to the project root as uk_la_data.json; you should get bit-for-bit-equivalent output to what this site serves, modulo upstream releases since our last build.

§ 10 · See also

Where else to look.