| Source | Active Reps | With Email |
|---|---|---|
local_councillors |
19,266 | 4,665 (24.2%) |
mayors |
27 | 27 (100.0%) |
ni_assembly |
90 | 89 (98.9%) |
scottish_parliament |
129 | 129 (100.0%) |
senedd |
96 | 96 (100.0%) |
uk_parliament |
650 | 647 (99.5%) |
| Loader | Status | Completed |
|---|---|---|
uk_parliament |
success | 2026-11-01 06:01:02 |
london_assembly |
failed | 2026-11-01 06:00:53 |
london_assembly |
failed | 2026-11-01 06:00:34 |
london_assembly |
failed | 2026-11-01 06:00:17 |
mayors |
success | 2026-11-01 06:00:17 |
| Type | Source | Records | Coverage | Status |
|---|---|---|---|---|
| UK Parliament MPs | members-api.parliament.uk | 650 | All of England, Wales, Scotland, NI | Implemented |
| └ Emails |
Same API (/Members/{id}/Contact) |
650 |
100% — firstname.lastname.mp@parliament.uk |
Not yet fetched |
| └ Photos |
Same API (thumbnailUrl field) |
650 | 100% | Stored in metadata |
| Scottish Parliament MSPs | data.parliament.scot OData | ~129 | All constituency + regional MSPs | Implemented |
| └ Emails |
Same API (/api/emailaddresses) |
~129 |
100% — firstname.lastname.msp@parliament.scot |
Not yet fetched |
| └ Photos |
Same API (PhotoURL field) |
~129 | Most | Stored in metadata |
| Local Councillors | opencouncildata.co.uk CSV | ~19,300 | 384 councils across UK | Implemented |
| └ Emails | Modern.gov council portals (scraping) | est. ~10,000 | ~200 councils (~50%) — see below | PoC built |
| └ Emails | CMIS council portals (scraping) | est. ~1,500 | ~21 councils — see below | PoC built |
| Senedd (Welsh) Members | data.senedd.wales or scrape | 60 | All of Wales | Not implemented |
| NI Assembly MLAs | data.niassembly.gov.uk SOAP/XML | 90 | All of Northern Ireland | Not implemented |
| London Assembly Members | london.gov.uk | 25 | 14 constituency + 11 London-wide | Not implemented |
| Area Type | Source | Records | Mapping | Status |
|---|---|---|---|---|
| Westminster constituencies | postcodes.io pg_dump | 650 |
postcodes.constituency_id → constituencies |
Imported |
| Scottish Parliament constituencies | postcodes.io pg_dump | 73 |
scottish_postcodes.scottish_constituency_id → scottish_constituencies |
Imported |
| Wards | postcodes.io pg_dump | ~8,400 |
postcodes.admin_ward_id → wards |
Imported |
| Local authorities | postcodes.io pg_dump | ~360 |
postcodes.admin_district_id → districts |
Imported |
| Senedd constituencies | Boundary data / Electoral Commission | 48 | Needs supplementary postcode mapping | Not imported |
| NI Assembly constituencies | EONI / boundary data | 18 | Needs supplementary postcode mapping | Not imported |
| London Assembly constituencies | GLA boundary data | 14 | Needs supplementary postcode mapping | Not imported |
| Dataset | Source | Records | Coverage | Status |
|---|---|---|---|---|
| Creditor payments (over £250) | Brighton & Hove monthly CSVs | 52 monthly files, April 2022 – July 2026, no gaps | Brighton and Hove only — 1 of 384 councils | Implemented; the 3 earlier quarterly files are not loadable, and Oct–Dec 2021 was never published — see below |
| Contracts register | Brighton & Hove In-tend portal | 1,734 rows / 1,732 contracts, one GET | Brighton and Hove only — 1 of 384 councils | Implemented — see below |
| Contract award notices | Contracts Finder (OCDS) | 99 for Brighton | National, ~£25k and up |
Implemented; monthly windows on ingest_windows |
~200 UK councils use the Civica Modern.gov platform for their democracy portals. These expose a mailing list endpoint (/mgCommitteeMailingList.aspx?EM=1&ID=0) that lists all councillor names and emails as CSV-formatted text.
Two URL patterns:
https://{council}.moderngov.co.uk — hosted by Civica (most common) https://democracy.{council}.gov.uk — self-hosted by the council | Council | Modern.gov URL | Councillors | Emails |
|---|---|---|---|
| Brighton and Hove |
democracy.brighton-hove.gov.uk |
54 | 53 |
| Doncaster |
doncaster.moderngov.co.uk |
60 | 55 |
| Barnet |
barnet.moderngov.co.uk |
63 | 63 |
| Bristol |
bristol.moderngov.co.uk |
- | - |
| Cardiff |
cardiff.moderngov.co.uk |
- | - |
| Sheffield |
democracy.sheffield.gov.uk |
- | - |
| Manchester |
democracy.manchester.gov.uk |
- | - |
| Cornwall |
democracy.cornwall.gov.uk |
- | - |
| Durham |
democracy.durham.gov.uk |
- | - |
| Blackpool |
democracy.blackpool.gov.uk |
- | - |
| Buckinghamshire |
buckinghamshire.moderngov.co.uk |
- | - |
| Bolton |
bolton.moderngov.co.uk |
- | - |
| Bradford |
bradford.moderngov.co.uk |
- | - |
Some councils use other platforms where a different scraping strategy would be needed:
| Platform | Example | Notes |
|---|---|---|
| CMIS |
Birmingham (birmingham.cmis.uk.com) |
Implemented — see below |
| Bespoke / in-house | Liverpool | Would need per-council scrapers |
| Scottish councils | Aberdeen, Angus, Argyll and Bute | Many use different systems |
| NI councils | Belfast, Antrim | Use NI-specific systems |
The enricher at lib/advocacy_api/enrichers/moderngov.ex maps council names (as they appear in the Open Council Data CSV) to their Modern.gov base URL. Adding a council is one line:
@council_urls %{
"Brighton and Hove" => "https://democracy.brighton-hove.gov.uk",
"Doncaster" => "https://doncaster.moderngov.co.uk",
# add more here...
}
Name matching between Open Council Data and Modern.gov is near-perfect — tested with Brighton and Hove where 53/53 names matched exactly (only the “Vacant” seat was unmatched).
To build out the full mapping, the URL slug can often be guessed from the council name, but not always (e.g., “Brighton and Hove” → brighton-hove, “South Cambridgeshire” → scambs). A manual mapping or a discovery script that tries both URL patterns would be the next step.
~60 UK councils use CMIS by Astech Consultants for their democracy portals. Unlike Modern.gov (which exposes a single mailing-list endpoint), CMIS requires a two-step scrape: first fetch the councillors list page to discover individual councillor URLs, then visit each detail page to extract the email address.
The enricher at lib/advocacy_api/enrichers/cmis.ex handles this automatically with concurrent detail-page fetching.
Three URL patterns:
https://{slug}.cmis.uk.com/{slug}/Councillors.aspx — hosted by Astech (most common) https://cmis.{council-domain}/cmis5/Councillors.aspx — self-hosted CMIS 5 https://cmispublic.{council-domain}/cmis/Councillors.aspx — self-hosted public
Adding a council requires one line in the @council_urls map:
@council_urls %{
"Birmingham" => "https://birmingham.cmis.uk.com/birmingham/Councillors.aspx",
"Dudley" => "https://dudley.cmis.uk.com/Councillors.aspx",
# add more here...
}
The council name must match exactly what appears in the Open Council Data CSV. The scraper visits each councillor’s detail page concurrently (4 at a time) so enrichment takes longer than Modern.gov but is still practical.
The API attaches statistical datasets to each postcode via its geography codes (LSOA, MSOA, constituency, local authority). Each dataset has typed headline columns for fast queries and a JSONB detail column for full breakdowns.
LSOAs are small statistical areas of roughly 1,500 people / 650 households. England has 32,844 LSOAs. These provide the most granular neighbourhood-level data.
| Dataset | Source | Date / Vintage | Geography | Coverage | Key Fields | Limitations |
|---|---|---|---|---|---|---|
| IMD 2019 | MHCLG | 2019 (based on 2015/16 data) | LSOA 2011 | England only | Rank, decile across 7 domains | Uses 2011 LSOA boundaries; last updated 2019; no Scotland/Wales/NI equivalent in this dataset |
| Census 2021 | ONS / Nomis | March 2021 | LSOA 2021 | England & Wales | Population, households, median age, ethnicity, tenure, health, qualifications, economic activity, household composition, language | Conducted during COVID — may not reflect normal patterns; Scotland ran Census 2022 separately (not included) |
| Fuel Poverty | DESNZ | 2022 estimates (published 2024) | LSOA 2021 | England only | % fuel poor, number of fuel-poor households | Modelled estimates using the Low Income Low Energy Efficiency (LILEE) definition; Scotland/Wales use different definitions |
| Benefits / Universal Credit | DWP Stat-Xplore | Rolling monthly (latest available) | LSOA 2021 | Great Britain | UC claimants, claimant rate | Counts suppressed where <10 to prevent identification; does not include legacy benefits still being migrated to UC |
| Crime | data.police.uk | Rolling 12 months | LSOA 2011 | England & Wales | Total crimes, rate per 1,000, breakdown by category | Location snapped to anonymous points (not exact); some forces have reporting delays; BTP crimes excluded from local counts |
| AHAH (Access to Healthy Assets & Hazards) | CDRC | v3 (2022) | LSOA 2011 | England & Wales | Overall index, retail environment, health services, physical environment sub-indices | Academic dataset — registration required; composite index may mask local variation in individual components |
| EPC (Energy Performance) | DLUHC Open Data | Cumulative (certificates from 2008+) | Aggregated to LSOA | England & Wales | Median energy efficiency score, band distribution | Only covers properties that have been sold/rented/built since 2008; older housing stock under-represented; self-selection bias |
| GP Registrations | NHS Digital | Quarterly | LSOA 2021 | England | Patient count, practice breakdown | Patients registered ≠ residents (students, commuters may register elsewhere); some practices span many LSOAs |
| Air Quality | DEFRA NAEI | 2022 modelled concentrations | 1km grid → LSOA | UK-wide | NO₂ annual mean (µg/m³), PM2.5 annual mean (µg/m³) | Modelled from 1km grid (not measured); may miss hyper-local pollution hotspots near roads; background concentrations only |
| Flood Risk | Environment Agency / Friends of the Earth | Based on EA flood maps (2023) | LSOA 2011 | England | % properties at risk | Fluvial and surface water risk only; does not include coastal or groundwater flooding; risk ≠ actual flooding history |
| Broadband | Ofcom Connected Nations | 2023 | LSOA 2021 | UK | Average download/upload speed (Mbps) | Averages mask variation within LSOA; based on ISP-reported data not speed tests; does not reflect actual user experience |
| Child Poverty | End Child Poverty / DfE | 2022/23 estimates | LSOA 2021 | England & Wales | Child poverty rate (%), FSM eligibility (%) | Modelled estimates (not direct counts); definitions of poverty vary between sources; FSM data is school-based not residential |
| House Prices | ONS HPSSA | Rolling year to latest quarter | LSOA 2021 | England & Wales | Median price paid | Based on Land Registry transactions only (not valuations); low-volume LSOAs may have unreliable medians; does not capture properties that haven’t sold recently |
MSOAs are larger areas of roughly 7,500 people. England has 6,791 MSOAs. Used for data that is too volatile at LSOA level.
| Dataset | Source | Date / Vintage | Coverage | Key Fields | Limitations |
|---|---|---|---|---|---|
| Life Expectancy | ONS | 2018–2020 (3-year pooled) | England & Wales | Life expectancy at birth (male/female), healthy life expectancy (male/female) | 3-year pooled estimates needed for statistical reliability; pre-dates COVID impact; MSOA boundaries may not align with community boundaries |
| Dataset | Source | Date / Vintage | Coverage | Key Fields | Limitations |
|---|---|---|---|---|---|
| 2024 General Election Results | House of Commons Library | July 2024 | All 650 UK constituencies | Winner, majority, turnout, full party results | New 2024 boundaries — historical comparison with pre-2024 results is not directly possible; by-election results not automatically updated |
| Dataset | Source | Date / Vintage | Coverage | Key Fields | Limitations |
|---|---|---|---|---|---|
| Life Expectancy | ONS | 2018–2020 | UK | Life expectancy at birth (male/female), healthy life expectancy | Same pooling caveats as MSOA; LA boundaries changed in several reorganisations (2019–2023) |
| Population | ONS Mid-Year Estimates | 2023 | UK | Total population | Based on Census 2021 rolled forward with births/deaths/migration estimates |
Table: care_boards — ods_code, name, region, postcode_count,
papers_collected.
Source: derived from the ONS Postcode Directory, not typed in.
AdvocacyApi.CareBoards.refresh/0 reads the postcodes.io icb lookup table
when the full dump has been imported (giving both code and name) and otherwise
falls back to the distinct values on postcodes.icb.
What we hold: the bodies, and which postcodes and councils fall in each. This is what makes a postcode lookup return a care board.
What we do not hold: board membership, meeting dates, papers, minutes and
decisions. papers_collected is false for every row, and every screen says so
in words rather than rendering an empty table. Collecting them means scraping
42 separate websites and has not been built.
Caveat: before the directory backfill has run, postcodes.icb carries the
ODS code rather than the resolved name, so a board can be stored with its code
as its name. CareBoardHTML.title/1 labels that case as “Care board QNX”
rather than presenting a code as a name.
Everything below was measured against Brighton and Hove on 8 September 2026. It is one council’s shape, not a national schema.
Source: the council publishes monthly creditor payment CSVs. The index page
/payments-over-ps250
links one page per financial year, each of which links that year’s monthly files.
52 monthly files are published, April 2022 to July 2026 — every month in that
range, with no gaps. Before that the council published quarterly, and only
partly: there are files for April–June 2021, July–September 2021 and
January–March 2022, and October–December 2021 is not published in any form.
Licence: Open Government Licence v3.0.
data.gov.uk is not a way in. The entry for this dataset is a dead stub — “There are no links to this data” — despite being marked updated 8 July 2026. The council’s own pages are the only route.
The threshold is £250, not the statutory £500. Brighton dropped from £500 in April 2013, so the over-£500 register is a filter over this data rather than a separate publication. 52–55% of rows clear £500.
Shape: eight columns, stable across every month sampled (June 2025, January 2026, April 2026, July 2026 — 37,373 rows):
Creditor Name, Transaction Number, Date, Net Amount, Expenditure Summary, Category, Directorate, Section
8,200–10,700 rows and £43m–£64m per month, across ~1,300–1,400 distinct creditors.
AtkinsRéalis UK Limited arrives as
AtkinsR<0xE9>alis. £ also turns up inside text fields
(H4U Thank you payment(£350), 246 rows). "Community Transport (Brighton, Hove & Area) Ltd",
"Elections, Reg Serv and LLC"). Backslash is a literal character, not an
escape — Telephones - Calls \ Rental is the actual value. 6624 means £6,624.00. Payment to Individual — redacted, with no attributable
supplier. Published in four spellings: capital and lowercase I, each with and
without a trailing space (17,208 / 16 / 10 / 1 across the sample). Salutem LD BidCo IV Ltd t/a Ambito Care and Educat. These are prefixes and
will not match a contract’s full spelling. Transaction Number is not unique — about a third of refs appear on more
than one row, one per invoice line. Across the four-month sample the full
published row is unique both within and across files, and no transaction ref
is reused in a later month, which is what row_hash relies on. 302 Location: <the same URL> with two set-cookie headers, forever — curl
stops at 50 redirects. Returning the cookies on a second request gets a 200.
The CSVs themselves are static files under /sites/default/files/ and need no
handshake. /sites/default/files/<upload month>/, which is not the data month: July
2025’s file is under 2025-09. Filename casing drifts (Over vs over), some
carry a - CSV suffix, some encode the £ sign
(Over %C2%A3250 August 2023), and Drupal appends _0 on re-upload
(June 2024_0.csv, December 2022_0.csv) — that underscore defeats a \b
word boundary when matching the year. April-June 2021,
July-September 2021, January-March 2022). The loader refuses to date these
rather than filing a quarter under its first month; loading them needs a window
granularity that can express a quarter. Note that these three do not tile the
period either — nothing covers October to December 2021. AdvocacyApi.Scrapers.CouncilSpend — link discovery, WAF handshake, window
walking. Source key council_spend, one window per council per month on
ingest_windows. AdvocacyApi.Payments.CreditorCsv — the Brighton parser. AdvocacyApi.Csv — RFC 4180 reader and the CP1252 sniff. AdvocacyApi.Payments — storage, idempotent on row_hash. Brighton publishes its own contracts register on its In-tend e-tendering portal. It holds 1,734 contracts against Contracts Finder’s 99 for the same council, because most council procurement is a framework call-off or a delegated award and neither generates a national notice. Three suppliers with no Contracts Finder notice at all appear here with a contract: FM Conway, Van Oord and Achieve together.
One public GET, no login, no paging:
https://in-tendhost.co.uk/brightonandhove/aspx/ViewContractsRegister.aspx?id=dec104fe-f8cb-4735-90b4-bffa79ed9a82
— 2.1 MB of HTML holding one table of 1,735 rows.
Six columns, and six is all there is: Reference | Title | Suppliers | Extension Date | Expiry Date | Value. The table contains zero hrefs —
there are no per-contract detail pages, only an ASP.NET postback into the same
page — so there is nothing more to crawl.
No Date Provided is a value, not a missing cell — 1,185 rows in the
Extension column, 531 in Expiry. The widget marks those cells
data-sort="1899-01-01", an Excel epoch sentinel. DD Month YYYY; values are £47,538.00 and are parsed as
Decimal, never float. BHCC - 009438
expires 09 Jan 2019, extends to 08 Jan 2020). The two columns are not a
period; only Expiry becomes ends_on and the extension goes to metadata. BHCC - 008176 is
published twice, once per supplier, under one internal id. Rows are grouped
by reference before writing or the second row’s supplier would replace the
first’s. £0.00 is 346 of 1,734 rows and often means “not recorded”: Van Oord’s
coastal scheme is published at £0.00 and has been paid £4.2m. Any total over
this source is a floor. ApexPrimeCare Ltd, Care Outlook Ltd, … against change, grow, live (CGL) and Knight, Kavanagh & Page. 24 cells contain a comma and it
is a separator in exactly half of them. The splitter takes a cell only when
it has three or more fragments, each with balanced brackets and either two
words or a capitalised single word; a cell it refuses is stored whole and
flagged in the contract’s metadata. ingest_windows ledger
Unlike council spend. A window is a period, and this source has none — one URL
returns the whole register as it stands. Filing it under invented months would
fill the coverage matrix with green cells that all mean “we fetched the same
page again”. A run is recorded in sync_logs instead.
AdvocacyApi.Scrapers.CouncilRegister — fetch, parse, group, upsert. Source
key council_register. (source, source_ref) — the council’s own reference — via the
partial unique index in 20260909111010_add_council_register_contract_key. bin/advocacy_api rpc 'AdvocacyApi.Release.load_register("Brighton and Hove")'.
Every URL hard-coded in priv/scripts/import_*.exs had rotted (404/403) by
September 2026. These are the replacements, re-verified, downloaded and staged
in /opt/advocacy-api/priv/data/ on the VPS — a directory the deploy rsync
excludes, so it survives deploys.
Almost none of them are CSV any more. They were converted with a stdlib
zipfile/openpyxl/xlrd pass, taking the named sheet and skipping the gov.uk
preamble rows; the Join column is the measured overlap against the codes
actually in postcodes (not an estimate).
| Staged file | Source URL | Original | Sheet | Geography | Join |
|---|---|---|---|---|---|
fuel_poverty.csv |
DESNZ sub-regional fuel poverty 2026 (2024 data) | xlsx |
Table 4 |
LSOA 2021 | 33,755 / 33,756 (100%) |
flood_risk.csv |
FoE flood risk & social vulnerability | xlsx | first | LSOA 2011 | 31,809 / 32,843 (96.9%) |
house_prices_lsoa.csv |
ONS HPSSA dataset 46 | zip → xls |
1a |
LSOA 2011 | 33,647 / 34,753 (96.8%) |
broadband_oa.csv, _laua, _pcon |
Ofcom Connected Nations 2025 fixed performance | zip → csv | — | OA 2021 | 238,861 / 238,864 (100%) |
life_expectancy_{le,hle,dfle}_msoa.csv |
ONS health state LE for MSOAs, 2019–2023 | xlsx |
1,2,3 |
MSOA 2021 | 7,264 / 7,264 (100%) |
ge2024_constituencies.csv |
electionresults.uk 2024 | csv | — | 2024 constituencies | 650 rows |
crime/YYYY-MM/*-street.csv |
data.police.uk archive | zip → csv | — | LSOA 2021 | 509 files, 12 months |
child_poverty_{constituency,la}.csv |
End Child Poverty AHC 2024/25 | xlsx |
3,2 |
LA + constituency | 650 / 361 rows |
hpssadataset09 URL was wrong twice over: dataset 46 lives under
its own slug (medianpricepaidbylowerlayersuperoutputareahpssadataset46), and
is a zipped legacy .xls, not a CSV. Its 114 columns are quarterly medians
from Dec 1995 to Mar 2023 — the last is the headline, the rest belong in
house_price_data. lsoa_data.child_poverty_pct. DWP took over the after-housing-costs method
for 2026 and End Child Poverty now publish LA and constituency only. Either
point the figures at constituency_data / local_authority_data, or take LSOA
numbers from DWP Stat-Xplore’s Children in Low Income Families release. <10, 10<30, … >=900
Mbit/s), so broadband_avg_download has to be derived; the bands themselves
fit broadband_data. It is Output Area, finer than LSOA — aggregate via
postcodes.oa21. Area type = 'MSOA' feeds msoa_data, 'Local Areas' feeds
local_authority_data. Sheets 2 and 3 add healthy and disability-free LE. epc.opendatacommunities.org now
redirects to get-energy-performance-data.communities.gov.uk, whose bulk
endpoints require registration; there is no anonymous download. import_crime.exs keeps only LSOAs starting E, so Welsh crime is
discarded even though the source covers Wales. | Dataset | Update frequency | Last imported |
|---|---|---|
| postcodes.io pg_dump | Periodic — check latest for new releases | 2025-12-18 |
| Representatives | Daily at 06:00 UTC via Oban cron | Ongoing |
| Councillor emails | On-demand via enricher (could be added to cron) | On-demand |
| Council spend (Brighton) | Monthly, published in arrears — the upload month is not the data month |
Walked via ingest_windows (council_spend) |
| Council contracts register (Brighton) | Live page, no publication schedule — re-fetch and re-upsert |
On demand: Release.load_register/1 |
| IMD 2019 | Static (no new release planned) |
Import script: import_imd.exs |
| Census 2021 | Static (next census ~2031) |
Import script: import_census.exs |
| Fuel Poverty | Annual (typically spring) |
Import script: import_fuel_poverty.exs |
| Crime | Monthly (bulk download) |
Import script: import_crime.exs |
| House Prices | Quarterly |
Import script: import_house_prices.exs |
| Broadband | Annual (autumn) |
Import script: import_broadband.exs |
| Election Results | After each general election |
Import script: import_election_results.exs |
| Life Expectancy | Biennial |
Import script: import_life_expectancy.exs |
| EPC | Cumulative (certificates added daily) |
Import script: import_epc.exs |
| Child Poverty | Annual |
Import script: import_child_poverty.exs |
| Flood Risk | Irregular (when EA updates maps) |
Import script: import_flood_risk.exs |
England-centric: Most LSOA-level datasets cover England only (or England & Wales). Scotland and Northern Ireland publish equivalent data but using different geographies and definitions, which are not yet integrated.
Boundary mismatches: IMD 2019 and some other datasets use 2011 LSOA boundaries, while Census 2021 introduced new 2021 LSOA boundaries. Most LSOAs are unchanged, but ~5% were split or merged. We join on LSOA code which handles this for matching LSOAs, but some 2011-only LSOAs may not match 2021 codes.
Point-in-time snapshots: Most datasets are annual or less frequent snapshots. The API returns the most recently imported version — it does not track changes over time (except house prices which include historical data in the detail JSONB).
Modelled vs measured: Several datasets (fuel poverty, child poverty, air quality) are modelled estimates, not direct measurements. They carry inherent uncertainty that increases at smaller geographies.
Small area suppression: DWP benefits data suppresses counts below 10 to prevent identification. This means the most deprived areas (few claimants) may show as null rather than a small number.
No real-time data: All datasets are retrospective. Crime data has a ~2 month lag, benefits data ~1 month, house prices ~3 months from transaction to publication.