Crossreferences your community database with free UK open data to identify where paid ads will have the highest impact.
- Pulls postcodes from your SQL database
- Maps each postcode to an LSOA and parliamentary constituency (via ONSPD)
- Enriches with:
- ONS Census 2021: % women, % age 18–45, % degree-educated
- 2024 General Election: progressive vote share (Labour + Green + LibDem + SNP + Plaid)
- Scores each area:
- Density score — how many Level Up members already live there
- Opportunity score — demographic/political signal, discounted for areas already well-covered
- Outputs:
scored_postcodes.csv— upload to Meta Ads or Google Adstargeting_map.html— interactive map to explore visually
pip install pandas numpy folium requests psycopg2-binary
# or for MySQL: pip install pymysqlEdit geo_targeting.py, find the DB_CONFIG block at the top:
DB_CONFIG = {
"engine": "postgres", # or "mysql" or "sqlite"
"host": "your-db-host",
"port": 5432,
"database": "your-db-name",
"user": "your-username",
"password": "your-password",
}
POSTCODE_QUERY = """
SELECT postcode FROM community_members
WHERE postcode IS NOT NULL
"""Also update POSTCODE_QUERY to match your actual table and column names.
python download_data.pyThis will auto-download Census 2021 and electoral data.
For ONSPD (the postcode directory), you need to download it manually because ONS changes the URL quarterly:
- Go to: https://geoportal.statistics.gov.uk/
- Search: ONS Postcode Directory
- Click the most recent version → CSV download (~800MB zip)
- Unzip and rename the main CSV file to
data/ONSPD_raw.csv - Re-run
python download_data.pyto slim it down
python geo_targeting.pyOutputs:
scored_postcodes.csvtargeting_map.html
| Dataset | Source | License |
|---|---|---|
| ONS Postcode Directory (ONSPD) | geoportal.statistics.gov.uk | Open Government Licence |
| Census 2021 (TS008, TS007, TS067) | NOMIS / ONS | Open Government Licence |
| 2024 GE Results | House of Commons Library | Open Parliament Licence |
All three are free, openly licensed, and do not require registration.
Members per 1,000 residents in that LSOA, normalised.
Weighted demographic index:
- % women aged 18–45 → 40% weight
- % with degree-level education → 15% weight
- % female population → 15% weight
- Progressive vote share (Lab+Grn+LD+SNP+PC) → 30% weight
Then discounted (up to 50%) for areas already well-covered by existing members.
High tier = strong demographics, not yet well-reached → prioritise for ads Low tier = already well-covered OR weak signal → deprioritise
- In Ads Manager → Audiences → Create Audience → Custom Audience
- Or use Location Targeting: paste the postcodes from the "High" tier rows
- You can also upload the full CSV as a customer list to find lookalike audiences
- Campaigns → Locations → Bulk add locations
- Paste postcode list from the "High" tier
Ideas for future iterations:
- Add Indices of Multiple Deprivation to weight by class/income
- Pull Twitter/X follower geodata (if available) to validate the model
- Add ONS Subnational Population Projections for growth areas
- Build a Flask/FastAPI endpoint to refresh scores automatically on a schedule