Compare commits

...

9 commits

Author SHA1 Message Date
badab57438 sus
Some checks failed
CI / Check (push) Failing after 7m12s
Build and publish Docker image / build-and-push (push) Successful in 7m25s
2026-07-12 15:11:21 +01:00
920119ff48 Boring changes 2026-07-12 15:10:26 +01:00
cfaf58dfba lgtm 2026-07-12 15:03:33 +01:00
982e0cc89c . 2026-07-03 19:27:02 +01:00
463bd4c647 .. 2026-07-03 18:47:28 +01:00
ab688243d7 .. 2026-07-03 18:39:34 +01:00
1ee796b282 . 2026-07-03 18:28:56 +01:00
909e241907 ok 2026-07-03 18:01:10 +01:00
c2070693fb wip 2026-06-28 11:59:44 +01:00
326 changed files with 18950 additions and 1589 deletions

View file

@ -30,7 +30,7 @@ jobs:
- name: Install frontend dependencies
working-directory: frontend
# Chrome isn't needed for these checks (lint/typecheck/vitest-jsdom), so
# skip puppeteer's postinstall browser download it's slow and a flaky
# skip puppeteer's postinstall browser download: it's slow and a flaky
# point of failure. The prerender build installs Chrome explicitly.
env:
PUPPETEER_SKIP_DOWNLOAD: "true"

View file

@ -13,7 +13,7 @@ jobs:
# The product-demo videos and their poster JPGs live in Git LFS (see
# .gitattributes). The checkout below needs `lfs: true` to smudge the real
# binaries, but the runner image ships without the git-lfs executable, so
# install it first — otherwise checkout fails with "Unable to locate
# install it first. Otherwise checkout fails with "Unable to locate
# executable file: git-lfs".
- name: Install Git LFS
run: |
@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v4
# Without lfs, checkout writes ~130-byte LFS pointer text files, the
# Docker build copies those stubs into frontend/dist/video/*, and the
# server serves text as video/mp4 so the videos never load in
# server serves text as video/mp4, so the videos never load in
# production. Smudge the real binaries instead.
with:
lfs: true

1
.gitignore vendored
View file

@ -28,3 +28,4 @@ r5-java/tmp
property-data
property-data-snapshot
property-data-snapshot2
video/.audit*

View file

@ -229,7 +229,7 @@ $(SATELLITE_HIGHRES_TILES): $(PMTILES_BIN) pipeline/download/satellite_highres.p
docker build -t $(GDAL_ECW_IMAGE) docker/gdal-ecw
uv run python -m pipeline.download.satellite_highres --output $@ --pmtiles-bin $(PMTILES_BIN) --pmtiles-version $(PMTILES_VERSION) --gdal-image $(GDAL_ECW_IMAGE) $(SATELLITE_HIGHRES_ARGS)
# EPC requires manual registration — fail with instructions
# EPC requires manual registration. Fail with instructions
$(EPC):
@echo ""
@echo "=== EPC dataset not found ==="
@ -409,7 +409,7 @@ $(TREE_DENSITY_PC): $(FR_TOW) $(NFI) $(ARCGIS) $(TREE_DENSITY_DEPS)
--arcgis $(ARCGIS) \
--output-postcodes $(TREE_DENSITY_PC)
# Postcode boundaries require manual generation — fail with instructions
# Postcode boundaries require manual generation. Fail with instructions
$(PC_BOUNDARIES):
@echo ""
@echo "=== Postcode boundaries not found ==="

View file

@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Postcode Boundary Quality Bank Station (1km radius)\n",
"# Postcode Boundary Quality: Bank Station (1km radius)\n",
"\n",
"Compares postcode boundaries **before** and **after** greenspace/water subtraction."
]

View file

@ -390,14 +390,14 @@
"name": "stdout",
"output_type": "stream",
"text": [
"PROPERTY_TYPE 5 distinct values:\n",
"PROPERTY_TYPE: 5 distinct values:\n",
" House 17,437,884\n",
" Flat 8,236,696\n",
" Bungalow 2,448,109\n",
" Maisonette 710,695\n",
" Park home 14,577\n",
"\n",
"BUILT_FORM 9 distinct values:\n",
"BUILT_FORM: 9 distinct values:\n",
" Semi-Detached 8,777,318\n",
" Mid-Terrace 7,972,697\n",
" Detached 6,428,144\n",
@ -414,7 +414,7 @@
"source": [
"for col_name in [\"PROPERTY_TYPE\", \"BUILT_FORM\"]:\n",
" counts = scan().group_by(col_name).len().sort(\"len\", descending=True).collect()\n",
" print(f\"{col_name} {len(counts)} distinct values:\")\n",
" print(f\"{col_name}: {len(counts)} distinct values:\")\n",
" for row in counts.iter_rows(named=True):\n",
" print(f\" {row[col_name]!s:30s} {row['len']:>12,}\")\n",
" print()"

View file

@ -5,9 +5,9 @@
"id": "21f27a93",
"metadata": {},
"source": [
"# Online Buy Listings data quality & cleanup\n",
"# Online Buy Listings: data quality & cleanup\n",
"\n",
"Source: `finder/data/online_listings_buy.parquet` ~112k UK *for-sale* property listings\n",
"Source: `finder/data/online_listings_buy.parquet`, ~112k UK *for-sale* property listings\n",
"(Greater London) scraped from **Rightmove**, **OnTheMarket** and **Zoopla** and merged by the\n",
"`finder` pipeline (`transform.py` / `onthemarket.py` / `zoopla.py` → `storage.py`).\n",
"\n",
@ -453,7 +453,7 @@
"id": "7ea9293c",
"metadata": {},
"source": [
"### 3.1 · 🔴 Mislabeled column `Number of bedrooms & living rooms` is actually `Bedrooms + Bathrooms`\n",
"### 3.1 · 🔴 Mislabeled column: `Number of bedrooms & living rooms` is actually `Bedrooms + Bathrooms`\n",
"\n",
"The column name promises *living rooms / receptions*, but `storage.py` recomputes it as\n",
"`Bedrooms + Bathrooms` for **every** row of **every** portal (Zoopla's `+ receptions` line is\n",
@ -529,7 +529,7 @@
"### 3.2 · 🔴 Price integrity\n",
"\n",
"- **Shared-ownership / part-buy** listings store the *share* price as `Asking price`, not full value\n",
" (median ~£146k vs ~£550k) — corrupts any price / £-per-sqm aggregate.\n",
" (median ~£146k vs ~£550k). Corrupts any price / £-per-sqm aggregate.\n",
"- **38% of rows carry a non-firm qualifier** (Guide / Offers Over / OIEO / From / Shared); `Asking price`\n",
" is stored identically regardless.\n",
"- A `<£10k` tail of auction land/garage lots + two `£1` placeholders; 364 nulls (price ≤ 0 → null)."
@ -657,7 +657,7 @@
"\n",
"- ~49% of `Total floor area (sqm)` is null (OnTheMarket ~87%).\n",
"- **Impossible tiny areas**: rows under 20 m² with ≥2 bedrooms (single-room dimensions parsed as total).\n",
"- **Suspiciously large areas** (>400 m²) likely sq ft never converted to m².\n",
"- **Suspiciously large areas** (>400 m²): likely sq ft never converted to m².\n",
"- `Asking price per sqm` is mechanically correct but faithfully amplifies bad areas (max £410,959/m²)."
]
},
@ -755,7 +755,7 @@
}
],
"source": [
"# Floor area vs bedrooms red points are physically impossible (area < beds * 8 m²)\n",
"# Floor area vs bedrooms: red points are physically impossible (area < beds * 8 m²)\n",
"s = raw.filter(fa.is_not_null() & (fa < 160)).select(\"Bedrooms\", \"Total floor area (sqm)\")\n",
"rng = np.random.default_rng(0)\n",
"xb = s[\"Bedrooms\"].to_numpy().astype(float)\n",
@ -946,7 +946,7 @@
}
],
"source": [
"# Listings by provider (downsampled) shows London footprint + centroid clusters\n",
"# Listings by provider (downsampled): shows London footprint + centroid clusters\n",
"g = raw.sample(min(25_000, raw.height), seed=0).select(\"lat\", \"lon\", \"provider\")\n",
"colors = {\"Rightmove\": \"#2563eb\", \"OnTheMarket\": \"#16a34a\", \"Zoopla\": \"#dc2626\"}\n",
"fig, ax = plt.subplots(figsize=(8, 7))\n",
@ -971,7 +971,7 @@
"\n",
"No cross-source identity resolution: the same physical property appears across portals, and is\n",
"re-listed multiple times within a portal (especially Zoopla). `UPRN` (Zoopla-only, 1.8% coverage)\n",
"is not 1:1 405 UPRNs repeat up to 7×, breaking the intended exact EPC join."
"is not 1:1. 405 UPRNs repeat up to 7×, breaking the intended exact EPC join."
]
},
{
@ -1024,7 +1024,7 @@
"- `Listing status` is a constant `\"For sale\"` (dead column).\n",
"- `Property sub-type` has 79 values with portal-spelling variants (`Apartment`↔`Flat`, `X House`↔`X`, …).\n",
"- `Price qualifier` has case-only duplicate pairs (Rightmove TitleCase vs OTM sentence-case).\n",
"- Missing values are encoded inconsistently empty-string in some columns, `null` in others."
"- Missing values are encoded inconsistently: empty-string in some columns, `null` in others."
]
},
{
@ -1155,7 +1155,7 @@
"### 3.8 · 🔴/🟡 Listing date\n",
"\n",
"`Listing date` is **null for 100% of OnTheMarket & Zoopla** (Rightmove-only `firstVisibleDate`), so any\n",
"recency analysis is biased to 82% of the data and it reaches back to **2011** in a live for-sale set."
"recency analysis is biased to 82% of the data, and it reaches back to **2011** in a live for-sale set."
]
},
{
@ -1293,7 +1293,7 @@
"id": "2491e545",
"metadata": {},
"source": [
"**Step A** drop dead/redundant columns, normalise empty-string → null, canonicalise `Price qualifier`, derive `price_basis` + `is_shared_ownership`."
"**Step A**: drop dead/redundant columns, normalise empty-string → null, canonicalise `Price qualifier`, derive `price_basis` + `is_shared_ownership`."
]
},
{
@ -1392,7 +1392,7 @@
"id": "58767c84",
"metadata": {},
"source": [
"**Step B** floor-area sanity (null impossibly-small, flag suspiciously-large) + recompute `Asking price per sqm` (excluding shared-ownership); null sentinel-`0` beds/baths for dwellings; canonicalise sub-types; add `is_residential`, `location_precision`; strip fake unit postcodes from outcode-only rows."
"**Step B**: floor-area sanity (null impossibly-small, flag suspiciously-large) + recompute `Asking price per sqm` (excluding shared-ownership); null sentinel-`0` beds/baths for dwellings; canonicalise sub-types; add `is_residential`, `location_precision`; strip fake unit postcodes from outcode-only rows."
]
},
{
@ -1481,7 +1481,7 @@
"id": "33e81cf5",
"metadata": {},
"source": [
"**Step C** de-duplicate to one row per physical property. Heuristic key: `(lat, lon, Asking price, Bedrooms)`, keeping the most-recently-listed row. This collapses both cross-portal and intra-portal duplicates; `raw` is retained for provenance."
"**Step C**: de-duplicate to one row per physical property. Heuristic key: `(lat, lon, Asking price, Bedrooms)`, keeping the most-recently-listed row. This collapses both cross-portal and intra-portal duplicates; `raw` is retained for provenance."
]
},
{
@ -1554,7 +1554,7 @@
"id": "7d17dd05",
"metadata": {},
"source": [
"## 5 · After cleanup re-show the stats"
"## 5 · After cleanup: re-show the stats"
]
},
{
@ -1737,7 +1737,7 @@
"id": "0a16d168",
"metadata": {},
"source": [
"**Missingness after cleanup** beds/baths now legitimately null where unknown; empty-strings gone; per-sqm follows cleaned area:"
"**Missingness after cleanup**: beds/baths now legitimately null where unknown; empty-strings gone; per-sqm follows cleaned area:"
]
},
{
@ -1924,7 +1924,7 @@
"id": "e833c578",
"metadata": {},
"source": [
"**Categoricals normalised** sub-type variants collapsed, qualifier casing merged:"
"**Categoricals normalised**: sub-type variants collapsed, qualifier casing merged:"
]
},
{
@ -2007,7 +2007,7 @@
"id": "94ac0585",
"metadata": {},
"source": [
"**Before/after key metrics chart:**"
"**Before/after key metrics chart:**"
]
},
{
@ -2076,7 +2076,7 @@
"\n",
"- **`clean` keeps every row** (issues are flagged, not dropped) so it stays comparable to `raw`; use the\n",
" `is_residential` / `is_shared_ownership` / `floor_area_suspect_*` / `location_precision` flags to filter.\n",
"- **Shared-ownership full prices cannot be recovered** here those rows are flagged and excluded from\n",
"- **Shared-ownership full prices cannot be recovered** here: those rows are flagged and excluded from\n",
" `£/sqm`, but their `Asking price` is still a share. Likewise **suspiciously-large areas are flagged,\n",
" not auto-converted** from sq ft (the conversion factor isn't certain per-row).\n",
"- **De-dup is heuristic** (`lat,lon,price,beds`); it can over-collapse distinct units sharing a\n",

View file

@ -5,7 +5,7 @@
"id": "46a28f40",
"metadata": {},
"source": [
"# School catchment model the working\n",
"# School catchment model: the working\n",
"\n",
"The postcode features **\"Good+/Outstanding primary/secondary school catchments\"** count the\n",
"rated state schools whose modelled *admission cutoff radius* covers a postcode. This notebook\n",
@ -17,7 +17,7 @@
"Pupil Database. What *is* public: where every school is and how many pupils it has (GIAS), how\n",
"many children live where (Census 2021), and the fact that most English admissions are run as\n",
"**deferred acceptance with distance tie-breaks**. That is enough to *solve for* each school's\n",
"cutoff distance — the \"last distance offered\" that councils publish each offer day — and those\n",
"cutoff distance (the \"last distance offered\" that councils publish each offer day) and those\n",
"published figures give us ground truth to calibrate against.\n",
"\n",
"The production code is `pipeline/transform/school_catchments.py`; the calibration harness is\n",
@ -81,14 +81,14 @@
"id": "e13f2bc4",
"metadata": {},
"source": [
"## 1. Supply schools and their phase fill targets\n",
"## 1. Supply: schools and their phase fill targets\n",
"\n",
"Every open, **non-selective** state school (academies, LA-maintained, free schools) takes part.\n",
"Grammar schools are excluded outright: their intakes are test-based and region-wide, so any\n",
"distance-based catchment would be fabricated. Independent, special and Welsh schools don't\n",
"admit by distance either.\n",
"\n",
"A school's *fill target* is `max(capacity, headcount)` an over-full school keeps its\n",
"A school's *fill target* is `max(capacity, headcount)`: an over-full school keeps its\n",
"demonstrated size, an under-full one can admit up to capacity (the feature asks \"would you get\n",
"a place?\", not \"does a pupil already live there?\"). The target is prorated over the cohort ages\n",
"the school teaches, parsed from its age range: nursery years weigh 0.5 and sixth-form years 0.6,\n",
@ -182,16 +182,16 @@
"id": "2905514f",
"metadata": {},
"source": [
"## 2. Demand children per postcode\n",
"## 2. Demand: children per postcode\n",
"\n",
"Census 2021 (TS007A) gives children by five-year band per LSOA. Bands don't align with school\n",
"phases, so phases take fractional shares primary (ages 410) = ⅕·(04) + (59) + ⅕·(1014);\n",
"secondary (1115) = ⅘·(1014) + ⅕·(1519) and each LSOA's total is split evenly across its\n",
"phases, so phases take fractional shares: primary (ages 410) = ⅕·(04) + (59) + ⅕·(1014);\n",
"secondary (1115) = ⅘·(1014) + ⅕·(1519), and each LSOA's total is split evenly across its\n",
"live postcodes (LSOAs hold ~40 postcodes, small enough at catchment scale).\n",
"\n",
"Not all of those children compete for state places: births fell ~10% between 2016 and 2021\n",
"(exactly the gap between the census stock and the cohorts reaching Reception by mid-decade) and\n",
"~7% attend independent schools or are home-educated. `DEMAND_SCALE = 0.8` absorbs both without\n",
"~7% attend independent schools or are home-educated. `DEMAND_SCALE = 0.8` absorbs both, without\n",
"it, modelled cutoffs run systematically tight and half the genuinely undersubscribed schools\n",
"look full (this was the single biggest correction the ground truth forced; see §7).\n"
]
@ -241,18 +241,18 @@
"id": "20d44b21",
"metadata": {},
"source": [
"## 3. Preferences grade bonuses and logit choice\n",
"## 3. Preferences: grade bonuses and logit choice\n",
"\n",
"Families don't just pick the nearest school. Two ingredients:\n",
"\n",
"- **Grade bonus** a school's *effective distance* is its real distance minus an Ofsted-grade\n",
"- **Grade bonus**: a school's *effective distance* is its real distance minus an Ofsted-grade\n",
" bonus (+0.6 km Outstanding, +0.3 km Good, 0.3/0.6 km for grade 3/4). A family accepts that\n",
" much extra travel for a better school.\n",
"- **Logit smearing** even so, not everyone at a postcode ranks the same school first. Each\n",
"- **Logit smearing**: even so, not everyone at a postcode ranks the same school first. Each\n",
" postcode's children split across the nearby feasible schools with weights\n",
" `softmax(effective_distance / τ)`, τ = 0.3 km. This matters more than it looks: with\n",
" deterministic choice a popular school fills entirely from its nearest band, putting its\n",
" marginal admitted child — and therefore its cutoff — unrealistically close (about 2× too\n",
" marginal admitted child (and therefore its cutoff) unrealistically close (about 2× too\n",
" tight against published cutoffs).\n",
"\n",
"Below: the share of applications a Good school captures against an unrated neighbour 1 km away.\n"
@ -306,7 +306,7 @@
"id": "04bcfbcf",
"metadata": {},
"source": [
"## 4. The equilibrium cutoff dynamics\n",
"## 4. The equilibrium: cutoff dynamics\n",
"\n",
"English admissions run deferred acceptance with distance priority; in a continuum economy that\n",
"is equivalent to finding **market-clearing cutoff distances** (Azevedo & Leshno 2016). The solver:\n",
@ -314,7 +314,7 @@
"1. start every school's cutoff at ∞;\n",
"2. every child unit applies to its preferred school(s) among those whose cutoff still covers it;\n",
"3. every oversubscribed school tightens its cutoff to the distance of its **marginal admitted\n",
" child** exactly the published \"last distance offered\";\n",
" child**, exactly the published \"last distance offered\";\n",
"4. repeat. Cutoffs only ever tighten, so the iteration converges to the deferred-acceptance\n",
" outcome. Schools that never fill keep no binding cutoff; their radius falls back to the\n",
" distance within which the local child population would cover their fill target.\n",
@ -533,7 +533,7 @@
"metadata": {},
"source": [
"The bimodal logic is visible: oversubscribed urban schools cluster well under 1 km while schools\n",
"with spare places reach further. A concrete slice Cambridge and its villages. Circles are the\n",
"with spare places reach further. A concrete slice: Cambridge and its villages. Circles are the\n",
"calibrated catchment radii of Good+ primary schools: tight in town, wide in the villages.\n"
]
},
@ -600,11 +600,11 @@
"id": "25770af8",
"metadata": {},
"source": [
"## 6. Calibration modelled vs published cutoffs\n",
"## 6. Calibration: modelled vs published cutoffs\n",
"\n",
"Councils publish each school's **last distance offered** in their allocation reports. We scraped\n",
"783 rows from nine authorities (Hertfordshire, Surrey, Stockport, Manchester, Bristol, Barnet,\n",
"Redbridge, Ealing, Lambeth `property-data/ground_truth/`), matched them to GIAS URNs, and\n",
"Redbridge, Ealing, Lambeth: `property-data/ground_truth/`), matched them to GIAS URNs, and\n",
"compare against the modelled radii. Faith schools are reported separately: their published\n",
"cutoff applies *within* faith priority, which a postcode model cannot see. \"All applicants\n",
"offered\" schools test whether the model agrees there was no binding cutoff at all.\n"
@ -823,11 +823,11 @@
"source": [
"## 8. Limitations\n",
"\n",
"- **Faith admissions are not modelled** whether a faith school's catchment is open to a given\n",
"- **Faith admissions are not modelled**: whether a faith school's catchment is open to a given\n",
" family depends on the family. Their fit is accordingly worse (the orange triangles above).\n",
"- **Cutoffs are single-year snapshots**; real ones move with each cohort. The model is a\n",
" steady-state estimate, not this September's number.\n",
"- **Straight-line distance** is used throughout — it is the modal LA tie-break, but some\n",
"- **Straight-line distance** is used throughout. It is the modal LA tie-break, but some\n",
" authorities measure walking routes, and none of sibling priority, feeder schools or\n",
" designated catchment polygons are visible to the model.\n",
"- Census 2021 child counts age; `DEMAND_SCALE` should drift upward as the birth-rate dip works\n",

View file

@ -5,7 +5,7 @@
"id": "db1n423kpm8",
"metadata": {},
"source": [
"# Rightmove vs Home.co.uk Source Overlap Analysis\n",
"# Rightmove vs Home.co.uk: Source Overlap Analysis\n",
"\n",
"The property scraper collects listings from two sources: **Rightmove** and **home.co.uk**. During merging, cross-source deduplication removes home.co.uk listings that match a Rightmove listing by `(postcode, bedrooms, price)`.\n",
"\n",
@ -81,7 +81,7 @@
"\n",
"The merged parquet contains already-deduplicated data. Home.co.uk listings that matched a Rightmove listing by `(postcode, bedrooms, price)` were removed during scraping. The log reported **2,220 cross-source dedupes** for BUY.\n",
"\n",
"So the true home.co.uk total was 20,650 (unique) + 2,220 (deduped) = **22,870** giving a **9.7% overlap rate** on the outcodes that were scraped."
"So the true home.co.uk total was 20,650 (unique) + 2,220 (deduped) = **22,870**, giving a **9.7% overlap rate** on the outcodes that were scraped."
]
},
{
@ -3164,7 +3164,7 @@
"---\n",
"## 3. Approximate Overlap via Fuzzy Matching\n",
"\n",
"The scraper deduped by exact `(postcode, bedrooms, price)`. We can also check for near-matches properties at the same postcode with similar price that might be the same listing with slightly different data."
"The scraper deduped by exact `(postcode, bedrooms, price)`. We can also check for near-matches: properties at the same postcode with similar price that might be the same listing with slightly different data."
]
},
{
@ -5246,7 +5246,7 @@
}
],
"source": [
"# Property sub-type comparison top home.co.uk sub-types\n",
"# Property sub-type comparison: top home.co.uk sub-types\n",
"hk_subtypes = (\n",
" buy.filter(pl.col(\"source\") == \"Home.co.uk\")[\"Property sub-type\"]\n",
" .value_counts()\n",
@ -6304,7 +6304,7 @@
"---\n",
"## 7. What Does Home.co.uk Add?\n",
"\n",
"Home.co.uk listings that passed the dedup filter are genuinely unique not on Rightmove at all (or listed with different price/beds). What do they look like?"
"Home.co.uk listings that passed the dedup filter are genuinely unique: not on Rightmove at all (or listed with different price/beds). What do they look like?"
]
},
{
@ -6409,13 +6409,13 @@
"output_type": "stream",
"text": [
"\n",
"Rightmove days on market:\n",
"Rightmove: days on market:\n",
" Median: 74\n",
" Mean: 140\n",
" P25: 26\n",
" P75: 189\n",
"\n",
"Home.co.uk days on market:\n",
"Home.co.uk: days on market:\n",
" Median: 156\n",
" Mean: 164\n",
" P25: 50\n",
@ -6424,7 +6424,7 @@
}
],
"source": [
"# Listing age comparison are home.co.uk listings older/newer?\n",
"# Listing age comparison: are home.co.uk listings older/newer?\n",
"import datetime\n",
"\n",
"now = datetime.datetime(2026, 3, 11)\n",
@ -6435,7 +6435,7 @@
"for src in [\"Rightmove\", \"Home.co.uk\"]:\n",
" age = with_age.filter(pl.col(\"source\") == src)[\"days_on_market\"].drop_nulls()\n",
" if len(age) > 0:\n",
" print(f\"\\n{src} days on market:\")\n",
" print(f\"\\n{src}: days on market:\")\n",
" print(f\" Median: {age.median():.0f}\")\n",
" print(f\" Mean: {age.mean():.0f}\")\n",
" print(f\" P25: {age.quantile(0.25):.0f}\")\n",
@ -7388,7 +7388,7 @@
" Projected unique additions: ~274,584\n",
" Projected merged dataset: ~728,899 (60.4% increase)\n",
"\n",
"⚠️ These are rough estimates the covered outcodes may not be representative\n"
"⚠️ These are rough estimates: the covered outcodes may not be representative\n"
]
}
],
@ -7425,7 +7425,7 @@
" f\" Projected merged dataset: ~{rm_buy + projected_unique:,} ({projected_unique / rm_buy * 100:.1f}% increase)\"\n",
")\n",
"print()\n",
"print(\"⚠️ These are rough estimates the covered outcodes may not be representative\")"
"print(\"⚠️ These are rough estimates: the covered outcodes may not be representative\")"
]
},
{

View file

@ -403120,7 +403120,7 @@
}
},
"title": {
"text": "Bank Median transit error (R5 TfL easy), minutes"
"text": "Bank: Median transit error (R5 TfL easy), minutes"
}
}
}
@ -403143,7 +403143,7 @@
" zoom=6,\n",
" center={\"lat\": 51.5, \"lon\": -0.1},\n",
" opacity=0.5,\n",
" title=\"Bank Median transit error (R5 TfL easy), minutes\",\n",
" title=\"Bank: Median transit error (R5 TfL easy), minutes\",\n",
" hover_data={\n",
" \"pcds\": True,\n",
" \"travel_minutes\": True,\n",
@ -804061,7 +804061,7 @@
}
},
"title": {
"text": "Bank Best transit error (R5 TfL quick), minutes"
"text": "Bank: Best transit error (R5 TfL quick), minutes"
}
}
}
@ -804081,7 +804081,7 @@
" zoom=6,\n",
" center={\"lat\": 51.5, \"lon\": -0.1},\n",
" opacity=0.5,\n",
" title=\"Bank Best transit error (R5 TfL quick), minutes\",\n",
" title=\"Bank: Best transit error (R5 TfL quick), minutes\",\n",
" hover_data={\n",
" \"pcds\": True,\n",
" \"best_minutes\": True,\n",
@ -1204999,7 +1204999,7 @@
}
},
"title": {
"text": "Bank Absolute median transit error |R5 TfL easy|, minutes"
"text": "Bank: Absolute median transit error |R5 TfL easy|, minutes"
}
}
}
@ -1205019,7 +1205019,7 @@
" zoom=6,\n",
" center={\"lat\": 51.5, \"lon\": -0.1},\n",
" opacity=0.5,\n",
" title=\"Bank Absolute median transit error |R5 TfL easy|, minutes\",\n",
" title=\"Bank: Absolute median transit error |R5 TfL easy|, minutes\",\n",
" hover_data={\n",
" \"pcds\": True,\n",
" \"travel_minutes\": True,\n",

299
analysis/build_pages.py Normal file
View file

@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""Build the SEO page batch from findings.
Emits, from analysis/out/findings/*.json:
- frontend/public/<slug>/index.html: standalone, crawlable, on-brand landing page per finding
- frontend/public/cheaper-twins/index.html: a hub page linking every twin (internal-link mesh)
- server-rs/src/generated_data_pages.rs: registry the og_middleware consults (path/title/desc + the
screenshot query so the OG card shows the finding, not a blank map)
- frontend/public/sitemap.xml: data-page <url> entries inserted between markers (idempotent)
These are static files: webpack copies public/ -> dist/, and the server serves dist/<path>/index.html exactly
like the existing prerendered pages. og_middleware must register each path or it 404s. That registration is generated_data_pages.rs.
English-only by design (no 6-locale i18n), per the growth strategy.
Run: source .venv/bin/activate && python analysis/build_pages.py (after cheaper_twins.py + generate_findings.py)
"""
from __future__ import annotations
import html
import json
import urllib.parse
from pathlib import Path
ROOT = Path(".")
FIND = Path("analysis/out/findings")
PUBLIC = ROOT / "frontend/public"
RUST = ROOT / "server-rs/src/generated_data_pages.rs"
SITEMAP = PUBLIC / "sitemap.xml"
SITE = "https://perfect-postcode.co.uk"
CSS = """
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
"""
def esc(s) -> str:
return html.escape(str(s), quote=True)
def gbp(n) -> str:
return f"£{int(n):,}"
def rust_str(s: str) -> str:
return '"' + str(s).replace("\\", "\\\\").replace('"', '\\"') + '"'
def page_shell(title: str, desc: str, path: str, jsonld: dict, body: str) -> str:
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{esc(title)} | Perfect Postcode</title>
<meta name="description" content="{esc(desc)}" />
<link rel="canonical" href="{SITE}{esc(path)}" />
<style>{CSS}</style>
<script type="application/ld+json">{json.dumps(jsonld)}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map </a></div>
{body}
<footer><div class="wrap">Sources: {esc(SOURCES)}. {esc(ATTRIB)}</div></footer>
</body>
</html>
"""
SOURCES = "HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk"
ATTRIB = "Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0."
def breadcrumb(path: str, name: str) -> dict:
return {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": SITE + "/"},
{"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": SITE + "/cheaper-twins"},
{"@type": "ListItem", "position": 3, "name": name, "item": SITE + path},
],
}
def twin_html(f: dict, siblings: list[dict]) -> str:
p, w, s = f["pricey"], f["twin"], f["stats"]
typ = s["dominant_type"].lower()
map_url = f["map_url"]
rows = [
("Estimated £/m²", gbp(p["est_psqm"]), gbp(w["est_psqm"])),
("On a 90 m² home", gbp(p["est_psqm"] * 90), f'<span class="cheaper">{gbp(w["est_psqm"]*90)}</span>'),
("Dominant property type", esc(s["dominant_type"]), esc(s["dominant_type"])),
("Typical build era", f"~{s['build_year']}", f"~{s['build_year']}"),
("Good+ secondary catchments", f"{s['good_secondary_catchments']:.1f}", f"{s['good_secondary_catchments']:.1f}"),
("Nearest station", f"~{s['station_km']} km", f"~{s['station_km']} km"),
("Sales in sample (N)", f"{p['n']:,}", f"{w['n']:,}"),
]
table_rows = "\n".join(
f"<tr><td>{esc(k)}</td><td class='val'>{a}</td><td class='val'>{b}</td></tr>" for k, a, b in rows
)
prose = (
f"{esc(p['label'])} and {esc(w['label'])} sit about {s['distance_km']} km apart, share the same "
f"dominant housing ({typ}, typically built around {s['build_year']}), comparable good-school catchments "
f"and the same level of station access. Yet an equivalent home works out roughly "
f"<b>{s['gap_pct']:.0f}% (about {gbp(s['gap_on_90sqm'])} on a 90 m² property) cheaper in "
f"{esc(w['name'] or w['sector'])}</b>. On the measures that move price they are near-identical; the gap "
f"is mostly the premium attached to the better-known name."
)
sib_links = "\n".join(
f'<a href="{esc(sf["page_path"])}"><b>{esc(sf["title"].split(":")[0])}</b><br>{esc(sf["hook"])}</a>'
for sf in siblings
)
body = f"""
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>{esc(f['title'])}</h1>
<div class="big">{esc(f['shocking_number'])} cheaper / </div>
<p class="hook">{esc(f['hook'])}</p>
<a class="cta" href="{esc(map_url)}">See both areas on the live map </a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>{esc(p['label'])}</th><th>{esc(w['label'])}</th></tr></thead>
<tbody>{table_rows}</tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>{prose}</p></section>
<section><h2>How we worked this out</h2><p>{esc(f['methodology'])}</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins </b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker </b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map </b><br>Rank England by what each £ buys.</a>
</div>
<div class="links">{sib_links}</div>
</section>
<p class="note">{esc(ATTRIB)} Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
"""
return page_shell(f["title"], meta_desc(f), f["page_path"], breadcrumb(f["page_path"], f["title"].split(":")[0]), body)
def national_html(f: dict) -> str:
b, d = f["stats"]["best"], f["stats"]["dearest"]
body = f"""
<div class="hero"><div class="wrap">
<p class="eyebrow">England · value index</p>
<h1>{esc(f['title'])}</h1>
<div class="big">{esc(f['shocking_number'])}</div>
<p class="hook">{esc(f['hook'])}</p>
<a class="cta" href="{SITE}/?{esc(f['map_query'])}">Explore the value map </a>
</div></div>
<div class="wrap">
<table><thead><tr><th>Sector</th><th>Est. £/</th><th> for £100k</th><th>N</th></tr></thead><tbody>
<tr><td>{esc(b['sector'])} (best value)</td><td class='val'>{gbp(b['est_psqm'])}</td><td class='val cheaper'>{b['sqm_per_100k']:.0f} </td><td class='val'>{b['n']:,}</td></tr>
<tr><td>{esc(d['sector'])} (dearest)</td><td class='val'>{gbp(d['est_psqm'])}</td><td class='val'>{d['sqm_per_100k']:.0f} </td><td class='val'>{d['n']:,}</td></tr>
</tbody></table>
<section><h2>How we worked this out</h2><p>{esc(f['methodology'])}</p></section>
<section><h2>More</h2><div class="links">
<a href="/cheaper-twins"><b>Cheaper twins </b><br>Pairs of areas priced apart for the name, not the home.</a>
<a href="/postcode-checker"><b>Postcode checker </b><br>Everything known about any postcode.</a>
</div></section>
<p class="note">{esc(ATTRIB)}</p>
</div>
"""
return page_shell(f["title"], meta_desc(f), f["page_path"], breadcrumb(f["page_path"], f["title"]), body)
def hub_html(twins: list[dict]) -> str:
cards = "\n".join(
f'<a class="card" href="{esc(f["page_path"])}"><div class="n">{esc(f["shocking_number"])}</div>'
f'<div>{esc(f["title"].split(":")[0])}</div></a>'
for f in twins
)
body = f"""
<div class="hero"><div class="wrap">
<p class="eyebrow">England</p>
<h1>Cheaper twins: pay for the home, not the name</h1>
<p class="hook">Pairs of neighbouring England postcodes that share a station, school catchment and build era,
but sell thousands apart because one name got bid up. Built from {len(twins)} verified pairs.</p>
<a class="cta" href="/?ref=twins-hub">Find your cheaper twin on the map </a>
</div></div>
<div class="wrap">
<section><div class="cards">{cards}</div></section>
<p class="note">{esc(ATTRIB)}</p>
</div>
"""
jsonld = {"@context": "https://schema.org", "@type": "CollectionPage", "name": "Cheaper twins", "url": SITE + "/cheaper-twins"}
return page_shell("Cheaper twin postcodes in England", "Neighbouring England postcodes priced apart for the name, not the home. Find the cheaper twin of a pricier area.", "/cheaper-twins", jsonld, body)
def meta_desc(f: dict) -> str:
return (f.get("hook") or f.get("title"))[:155]
def write_page(path: str, content: str):
out = PUBLIC / path.strip("/") / "index.html"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(content)
def main():
findings = [json.loads(p.read_text()) for p in sorted(FIND.glob("*.json"))]
twins = [f for f in findings if f["type"] == "cheaper_twin"]
nationals = [f for f in findings if f["type"] == "national_table"]
pages = [] # (path, title, description, screenshot_query)
for i, f in enumerate(twins):
siblings = [twins[(i + k) % len(twins)] for k in (1, 2, 3)][: max(0, len(twins) - 1)]
write_page(f["page_path"], twin_html(f, siblings))
pages.append((f["page_path"], f["title"], meta_desc(f), f["map_query"]))
for f in nationals:
write_page(f["page_path"], national_html(f))
pages.append((f["page_path"], f["title"], meta_desc(f), f["map_query"]))
write_page("/cheaper-twins", hub_html(twins))
pages.append(("/cheaper-twins", "Cheaper twin postcodes in England", "Neighbouring England postcodes priced apart for the name, not the home.", ""))
# Rust registry
entries = "\n".join(
f" DataPage {{ path: {rust_str(p)}, title: {rust_str(t)}, description: {rust_str(d)}, screenshot_query: {rust_str(q)} }},"
for p, t, d, q in pages
)
RUST.write_text(
"// @generated by analysis/build_pages.py. Do not edit by hand.\n"
"// Registers the data-driven growth pages so og_middleware serves them (not 404) with the\n"
"// right title/description and an OG card pointed at the finding's map view.\n\n"
"pub struct DataPage {\n"
" pub path: &'static str,\n"
" pub title: &'static str,\n"
" pub description: &'static str,\n"
" /// Map query string the OG screenshot should frame (empty = default map).\n"
" pub screenshot_query: &'static str,\n"
"}\n\n"
f"pub static DATA_PAGES: &[DataPage] = &[\n{entries}\n];\n\n"
"/// Look up a generated data page by request path (already trailing-slash-trimmed).\n"
"pub fn data_page(path: &str) -> Option<&'static DataPage> {\n"
" DATA_PAGES.iter().find(|p| p.path == path)\n"
"}\n"
)
# Sitemap: replace the block between markers (idempotent), else insert before </urlset>.
start, end = "<!-- DATA_PAGES_START -->", "<!-- DATA_PAGES_END -->"
block = [start]
for p, *_ in pages:
block.append(f" <url>\n <loc>{SITE}{p}</loc>\n <changefreq>monthly</changefreq>\n <priority>0.7</priority>\n </url>")
block.append(end)
block_str = "\n".join(block)
sm = SITEMAP.read_text()
if start in sm and end in sm:
pre, rest = sm.split(start, 1)
_, post = rest.split(end, 1)
sm = pre + block_str + post
else:
sm = sm.replace("</urlset>", block_str + "\n</urlset>")
SITEMAP.write_text(sm)
print(f"Wrote {len(pages)} pages to {PUBLIC}/ (+ hub), {RUST}, and {len(pages)} sitemap entries.")
print("Pages:")
for p, t, *_ in pages:
print(f" {p}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""Turn each finding into a ready-to-film VIDEO KIT.
The founder films manually (screen-recording the live map), so this emits everything needed to shoot a
payoff-first "cheaper twin" video without writing anything: the hook, a beat-by-beat shot list tied to
applying each filter, a human-VO narration script, 6-word captions, the exact map URL to record, and the
YouTube title/description/chapters/tags/thumbnail. It also prints a storyboard spec (filters + suggested
city) for the optional automated render path (video/src/storyboard.ts AD_CONFIGS + render.sh, which needs the
running stack + login creds, so that part is yours to run).
Outputs:
analysis/out/video_scripts/<slug>.md: one filming kit per finding
analysis/out/video_scripts/INDEX.md: overview + how to film
Run: source .venv/bin/activate && python analysis/build_video_scripts.py (after generate_findings.py)
"""
from __future__ import annotations
import json
import re
from pathlib import Path
FIND = Path("analysis/out/findings")
OUT = Path("analysis/out/video_scripts")
SITE = "https://perfect-postcode.co.uk"
SOURCES = "Land Registry, EPC, Ofsted, DfT, Police.uk"
# Suggested CityKey for the optional auto-render path (storyboard.ts CityKey union).
CITY_OUTCODES = {
"manchester": {"M", "SK", "OL", "BL", "WN"},
"birmingham": {"B"},
"bristol": {"BS"},
"leeds": {"LS", "WF", "BD"},
}
LONDON = {"E", "EC", "WC", "W", "SW", "SE", "N", "NW", "BR", "IG", "RM", "TW", "KT", "HA", "SM", "CR", "UB", "EN", "DA"}
def gbp(n) -> str:
return f"£{int(n):,}"
def outward_area(sector: str) -> str:
return re.match(r"^[A-Z]+", sector).group(0)
def city_key(pricey_sector: str) -> str:
area = outward_area(pricey_sector)
if area in LONDON:
return "london"
for city, codes in CITY_OUTCODES.items():
if area in codes:
return city
return "london" # fallback; only matters for the auto-render variant
def twin_kit(f: dict) -> str:
p, w, s = f["pricey"], f["twin"], f["stats"]
pn, wn = p["name"] or p["sector"], w["name"] or w["sector"]
typ = s["dominant_type"].lower()
plural = {
"Flats/Maisonettes": "flats",
"Terraced": "terraced houses",
"Semi-Detached": "semi-detached houses",
"Detached": "detached houses",
}.get(s["dominant_type"], typ + "s")
gap = f"{s['gap_pct']:.0f}%"
money = gbp(s["gap_on_90sqm"])
url = f["map_url"]
titles = [
f["title"],
f"{pn} vs {wn}: same station, same schools, {money} cheaper",
f"Why {wn} is the smart-money version of {pn} ({gap} less per m²)",
]
captions = [
f"{pn} vs {wn}",
f"Same station. Same schools.",
f"{money} cheaper",
f"Same {typ}, ~{s['build_year']}",
f"{gap} less per m²",
"Find your cheaper twin, free",
]
narration = (
f"This is {pn}. And this is {wn}, right next door. Same station. "
f"Same {'secondary school catchment' if s['good_secondary_catchments'] else 'schools'}. "
f"The same kind of home: {plural} built around {s['build_year']}. "
f"On every measure that moves price, they're twins. "
f"But watch the price per square metre. {pn}: {gbp(p['est_psqm'])}. {wn}: {gbp(w['est_psqm'])}. "
f"That's {gap} cheaper, about {money} on a typical 90-square-metre home, "
f"for the same life, one postcode over. You're not paying for the house. You're paying for the name. "
f"You can find the cheaper twin of any postcode in England on the map for free, no signup."
)
chapters = [
("0:00", f"The two postcodes ({pn} & {wn})"),
("0:08", "Same station"),
("0:18", "Same school catchment"),
("0:28", "Same kind of home"),
("0:38", "The price-per-m² reveal"),
("0:52", "Find your own cheaper twin (free map)"),
]
description = (
f"{url}\n\n"
f"{pn} and {wn} share a station, a school catchment and the same era of housing, but {wn} costs about "
f"{gap} less per square metre ({money} on a 90 m² home). I built a map that ranks every postcode in "
f"England by what each pound actually buys, from official open data ({SOURCES}). Find the cheaper twin "
f"of any area, free and with no signup, at {SITE}.\n\n"
+ "\n".join(f"{ts} {label}" for ts, label in chapters)
+ "\n\nData: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. "
"Figures are estimates aggregated to postcode sector, not valuations."
)
shotlist = [
("0:000:06", "COLD OPEN: payoff first", f"Open on the map already showing both areas with the £/m² gap visible. Caption: '{money} cheaper'. Say the hook.", "Land on the map URL below (filters pre-applied)."),
("0:060:18", "Same station", "Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want.", "Caption: 'Same station.'"),
("0:180:28", "Same schools", "Show the Good+ secondary catchment covering both.", "Caption: 'Same school catchment.'"),
("0:280:38", "Same homes", f"Note the dominant type ({plural}) and build era (~{s['build_year']}).", "Caption: 'Same homes.'"),
("0:380:52", "THE REVEAL", f"Show the £/m² side by side: {pn} {gbp(p['est_psqm'])} vs {wn} {gbp(w['est_psqm'])}.", f"Caption: '{gap} less per m²'."),
("0:521:00", "CTA", "End on the map; invite them to find their own cheaper twin.", "Caption: 'Free. No signup.'"),
]
ck = city_key(p["sector"])
psqm_cap = int(round(w["est_psqm"] * 1.05, -2))
spec = {
"name": f"twin-{f['slug'].split('/')[-1]}",
"city": ck,
"promptText": f"Best value {typ}s near {pn}: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [0, psqm_cap],
"Good+ secondary school catchments": [1, 11],
},
"outroLine": f"{wn}: same life, {gap} cheaper.",
}
lines = [
f"# Video kit: {pn} vs {wn}",
"",
f"**Page:** {SITE}{f['page_path']} · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut",
"",
f"## 🎬 Map URL to record (open this, hit record)",
f"`{url}`",
"*(filters are pre-applied so the value is on screen immediately)*",
"",
"## Hook (first 2 seconds, on screen + said)",
f"**\"{money} cheaper. Same station. Same schools.\"**",
"",
"## Shot list",
"| Time | Beat | What to show | On-screen |",
"|------|------|--------------|-----------|",
]
for t, beat, show, cap in shotlist:
lines.append(f"| {t} | {beat} | {show} | {cap} |")
lines += [
"",
"## Narration (human voiceover, never raw TTS for a property audience)",
f"> {narration}",
"",
"## Captions (≤6 words, sound-off)",
*[f"- {c}" for c in captions],
"",
"## YouTube",
"**Title options:**",
*[f"{i+1}. {t}" for i, t in enumerate(titles)],
"",
"**Thumbnail text:** big number `" + money + " cheaper` + the two names `" + f"{pn}{wn}`",
"",
"**Description (paste as-is):**",
"```",
description,
"```",
"",
"## 9:16 Short (cut from the same recording)",
f"First 3 seconds: the £/m² reveal ({pn} {gbp(p['est_psqm'])}{wn} {gbp(w['est_psqm'])}) + caption '{gap} less'. "
"End card: 'Find your cheaper twin, free, no signup.'",
"",
"## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)",
"Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). "
"Filter names must match live `/api/features` or preflight fails.",
"```json",
json.dumps(spec, indent=2),
"```",
]
return "\n".join(lines)
def main():
OUT.mkdir(parents=True, exist_ok=True)
findings = [json.loads(p.read_text()) for p in sorted(FIND.glob("*.json"))]
twins = [f for f in findings if f["type"] == "cheaper_twin"]
made = []
for f in twins:
slug = f["slug"].split("/")[-1]
(OUT / f"{slug}.md").write_text(twin_kit(f))
made.append((slug, f))
index = [
"# Video kits: film one per 12 weeks",
"",
"Each kit is a complete, payoff-first faceless video you can screen-record off the live map. "
"Pick one, open its Map URL, record, read the narration (human voice), export one clean cut + a 9:16 Short.",
"",
"**Priority order (relatable family-home twins first, since they convert better than prime London):**",
"",
"| Kit | Hook | File |",
"|-----|------|------|",
]
# Family homes first, then the rest, by £ gap.
fam = [m for m in made if m[1]["stats"]["dominant_type"] in ("Terraced", "Semi-Detached", "Detached")]
rest = [m for m in made if m not in fam]
for slug, f in sorted(fam, key=lambda m: m[1]["stats"]["gap_on_90sqm"], reverse=True) + sorted(
rest, key=lambda m: m[1]["stats"]["gap_on_90sqm"], reverse=True
):
p, w = f["pricey"], f["twin"]
index.append(f"| {p['name'] or p['sector']}{w['name'] or w['sector']} | {f['shocking_number']} / {gbp(f['stats']['gap_on_90sqm'])} | `{slug}.md` |")
(OUT / "INDEX.md").write_text("\n".join(index))
print(f"Wrote {len(made)} video kits + INDEX.md to {OUT}/")
for slug, f in made[:6]:
print(f" {slug}.md: {f['title']}")
if __name__ == "__main__":
main()

341
analysis/cheaper_twins.py Normal file
View file

@ -0,0 +1,341 @@
#!/usr/bin/env python3
"""Cheaper-twin / name-premium index over all-England property data.
This is the ROOT growth artifact: every page, OG card, video and outreach number derives from its
output. It reads the local property data, aggregates to POSTCODE SECTOR grain (e.g. "N1 1", the same
grain the homepage TwinProof block uses, which is load-bearing), and finds "cheaper twins": nearby
sectors that match on property type, build era, school provision and station access but differ
materially in estimated price per square metre.
Defensibility rules baked in (see growth/README.md):
- Sector aggregation only, never address-level output (Royal Mail / OS rights).
- Minimum sample sizes per sector (--min-props, --min-recorded).
- Robust statistics: median £/sqm; a sector needs real recorded sales, not just modelled estimates.
- England only (ctry25cd starts with "E").
- Every row stamps its N.
- "Twin" requires genuine like-for-like matching before any price claim is made.
Outputs (analysis/out/):
- sector_index.parquet / .csv: per-sector value table (powers "best value", "£100k buys X m²", etc.)
- cheaper_twins.parquet / .csv: ranked twin pairs (pricey name -> cheaper twin)
- national_facts.json: headline stats for collateral/finding placeholders
Run: source .venv/bin/activate && python analysis/cheaper_twins.py
"""
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
import numpy as np
import polars as pl
from scipy.spatial import cKDTree
DATA = Path("property-data")
OUT = Path("analysis/out")
# Postcode sector = outward code + first inward digit, e.g. "N1 1", "M20 2".
SECTOR_RE = r"^([A-Z]{1,2}[0-9][A-Z0-9]? [0-9])"
STATION_DIST_COLS = [
"Distance to nearest amenity (Rail station) (km)",
"Distance to nearest amenity (Tube station) (km)",
"Distance to nearest amenity (DLR station) (km)",
"Distance to nearest amenity (Tram & Metro stop) (km)",
]
AREA_COLS = [
"lat",
"lon",
"Good+ primary school catchments",
"Good+ secondary school catchments",
"Outstanding primary school catchments",
"Outstanding secondary school catchments",
"Serious crime (/yr, 7y)",
"Minor crime (/yr, 7y)",
"Noise (dB)",
"Max available download speed (Mbps)",
"Median age",
"% Owner occupied",
"% Degree or higher",
*STATION_DIST_COLS,
]
def _collect(lf: pl.LazyFrame) -> pl.DataFrame:
"""Collect with streaming if the installed polars supports it, else fall back."""
try:
return lf.collect(streaming=True)
except Exception:
return lf.collect()
def property_aggregates() -> pl.DataFrame:
props = (
pl.scan_parquet(DATA / "properties.parquet")
.with_columns(pl.col("Postcode").str.extract(SECTOR_RE, 1).alias("Sector"))
.filter(pl.col("Sector").is_not_null())
)
agg = props.group_by("Sector").agg(
pl.len().alias("n_props"),
pl.col("Price per sqm").drop_nulls().len().alias("n_recorded"),
pl.col("Est. price per sqm").median().alias("est_psqm"),
pl.col("Price per sqm").median().alias("recorded_psqm"),
pl.col("Last known price").median().alias("median_price"),
pl.col("Estimated current price").median().alias("est_price"),
pl.col("Total floor area (sqm)").median().alias("median_floor"),
pl.col("Number of bedrooms & living rooms").median().alias("median_rooms"),
pl.col("Construction year")
.filter(pl.col("Construction year") > 1800)
.median()
.alias("median_build_year"),
)
# Dominant property type + its share, per sector (robust mode without ordering assumptions).
tc = props.group_by(["Sector", "Property type"]).agg(pl.len().alias("c"))
tc = tc.with_columns(
(pl.col("c") / pl.col("c").sum().over("Sector")).alias("share"),
pl.col("c").max().over("Sector").alias("maxc"),
)
dom = (
tc.filter(pl.col("c") == pl.col("maxc"))
.unique(subset="Sector", keep="first")
.select(
pl.col("Sector"),
pl.col("Property type").alias("dominant_type"),
pl.col("share").alias("dominant_share"),
)
)
return _collect(agg).join(_collect(dom), on="Sector", how="left")
def area_aggregates() -> pl.DataFrame:
# Property counts per postcode unit -> weights, so area features are weighted by housing stock.
unit_counts = _collect(
pl.scan_parquet(DATA / "properties.parquet")
.group_by("Postcode")
.agg(pl.len().alias("n"))
)
pc = pl.read_parquet(DATA / "postcode.parquet", columns=["Postcode", "ctry25cd", *AREA_COLS])
pc = pc.filter(pl.col("ctry25cd").str.starts_with("E")) # England only
pc = pc.join(unit_counts, on="Postcode", how="inner") # only units that contain homes
pc = pc.with_columns(
pl.col("Postcode").str.extract(SECTOR_RE, 1).alias("Sector"),
pl.min_horizontal(STATION_DIST_COLS).alias("dist_station_km"),
).filter(pl.col("Sector").is_not_null())
def wmean(col: str, alias: str) -> pl.Expr:
# weighted mean ignoring nulls: sum(col*n) / sum(n where col not null)
num = (pl.col(col) * pl.col("n")).sum()
den = pl.col("n").filter(pl.col(col).is_not_null()).sum()
return (num / den).alias(alias)
return pc.group_by("Sector").agg(
pl.col("n").sum().alias("area_n"),
wmean("lat", "lat"),
wmean("lon", "lon"),
wmean("Good+ primary school catchments", "good_primary"),
wmean("Good+ secondary school catchments", "good_secondary"),
wmean("Outstanding primary school catchments", "outstanding_primary"),
wmean("Outstanding secondary school catchments", "outstanding_secondary"),
wmean("Serious crime (/yr, 7y)", "serious_crime"),
wmean("Noise (dB)", "noise_db"),
wmean("Max available download speed (Mbps)", "broadband_mbps"),
wmean("Median age", "median_age"),
wmean("% Owner occupied", "pct_owner"),
wmean("% Degree or higher", "pct_degree"),
wmean("dist_station_km", "dist_station_km"),
)
def build_index(args) -> pl.DataFrame:
idx = property_aggregates().join(area_aggregates(), on="Sector", how="inner")
idx = idx.filter(
(pl.col("n_props") >= args.min_props)
& (pl.col("n_recorded") >= args.min_recorded)
& pl.col("est_psqm").is_not_null()
& (pl.col("est_psqm") > 0)
& pl.col("lat").is_not_null()
& pl.col("lon").is_not_null()
& pl.col("dist_station_km").is_not_null()
)
idx = idx.with_columns(
(100_000 / pl.col("est_psqm")).round(1).alias("sqm_per_100k"),
pl.col("est_psqm").round().cast(pl.Int64),
pl.col("recorded_psqm").round().cast(pl.Int64),
)
return idx.sort("est_psqm", descending=True)
def find_twins(idx: pl.DataFrame, args) -> pl.DataFrame:
d = idx.to_dict(as_series=False)
n = len(d["Sector"])
lat = np.array(d["lat"], float)
lon = np.array(d["lon"], float)
psqm = np.array(d["est_psqm"], float)
floor = np.array([f if f is not None else np.nan for f in d["median_floor"]], float)
build = np.array([b if b is not None else np.nan for b in d["median_build_year"]], float)
dom = d["dominant_type"]
good_sec = np.array(d["good_secondary"], float)
good_pri = np.array(d["good_primary"], float)
crime = np.array(d["serious_crime"], float)
station = np.array(d["dist_station_km"], float)
owner = np.array(d["pct_owner"], float)
degree = np.array(d["pct_degree"], float)
age = np.array(d["median_age"], float)
# Planar projection (km) for a local KD-tree radius search.
lat0 = math.radians(float(np.nanmean(lat)))
x = lon * 111.320 * math.cos(lat0)
y = lat * 110.574
tree = cKDTree(np.column_stack([x, y]))
rows = []
for i in range(n):
neigh = tree.query_ball_point([x[i], y[i]], args.max_km)
best_j, best_gap = -1, -1.0
for j in neigh:
if j == i or psqm[i] <= psqm[j]:
continue # i must be the pricier side
gap = 1.0 - psqm[j] / psqm[i]
# A genuine "twin" sits in a believable band: below min it's not a story,
# above max it's a different market tier (city-centre premium / prime), not a twin.
if gap < args.min_gap or gap > args.max_gap:
continue
if dom[i] != dom[j]:
continue
if not (np.isfinite(build[i]) and np.isfinite(build[j])) or abs(build[i] - build[j]) > args.build_band:
continue
if abs(good_sec[i] - good_sec[j]) > args.school_tol or abs(good_pri[i] - good_pri[j]) > args.school_tol:
continue
if station[i] > args.station_max or station[j] > args.station_max or abs(station[i] - station[j]) > args.station_tol:
continue
# Similarity gates: the two must be the SAME KIND of neighbourhood, so the gap
# reads as a name premium, not a tier jump (deprivation/tenure/education/age/safety/size).
if crime[j] > crime[i] * args.crime_ratio or crime[i] > crime[j] * args.crime_ratio:
continue
if abs(owner[i] - owner[j]) > args.owner_tol:
continue
if abs(degree[i] - degree[j]) > args.degree_tol:
continue
if np.isfinite(age[i]) and np.isfinite(age[j]) and abs(age[i] - age[j]) > args.age_tol:
continue
if np.isfinite(floor[i]) and np.isfinite(floor[j]) and floor[j] > 0:
fr = floor[i] / floor[j]
if fr < args.floor_ratio or fr > 1.0 / args.floor_ratio:
continue
if gap > best_gap:
best_gap, best_j = gap, j
if best_j < 0:
continue
j = best_j
avg_floor = np.nanmean([floor[i], floor[j]])
if not np.isfinite(avg_floor):
avg_floor = 90.0
rows.append(
{
"pricey_sector": d["Sector"][i],
"twin_sector": d["Sector"][j],
"pricey_psqm": int(psqm[i]),
"twin_psqm": int(psqm[j]),
"gap_pct": round(best_gap * 100, 1),
"gap_per_sqm": int(psqm[i] - psqm[j]),
"gap_on_avg_home": int((psqm[i] - psqm[j]) * avg_floor),
"gap_on_90sqm": int((psqm[i] - psqm[j]) * 90),
"dist_km": round(math.hypot(x[i] - x[j], y[i] - y[j]), 2),
"dominant_type": dom[i],
"build_year": None if not np.isfinite(build[i]) else int(build[i]),
"good_secondary": round(float(good_sec[i]), 1),
"station_km": round(float(station[i]), 2),
"pricey_lat": round(float(lat[i]), 5),
"pricey_lon": round(float(lon[i]), 5),
"twin_lat": round(float(lat[j]), 5),
"twin_lon": round(float(lon[j]), 5),
"pricey_n": int(d["n_props"][i]),
"twin_n": int(d["n_props"][j]),
}
)
if not rows:
return pl.DataFrame()
tw = pl.DataFrame(rows)
# Dedup unordered pairs, keep the biggest gap.
tw = tw.with_columns(
pl.concat_list(
[
pl.min_horizontal("pricey_sector", "twin_sector"),
pl.max_horizontal("pricey_sector", "twin_sector"),
]
)
.list.join("|")
.alias("_pair")
)
tw = tw.sort("gap_pct", descending=True).unique(subset="_pair", keep="first").drop("_pair")
return tw.filter(pl.col("gap_on_90sqm") >= args.min_abs_gap).sort("gap_pct", descending=True)
def national_facts(idx: pl.DataFrame, twins: pl.DataFrame, args) -> dict:
valid = idx.filter(pl.col("n_props") >= args.min_props)
cheapest = valid.sort("est_psqm").head(1).to_dicts()[0]
dearest = valid.sort("est_psqm", descending=True).head(1).to_dicts()[0]
facts = {
"generated_with": "analysis/cheaper_twins.py",
"params": vars(args),
"n_sectors": idx.height,
"n_twin_pairs": twins.height,
"attribution": "Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"best_value_sector": {"sector": cheapest["Sector"], "est_psqm": cheapest["est_psqm"], "sqm_per_100k": cheapest["sqm_per_100k"], "n": cheapest["n_props"]},
"dearest_sector": {"sector": dearest["Sector"], "est_psqm": dearest["est_psqm"], "sqm_per_100k": dearest["sqm_per_100k"], "n": dearest["n_props"]},
}
if twins.height:
top = twins.head(10).to_dicts()
facts["biggest_twin_gap"] = top[0]
facts["top_twins"] = top
return facts
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--min-props", type=int, default=150, help="min properties per sector")
p.add_argument("--min-recorded", type=int, default=40, help="min recorded sales (with floor area) per sector")
p.add_argument("--max-km", type=float, default=3.0, help="max centroid distance for a twin (km)")
p.add_argument("--min-gap", type=float, default=0.15, help="min fractional £/sqm gap")
p.add_argument("--max-gap", type=float, default=0.45, help="max fractional gap (above this it's a tier jump, not a twin)")
p.add_argument("--build-band", type=float, default=30, help="max build-year difference")
p.add_argument("--school-tol", type=float, default=1.5, help="max difference in good-catchment counts")
p.add_argument("--station-max", type=float, default=1.5, help="both sectors must be within this many km of a station")
p.add_argument("--station-tol", type=float, default=0.9, help="max difference in station distance (km)")
p.add_argument("--crime-ratio", type=float, default=1.5, help="serious crime must be within this ratio either way")
p.add_argument("--owner-tol", type=float, default=22, help="max difference in %% owner-occupied")
p.add_argument("--degree-tol", type=float, default=22, help="max difference in %% degree-or-higher")
p.add_argument("--age-tol", type=float, default=12, help="max difference in median age (years)")
p.add_argument("--floor-ratio", type=float, default=0.72, help="median floor area must be within this ratio either way")
p.add_argument("--min-abs-gap", type=int, default=20000, help="min £ gap on a 90 sqm home for the twin list")
args = p.parse_args()
OUT.mkdir(parents=True, exist_ok=True)
print("Aggregating 22.4M properties to sector grain ...")
idx = build_index(args)
print(f" {idx.height} valid England sectors (>= {args.min_props} props, >= {args.min_recorded} recorded sales)")
idx.write_parquet(OUT / "sector_index.parquet")
idx.write_csv(OUT / "sector_index.csv")
print("Matching cheaper twins ...")
twins = find_twins(idx, args)
print(f" {twins.height} twin pairs")
if twins.height:
twins.write_parquet(OUT / "cheaper_twins.parquet")
twins.write_csv(OUT / "cheaper_twins.csv")
facts = national_facts(idx, twins, args)
(OUT / "national_facts.json").write_text(json.dumps(facts, indent=2, default=str))
print(f"Wrote outputs to {OUT}/")
if twins.height:
print("\nTop 10 cheaper twins (pricey -> twin, gap%, £ on 90sqm):")
for r in twins.head(10).to_dicts():
print(f" {r['pricey_sector']:>7} -> {r['twin_sector']:<7} {r['gap_pct']:>5}% £{r['gap_on_90sqm']:,} ({r['dominant_type']}, ~{r['build_year']})")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,220 @@
#!/usr/bin/env python3
"""Turn the cheaper-twin index into publishable FINDINGS.
Each finding is the single artifact the whole engine renders four ways (growth/README.md):
an SEO page, an OG/unfurl card, a video storyboard, and a 3-filter deep-link CTA into the live map.
This script curates the raw 415 twin pairs into a reviewed set of findings and emits:
analysis/out/findings/<slug>.json: one machine-readable finding per page/video
analysis/out/findings_review.md: human-readable sheet for the founder to eyeball + fix names
Place labels come from analysis/place_names.json (APPROXIMATE, verify before publishing).
Run: source .venv/bin/activate && python analysis/generate_findings.py
"""
from __future__ import annotations
import json
import urllib.parse
from pathlib import Path
import polars as pl
OUT = Path("analysis/out")
FIND = OUT / "findings"
SITE = "https://perfect-postcode.co.uk"
ATTRIB = "Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0."
SOURCES = "HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk"
TYPE_SINGULAR = {
"Flats/Maisonettes": "flat",
"Terraced": "terraced house",
"Semi-Detached": "semi-detached house",
"Detached": "detached house",
}
NAMES = json.loads(Path("analysis/place_names.json").read_text())
def label(sector: str) -> dict:
outward = sector.split(" ")[0]
name = NAMES.get(outward)
return {"sector": sector, "name": name, "label": f"{name} ({sector})" if name else sector, "named": bool(name)}
def sector_slug(sector: str) -> str:
return sector.lower().replace(" ", "-")
def map_query(t: dict) -> str:
"""A ≤3-filter deep link that frames the value: centre between the pair, cap £/sqm near the
cheaper twin, require a good secondary catchment. Reproducible by non-payers (DEMO_MAX_FILTERS=3)."""
mid_lat = round((t["pricey_lat"] + t["twin_lat"]) / 2, 5)
mid_lon = round((t["pricey_lon"] + t["twin_lon"]) / 2, 5)
psqm_cap = int(round(t["twin_psqm"] * 1.05, -2))
enc_psqm = urllib.parse.quote("Est. price per sqm", safe="")
enc_school = urllib.parse.quote("Good+ secondary school catchments", safe="")
# Two plain numeric filters (within the 3-filter demo cap): frames value + good schools and
# uses the well-understood filter=NAME:MIN:MAX form so the OG-card filter-name guard passes.
parts = [
f"lat={mid_lat}",
f"lon={mid_lon}",
"zoom=12.5",
f"filter={enc_psqm}:0:{psqm_cap}",
f"filter={enc_school}:1:11",
]
return "&".join(parts)
def twin_finding(t: dict) -> dict:
p, w = label(t["pricey_sector"]), label(t["twin_sector"])
typ = TYPE_SINGULAR.get(t["dominant_type"], "home")
slug = f"cheaper-twin/{sector_slug(t['pricey_sector'])}-vs-{sector_slug(t['twin_sector'])}"
q = map_query(t)
headline_name = f"{p['name']} vs {w['name']}" if p["named"] and w["named"] else f"{t['pricey_sector']} vs {t['twin_sector']}"
return {
"slug": slug,
"type": "cheaper_twin",
"page_path": f"/{slug}",
"title": f"{headline_name}: the same {typ}, about {t['gap_pct']:.0f}% cheaper per m²",
"hook": f"£{t['gap_on_90sqm']:,} less for an equivalent {typ}: same station, similar schools, ~{t['dist_km']}km apart",
"shocking_number": f"{t['gap_pct']:.0f}%",
"pricey": {**p, "est_psqm": t["pricey_psqm"], "n": t["pricey_n"]},
"twin": {**w, "est_psqm": t["twin_psqm"], "n": t["twin_n"]},
"stats": {
"gap_pct": t["gap_pct"],
"gap_per_sqm": t["gap_per_sqm"],
"gap_on_90sqm": t["gap_on_90sqm"],
"gap_on_avg_home": t["gap_on_avg_home"],
"dominant_type": t["dominant_type"],
"build_year": t["build_year"],
"good_secondary_catchments": t["good_secondary"],
"station_km": t["station_km"],
"distance_km": t["dist_km"],
},
"map_query": q,
"map_url": f"{SITE}/?{q}",
"og_image": f"{SITE}/api/screenshot?og=1&{q}",
"methodology": (
"Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only "
"called a 'twin' when the two sectors share the dominant property type, build era (±30y), "
"good-school catchment provision, station access, deprivation/tenure, education, age and "
"home size, so the price gap reflects a name premium, not a different kind of area. "
"Estimates, not valuations; aggregated to sector, never address-level."
),
"needs_name_check": not (p["named"] and w["named"]),
"attribution": ATTRIB,
"sources": SOURCES,
}
def curate(tw: pl.DataFrame) -> list[dict]:
rows = tw.to_dicts()
london = ("E", "EC", "WC", "W", "SW", "SE", "N", "NW")
def area(s):
import re
return re.match(r"^[A-Z]+", s).group(0)
family = [r for r in rows if r["dominant_type"] in ("Terraced", "Semi-Detached", "Detached")]
prime = sorted(rows, key=lambda r: r["gap_on_90sqm"], reverse=True)
regional_family = sorted(
[r for r in family if area(r["pricey_sector"]) not in london],
key=lambda r: r["gap_on_90sqm"],
reverse=True,
)
london_family = sorted(
[r for r in family if area(r["pricey_sector"]) in london],
key=lambda r: r["gap_on_90sqm"],
reverse=True,
)
# A spread: biggest national name premiums (PR) + relatable family-home twins (buyer video).
picks, seen = [], set()
for r in prime[:6] + regional_family[:8] + london_family[:6]:
key = r["pricey_sector"] + "|" + r["twin_sector"]
if key not in seen:
seen.add(key)
picks.append(r)
return picks
def national_findings(idx: pl.DataFrame, facts: dict) -> list[dict]:
named = idx.with_columns(
pl.col("Sector").str.extract(r"^([A-Z]+[0-9][A-Z0-9]?)", 1).alias("outward")
)
best = facts["best_value_sector"]
dear = facts["dearest_sector"]
return [
{
"slug": "square-metres-per-100k",
"type": "national_table",
"page_path": "/square-metres-per-100k",
"title": "How many square metres £100,000 buys across England",
"shocking_number": f"{best['sqm_per_100k']:.0f} m² vs {dear['sqm_per_100k']:.0f}",
"hook": (
f"£100k buys ~{best['sqm_per_100k']:.0f} m² of floor space in {label(best['sector'])['label']} "
f"but only ~{dear['sqm_per_100k']:.0f} m² in {label(dear['sector'])['label']}"
),
"stats": {"best": best, "dearest": dear, "n_sectors": facts["n_sectors"]},
"map_query": "zoom=6&filter=" + urllib.parse.quote("Est. price per sqm", safe="") + ":0:4000",
"methodology": "100000 ÷ median estimated £/m², per England postcode sector with sufficient sales.",
"needs_name_check": not (label(best["sector"])["named"] and label(dear["sector"])["named"]),
"attribution": ATTRIB,
"sources": SOURCES,
}
]
def main():
FIND.mkdir(parents=True, exist_ok=True)
tw = pl.read_parquet(OUT / "cheaper_twins.parquet")
idx = pl.read_parquet(OUT / "sector_index.parquet")
facts = json.loads((OUT / "national_facts.json").read_text())
findings = [twin_finding(r) for r in curate(tw)] + national_findings(idx, facts)
for f in findings:
(FIND / (f["slug"].replace("/", "__") + ".json")).write_text(json.dumps(f, indent=2, default=str))
# Human review sheet
lines = [
"# Findings: review before publishing",
"",
f"{len(findings)} findings generated from analysis/out/cheaper_twins.parquet.",
"**Check the place names** (⚠ = unnamed sector, needs a label in analysis/place_names.json) "
"and spot-check a couple of numbers. Then these feed the page batch + video factory.",
"",
"| ⚠ | Title | Hook number | Page path | Deep link |",
"|---|-------|-------------|-----------|-----------|",
]
for f in findings:
warn = "" if f.get("needs_name_check") else ""
lines.append(
f"| {warn} | {f['title']} | {f.get('shocking_number','')} | `{f['page_path']}` | "
f"[map]({f.get('map_url', SITE + '/?' + f['map_query'])}) |"
)
lines += ["", "## Per-finding detail", ""]
for f in findings:
lines.append(f"### {f['title']}")
lines.append(f"- **Type:** {f['type']} · **Page:** `{f['page_path']}`")
lines.append(f"- **Hook:** {f['hook']}")
if f["type"] == "cheaper_twin":
lines.append(
f"- **{f['pricey']['label']}** £{f['pricey']['est_psqm']:,}/m² (n={f['pricey']['n']:,}) → "
f"**{f['twin']['label']}** £{f['twin']['est_psqm']:,}/m² (n={f['twin']['n']:,}) · "
f"gap {f['stats']['gap_pct']}% · {f['stats']['dominant_type']}, ~{f['stats']['build_year']}"
)
lines.append(f"- **OG card / deep link:** `{f['map_query']}`")
lines.append("")
(OUT / "findings_review.md").write_text("\n".join(lines))
n_named = sum(1 for f in findings if not f.get("needs_name_check"))
print(f"Wrote {len(findings)} findings to {FIND}/ ({n_named} fully named, {len(findings)-n_named} need a name check)")
print(f"Review sheet: {OUT / 'findings_review.md'}")
for f in findings[:14]:
flag = " ⚠needs-name" if f.get("needs_name_check") else ""
print(f" [{f['shocking_number']:>14}] {f['title']}{flag}")
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

175
analysis/og_preflight.py Normal file
View file

@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""OG / share-card preflight for Perfect Postcode growth findings.
Before publishing growth findings we must confirm that each finding's Open
Graph share card actually renders, and that the filter name baked into its
deep-link map query still matches a live feature. The OG render has no guard
today, so a wrong/renamed filter silently ships a card whose map contradicts
the headline.
For every analysis/out/findings/*.json this script:
1. Fetches the OG render at {BASE}/api/screenshot?og=1&{map_query} and checks
it is a non-trivial image (HTTP 200, image/*, > MIN_IMAGE_BYTES, and
~1200x630 when Pillow is installed).
2. Cross-checks every `filter=<NAME>:...` in the map query against the live
feature list from {BASE}/api/features, failing on any drifted name.
Exits non-zero if any finding fails, so it can gate a publish step.
Usage:
source .venv/bin/activate && python analysis/og_preflight.py --base https://perfect-postcode.co.uk
Needs the server (local dev or prod) reachable at --base / $OG_BASE
(default http://localhost:8001); it makes live HTTP requests.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import urllib.parse
import urllib.request
from io import BytesIO
from pathlib import Path
# Pillow is optional: when present we additionally decode the image and verify
# its dimensions; otherwise we fall back to status/content-type/size checks.
try:
from PIL import Image # type: ignore
_HAVE_PIL = True
except Exception:
_HAVE_PIL = False
FINDINGS_DIR = Path(__file__).resolve().parent / "out" / "findings"
DEFAULT_BASE = os.environ.get("OG_BASE", "http://localhost:8001")
MIN_IMAGE_BYTES = 5 * 1024 # a blank/error render is far smaller than this
OG_SIZE = (1200, 630) # screenshot service VIEWPORT, device scale factor 1
SIZE_TOLERANCE = 4 # px slack when checking decoded dimensions
def http_get(url: str, timeout: float):
"""GET a URL, returning (status, content_type, body_bytes). Raises on error."""
req = urllib.request.Request(url, headers={"User-Agent": "og-preflight"})
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status, resp.headers.get("Content-Type", ""), resp.read()
def filter_names(map_query: str) -> list[str]:
"""Extract the feature name from each `filter=<name>:...` query param.
Mirrors the frontend parser (url-state.ts): the name is everything before
the first colon of a URL-decoded filter value, e.g. "Est. price per sqm".
Non-filter params (school=, crime=, ...) carry no feature name and are
ignored here.
"""
names = []
for key, value in urllib.parse.parse_qsl(map_query, keep_blank_values=True):
if key == "filter" and ":" in value:
names.append(value.split(":", 1)[0])
return names
def live_feature_names(base: str, timeout: float) -> set[str]:
"""Collect every feature `name` from the grouped {base}/api/features response."""
status, _ctype, body = http_get(f"{base}/api/features", timeout)
if status != 200:
raise RuntimeError(f"/api/features returned HTTP {status}")
payload = json.loads(body)
names: set[str] = set()
for group in payload.get("groups", []):
for feature in group.get("features", []):
name = feature.get("name")
if name:
names.add(name)
return names
def check_og(map_query: str, base: str, timeout: float) -> tuple[bool, str]:
"""Render the OG card and validate it looks like a real image."""
url = f"{base}/api/screenshot?og=1&{map_query}"
try:
status, ctype, body = http_get(url, timeout)
except Exception as exc: # network / HTTP error
return False, f"request failed: {exc}"
if status != 200:
return False, f"HTTP {status}"
if not ctype.lower().startswith("image/"):
return False, f"content-type {ctype!r} is not an image"
if len(body) < MIN_IMAGE_BYTES:
return False, f"image too small ({len(body)} B) - likely blank/error render"
if _HAVE_PIL:
try:
img = Image.open(BytesIO(body))
img.load() # force a full decode; raises on a corrupt image
w, h = img.size
except Exception as exc:
return False, f"undecodable image: {exc}"
if abs(w - OG_SIZE[0]) > SIZE_TOLERANCE or abs(h - OG_SIZE[1]) > SIZE_TOLERANCE:
return False, f"unexpected size {w}x{h} (want ~{OG_SIZE[0]}x{OG_SIZE[1]})"
return True, f"{len(body)} B, {w}x{h}"
return True, f"{len(body)} B"
def main() -> int:
parser = argparse.ArgumentParser(description="Preflight OG cards for growth findings.")
parser.add_argument(
"--base", default=DEFAULT_BASE, help=f"server base URL (default {DEFAULT_BASE}; or $OG_BASE)"
)
parser.add_argument(
"--findings-dir", default=str(FINDINGS_DIR), help="directory of finding *.json files"
)
parser.add_argument(
"--timeout", type=float, default=30.0, help="per-request timeout in seconds (default 30)"
)
args = parser.parse_args()
base = args.base.rstrip("/")
finding_paths = sorted(Path(args.findings_dir).glob("*.json"))
if not finding_paths:
print(f"No findings found in {args.findings_dir}", file=sys.stderr)
return 2
# The drift guard needs the live feature list; if we cannot fetch it the
# whole gate is inconclusive, so bail out non-zero before checking cards.
try:
live_names = live_feature_names(base, args.timeout)
except Exception as exc:
print(f"FATAL: could not load {base}/api/features: {exc}", file=sys.stderr)
return 2
print(f"Loaded {len(live_names)} live feature names from {base}/api/features")
if not _HAVE_PIL:
print("(Pillow not installed - skipping image dimension check)")
passed = failed = 0
for path in finding_paths:
finding = json.loads(path.read_text())
slug = finding.get("slug", path.stem)
map_query = finding.get("map_query")
if not map_query:
print(f"FAIL {slug}: no map_query field")
failed += 1
continue
# Drift guard first: a renamed filter is the silent-ship bug we most
# want to catch, and it makes the rendered card meaningless anyway.
drifted = [n for n in filter_names(map_query) if n not in live_names]
og_ok, og_reason = check_og(map_query, base, args.timeout)
if drifted:
print(f"FAIL {slug}: unknown filter name(s): {', '.join(drifted)}")
failed += 1
elif not og_ok:
print(f"FAIL {slug}: OG render - {og_reason}")
failed += 1
else:
print(f"PASS {slug}: OG {og_reason}")
passed += 1
print(f"\n{passed} passed / {failed} failed (of {passed + failed})")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,416 @@
pricey_sector,twin_sector,pricey_psqm,twin_psqm,gap_pct,gap_per_sqm,gap_on_avg_home,gap_on_90sqm,dist_km,dominant_type,build_year,good_secondary,station_km,pricey_lat,pricey_lon,twin_lat,twin_lon,pricey_n,twin_n
W1U 3,EC1N 7,16948,9362,44.8,7586,504469,682740,2.89,Flats/Maisonettes,1940,1.3,0.41,51.51712,-0.15271,51.52057,-0.11049,302,747
WC2R 1,SW1V 1,19997,11119,44.4,8878,665850,799020,2.82,Flats/Maisonettes,2017,2.0,0.21,51.51228,-0.11525,51.49297,-0.14234,316,1408
L8 7,L7 0,2757,1541,44.1,1216,78432,109440,2.36,Flats/Maisonettes,1914,2.3,1.04,53.39791,-2.96459,53.41174,-2.93813,2054,2208
L3 2,L5 5,1642,920,44.0,722,47291,64980,1.61,Flats/Maisonettes,2004,1.5,0.47,53.41155,-2.98583,53.42544,-2.97852,1341,706
S2 4,S3 9,2468,1402,43.2,1066,73554,95940,2.82,Flats/Maisonettes,1986,2.6,0.72,53.36993,-1.46978,53.39521,-1.46478,2423,1778
W1U 4,NW1 4,24238,13766,43.2,10472,759220,942480,0.97,Flats/Maisonettes,1940,1.0,0.44,51.51958,-0.15295,51.52803,-0.14886,984,1340
WC2A 2,EC2A 2,25482,14576,42.8,10906,834309,981540,2.3,Flats/Maisonettes,2019,2.0,0.42,51.51496,-0.11456,51.52119,-0.08217,254,772
M40 5,M9 4,2812,1626,42.2,1186,91915,106740,1.18,Terraced,1958,2.6,0.72,53.51372,-2.18713,53.51214,-2.20436,1632,3530
W11 2,NW1 6,19262,11154,42.1,8108,482426,729720,2.94,Flats/Maisonettes,1890,4.0,0.53,51.51407,-0.20373,51.5237,-0.16342,4082,3312
N10 3,N12 0,9590,5554,42.1,4036,296646,363240,2.97,Flats/Maisonettes,1914,3.9,1.16,51.58839,-0.14404,51.60872,-0.17254,3984,3282
W1U 1,SW1V 1,19096,11119,41.8,7977,506539,717930,2.54,Flats/Maisonettes,1986,2.0,0.17,51.51535,-0.15064,51.49297,-0.14234,416,1408
SW1X 8,SW7 2,26735,15611,41.6,11124,1301508,1001160,1.31,Flats/Maisonettes,1890,3.7,0.43,51.49787,-0.15472,51.49729,-0.17406,1410,1126
W1U 5,WC1E 6,20074,11756,41.4,8318,486603,748620,1.34,Flats/Maisonettes,1940,1.0,0.26,51.5214,-0.15392,51.52242,-0.13419,796,424
SW3 1,SW5 0,22813,13361,41.4,9452,652188,850680,1.82,Flats/Maisonettes,1914,3.0,0.39,51.49847,-0.16393,51.4925,-0.18898,1104,4266
HD4 5,HD1 3,2219,1301,41.4,918,65637,82620,1.79,Terraced,1940,5.0,1.12,53.63444,-1.81642,53.63808,-1.79069,4888,3052
W11 3,W12 9,15942,9343,41.4,6599,458630,593910,2.87,Flats/Maisonettes,1914,3.5,0.26,51.50961,-0.20173,51.50318,-0.24269,2643,5847
W1J 7,SW7 3,32986,19392,41.2,13594,1077324,1223460,2.6,Flats/Maisonettes,1914,2.0,0.3,51.5059,-0.14756,51.49122,-0.1775,724,2581
EC3N 1,EC1V 8,15538,9196,40.8,6342,383691,570780,2.25,Flats/Maisonettes,2021,0.0,0.15,51.51271,-0.07495,51.52808,-0.09657,162,1192
SE1 8,SE1 5,11517,6831,40.7,4686,285846,421740,2.8,Flats/Maisonettes,1998,4.8,0.26,51.50309,-0.10794,51.48984,-0.07272,2354,4478
EC1A 7,E1W 1,16062,9651,39.9,6411,413509,576990,2.52,Flats/Maisonettes,1999,0.0,0.2,51.51834,-0.09869,51.50581,-0.06777,574,1353
WC1B 5,EC1N 7,15504,9362,39.6,6142,411514,552780,0.98,Flats/Maisonettes,1914,1.4,0.26,51.52107,-0.12492,51.52057,-0.11049,168,747
EC3R 6,EC1A 9,19039,11553,39.3,7486,426702,673740,1.82,Flats/Maisonettes,2018,1.0,0.31,51.50832,-0.08023,51.5181,-0.10183,325,367
S2 5,S3 9,2308,1402,39.3,906,63873,81540,2.0,Flats/Maisonettes,1958,1.2,0.51,53.37989,-1.44909,53.39521,-1.46478,3807,1778
W1K 5,W2 3,19600,11893,39.3,7707,454713,693630,2.17,Flats/Maisonettes,1890,2.0,0.11,51.51346,-0.14947,51.513,-0.18143,233,5113
W1S 1,WC2E 7,24324,14910,38.7,9414,663687,847260,1.53,Flats/Maisonettes,2012,2.0,0.17,51.51339,-0.14384,51.51193,-0.1214,274,337
W13 0,UB1 3,8242,5063,38.6,3179,220940,286110,2.55,Flats/Maisonettes,1971,3.0,0.41,51.51603,-0.3258,51.51377,-0.36322,4716,2761
L9 9,L4 6,2410,1482,38.5,928,76096,83520,2.76,Terraced,1940,1.2,0.52,53.46789,-2.94392,53.44478,-2.95911,1898,1185
W1T 6,EC1N 7,15122,9362,38.1,5760,322560,518400,2.07,Flats/Maisonettes,1914,1.6,0.25,51.52266,-0.14074,51.52057,-0.11049,562,747
SW3 2,SW10 9,21360,13240,38.0,8120,596820,730800,1.66,Flats/Maisonettes,1890,3.5,0.44,51.49428,-0.16481,51.48648,-0.18567,2546,5825
WC2E 9,SW1V 2,16982,10522,38.0,6460,381140,581400,2.55,Flats/Maisonettes,1940,2.0,0.14,51.51211,-0.1249,51.49031,-0.13734,406,3528
SW10 0,SW11 3,12094,7514,37.9,4580,329760,412200,1.08,Flats/Maisonettes,1971,6.1,0.65,51.48064,-0.18125,51.47181,-0.17455,5124,7592
N2 9,N12 0,8942,5554,37.9,3388,255794,304920,1.92,Flats/Maisonettes,1940,3.5,0.73,51.59254,-0.16238,51.60872,-0.17254,2927,3282
B11 3,B11 1,2787,1741,37.5,1046,82111,94140,1.99,Terraced,1914,2.8,0.86,52.44872,-1.84954,52.46166,-1.86989,4748,3202
W1J 8,SW1A 2,27270,17088,37.3,10182,1089474,916380,1.31,Flats/Maisonettes,2000,2.0,0.18,51.50769,-0.1444,51.50557,-0.12549,295,261
W1H 6,NW1 4,21897,13766,37.1,8131,719593,731790,1.38,Flats/Maisonettes,1940,1.3,0.45,51.5162,-0.15546,51.52803,-0.14886,233,1340
B5 6,B16 8,4080,2588,36.6,1492,90266,134280,1.78,Flats/Maisonettes,2022,3.0,0.58,52.47243,-1.89503,52.47603,-1.92066,2784,5647
W1K 3,SW7 2,24608,15611,36.6,8997,1025658,809730,2.35,Flats/Maisonettes,1914,2.0,0.34,51.5108,-0.14739,51.49729,-0.17406,263,1126
W11 1,W12 8,13227,8399,36.5,4828,272782,434520,2.07,Flats/Maisonettes,1940,4.3,0.27,51.51697,-0.20643,51.5037,-0.22797,4436,4802
W1J 5,W8 5,26103,16629,36.3,9474,916609,852660,2.89,Flats/Maisonettes,1914,2.0,0.4,51.50796,-0.14833,51.49913,-0.1884,503,2829
E7 0,IG1 3,7737,4926,36.3,2811,195364,252990,2.75,Flats/Maisonettes,1914,2.1,0.38,51.552,0.02729,51.56639,0.06025,4114,5219
BB1 8,BB1 6,2318,1498,35.4,820,75030,73800,0.97,Terraced,1958,3.0,1.48,53.76182,-2.48651,53.75666,-2.47488,3550,1175
M4 6,M3 1,4332,2797,35.4,1535,103612,138150,1.56,Flats/Maisonettes,2016,3.2,0.46,53.48377,-2.2243,53.48836,-2.24596,3944,1313
N10 2,N12 0,8571,5554,35.2,3017,209681,271530,2.29,Flats/Maisonettes,1940,3.7,1.29,51.59958,-0.14231,51.60872,-0.17254,3792,3282
E2 7,E1 4,10016,6518,34.9,3498,227370,314820,1.87,Flats/Maisonettes,1958,4.9,0.41,51.52816,-0.07104,51.52181,-0.04552,3508,4193
W11 4,W6 0,15604,10152,34.9,5452,346202,490680,1.98,Flats/Maisonettes,1958,3.6,0.35,51.50884,-0.21361,51.49621,-0.23419,4044,6681
NW10 1,NW10 0,7284,4776,34.4,2508,169290,225720,1.21,Flats/Maisonettes,1940,2.1,0.37,51.55484,-0.2425,51.55677,-0.25998,3163,3308
NW1 8,N7 9,12470,8179,34.4,4291,276769,386190,2.09,Flats/Maisonettes,1958,2.8,0.32,51.54247,-0.15026,51.54901,-0.12137,4113,4630
M19 3,M18 8,2911,1916,34.2,995,74127,89550,2.51,Terraced,1914,3.3,0.61,53.44574,-2.18452,53.46589,-2.16737,4046,4415
SW1X 0,SW7 5,23489,15480,34.1,8009,784882,720810,1.31,Flats/Maisonettes,1890,3.1,0.45,51.49639,-0.16144,51.49667,-0.18078,1606,3238
W1K 6,W8 4,23400,15425,34.1,7975,626037,717750,2.83,Flats/Maisonettes,1914,2.0,0.29,51.5128,-0.15349,51.50498,-0.19312,562,2309
NW3 1,NW6 3,16761,11048,34.1,5713,437044,514170,1.65,Flats/Maisonettes,1914,1.2,0.33,51.5575,-0.17458,51.54407,-0.18523,2098,4073
WC1A 2,EC1N 7,14183,9362,34.0,4821,310954,433890,0.94,Flats/Maisonettes,1914,1.7,0.28,51.51831,-0.12387,51.52057,-0.11049,234,747
E9 7,E3 4,9732,6430,33.9,3302,224536,297180,2.4,Flats/Maisonettes,1958,5.6,0.6,51.53924,-0.04918,51.52221,-0.02733,4570,6823
BD21 1,BD21 2,1419,938,33.9,481,37999,43290,1.01,Terraced,1914,1.7,0.7,53.85976,-1.91701,53.86885,-1.9178,2292,2303
SW10 9,W14 9,13240,8770,33.8,4470,270435,402300,1.55,Flats/Maisonettes,1914,4.5,0.63,51.48648,-0.18567,51.48883,-0.20815,5825,7258
L22 5,L21 3,2224,1472,33.8,752,48880,67680,1.65,Flats/Maisonettes,1958,0.8,0.28,53.47329,-3.02717,53.46477,-3.00727,821,259
EC2A 2,E1W 1,14576,9651,33.8,4925,354600,443250,1.96,Flats/Maisonettes,2021,0.8,0.33,51.52119,-0.08217,51.50581,-0.06777,772,1353
L20 7,L5 7,1728,1146,33.7,582,42195,52380,1.59,Terraced,1979,2.0,0.39,53.44391,-2.99118,53.42998,-2.98541,1243,848
B6 6,B21 9,2148,1428,33.5,720,63000,64800,2.92,Terraced,1914,3.9,0.74,52.50775,-1.89127,52.50746,-1.93433,2834,3409
B15 1,B16 8,3888,2588,33.4,1300,74100,117000,0.49,Flats/Maisonettes,2018,2.2,0.17,52.47397,-1.9142,52.47603,-1.92066,2987,5647
W1F 9,SW1V 2,15806,10522,33.4,5284,272126,475560,2.44,Flats/Maisonettes,1940,2.0,0.32,51.51241,-0.13704,51.49031,-0.13734,340,3528
E17 6,N15 6,8263,5515,33.3,2748,186864,247320,2.95,Flats/Maisonettes,1958,2.9,0.49,51.58862,-0.03531,51.57766,-0.07499,8292,4196
N5 1,N7 7,10770,7185,33.3,3585,227647,322650,0.69,Flats/Maisonettes,1958,3.5,0.31,51.55439,-0.10301,51.55818,-0.11109,5686,3539
W1W 5,EC1R 4,14857,9931,33.2,4926,310338,443340,2.41,Flats/Maisonettes,1971,1.0,0.19,51.52234,-0.14327,51.52666,-0.10846,790,634
W1H 7,NW1 5,15131,10126,33.1,5005,402902,450450,0.69,Flats/Maisonettes,1914,1.4,0.33,51.51532,-0.15973,51.52135,-0.16228,1094,1593
SW11 4,SW11 3,11216,7514,33.0,3702,264693,333180,1.24,Flats/Maisonettes,1971,5.4,0.72,51.47665,-0.15803,51.47181,-0.17455,5268,7592
W7 3,UB1 3,7546,5063,32.9,2483,175051,223470,1.66,Flats/Maisonettes,1958,3.8,0.56,51.51276,-0.33883,51.51377,-0.36322,4515,2761
B68 8,B67 7,3056,2053,32.8,1003,79237,90270,2.09,Terraced,1958,3.1,0.84,52.48621,-2.00783,52.49476,-1.98036,3700,2852
W1F 7,SW1V 1,16512,11119,32.7,5393,320883,485370,2.28,Flats/Maisonettes,1971,2.0,0.27,51.51336,-0.13775,51.49297,-0.14234,382,1408
L32 0,L33 9,2106,1422,32.5,684,53010,61560,2.16,Terraced,1958,1.9,0.42,53.48356,-2.90176,53.49051,-2.87196,1371,821
L7 7,L3 2,2432,1642,32.5,790,46215,71100,1.87,Flats/Maisonettes,2004,1.2,1.01,53.40015,-2.96557,53.41155,-2.98583,910,1341
L13 1,L7 4,2015,1363,32.4,652,42054,58680,2.09,Terraced,1940,4.3,0.4,53.40739,-2.91819,53.39725,-2.94425,610,401
W1G 9,WC1E 7,18377,12426,32.4,5951,499884,535590,1.07,Flats/Maisonettes,1940,1.5,0.42,51.51814,-0.14732,51.52141,-0.13245,426,386
L8 3,L8 0,2096,1420,32.3,676,42926,60840,0.81,Flats/Maisonettes,1914,5.0,1.28,53.38744,-2.95519,53.39354,-2.94861,2200,3137
E2 8,E1 4,9634,6518,32.3,3116,200982,280440,2.17,Flats/Maisonettes,1995,6.8,0.36,51.53258,-0.07231,51.52181,-0.04552,3891,4193
N1 1,NW1 9,13560,9213,32.1,4347,269514,391230,1.64,Flats/Maisonettes,1914,2.3,0.41,51.54168,-0.10954,51.544,-0.13333,5369,4459
W3 6,NW10 8,7977,5414,32.1,2563,144809,230670,2.88,Flats/Maisonettes,1999,2.8,0.28,51.51541,-0.26423,51.54116,-0.25786,8573,4510
NW1 4,EC1N 7,13766,9362,32.0,4404,295068,396360,2.73,Flats/Maisonettes,1940,1.9,0.42,51.52803,-0.14886,51.52057,-0.11049,1340,747
L1 5,L3 2,2416,1642,32.0,774,47988,69660,1.28,Flats/Maisonettes,2004,2.8,0.51,53.40039,-2.98092,53.41155,-2.98583,1724,1341
N1 4,E2 0,10333,7036,31.9,3297,212656,296730,2.99,Flats/Maisonettes,1940,7.1,0.41,51.54667,-0.0819,51.52807,-0.04998,3830,4330
SE1 9,SE1 3,13534,9218,31.9,4316,308594,388440,1.69,Flats/Maisonettes,2009,3.4,0.4,51.50702,-0.10046,51.49842,-0.07986,2358,4683
SW7 3,SW10 9,19392,13240,31.7,6152,453710,553680,0.76,Flats/Maisonettes,1890,3.3,0.41,51.49122,-0.1775,51.48648,-0.18567,2581,5825
W1K 2,SW1X 0,34362,23489,31.6,10873,1293887,978570,1.62,Flats/Maisonettes,1914,2.0,0.5,51.50983,-0.15202,51.49639,-0.16144,591,1606
E8 4,E2 0,10272,7036,31.5,3236,207104,291240,1.79,Flats/Maisonettes,1979,7.2,0.34,51.53852,-0.07013,51.52807,-0.04998,4554,4330
SW12 9,SW16 2,9262,6351,31.4,2911,190670,261990,2.33,Flats/Maisonettes,1914,5.7,0.38,51.44479,-0.1475,51.43068,-0.12196,5519,7620
BR3 3,CR0 7,7153,4910,31.4,2243,214206,201870,2.02,Terraced,1940,7.8,0.73,51.3949,-0.03019,51.38447,-0.05469,4514,5143
NE8 3,NE6 2,1812,1248,31.1,564,35814,50760,2.38,Flats/Maisonettes,1986,1.5,0.5,54.95623,-1.58964,54.97313,-1.568,3727,4445
KT13 8,KT15 2,6738,4663,30.8,2075,150437,186750,1.98,Flats/Maisonettes,1971,3.7,1.25,51.37281,-0.45766,51.3727,-0.48684,3317,4145
W2 5,W10 4,13145,9102,30.8,4043,254709,363870,1.73,Flats/Maisonettes,1914,3.6,0.38,51.51783,-0.19337,51.52944,-0.21048,5062,3629
WC1A 1,W1T 1,23988,16609,30.8,7379,472256,664110,0.48,Flats/Maisonettes,2016,2.0,0.21,51.517,-0.1274,51.51779,-0.1344,241,584
EC1V 2,EC1V 8,13244,9196,30.6,4048,267168,364320,0.11,Flats/Maisonettes,2020,0.1,0.55,51.52885,-0.09564,51.52808,-0.09657,1056,1192
W2 4,W1H 5,15000,10428,30.5,4572,290322,411480,2.0,Flats/Maisonettes,1914,2.1,0.28,51.51248,-0.19214,51.5169,-0.16353,5441,1194
N15 5,N17 0,7488,5207,30.5,2281,141422,205290,2.58,Flats/Maisonettes,1940,2.6,0.47,51.58315,-0.08094,51.60306,-0.06115,3757,4447
W1K 4,W1G 6,22808,15856,30.5,6952,590920,625680,1.05,Flats/Maisonettes,1940,2.0,0.23,51.51197,-0.14832,51.52144,-0.15008,229,588
NW2 1,NW2 7,7494,5224,30.3,2270,164575,204300,1.95,Flats/Maisonettes,1958,1.4,0.52,51.56613,-0.21393,51.56404,-0.24245,3610,3368
SE4 2,SE6 2,7845,5474,30.2,2371,156486,213390,2.85,Flats/Maisonettes,1914,1.0,0.39,51.46145,-0.04025,51.44106,-0.01466,5138,5519
B18 4,B21 9,2042,1428,30.1,614,48506,55260,1.65,Terraced,1914,2.7,0.69,52.49286,-1.93966,52.50746,-1.93433,1927,3409
L8 8,L7 4,1950,1363,30.1,587,40796,52830,1.55,Terraced,1940,3.8,1.13,53.39004,-2.96384,53.39725,-2.94425,1868,401
NW10 5,NW10 2,10106,7078,30.0,3028,199848,272520,1.75,Flats/Maisonettes,1914,3.0,0.4,51.5329,-0.2278,51.54767,-0.23691,5023,3505
B19 3,B7 4,3417,2397,29.9,1020,69360,91800,1.88,Flats/Maisonettes,1958,2.0,0.47,52.49095,-1.90381,52.48861,-1.87637,1584,1469
W1U 7,WC1N 2,18854,13224,29.9,5630,375802,506700,2.67,Flats/Maisonettes,1914,1.0,0.44,51.51885,-0.15472,51.52321,-0.11611,216,514
L15 4,L7 4,1942,1363,29.8,579,39661,52110,1.33,Terraced,1914,3.2,0.52,53.40136,-2.9259,53.39725,-2.94425,2027,401
CH62 8,CH62 9,3363,2362,29.8,1001,81831,90090,0.74,Semi-Detached,1958,4.0,0.91,53.31456,-2.97287,53.30801,-2.97057,1775,1274
SE1 0,SE1 5,9712,6831,29.7,2881,167098,259290,2.32,Flats/Maisonettes,2005,5.6,0.41,51.5023,-0.10018,51.48984,-0.07272,2736,4478
SE28 8,DA18 4,4850,3409,29.7,1441,104112,129690,1.72,Terraced,1993,2.8,1.39,51.5066,0.11805,51.49419,0.1333,5033,1063
BD21 2,BD21 3,938,660,29.6,278,24186,25020,0.88,Terraced,1914,2.0,1.07,53.86885,-1.9178,53.87113,-1.90541,2303,1377
W7 1,UB1 3,7184,5063,29.5,2121,148470,190890,2.06,Flats/Maisonettes,1958,3.8,0.36,51.51927,-0.3343,51.51377,-0.36322,3765,2761
L16 7,L14 6,4026,2840,29.5,1186,117414,106740,1.88,Semi-Detached,1940,5.1,1.22,53.39586,-2.89157,53.41102,-2.87901,500,809
W1G 8,WC1N 2,18737,13224,29.4,5513,395557,496170,2.31,Flats/Maisonettes,1914,1.0,0.49,51.51913,-0.14944,51.52321,-0.11611,707,514
L6 1,L5 8,2573,1818,29.3,755,47376,67950,1.72,Flats/Maisonettes,2009,2.0,1.1,53.41331,-2.96373,53.42093,-2.98578,1468,800
W12 7,W12 0,11848,8378,29.3,3470,225550,312300,0.73,Flats/Maisonettes,1958,2.7,0.37,51.51063,-0.22867,51.51319,-0.23865,7409,5147
NW8 0,W2 2,13358,9441,29.3,3917,272231,352530,2.73,Flats/Maisonettes,1958,3.0,0.38,51.53784,-0.18192,51.51475,-0.1679,2625,4150
W1K 1,W1G 7,28754,20358,29.2,8396,812313,755640,1.52,Flats/Maisonettes,1940,2.0,0.49,51.50754,-0.15204,51.52096,-0.14696,288,280
WC1N 2,EC1N 7,13224,9362,29.2,3862,230754,347580,0.48,Flats/Maisonettes,1914,0.9,0.53,51.52321,-0.11611,51.52057,-0.11049,514,747
BB11 2,BB11 3,1455,1032,29.1,423,32042,38070,0.74,Terraced,1914,0.1,0.71,53.78136,-2.24549,53.78377,-2.23529,1925,2642
E11 3,E12 5,7858,5570,29.1,2288,156728,205920,2.78,Flats/Maisonettes,1940,2.9,0.65,51.56154,0.01383,51.55444,0.05315,5821,5288
L3 5,L3 2,2315,1642,29.1,673,38697,60570,0.98,Flats/Maisonettes,2004,1.5,0.34,53.40732,-2.97319,53.41155,-2.98583,1300,1341
IG1 2,IG11 8,5300,3768,28.9,1532,98814,137880,1.16,Flats/Maisonettes,1986,2.6,0.81,51.5513,0.07504,51.5409,0.07766,7114,4525
E8 3,E1 4,9141,6518,28.7,2623,174429,236070,2.67,Flats/Maisonettes,1999,6.7,0.32,51.54269,-0.0654,51.52181,-0.04552,4980,4193
E17 9,E10 7,9498,6778,28.6,2720,175440,244800,1.56,Flats/Maisonettes,1914,2.3,0.52,51.5812,-0.01156,51.56938,-0.02405,5616,4807
B20 3,B21 9,2000,1428,28.6,572,49764,51480,2.0,Terraced,1914,2.4,0.76,52.51116,-1.90551,52.50746,-1.93433,4683,3409
SW1Y 6,WC2H 9,19050,13619,28.5,5431,353015,488790,1.02,Flats/Maisonettes,1914,2.0,0.26,51.50775,-0.13677,51.51405,-0.12585,343,726
EC2Y 8,E1W 1,13481,9651,28.4,3830,268100,344700,2.4,Flats/Maisonettes,1971,0.0,0.2,51.52001,-0.09446,51.50581,-0.06777,2082,1353
L15 1,L7 4,1902,1363,28.3,539,35843,48510,0.76,Terraced,1914,4.0,0.84,53.39986,-2.93392,53.39725,-2.94425,1301,401
NW6 2,NW2 5,9924,7120,28.3,2804,165436,252360,2.23,Flats/Maisonettes,1940,1.5,0.27,51.54603,-0.19676,51.54856,-0.22936,3822,4370
NW3 6,NW11 8,10350,7423,28.3,2927,191718,263430,2.42,Flats/Maisonettes,1914,1.8,0.22,51.55188,-0.1812,51.57032,-0.2005,3182,2951
NE1 2,NE8 3,2524,1812,28.2,712,41296,64080,1.88,Flats/Maisonettes,2003,2.6,0.41,54.97209,-1.59959,54.95623,-1.58964,1713,3727
WC1R 4,EC1Y 8,12388,8894,28.2,3494,204399,314460,1.82,Flats/Maisonettes,1958,1.0,0.28,51.51938,-0.11755,51.5234,-0.09159,362,1020
B8 3,B8 1,2451,1761,28.2,690,55200,62100,0.95,Terraced,1914,3.9,1.07,52.48814,-1.84204,52.49094,-1.85524,4026,2834
TW12 2,TW16 5,8001,5764,28.0,2237,189585,201330,2.42,Flats/Maisonettes,1958,1.5,0.51,51.41708,-0.37008,51.41307,-0.4051,3364,2205
W4 1,W3 0,10328,7440,28.0,2888,254144,259920,2.74,Flats/Maisonettes,1914,2.3,0.41,51.49721,-0.25518,51.5193,-0.2734,3571,2316
B1 3,B16 8,3590,2588,27.9,1002,60120,90180,1.04,Flats/Maisonettes,2017,2.0,0.52,52.48455,-1.91433,52.47603,-1.92066,2657,5647
W1W 7,SW1P 4,15530,11191,27.9,4339,269018,390510,2.94,Flats/Maisonettes,1979,1.9,0.35,51.51856,-0.14057,51.49278,-0.1299,602,3443
WC1N 1,WC1H 8,12880,9294,27.8,3586,188265,322740,0.42,Flats/Maisonettes,1940,2.3,0.21,51.52432,-0.12359,51.5279,-0.12161,1191,815
SW8 1,SE5 0,9368,6774,27.7,2594,164719,233460,1.7,Flats/Maisonettes,1960,4.3,0.37,51.48059,-0.12115,51.47961,-0.09617,6064,3441
E20 1,E15 2,8586,6208,27.7,2378,160515,214020,0.88,Flats/Maisonettes,2017,3.9,0.28,51.54601,-0.00917,51.53827,-0.00621,6585,6157
EC1Y 1,EC1V 9,11663,8443,27.6,3220,214130,289800,0.26,Flats/Maisonettes,2010,1.0,0.1,51.52492,-0.08698,51.52596,-0.09038,155,960
N15 3,N17 0,7178,5207,27.5,1971,116289,177390,3.0,Flats/Maisonettes,1914,3.4,0.67,51.58598,-0.09545,51.60306,-0.06115,4481,4447
W1T 1,SW1Y 4,16609,12054,27.4,4555,247108,409950,1.01,Flats/Maisonettes,1993,2.0,0.2,51.51779,-0.1344,51.50869,-0.1327,584,176
N7 9,NW1 3,8179,5948,27.3,2231,142784,200790,2.66,Flats/Maisonettes,1971,3.7,0.39,51.54901,-0.12137,51.5281,-0.14076,4630,1949
L22 9,L22 4,2838,2067,27.2,771,74208,69390,0.51,Terraced,1914,1.4,0.47,53.47895,-3.02768,53.47998,-3.02039,567,716
HU1 2,HU2 8,2320,1690,27.2,630,34335,56700,0.77,Flats/Maisonettes,1979,1.1,0.49,53.74094,-0.34259,53.74792,-0.34183,1177,1146
SE1 3,SE16 3,9218,6721,27.1,2497,157311,224730,1.33,Flats/Maisonettes,1999,4.8,0.77,51.49842,-0.07986,51.49091,-0.06454,4683,4240
LE1 6,LE1 3,2159,1576,27.0,583,26526,52470,0.95,Flats/Maisonettes,2004,5.4,0.42,52.63073,-1.13046,52.63932,-1.13017,2093,1426
BB2 3,BB1 1,1608,1174,27.0,434,35154,39060,1.5,Terraced,1971,3.5,1.42,53.73342,-2.47718,53.74438,-2.4641,4798,3580
N1 2,N7 6,11472,8394,26.8,3078,192375,277020,2.21,Flats/Maisonettes,1940,3.2,0.32,51.54325,-0.09732,51.55896,-0.11742,4823,3705
L35 3,L34 6,2438,1786,26.7,652,48574,58680,2.38,Terraced,1971,0.8,0.61,53.41043,-2.7958,53.43186,-2.79945,3080,858
B33 0,B37 5,2704,1984,26.6,720,53640,64800,1.46,Terraced,1958,4.1,1.06,52.47541,-1.77066,52.47855,-1.74975,4058,3580
W1W 6,EC1N 7,12734,9362,26.5,3372,180402,303480,2.11,Flats/Maisonettes,1914,1.0,0.41,51.52013,-0.14163,51.52057,-0.11049,946,747
E2 6,E1 4,8850,6518,26.4,2332,148082,209880,1.38,Flats/Maisonettes,1986,5.7,0.41,51.52646,-0.06445,51.52181,-0.04552,3414,4193
SW17 8,SW16 2,8612,6351,26.3,2261,151487,203490,2.36,Flats/Maisonettes,1914,5.9,0.49,51.43266,-0.1565,51.43068,-0.12196,7040,7620
LE1 4,LE1 3,2136,1576,26.2,560,26600,50400,0.58,Flats/Maisonettes,2018,4.5,1.17,52.63787,-1.13834,52.63932,-1.13017,1436,1426
B15 2,B16 8,3508,2588,26.2,920,57960,82800,1.21,Flats/Maisonettes,2004,2.5,0.59,52.46686,-1.91097,52.47603,-1.92066,4678,5647
NE31 2,NE32 3,2197,1629,25.9,568,43452,51120,2.31,Semi-Detached,1958,1.7,1.14,54.96595,-1.50844,54.97725,-1.47983,4787,2350
W1T 2,EC4V 5,15137,11220,25.9,3917,230123,352530,2.37,Flats/Maisonettes,1914,1.5,0.15,51.51933,-0.13475,51.51284,-0.10141,334,208
SK6 6,SK6 3,4485,3329,25.8,1156,99416,104040,2.56,Semi-Detached,1958,1.0,0.5,53.3973,-2.07179,53.41157,-2.1014,2365,1915
SE1 1,SE16 3,9062,6721,25.8,2341,145142,210690,2.34,Flats/Maisonettes,1999,4.3,0.23,51.50157,-0.09427,51.49091,-0.06454,1565,4240
SW3 3,SW10 9,17851,13240,25.8,4611,262827,414990,1.42,Flats/Maisonettes,1940,4.1,0.55,51.49144,-0.1664,51.48648,-0.18567,3413,5825
E12 5,IG1 1,5570,4137,25.7,1433,99593,128970,1.98,Flats/Maisonettes,1940,1.6,0.73,51.55444,0.05315,51.55757,0.0819,5288,4839
WC1X 0,EC1A 4,13653,10144,25.7,3509,236857,315810,1.29,Flats/Maisonettes,2012,0.2,0.62,51.52581,-0.11375,51.51977,-0.09756,1688,188
B33 9,B37 5,2667,1984,25.6,683,49176,61470,2.98,Terraced,1958,3.0,0.74,52.48615,-1.79185,52.47855,-1.74975,3962,3580
W1B 1,W1G 9,24699,18377,25.6,6322,678034,568980,0.43,Flats/Maisonettes,1958,1.1,0.17,51.52197,-0.14618,51.51814,-0.14732,466,426
SW17 9,CR4 2,8157,6067,25.6,2090,145777,188100,1.15,Flats/Maisonettes,1914,5.0,0.44,51.42261,-0.16219,51.41288,-0.15632,6187,4538
EC1Y 0,EC1Y 8,11961,8894,25.6,3067,181719,276030,0.3,Flats/Maisonettes,1971,0.0,0.29,51.52254,-0.09582,51.5234,-0.09159,686,1020
IG8 7,IG6 2,7148,5325,25.5,1823,143105,164070,2.98,Terraced,1958,2.8,0.58,51.60562,0.03933,51.59914,0.08194,2965,4423
LS28 5,LS28 6,3598,2682,25.5,916,68242,82440,1.38,Terraced,1958,1.6,1.24,53.81629,-1.67775,53.80707,-1.66403,4799,1534
WC2E 7,SW1V 1,14910,11119,25.4,3791,242624,341190,2.53,Flats/Maisonettes,1986,2.0,0.26,51.51193,-0.1214,51.49297,-0.14234,337,1408
DN35 7,DN32 9,1278,954,25.4,324,27864,29160,1.8,Terraced,1914,1.2,0.9,53.56767,-0.04891,53.56012,-0.07242,4576,3714
SE22 9,SE23 1,10024,7474,25.4,2550,178500,229500,2.36,Flats/Maisonettes,1914,4.6,0.82,51.4571,-0.0718,51.446,-0.04209,3946,3974
N16 0,E5 9,10653,7944,25.4,2709,174730,243810,1.72,Flats/Maisonettes,1940,5.9,0.64,51.56195,-0.07997,51.56528,-0.0553,3007,5763
CH43 7,CH49 8,2680,2001,25.3,679,53301,61110,2.7,Terraced,1979,4.2,0.84,53.39925,-3.07298,53.37667,-3.08801,2619,1101
CH1 2,CH1 3,3642,2720,25.3,922,59008,82980,0.87,Flats/Maisonettes,1994,2.7,1.23,53.19006,-2.89451,53.19535,-2.88502,842,2725
N1C 4,N1 7,14255,10642,25.3,3613,251103,325170,2.36,Flats/Maisonettes,2018,2.5,0.6,51.53756,-0.12621,51.5324,-0.09254,1949,5352
NW2 2,NW2 7,6991,5224,25.3,1767,122806,159030,2.74,Flats/Maisonettes,1958,0.9,0.82,51.56242,-0.20213,51.56404,-0.24245,3603,3368
SW15 6,SW19 6,8335,6233,25.2,2102,145038,189180,1.89,Flats/Maisonettes,1958,4.0,0.64,51.4602,-0.22549,51.44413,-0.21583,4327,4199
HA0 4,NW10 0,6382,4776,25.2,1606,114026,144540,2.84,Flats/Maisonettes,1940,3.7,0.41,51.54606,-0.29796,51.55677,-0.25998,3934,3308
NE22 7,NE22 5,2220,1662,25.1,558,41292,50220,1.25,Semi-Detached,1958,2.8,0.9,55.1417,-1.56867,55.13372,-1.58167,1369,3178
B13 8,B13 9,3557,2669,25.0,888,59940,79920,1.35,Flats/Maisonettes,1958,3.3,0.65,52.44751,-1.89353,52.44314,-1.87493,3567,6454
L1 2,L3 8,2481,1863,24.9,618,35226,55620,0.97,Flats/Maisonettes,2006,1.0,0.33,53.40313,-2.97495,53.41171,-2.97208,531,1603
SE21 8,SW16 3,7738,5811,24.9,1927,150306,173430,2.71,Flats/Maisonettes,1940,5.2,0.47,51.4369,-0.09319,51.4182,-0.11905,4505,3133
SW7 1,SW1A 1,22459,16895,24.8,5564,730275,500760,1.99,Flats/Maisonettes,1940,2.9,0.46,51.50025,-0.16756,51.50602,-0.13973,1864,229
N3 2,N12 0,7373,5554,24.7,1819,123692,163710,1.22,Flats/Maisonettes,1940,3.2,0.52,51.60127,-0.18578,51.60872,-0.17254,3926,3282
W1H 1,W1H 4,18118,13634,24.7,4484,242136,403560,0.25,Flats/Maisonettes,1914,2.5,0.28,51.52019,-0.16119,51.51941,-0.16464,626,484
SW5 9,W6 8,13297,10011,24.7,3286,190588,295740,1.49,Flats/Maisonettes,1914,3.4,0.24,51.49114,-0.19565,51.48654,-0.21631,5808,3253
SE15 4,SE23 1,9910,7474,24.6,2436,171738,219240,2.95,Flats/Maisonettes,1914,3.4,0.46,51.46584,-0.07115,51.446,-0.04209,3545,3974
E8 2,E2 0,9330,7036,24.6,2294,144522,206460,2.8,Flats/Maisonettes,1940,6.8,0.39,51.55034,-0.0696,51.52807,-0.04998,4333,4330
W1U 6,W2 6,14569,10990,24.6,3579,238003,322110,1.81,Flats/Maisonettes,1914,1.0,0.23,51.52059,-0.15781,51.51704,-0.18386,1075,4210
SE16 6,E14 3,8241,6225,24.5,2016,139104,181440,2.21,Flats/Maisonettes,1993,1.7,0.54,51.50061,-0.04314,51.49193,-0.01389,1940,8179
SW13 9,W4 5,12220,9220,24.5,3000,231750,270000,2.61,Flats/Maisonettes,1940,2.5,0.93,51.48023,-0.24118,51.49745,-0.26758,2631,4935
L22 6,L22 7,3710,2800,24.5,910,73710,81900,0.4,Semi-Detached,1940,0.0,0.7,53.48144,-3.04085,53.47948,-3.03589,464,450
L3 0,L5 9,2906,2198,24.4,708,47790,63720,1.03,Flats/Maisonettes,2001,1.0,1.03,53.41642,-3.00063,53.42433,-2.99258,1085,525
WC2B 5,SW1V 1,14709,11119,24.4,3590,211810,323100,2.86,Flats/Maisonettes,1971,2.0,0.21,51.5155,-0.1218,51.49297,-0.14234,891,1408
W10 5,NW6 5,10968,8303,24.3,2665,163897,239850,1.66,Flats/Maisonettes,1971,4.0,0.55,51.5225,-0.21145,51.53337,-0.19455,5247,4901
L16 5,L14 6,3753,2840,24.3,913,79431,82170,1.4,Semi-Detached,1940,4.7,0.97,53.3991,-2.88607,53.41102,-2.87901,453,809
NE1 3,NE1 6,2754,2090,24.1,664,36520,59760,0.55,Flats/Maisonettes,2008,2.3,0.35,54.96753,-1.61131,54.972,-1.60784,903,712
W1T 3,SW1P 2,15879,12102,23.8,3777,247393,339930,2.58,Flats/Maisonettes,2015,1.9,0.3,51.51842,-0.13787,51.49529,-0.13253,827,2039
N1 8,NW1 9,12078,9213,23.7,2865,181927,257850,2.53,Flats/Maisonettes,1940,1.4,0.41,51.53533,-0.09877,51.544,-0.13333,3569,4459
SW1W 0,SW1E 5,16694,12733,23.7,3961,354509,356490,0.38,Flats/Maisonettes,1979,3.3,0.21,51.49667,-0.146,51.49843,-0.14124,595,272
L1 3,L2 0,2929,2234,23.7,695,33360,62550,0.5,Flats/Maisonettes,2009,2.0,0.39,53.40327,-2.98663,53.40563,-2.99288,365,609
KT2 5,KT2 7,8290,6333,23.6,1957,148732,176130,1.59,Flats/Maisonettes,1995,2.4,0.94,51.42071,-0.30084,51.41822,-0.27782,6209,3298
E1 2,E1 4,8529,6518,23.6,2011,120660,180990,1.1,Flats/Maisonettes,1999,6.2,0.3,51.51519,-0.0577,51.52181,-0.04552,3511,4193
SW1H 9,EC4V 3,14058,10757,23.5,3301,209613,297090,2.75,Flats/Maisonettes,1999,3.0,0.2,51.50018,-0.13284,51.51065,-0.09613,289,315
WV2 3,WV3 0,2407,1842,23.5,565,42375,50850,1.33,Terraced,1914,2.5,1.27,52.57102,-2.12564,52.57828,-2.14126,1725,3074
W1H 4,W1H 5,13634,10428,23.5,3206,201978,288540,0.29,Flats/Maisonettes,1940,3.3,0.27,51.51941,-0.16464,51.5169,-0.16353,484,1194
W4 5,TW8 9,9220,7062,23.4,2158,132717,194220,2.91,Flats/Maisonettes,1958,2.8,0.32,51.49745,-0.26758,51.49138,-0.30937,4935,2835
N15 4,N17 0,6797,5207,23.4,1590,94605,143100,2.03,Flats/Maisonettes,1958,2.4,0.48,51.58626,-0.07309,51.60306,-0.06115,5055,4447
B3 1,B16 8,3374,2588,23.3,786,44802,70740,1.35,Flats/Maisonettes,2009,2.0,0.31,52.48427,-1.90599,52.47603,-1.92066,3141,5647
BB9 8,BB9 7,1452,1115,23.2,337,27465,30330,1.05,Terraced,1940,1.4,1.36,53.84457,-2.20627,53.838,-2.21735,3407,2693
B4 6,B5 5,4045,3105,23.2,940,45590,84600,0.55,Flats/Maisonettes,2017,2.0,0.23,52.4845,-1.89523,52.48086,-1.88965,2116,434
EC1V 1,EC1V 8,11949,9196,23.0,2753,173439,247770,0.23,Flats/Maisonettes,2016,0.5,0.44,51.52897,-0.09357,51.52808,-0.09657,1360,1192
NW6 1,NW2 4,10139,7802,23.0,2337,157747,210330,1.63,Flats/Maisonettes,1914,1.4,0.42,51.55137,-0.19415,51.55025,-0.21803,5669,3614
NW5 1,N19 3,11360,8742,23.0,2618,175406,235620,1.65,Flats/Maisonettes,1914,4.4,0.39,51.55724,-0.14407,51.56879,-0.12869,3114,5518
L9 8,L20 9,1951,1502,23.0,449,40410,40410,1.84,Terraced,1940,2.4,0.44,53.46493,-2.9656,53.45061,-2.97938,1769,2306
SW17 7,SW16 6,8557,6603,22.8,1954,132872,175860,2.25,Flats/Maisonettes,1940,4.1,0.41,51.43851,-0.16223,51.42335,-0.14006,6006,6534
WC2H 9,SW1V 2,13619,10522,22.7,3097,170335,278730,2.74,Flats/Maisonettes,1940,2.0,0.18,51.51405,-0.12585,51.49031,-0.13734,726,3528
NE8 2,NE8 3,2343,1812,22.7,531,34780,47790,2.06,Flats/Maisonettes,2004,2.0,0.87,54.95767,-1.61988,54.95623,-1.58964,3437,3727
NE6 1,NE8 3,2345,1812,22.7,533,33845,47970,1.96,Flats/Maisonettes,1986,1.8,0.6,54.97331,-1.58183,54.95623,-1.58964,1688,3727
LE1 5,LE1 3,2034,1576,22.5,458,20381,41220,0.93,Flats/Maisonettes,1999,5.0,0.73,52.6316,-1.13556,52.63932,-1.13017,1626,1426
WC1N 3,WC1H 8,11955,9294,22.3,2661,154338,239490,0.73,Flats/Maisonettes,1940,1.0,0.38,51.52146,-0.11947,51.5279,-0.12161,917,815
E1 0,E1 4,8390,6518,22.3,1872,123552,168480,0.99,Flats/Maisonettes,1971,5.1,0.35,51.51313,-0.04908,51.52181,-0.04552,3486,4193
BB11 4,BB11 3,1328,1032,22.3,296,21090,26640,1.59,Terraced,1914,0.0,0.63,53.78411,-2.25864,53.78377,-2.23529,3293,2642
SE3 7,SE12 8,7893,6137,22.2,1756,126432,158040,2.72,Flats/Maisonettes,1940,3.6,0.65,51.47871,0.0152,51.45411,0.01431,3853,3365
B42 2,B20 1,3168,2464,22.2,704,54560,63360,2.14,Semi-Detached,1958,2.6,1.27,52.53357,-1.90811,52.52345,-1.93504,5931,2309
E17 7,E10 7,8712,6778,22.2,1934,126677,174060,1.45,Flats/Maisonettes,1940,2.0,0.28,51.58188,-0.03031,51.56938,-0.02405,6041,4807
W1G 7,W1G 6,20358,15856,22.1,4502,387172,405180,0.22,Flats/Maisonettes,1940,1.0,0.29,51.52096,-0.14696,51.52144,-0.15008,280,588
FY1 5,FY1 3,1190,927,22.1,263,20251,23670,1.43,Terraced,1940,0.0,0.71,53.80886,-3.04494,53.8218,-3.0435,3510,2642
L17 8,L8 3,2687,2096,22.0,591,37233,53190,0.78,Flats/Maisonettes,1914,5.7,0.79,53.3819,-2.94808,53.38744,-2.95519,2017,2200
SE10 0,E14 2,7544,5898,21.8,1646,116866,148140,1.89,Flats/Maisonettes,2016,2.0,0.58,51.49297,0.01055,51.50866,-0.00067,10015,844
E3 5,E3 4,8220,6430,21.8,1790,126195,161100,1.18,Flats/Maisonettes,1971,5.5,0.74,51.53199,-0.03426,51.52221,-0.02733,4925,6823
DL1 1,DL1 4,2015,1576,21.8,439,32486,39510,1.07,Semi-Detached,1986,2.2,0.97,54.52695,-1.53562,54.51731,-1.53379,3425,5446
NW5 3,NW1 1,11150,8731,21.7,2419,151187,217710,1.95,Flats/Maisonettes,1958,2.7,0.23,51.54742,-0.14738,51.53218,-0.13296,1597,2651
AL8 6,AL7 1,6072,4755,21.7,1317,90873,118530,1.96,Flats/Maisonettes,1958,2.0,0.84,51.79764,-0.21351,51.80767,-0.18975,2609,3209
N8 0,N22 6,8463,6637,21.6,1826,115038,164340,0.83,Flats/Maisonettes,1940,3.5,0.44,51.58777,-0.10664,51.59482,-0.10227,6469,4971
E14 5,E14 2,7510,5898,21.5,1612,114452,145080,0.85,Flats/Maisonettes,2000,0.8,0.37,51.50474,-0.0115,51.50866,-0.00067,712,844
N8 7,N22 8,7980,6261,21.5,1719,102280,154710,1.96,Flats/Maisonettes,1958,2.9,0.6,51.58797,-0.11977,51.6055,-0.11564,4703,4514
GU16 7,GU15 3,4558,3581,21.4,977,70832,87930,2.46,Flats/Maisonettes,1971,2.1,0.46,51.31468,-0.74357,51.33663,-0.7494,953,3911
E9 5,E15 4,7728,6072,21.4,1656,106812,149040,2.92,Flats/Maisonettes,1979,4.7,0.44,51.54638,-0.03302,51.54158,0.00934,4708,4460
B3 2,B16 8,3290,2588,21.3,702,38610,63180,1.43,Flats/Maisonettes,2014,2.0,0.23,52.48237,-1.90234,52.47603,-1.92066,178,5647
E1W 1,SE16 5,9651,7596,21.3,2055,146932,184950,1.93,Flats/Maisonettes,1998,1.4,0.57,51.50581,-0.06777,51.50438,-0.03943,1353,2901
L16 1,L14 6,3607,2840,21.3,767,61360,69030,1.18,Semi-Detached,1940,5.2,0.51,53.40267,-2.88984,53.41102,-2.87901,434,809
LA14 2,LA14 1,1252,985,21.3,267,18022,24030,0.81,Terraced,1914,1.1,1.37,54.10749,-3.22482,54.11479,-3.22584,3502,2195
B11 2,B11 1,2208,1741,21.2,467,36192,42030,1.57,Terraced,1914,2.2,0.64,52.4551,-1.84936,52.46166,-1.86989,1291,3202
L13 3,L13 2,2078,1637,21.2,441,34618,39690,0.48,Terraced,1914,4.1,1.34,53.41643,-2.91645,53.41304,-2.92082,1350,1340
E14 9,E14 3,7903,6225,21.2,1678,111587,151020,0.99,Flats/Maisonettes,2013,0.8,0.26,51.50073,-0.01626,51.49193,-0.01389,18659,8179
B25 8,B10 0,2476,1952,21.2,524,38514,47160,2.64,Terraced,1940,3.8,1.45,52.4666,-1.81995,52.46765,-1.85885,5605,2837
SW3 6,SW10 9,16803,13240,21.2,3563,260099,320670,0.84,Flats/Maisonettes,1914,4.3,0.67,51.48869,-0.17386,51.48648,-0.18567,1594,5825
WC1B 3,EC4V 5,14214,11220,21.1,2994,210328,269460,1.92,Flats/Maisonettes,1914,1.9,0.23,51.51799,-0.12848,51.51284,-0.10141,322,208
NW1 6,NW6 4,11154,8814,21.0,2340,136890,210600,2.62,Flats/Maisonettes,1914,3.1,0.23,51.5237,-0.16342,51.54102,-0.18969,3312,3641
EC1V 4,EC1Y 8,11255,8894,21.0,2361,151104,212490,0.83,Flats/Maisonettes,1999,0.0,0.43,51.52548,-0.10341,51.5234,-0.09159,897,1020
NW5 2,N19 3,11064,8742,21.0,2322,145125,208980,2.09,Flats/Maisonettes,1914,3.9,0.35,51.5506,-0.13698,51.56879,-0.12869,4088,5518
BB12 0,BB12 6,2222,1756,21.0,466,35882,41940,1.82,Terraced,1971,0.3,0.76,53.79775,-2.257,53.79231,-2.28235,3008,3957
L8 4,L7 6,2126,1682,20.9,444,30636,39960,2.4,Terraced,1958,3.5,0.69,53.3828,-2.9662,53.4,-2.94456,1582,885
N22 7,N11 2,8317,6576,20.9,1741,127093,156690,1.09,Flats/Maisonettes,1914,3.9,0.49,51.59943,-0.12437,51.60923,-0.12642,2841,3946
SW17 0,CR4 2,7672,6067,20.9,1605,111948,144450,2.42,Flats/Maisonettes,1958,5.0,0.6,51.43102,-0.17616,51.41288,-0.15632,7498,4538
TW7 7,TW7 4,6663,5276,20.8,1387,101944,124830,1.84,Flats/Maisonettes,1971,3.4,1.05,51.46225,-0.3339,51.47701,-0.34628,3353,3229
L16 2,L36 4,3306,2618,20.8,688,65016,61920,1.21,Semi-Detached,1958,3.5,1.21,53.40324,-2.87211,53.41162,-2.86068,807,2242
CH42 9,CH42 0,1726,1367,20.8,359,30694,32310,0.8,Terraced,1914,4.9,1.49,53.37733,-3.03646,53.38266,-3.02855,1821,1277
ST4 2,ST1 4,1611,1276,20.8,335,23785,30150,1.7,Terraced,1914,1.0,0.79,53.00742,-2.17135,53.01975,-2.18626,2587,1893
L36 9,L36 5,3227,2558,20.7,669,63555,60210,0.94,Semi-Detached,1986,2.4,0.35,53.41196,-2.84932,53.40572,-2.83991,824,1467
E2 9,E1 4,8218,6518,20.7,1700,105400,153000,1.34,Flats/Maisonettes,1971,6.3,0.31,51.53204,-0.05617,51.52181,-0.04552,3336,4193
N1 0,NW5 4,11018,8753,20.6,2265,144960,203850,2.96,Flats/Maisonettes,1958,1.6,0.54,51.53742,-0.11419,51.55049,-0.15229,4577,2697
NG7 7,NG7 6,2175,1730,20.5,445,31595,40050,0.71,Terraced,1914,4.1,0.56,52.97526,-1.16804,52.96909,-1.16531,2339,3559
NG7 2,NG2 1,2801,2228,20.5,573,42688,51570,1.76,Terraced,1958,4.4,0.58,52.94656,-1.17989,52.94167,-1.15523,3214,1056
NG1 7,NG1 1,2786,2216,20.5,570,30210,51300,0.49,Flats/Maisonettes,2010,3.4,0.2,52.94995,-1.14699,52.95301,-1.14178,945,2895
NR32 1,NR32 2,2311,1840,20.4,471,32734,42390,0.81,Terraced,1914,2.0,0.86,52.48169,1.75296,52.47962,1.74151,2532,4026
TW4 5,TW3 3,5776,4597,20.4,1179,76045,106110,1.08,Flats/Maisonettes,1979,5.5,1.24,51.45786,-0.3795,51.46635,-0.37157,3500,4518
L15 0,L7 4,1712,1363,20.4,349,23906,31410,0.75,Terraced,1914,4.8,1.06,53.39699,-2.93315,53.39725,-2.94425,1329,401
WF8 2,WF11 8,2705,2152,20.4,553,42304,49770,2.99,Semi-Detached,1958,1.8,0.8,53.6932,-1.29594,53.70935,-1.26056,7519,2858
CH42 2,CH42 0,1715,1367,20.3,348,28536,31320,1.85,Terraced,1940,2.9,0.32,53.37035,-3.01,53.38266,-3.02855,1267,1277
SW9 0,SE5 0,8502,6774,20.3,1728,112320,155520,1.46,Flats/Maisonettes,1958,3.7,0.46,51.47388,-0.11558,51.47961,-0.09617,4980,3441
LS6 3,LS6 2,3230,2577,20.2,653,51587,58770,1.69,Flats/Maisonettes,1940,1.6,0.68,53.82002,-1.58519,53.81733,-1.56075,4033,3873
SW20 0,KT3 4,8740,6972,20.2,1768,144092,159120,1.41,Flats/Maisonettes,1958,4.8,0.75,51.41269,-0.23796,51.40313,-0.25172,3673,2531
RM14 2,RM12 5,6360,5079,20.1,1281,111447,115290,2.99,Semi-Detached,1940,3.7,0.77,51.55277,0.24307,51.54507,0.20079,3026,3133
N7 8,N7 7,8998,7185,20.1,1813,110593,163170,1.11,Flats/Maisonettes,1979,3.2,0.28,51.54816,-0.11264,51.55818,-0.11109,4607,3539
B19 1,B21 9,1788,1428,20.1,360,30060,32400,1.69,Terraced,1914,3.6,1.2,52.50297,-1.91051,52.50746,-1.93433,3162,3409
L18 6,L15 6,3877,3098,20.1,779,88806,70110,1.54,Semi-Detached,1940,4.8,0.87,53.38188,-2.90414,53.39565,-2.90712,564,901
WV14 6,WV14 7,2774,2218,20.0,556,41700,50040,1.05,Semi-Detached,1958,3.5,0.74,52.57348,-2.0784,52.56866,-2.06512,2963,1517
DN35 8,DN35 7,1598,1278,20.0,320,26400,28800,1.7,Terraced,1914,0.7,0.65,53.55706,-0.03085,53.56767,-0.04891,3803,4576
NE4 5,NE8 1,1503,1202,20.0,301,21070,27090,2.84,Flats/Maisonettes,1914,2.0,0.96,54.97595,-1.63497,54.95588,-1.60896,3018,2310
B9 5,B10 9,2597,2079,19.9,518,44289,46620,0.95,Terraced,1940,5.5,1.05,52.47881,-1.84057,52.47059,-1.84439,5554,4180
SE11 4,SE5 0,8462,6774,19.9,1688,111408,151920,1.4,Flats/Maisonettes,1971,5.3,0.32,51.49044,-0.10679,51.47961,-0.09617,4439,3441
SE10 9,E14 3,7769,6225,19.9,1544,107308,138960,1.14,Flats/Maisonettes,2012,2.0,0.31,51.48279,-0.00612,51.49193,-0.01389,6113,8179
W2 6,NW6 4,10990,8814,19.8,2176,129472,195840,2.68,Flats/Maisonettes,1914,2.5,0.3,51.51704,-0.18386,51.54102,-0.18969,4210,3641
SE11 6,SE1 5,8493,6831,19.6,1662,103044,149580,2.81,Flats/Maisonettes,1971,4.7,0.58,51.49243,-0.11392,51.48984,-0.07272,2536,4478
TW10 6,SW14 7,11640,9375,19.5,2265,175537,203850,1.91,Flats/Maisonettes,1914,1.8,0.79,51.45695,-0.29727,51.46517,-0.27252,4157,2831
N2 0,NW11 0,8195,6594,19.5,1601,123277,144090,2.02,Flats/Maisonettes,1940,1.8,0.83,51.5874,-0.17339,51.58225,-0.2019,3685,2109
S1 2,S1 4,2568,2069,19.4,499,21207,44910,0.47,Flats/Maisonettes,2012,3.2,0.24,53.38187,-1.46947,53.37885,-1.47441,2182,5583
TW2 7,TW3 2,6971,5620,19.4,1351,113821,121590,1.0,Semi-Detached,1940,5.4,0.59,51.45266,-0.35393,51.4609,-0.36011,3377,2964
L15 7,L14 6,3518,2840,19.3,678,56274,61020,2.0,Semi-Detached,1940,5.1,0.83,53.40286,-2.90524,53.41102,-2.87901,1032,809
CH45 1,CH45 4,2213,1785,19.3,428,38948,38520,1.26,Semi-Detached,1914,1.7,0.83,53.43265,-3.03879,53.42321,-3.04925,1283,1465
N1 9,N1 6,10402,8392,19.3,2010,115575,180900,2.16,Flats/Maisonettes,1971,1.1,0.31,51.53273,-0.1152,51.52938,-0.08379,2833,3384
EC3R 8,E1 7,10309,8330,19.2,1979,101918,178110,0.99,Flats/Maisonettes,1986,0.0,0.19,51.50999,-0.08419,51.51629,-0.07389,221,2146
L18 9,L19 4,3921,3169,19.2,752,69184,67680,0.66,Semi-Detached,1958,4.2,0.67,53.36947,-2.89798,53.3647,-2.89212,1296,1311
BB9 0,BB9 7,1378,1115,19.1,263,22092,23670,1.05,Terraced,1914,1.0,0.84,53.82989,-2.20929,53.838,-2.21735,4211,2693
M25 9,M27 4,3607,2917,19.1,690,55890,62100,2.99,Semi-Detached,1958,1.5,1.33,53.52156,-2.2855,53.5097,-2.32514,2959,2295
E16 2,SE18 5,6146,4972,19.1,1174,78658,105660,1.26,Flats/Maisonettes,2017,3.1,0.33,51.502,0.04566,51.49158,0.05314,11558,3903
SK15 2,OL6 6,3024,2450,19.0,574,40754,51660,2.77,Terraced,1958,2.2,1.36,53.47937,-2.04467,53.48856,-2.08261,3742,2416
WV14 8,WS10 7,3186,2581,19.0,605,42652,54450,2.72,Semi-Detached,1958,1.7,0.62,52.54996,-2.06972,52.55734,-2.03155,5736,2725
TW12 3,KT8 1,7042,5708,18.9,1334,107053,120060,2.23,Terraced,1979,3.2,1.27,51.42588,-0.37796,51.40643,-0.36933,2527,1567
LS1 4,LS11 9,3513,2848,18.9,665,40897,59850,0.82,Flats/Maisonettes,2006,3.0,0.46,53.79487,-1.55334,53.78743,-1.55422,2171,2841
NE3 4,NE3 2,3298,2676,18.9,622,53803,55980,1.92,Semi-Detached,1958,4.9,1.25,55.00102,-1.63652,55.01655,-1.64912,3991,4588
CT19 5,CT19 4,3399,2761,18.8,638,53592,57420,2.06,Terraced,1940,3.0,0.77,51.08812,1.17314,51.08929,1.14286,4481,3250
L31 3,L10 2,3176,2578,18.8,598,56212,53820,2.77,Semi-Detached,1971,2.0,0.35,53.50739,-2.93196,53.48371,-2.94544,661,347
L9 0,L20 9,1849,1502,18.8,347,30189,31230,2.77,Terraced,1914,1.9,0.6,53.46914,-2.95196,53.45061,-2.97938,1477,2306
L30 7,L30 8,2650,2154,18.7,496,40176,44640,0.79,Semi-Detached,1979,3.0,1.02,53.49196,-2.96368,53.48815,-2.95383,790,425
BN2 0,BN2 4,5633,4582,18.7,1051,69366,94590,2.32,Flats/Maisonettes,1958,3.0,1.35,50.82353,-0.12438,50.84309,-0.11204,3130,5322
SW3 5,SW10 9,16271,13240,18.6,3031,207623,272790,1.09,Flats/Maisonettes,1914,4.8,1.05,51.48503,-0.16986,51.48648,-0.18567,3183,5825
IP4 1,IP1 1,2606,2120,18.6,486,28674,43740,0.79,Flats/Maisonettes,2004,2.1,0.95,52.05439,1.16289,52.05524,1.15128,2603,1100
NR33 0,NR32 2,2261,1840,18.6,421,30943,37890,1.62,Terraced,1914,2.0,1.29,52.46508,1.739,52.47962,1.74151,3979,4026
L9 2,L9 4,1807,1472,18.5,335,27637,30150,1.02,Terraced,1940,1.8,0.37,53.45663,-2.95873,53.46584,-2.95873,1133,551
B12 8,B11 1,2135,1741,18.5,394,30929,35460,0.88,Terraced,1914,1.8,1.16,52.45575,-1.87847,52.46166,-1.86989,2418,3202
NW3 5,NW3 6,12706,10350,18.5,2356,174344,212040,0.5,Flats/Maisonettes,1914,2.6,0.36,51.54962,-0.17479,51.55188,-0.1812,3173,3182
SW4 6,SE5 0,8312,6774,18.5,1538,100739,138420,2.69,Flats/Maisonettes,1958,3.4,0.27,51.46863,-0.13154,51.47961,-0.09617,4563,3441
L23 0,L21 9,3007,2450,18.5,557,49016,50130,1.48,Semi-Detached,1940,2.0,1.2,53.48494,-3.01832,53.47603,-3.00214,1547,1758
SW1W 8,SW1P 4,13714,11191,18.4,2523,166518,227070,1.55,Flats/Maisonettes,1993,3.8,0.47,51.48954,-0.15204,51.49278,-0.1299,3312,3443
TS4 3,TS4 2,1675,1367,18.4,308,23100,27720,1.7,Terraced,1958,1.6,1.03,54.548,-1.22043,54.56321,-1.22413,3973,4228
CR4 2,CR4 4,6067,4969,18.1,1098,76036,98820,2.29,Flats/Maisonettes,1940,4.4,0.48,51.41288,-0.15632,51.39323,-0.16681,4538,3840
PL1 3,PL4 0,2904,2378,18.1,526,35242,47340,1.79,Flats/Maisonettes,1986,2.3,1.41,50.36686,-4.15566,50.36895,-4.12957,4256,1753
E9 6,E1 4,7954,6518,18.1,1436,92622,129240,2.76,Flats/Maisonettes,1971,6.8,0.38,51.54675,-0.04816,51.52181,-0.04552,3947,4193
LS18 5,LS16 6,4110,3367,18.1,743,59068,66870,1.91,Semi-Detached,1958,2.8,1.03,53.84265,-1.63652,53.84651,-1.60911,3580,4155
E1 1,E14 7,7686,6306,18.0,1380,84180,124200,2.27,Flats/Maisonettes,2000,3.4,0.38,51.51507,-0.06529,51.51406,-0.03192,3545,5687
SW7 4,W14 8,14200,11646,18.0,2554,176226,229860,1.54,Flats/Maisonettes,1914,2.6,0.31,51.49492,-0.18518,51.49743,-0.20756,3430,7221
SW20 8,KT3 4,8500,6972,18.0,1528,116892,137520,2.23,Flats/Maisonettes,1940,4.6,0.46,51.41147,-0.22174,51.40313,-0.25172,5155,2531
L4 5,L20 2,1410,1158,17.9,252,19908,22680,1.18,Terraced,1914,2.0,0.99,53.44347,-2.96715,53.44207,-2.98445,2817,1300
SM6 7,SM6 0,5628,4620,17.9,1008,67032,90720,1.97,Flats/Maisonettes,1996,4.0,0.55,51.37513,-0.15286,51.35737,-0.15137,3418,3099
NW5 4,N7 7,8753,7185,17.9,1568,94864,141120,2.92,Flats/Maisonettes,1971,3.0,0.42,51.55049,-0.15229,51.55818,-0.11109,2697,3539
M3 7,M3 6,3816,3134,17.9,682,40579,61380,0.7,Flats/Maisonettes,2018,3.8,0.46,53.48713,-2.25085,53.4858,-2.26093,5941,3553
NE2 2,NE2 1,3146,2584,17.9,562,44960,50580,1.01,Flats/Maisonettes,1914,3.3,0.63,54.99126,-1.60136,54.98237,-1.5983,2656,3970
W4 2,W4 4,10174,8372,17.7,1802,128843,162180,1.1,Flats/Maisonettes,1914,1.8,0.69,51.48794,-0.25401,51.49057,-0.26957,4027,2732
SW4 0,SW2 1,9983,8214,17.7,1769,113216,159210,2.14,Flats/Maisonettes,1940,4.2,0.52,51.4646,-0.14415,51.45683,-0.11529,3931,3735
EC1M 5,EC1Y 8,10803,8894,17.7,1909,128857,171810,0.78,Flats/Maisonettes,1999,0.0,0.18,51.52179,-0.10282,51.5234,-0.09159,521,1020
L4 1,L20 7,2098,1728,17.6,370,27565,33300,1.2,Terraced,1979,2.3,0.49,53.43506,-2.98101,53.44391,-2.99118,1667,1243
EC3N 2,EC1V 9,10244,8443,17.6,1801,112562,162090,1.86,Flats/Maisonettes,1999,0.0,0.08,51.5113,-0.07707,51.52596,-0.09038,166,960
SE27 0,SW16 3,7049,5811,17.6,1238,95326,111420,1.5,Flats/Maisonettes,1940,5.6,0.53,51.42953,-0.10688,51.4182,-0.11905,5574,3133
B43 5,B20 1,2989,2464,17.6,525,43312,47250,1.8,Semi-Detached,1958,2.2,1.28,52.5392,-1.94182,52.52345,-1.93504,3455,2309
W6 0,W12 0,10152,8378,17.5,1774,116197,159660,1.9,Flats/Maisonettes,1940,3.2,0.33,51.49621,-0.23419,51.51319,-0.23865,6681,5147
UB3 4,UB7 9,6151,5076,17.5,1075,76325,96750,2.94,Flats/Maisonettes,2004,1.9,0.55,51.49951,-0.41996,51.50394,-0.46273,4607,4365
M7 1,M5 3,3274,2701,17.5,573,39823,51570,2.67,Flats/Maisonettes,2007,3.1,1.23,53.49476,-2.26295,53.47213,-2.27652,2375,5106
SW1V 4,SW1V 2,12739,10522,17.4,2217,116392,199530,0.47,Flats/Maisonettes,1890,3.0,0.61,51.48917,-0.14394,51.49031,-0.13734,3227,3528
HA7 2,HA3 0,6834,5646,17.4,1188,108108,106920,2.57,Semi-Detached,1940,2.9,1.31,51.60329,-0.31221,51.58069,-0.30359,2775,3122
EC1R 0,EC1R 4,12020,9931,17.4,2089,133696,188010,0.25,Flats/Maisonettes,1971,0.0,0.51,51.52501,-0.10599,51.52666,-0.10846,532,634
L19 7,L19 9,4230,3496,17.4,734,72666,66060,0.48,Semi-Detached,1940,4.0,0.53,53.36525,-2.90341,53.36383,-2.91015,446,1110
B90 2,B28 0,4200,3470,17.4,730,63145,65700,1.76,Semi-Detached,1958,1.9,0.99,52.40657,-1.83487,52.42046,-1.84765,4449,4603
SE8 5,E14 3,7524,6225,17.3,1299,88981,116910,1.7,Flats/Maisonettes,1986,1.6,0.67,51.48642,-0.03731,51.49193,-0.01389,5494,8179
SE10 8,E14 3,7531,6225,17.3,1306,90767,117540,1.98,Flats/Maisonettes,2001,1.0,0.36,51.47401,-0.01359,51.49193,-0.01389,5455,8179
BN7 1,BN7 2,6210,5138,17.3,1072,82544,96480,1.0,Terraced,1958,1.8,1.01,50.87346,-0.00155,50.87753,0.01166,3196,3571
NW10 4,NW10 9,7114,5893,17.2,1221,73260,109890,0.84,Flats/Maisonettes,1914,2.8,0.51,51.53703,-0.24549,51.5439,-0.25092,4233,3624
E14 0,E15 2,7497,6208,17.2,1289,83140,116010,2.9,Flats/Maisonettes,2016,2.5,0.36,51.51208,-0.0033,51.53827,-0.00621,8378,6157
UB5 4,UB5 5,5964,4938,17.2,1026,72333,92340,1.65,Flats/Maisonettes,1958,2.9,0.58,51.5526,-0.36148,51.54333,-0.38054,5251,4277
WC1X 9,WC1H 8,11211,9294,17.1,1917,106393,172530,0.45,Flats/Maisonettes,1940,0.5,0.41,51.52919,-0.11524,51.5279,-0.12161,1540,815
S6 4,S6 1,2988,2476,17.1,512,40192,46080,1.64,Terraced,1940,1.9,0.49,53.40639,-1.51241,53.42047,-1.50486,4963,4420
SO23 9,SO23 0,6311,5240,17.0,1071,79789,96390,1.31,Flats/Maisonettes,1971,3.2,1.28,51.05626,-1.31826,51.06197,-1.30127,2026,2127
L39 5,L39 6,3842,3187,17.0,655,74997,58950,1.36,Detached,1971,0.0,0.59,53.55169,-2.90318,53.53975,-2.90771,1515,821
SW11 8,SW11 7,13444,11155,17.0,2289,168241,206010,0.78,Flats/Maisonettes,2016,3.2,0.38,51.48159,-0.14506,51.48145,-0.13354,5298,4866
TS19 0,TS18 4,1750,1453,17.0,297,25096,26730,1.3,Semi-Detached,1958,2.3,1.23,54.57472,-1.33294,54.56297,-1.33273,4499,2209
CH63 3,CH63 2,3484,2896,16.9,588,51450,52920,1.01,Semi-Detached,1940,2.7,0.83,53.34542,-3.0082,53.3507,-3.02031,1436,1530
CH41 8,CH41 7,1806,1503,16.8,303,22270,27270,1.1,Terraced,1958,4.5,0.41,53.40007,-3.04816,53.40356,-3.06336,1683,1078
WS12 4,WS11 6,3130,2604,16.8,526,39450,47340,2.04,Semi-Detached,1986,2.9,1.48,52.71611,-2.0179,52.69769,-2.01587,6766,1607
SE24 0,SE5 9,9093,7561,16.8,1532,101112,137880,1.33,Flats/Maisonettes,1914,3.1,0.44,51.45863,-0.10307,51.47044,-0.09938,4116,4741
WF10 1,WF10 4,2506,2084,16.8,422,31861,37980,1.76,Terraced,1958,0.9,0.94,53.72625,-1.36697,53.72026,-1.34292,2055,4531
S20 7,S20 1,2922,2430,16.8,492,35424,44280,1.31,Semi-Detached,1979,0.2,0.39,53.33769,-1.35432,53.34676,-1.342,1346,2233
NW10 2,NW10 9,7078,5893,16.7,1185,71100,106650,1.04,Flats/Maisonettes,1940,2.2,0.56,51.54767,-0.23691,51.5439,-0.25092,3505,3624
L3 6,L5 3,2356,1962,16.7,394,24034,35460,1.21,Flats/Maisonettes,2003,1.5,0.64,53.41455,-2.99003,53.42081,-2.97545,1725,1454
L22 0,L22 1,2609,2175,16.6,434,28861,39060,0.66,Flats/Maisonettes,1914,1.3,0.24,53.47671,-3.02473,53.4711,-3.02158,710,617
SW4 9,SW2 5,10076,8400,16.6,1676,118158,150840,1.23,Flats/Maisonettes,1914,5.1,0.49,51.45624,-0.14204,51.45674,-0.1239,4010,5125
L9 3,L20 9,1802,1502,16.6,300,27450,27000,1.34,Terraced,1914,3.0,0.29,53.46011,-2.96714,53.45061,-2.97938,864,2306
BB2 2,BB2 1,1405,1172,16.6,233,16892,20970,0.83,Terraced,1940,4.7,0.56,53.73966,-2.50046,53.74659,-2.49571,2449,2156
NG8 5,NG8 6,2654,2216,16.5,438,29346,39420,1.54,Terraced,1940,3.4,1.11,52.97537,-1.19714,52.97907,-1.21893,5034,4450
KT1 4,KT2 7,7580,6333,16.5,1247,88537,112230,2.4,Flats/Maisonettes,1971,2.4,0.23,51.41458,-0.31269,51.41822,-0.27782,1490,3298
EC1N 8,E1 7,9979,8330,16.5,1649,96466,148410,2.35,Flats/Maisonettes,2002,0.8,0.21,51.52047,-0.10778,51.51629,-0.07389,573,2146
SE19 1,SW16 3,6947,5811,16.4,1136,87472,102240,2.43,Flats/Maisonettes,1958,4.9,0.37,51.42311,-0.08413,51.4182,-0.11905,4346,3133
TW1 2,TW1 1,9945,8322,16.3,1623,120913,146070,1.04,Flats/Maisonettes,1914,3.0,0.6,51.45459,-0.31202,51.45653,-0.32698,2753,3637
SW9 9,SW9 7,8416,7041,16.3,1375,89375,123750,1.0,Flats/Maisonettes,1940,2.8,0.36,51.46731,-0.123,51.46801,-0.10832,6031,3760
BN3 5,BN3 3,6741,5645,16.3,1096,74528,98640,1.24,Flats/Maisonettes,1914,5.6,0.44,50.83387,-0.18759,50.83132,-0.16973,5310,8935
L2 5,L1 6,1878,1574,16.2,304,14592,27360,0.16,Flats/Maisonettes,2006,1.0,0.19,53.40738,-2.98838,53.40797,-2.98616,369,881
L4 3,L20 2,1382,1158,16.2,224,18144,20160,0.68,Terraced,1914,2.2,0.47,53.44124,-2.97449,53.44207,-2.98445,1796,1300
L31 7,L10 3,3036,2545,16.2,491,44926,44190,2.43,Semi-Detached,1958,2.0,1.21,53.50819,-2.94793,53.48632,-2.94406,886,418
SM2 7,KT17 3,6904,5790,16.1,1114,173227,100260,2.55,Detached,1940,2.8,0.72,51.34792,-0.21697,51.32954,-0.23953,1934,1519
NW4 2,NW9 0,6465,5422,16.1,1043,75617,93870,2.92,Flats/Maisonettes,1940,5.2,0.69,51.58597,-0.21789,51.58914,-0.26066,3448,3514
BN1 3,BN2 3,6411,5387,16.0,1024,61952,92160,1.62,Flats/Maisonettes,1890,4.0,0.51,50.8283,-0.1474,50.83424,-0.12553,7052,5567
NW11 6,N2 0,9752,8195,16.0,1557,130788,140130,1.13,Flats/Maisonettes,1914,1.9,1.34,51.58492,-0.1896,51.5874,-0.17339,2269,3685
SN2 1,SN2 8,3293,2767,16.0,526,38924,47340,0.91,Terraced,1958,2.5,1.12,51.57453,-1.78706,51.57392,-1.77363,4720,1056
WA10 2,WA9 1,1701,1431,15.9,270,18225,24300,2.12,Terraced,1979,1.5,1.14,53.45722,-2.74475,53.4528,-2.71443,2814,3230
WD24 4,WD18 7,5679,4774,15.9,905,62445,81450,2.17,Flats/Maisonettes,1993,4.0,0.51,51.6669,-0.39275,51.65289,-0.41509,2014,4190
RM20 3,RM20 4,4648,3908,15.9,740,49580,66600,1.37,Terraced,2012,3.0,1.06,51.47874,0.27854,51.47707,0.29861,1330,1542
CH46 2,CH44 5,2334,1962,15.9,372,30504,33480,3.0,Terraced,1958,1.8,1.0,53.41644,-3.09066,53.41539,-3.04651,966,1407
SE15 3,SE15 1,8041,6766,15.9,1275,87337,114750,2.04,Flats/Maisonettes,1958,2.1,0.65,51.46242,-0.05615,51.48086,-0.05779,4646,3041
NW7 2,NW4 1,6545,5506,15.9,1039,81042,93510,1.94,Flats/Maisonettes,1958,5.9,0.88,51.60921,-0.23469,51.59454,-0.219,2863,3256
CH66 2,CH66 1,3004,2530,15.8,474,39342,42660,2.84,Semi-Detached,1971,1.3,1.4,53.26372,-2.92341,53.28903,-2.93023,4041,2959
PR8 3,PR8 4,2962,2498,15.7,464,43152,41760,2.97,Semi-Detached,1958,1.9,0.94,53.60196,-3.03175,53.6256,-3.01102,4601,3996
SM6 8,SM6 0,5483,4620,15.7,863,59547,77670,0.94,Flats/Maisonettes,1958,4.7,0.68,51.3632,-0.14127,51.35737,-0.15137,4470,3099
L32 5,L33 0,1870,1576,15.7,294,20874,26460,1.24,Terraced,1993,2.0,0.9,53.47912,-2.89684,53.4823,-2.8793,431,591
SW1P 1,SW1V 2,12483,10522,15.7,1961,126484,176490,0.55,Flats/Maisonettes,1914,3.0,0.35,51.49525,-0.13811,51.49031,-0.13734,1402,3528
M3 5,M5 4,4152,3503,15.6,649,39264,58410,1.18,Flats/Maisonettes,2020,2.9,0.3,53.48267,-2.25473,53.47828,-2.27064,4203,7855
SW1W 9,SW3 6,19900,16803,15.6,3097,277181,278730,1.68,Flats/Maisonettes,1914,4.0,0.38,51.49389,-0.15063,51.48869,-0.17386,1512,1594
L21 8,L20 5,1536,1297,15.6,239,19239,21510,0.75,Terraced,1940,2.0,0.76,53.46498,-2.99614,53.45858,-2.99252,1753,1206
NG1 5,NG7 3,2298,1941,15.5,357,21063,32130,1.12,Flats/Maisonettes,2004,4.1,0.35,52.95503,-1.15757,52.95802,-1.17335,1227,3268
PL31 1,PL31 2,2902,2451,15.5,451,35403,40590,0.68,Terraced,1979,1.0,0.91,50.46537,-4.72475,50.47146,-4.72295,3265,3650
N21 3,EN1 2,6539,5529,15.4,1010,85850,90900,2.0,Flats/Maisonettes,1940,2.4,0.67,51.62986,-0.09786,51.64171,-0.07559,2506,2804
NW3 2,NW3 6,12230,10350,15.4,1880,123140,169200,1.42,Flats/Maisonettes,1940,2.7,0.31,51.55255,-0.16025,51.55188,-0.1812,5663,3182
L16 3,L14 6,3353,2840,15.3,513,43348,46170,0.83,Semi-Detached,1958,5.4,0.79,53.4038,-2.8825,53.41102,-2.87901,646,809
ME7 2,ME7 5,3614,3061,15.3,553,42304,49770,1.62,Terraced,1940,3.0,1.29,51.38449,0.56629,51.38258,0.54265,4375,3928
B10 9,B8 1,2079,1761,15.3,318,26871,28620,2.37,Terraced,1914,4.9,1.28,52.47059,-1.84439,52.49094,-1.85524,4180,2834
SM5 2,SM6 0,5455,4620,15.3,835,55945,75150,2.06,Flats/Maisonettes,1971,4.6,0.63,51.37365,-0.16616,51.35737,-0.15137,4877,3099
S12 3,S12 4,2902,2461,15.2,441,33957,39690,1.88,Semi-Detached,1958,0.0,0.45,53.34167,-1.41358,53.34743,-1.38757,3092,4427
EC1V 7,EC1Y 8,10494,8894,15.2,1600,100800,144000,0.9,Flats/Maisonettes,1999,0.0,0.47,51.52856,-0.10175,51.5234,-0.09159,1368,1020
SW9 8,SW9 7,8306,7041,15.2,1265,81592,113850,0.69,Flats/Maisonettes,1979,3.1,0.37,51.46184,-0.1102,51.46801,-0.10832,3059,3760
N16 6,N15 6,6500,5515,15.2,985,65010,88650,1.01,Flats/Maisonettes,1914,3.7,0.51,51.5696,-0.06789,51.57766,-0.07499,4716,4196
EN2 6,EN1 1,6398,5424,15.2,974,67693,87660,1.32,Flats/Maisonettes,1958,4.5,0.42,51.65156,-0.08438,51.64635,-0.06695,1875,5034
SW11 6,SW18 3,10999,9326,15.2,1673,157262,150570,1.75,Flats/Maisonettes,1914,5.2,0.84,51.4556,-0.16199,51.44534,-0.18171,4237,5500
BB1 7,BB1 6,1764,1498,15.1,266,25935,23940,0.62,Terraced,1914,4.1,0.91,53.75417,-2.48301,53.75666,-2.47488,850,1175
WF6 2,WF6 1,2630,2234,15.1,396,30492,35640,1.36,Semi-Detached,1971,1.2,0.94,53.70552,-1.42098,53.69415,-1.41339,4466,4718
W9 2,W10 4,10718,9102,15.1,1616,103424,145440,1.32,Flats/Maisonettes,1914,4.1,0.48,51.52498,-0.19242,51.52944,-0.21048,5679,3629
NW11 7,NW2 2,8238,6991,15.1,1247,86978,112230,1.51,Flats/Maisonettes,1940,0.5,0.56,51.57475,-0.19246,51.56242,-0.20213,2668,3603
WR1 3,WR1 1,3131,2660,15.0,471,28731,42390,0.39,Flats/Maisonettes,1958,3.5,0.69,52.1988,-2.22707,52.19991,-2.22163,1651,2759
1 pricey_sector twin_sector pricey_psqm twin_psqm gap_pct gap_per_sqm gap_on_avg_home gap_on_90sqm dist_km dominant_type build_year good_secondary station_km pricey_lat pricey_lon twin_lat twin_lon pricey_n twin_n
2 W1U 3 EC1N 7 16948 9362 44.8 7586 504469 682740 2.89 Flats/Maisonettes 1940 1.3 0.41 51.51712 -0.15271 51.52057 -0.11049 302 747
3 WC2R 1 SW1V 1 19997 11119 44.4 8878 665850 799020 2.82 Flats/Maisonettes 2017 2.0 0.21 51.51228 -0.11525 51.49297 -0.14234 316 1408
4 L8 7 L7 0 2757 1541 44.1 1216 78432 109440 2.36 Flats/Maisonettes 1914 2.3 1.04 53.39791 -2.96459 53.41174 -2.93813 2054 2208
5 L3 2 L5 5 1642 920 44.0 722 47291 64980 1.61 Flats/Maisonettes 2004 1.5 0.47 53.41155 -2.98583 53.42544 -2.97852 1341 706
6 S2 4 S3 9 2468 1402 43.2 1066 73554 95940 2.82 Flats/Maisonettes 1986 2.6 0.72 53.36993 -1.46978 53.39521 -1.46478 2423 1778
7 W1U 4 NW1 4 24238 13766 43.2 10472 759220 942480 0.97 Flats/Maisonettes 1940 1.0 0.44 51.51958 -0.15295 51.52803 -0.14886 984 1340
8 WC2A 2 EC2A 2 25482 14576 42.8 10906 834309 981540 2.3 Flats/Maisonettes 2019 2.0 0.42 51.51496 -0.11456 51.52119 -0.08217 254 772
9 M40 5 M9 4 2812 1626 42.2 1186 91915 106740 1.18 Terraced 1958 2.6 0.72 53.51372 -2.18713 53.51214 -2.20436 1632 3530
10 W11 2 NW1 6 19262 11154 42.1 8108 482426 729720 2.94 Flats/Maisonettes 1890 4.0 0.53 51.51407 -0.20373 51.5237 -0.16342 4082 3312
11 N10 3 N12 0 9590 5554 42.1 4036 296646 363240 2.97 Flats/Maisonettes 1914 3.9 1.16 51.58839 -0.14404 51.60872 -0.17254 3984 3282
12 W1U 1 SW1V 1 19096 11119 41.8 7977 506539 717930 2.54 Flats/Maisonettes 1986 2.0 0.17 51.51535 -0.15064 51.49297 -0.14234 416 1408
13 SW1X 8 SW7 2 26735 15611 41.6 11124 1301508 1001160 1.31 Flats/Maisonettes 1890 3.7 0.43 51.49787 -0.15472 51.49729 -0.17406 1410 1126
14 W1U 5 WC1E 6 20074 11756 41.4 8318 486603 748620 1.34 Flats/Maisonettes 1940 1.0 0.26 51.5214 -0.15392 51.52242 -0.13419 796 424
15 SW3 1 SW5 0 22813 13361 41.4 9452 652188 850680 1.82 Flats/Maisonettes 1914 3.0 0.39 51.49847 -0.16393 51.4925 -0.18898 1104 4266
16 HD4 5 HD1 3 2219 1301 41.4 918 65637 82620 1.79 Terraced 1940 5.0 1.12 53.63444 -1.81642 53.63808 -1.79069 4888 3052
17 W11 3 W12 9 15942 9343 41.4 6599 458630 593910 2.87 Flats/Maisonettes 1914 3.5 0.26 51.50961 -0.20173 51.50318 -0.24269 2643 5847
18 W1J 7 SW7 3 32986 19392 41.2 13594 1077324 1223460 2.6 Flats/Maisonettes 1914 2.0 0.3 51.5059 -0.14756 51.49122 -0.1775 724 2581
19 EC3N 1 EC1V 8 15538 9196 40.8 6342 383691 570780 2.25 Flats/Maisonettes 2021 0.0 0.15 51.51271 -0.07495 51.52808 -0.09657 162 1192
20 SE1 8 SE1 5 11517 6831 40.7 4686 285846 421740 2.8 Flats/Maisonettes 1998 4.8 0.26 51.50309 -0.10794 51.48984 -0.07272 2354 4478
21 EC1A 7 E1W 1 16062 9651 39.9 6411 413509 576990 2.52 Flats/Maisonettes 1999 0.0 0.2 51.51834 -0.09869 51.50581 -0.06777 574 1353
22 WC1B 5 EC1N 7 15504 9362 39.6 6142 411514 552780 0.98 Flats/Maisonettes 1914 1.4 0.26 51.52107 -0.12492 51.52057 -0.11049 168 747
23 EC3R 6 EC1A 9 19039 11553 39.3 7486 426702 673740 1.82 Flats/Maisonettes 2018 1.0 0.31 51.50832 -0.08023 51.5181 -0.10183 325 367
24 S2 5 S3 9 2308 1402 39.3 906 63873 81540 2.0 Flats/Maisonettes 1958 1.2 0.51 53.37989 -1.44909 53.39521 -1.46478 3807 1778
25 W1K 5 W2 3 19600 11893 39.3 7707 454713 693630 2.17 Flats/Maisonettes 1890 2.0 0.11 51.51346 -0.14947 51.513 -0.18143 233 5113
26 W1S 1 WC2E 7 24324 14910 38.7 9414 663687 847260 1.53 Flats/Maisonettes 2012 2.0 0.17 51.51339 -0.14384 51.51193 -0.1214 274 337
27 W13 0 UB1 3 8242 5063 38.6 3179 220940 286110 2.55 Flats/Maisonettes 1971 3.0 0.41 51.51603 -0.3258 51.51377 -0.36322 4716 2761
28 L9 9 L4 6 2410 1482 38.5 928 76096 83520 2.76 Terraced 1940 1.2 0.52 53.46789 -2.94392 53.44478 -2.95911 1898 1185
29 W1T 6 EC1N 7 15122 9362 38.1 5760 322560 518400 2.07 Flats/Maisonettes 1914 1.6 0.25 51.52266 -0.14074 51.52057 -0.11049 562 747
30 SW3 2 SW10 9 21360 13240 38.0 8120 596820 730800 1.66 Flats/Maisonettes 1890 3.5 0.44 51.49428 -0.16481 51.48648 -0.18567 2546 5825
31 WC2E 9 SW1V 2 16982 10522 38.0 6460 381140 581400 2.55 Flats/Maisonettes 1940 2.0 0.14 51.51211 -0.1249 51.49031 -0.13734 406 3528
32 SW10 0 SW11 3 12094 7514 37.9 4580 329760 412200 1.08 Flats/Maisonettes 1971 6.1 0.65 51.48064 -0.18125 51.47181 -0.17455 5124 7592
33 N2 9 N12 0 8942 5554 37.9 3388 255794 304920 1.92 Flats/Maisonettes 1940 3.5 0.73 51.59254 -0.16238 51.60872 -0.17254 2927 3282
34 B11 3 B11 1 2787 1741 37.5 1046 82111 94140 1.99 Terraced 1914 2.8 0.86 52.44872 -1.84954 52.46166 -1.86989 4748 3202
35 W1J 8 SW1A 2 27270 17088 37.3 10182 1089474 916380 1.31 Flats/Maisonettes 2000 2.0 0.18 51.50769 -0.1444 51.50557 -0.12549 295 261
36 W1H 6 NW1 4 21897 13766 37.1 8131 719593 731790 1.38 Flats/Maisonettes 1940 1.3 0.45 51.5162 -0.15546 51.52803 -0.14886 233 1340
37 B5 6 B16 8 4080 2588 36.6 1492 90266 134280 1.78 Flats/Maisonettes 2022 3.0 0.58 52.47243 -1.89503 52.47603 -1.92066 2784 5647
38 W1K 3 SW7 2 24608 15611 36.6 8997 1025658 809730 2.35 Flats/Maisonettes 1914 2.0 0.34 51.5108 -0.14739 51.49729 -0.17406 263 1126
39 W11 1 W12 8 13227 8399 36.5 4828 272782 434520 2.07 Flats/Maisonettes 1940 4.3 0.27 51.51697 -0.20643 51.5037 -0.22797 4436 4802
40 W1J 5 W8 5 26103 16629 36.3 9474 916609 852660 2.89 Flats/Maisonettes 1914 2.0 0.4 51.50796 -0.14833 51.49913 -0.1884 503 2829
41 E7 0 IG1 3 7737 4926 36.3 2811 195364 252990 2.75 Flats/Maisonettes 1914 2.1 0.38 51.552 0.02729 51.56639 0.06025 4114 5219
42 BB1 8 BB1 6 2318 1498 35.4 820 75030 73800 0.97 Terraced 1958 3.0 1.48 53.76182 -2.48651 53.75666 -2.47488 3550 1175
43 M4 6 M3 1 4332 2797 35.4 1535 103612 138150 1.56 Flats/Maisonettes 2016 3.2 0.46 53.48377 -2.2243 53.48836 -2.24596 3944 1313
44 N10 2 N12 0 8571 5554 35.2 3017 209681 271530 2.29 Flats/Maisonettes 1940 3.7 1.29 51.59958 -0.14231 51.60872 -0.17254 3792 3282
45 E2 7 E1 4 10016 6518 34.9 3498 227370 314820 1.87 Flats/Maisonettes 1958 4.9 0.41 51.52816 -0.07104 51.52181 -0.04552 3508 4193
46 W11 4 W6 0 15604 10152 34.9 5452 346202 490680 1.98 Flats/Maisonettes 1958 3.6 0.35 51.50884 -0.21361 51.49621 -0.23419 4044 6681
47 NW10 1 NW10 0 7284 4776 34.4 2508 169290 225720 1.21 Flats/Maisonettes 1940 2.1 0.37 51.55484 -0.2425 51.55677 -0.25998 3163 3308
48 NW1 8 N7 9 12470 8179 34.4 4291 276769 386190 2.09 Flats/Maisonettes 1958 2.8 0.32 51.54247 -0.15026 51.54901 -0.12137 4113 4630
49 M19 3 M18 8 2911 1916 34.2 995 74127 89550 2.51 Terraced 1914 3.3 0.61 53.44574 -2.18452 53.46589 -2.16737 4046 4415
50 SW1X 0 SW7 5 23489 15480 34.1 8009 784882 720810 1.31 Flats/Maisonettes 1890 3.1 0.45 51.49639 -0.16144 51.49667 -0.18078 1606 3238
51 W1K 6 W8 4 23400 15425 34.1 7975 626037 717750 2.83 Flats/Maisonettes 1914 2.0 0.29 51.5128 -0.15349 51.50498 -0.19312 562 2309
52 NW3 1 NW6 3 16761 11048 34.1 5713 437044 514170 1.65 Flats/Maisonettes 1914 1.2 0.33 51.5575 -0.17458 51.54407 -0.18523 2098 4073
53 WC1A 2 EC1N 7 14183 9362 34.0 4821 310954 433890 0.94 Flats/Maisonettes 1914 1.7 0.28 51.51831 -0.12387 51.52057 -0.11049 234 747
54 E9 7 E3 4 9732 6430 33.9 3302 224536 297180 2.4 Flats/Maisonettes 1958 5.6 0.6 51.53924 -0.04918 51.52221 -0.02733 4570 6823
55 BD21 1 BD21 2 1419 938 33.9 481 37999 43290 1.01 Terraced 1914 1.7 0.7 53.85976 -1.91701 53.86885 -1.9178 2292 2303
56 SW10 9 W14 9 13240 8770 33.8 4470 270435 402300 1.55 Flats/Maisonettes 1914 4.5 0.63 51.48648 -0.18567 51.48883 -0.20815 5825 7258
57 L22 5 L21 3 2224 1472 33.8 752 48880 67680 1.65 Flats/Maisonettes 1958 0.8 0.28 53.47329 -3.02717 53.46477 -3.00727 821 259
58 EC2A 2 E1W 1 14576 9651 33.8 4925 354600 443250 1.96 Flats/Maisonettes 2021 0.8 0.33 51.52119 -0.08217 51.50581 -0.06777 772 1353
59 L20 7 L5 7 1728 1146 33.7 582 42195 52380 1.59 Terraced 1979 2.0 0.39 53.44391 -2.99118 53.42998 -2.98541 1243 848
60 B6 6 B21 9 2148 1428 33.5 720 63000 64800 2.92 Terraced 1914 3.9 0.74 52.50775 -1.89127 52.50746 -1.93433 2834 3409
61 B15 1 B16 8 3888 2588 33.4 1300 74100 117000 0.49 Flats/Maisonettes 2018 2.2 0.17 52.47397 -1.9142 52.47603 -1.92066 2987 5647
62 W1F 9 SW1V 2 15806 10522 33.4 5284 272126 475560 2.44 Flats/Maisonettes 1940 2.0 0.32 51.51241 -0.13704 51.49031 -0.13734 340 3528
63 E17 6 N15 6 8263 5515 33.3 2748 186864 247320 2.95 Flats/Maisonettes 1958 2.9 0.49 51.58862 -0.03531 51.57766 -0.07499 8292 4196
64 N5 1 N7 7 10770 7185 33.3 3585 227647 322650 0.69 Flats/Maisonettes 1958 3.5 0.31 51.55439 -0.10301 51.55818 -0.11109 5686 3539
65 W1W 5 EC1R 4 14857 9931 33.2 4926 310338 443340 2.41 Flats/Maisonettes 1971 1.0 0.19 51.52234 -0.14327 51.52666 -0.10846 790 634
66 W1H 7 NW1 5 15131 10126 33.1 5005 402902 450450 0.69 Flats/Maisonettes 1914 1.4 0.33 51.51532 -0.15973 51.52135 -0.16228 1094 1593
67 SW11 4 SW11 3 11216 7514 33.0 3702 264693 333180 1.24 Flats/Maisonettes 1971 5.4 0.72 51.47665 -0.15803 51.47181 -0.17455 5268 7592
68 W7 3 UB1 3 7546 5063 32.9 2483 175051 223470 1.66 Flats/Maisonettes 1958 3.8 0.56 51.51276 -0.33883 51.51377 -0.36322 4515 2761
69 B68 8 B67 7 3056 2053 32.8 1003 79237 90270 2.09 Terraced 1958 3.1 0.84 52.48621 -2.00783 52.49476 -1.98036 3700 2852
70 W1F 7 SW1V 1 16512 11119 32.7 5393 320883 485370 2.28 Flats/Maisonettes 1971 2.0 0.27 51.51336 -0.13775 51.49297 -0.14234 382 1408
71 L32 0 L33 9 2106 1422 32.5 684 53010 61560 2.16 Terraced 1958 1.9 0.42 53.48356 -2.90176 53.49051 -2.87196 1371 821
72 L7 7 L3 2 2432 1642 32.5 790 46215 71100 1.87 Flats/Maisonettes 2004 1.2 1.01 53.40015 -2.96557 53.41155 -2.98583 910 1341
73 L13 1 L7 4 2015 1363 32.4 652 42054 58680 2.09 Terraced 1940 4.3 0.4 53.40739 -2.91819 53.39725 -2.94425 610 401
74 W1G 9 WC1E 7 18377 12426 32.4 5951 499884 535590 1.07 Flats/Maisonettes 1940 1.5 0.42 51.51814 -0.14732 51.52141 -0.13245 426 386
75 L8 3 L8 0 2096 1420 32.3 676 42926 60840 0.81 Flats/Maisonettes 1914 5.0 1.28 53.38744 -2.95519 53.39354 -2.94861 2200 3137
76 E2 8 E1 4 9634 6518 32.3 3116 200982 280440 2.17 Flats/Maisonettes 1995 6.8 0.36 51.53258 -0.07231 51.52181 -0.04552 3891 4193
77 N1 1 NW1 9 13560 9213 32.1 4347 269514 391230 1.64 Flats/Maisonettes 1914 2.3 0.41 51.54168 -0.10954 51.544 -0.13333 5369 4459
78 W3 6 NW10 8 7977 5414 32.1 2563 144809 230670 2.88 Flats/Maisonettes 1999 2.8 0.28 51.51541 -0.26423 51.54116 -0.25786 8573 4510
79 NW1 4 EC1N 7 13766 9362 32.0 4404 295068 396360 2.73 Flats/Maisonettes 1940 1.9 0.42 51.52803 -0.14886 51.52057 -0.11049 1340 747
80 L1 5 L3 2 2416 1642 32.0 774 47988 69660 1.28 Flats/Maisonettes 2004 2.8 0.51 53.40039 -2.98092 53.41155 -2.98583 1724 1341
81 N1 4 E2 0 10333 7036 31.9 3297 212656 296730 2.99 Flats/Maisonettes 1940 7.1 0.41 51.54667 -0.0819 51.52807 -0.04998 3830 4330
82 SE1 9 SE1 3 13534 9218 31.9 4316 308594 388440 1.69 Flats/Maisonettes 2009 3.4 0.4 51.50702 -0.10046 51.49842 -0.07986 2358 4683
83 SW7 3 SW10 9 19392 13240 31.7 6152 453710 553680 0.76 Flats/Maisonettes 1890 3.3 0.41 51.49122 -0.1775 51.48648 -0.18567 2581 5825
84 W1K 2 SW1X 0 34362 23489 31.6 10873 1293887 978570 1.62 Flats/Maisonettes 1914 2.0 0.5 51.50983 -0.15202 51.49639 -0.16144 591 1606
85 E8 4 E2 0 10272 7036 31.5 3236 207104 291240 1.79 Flats/Maisonettes 1979 7.2 0.34 51.53852 -0.07013 51.52807 -0.04998 4554 4330
86 SW12 9 SW16 2 9262 6351 31.4 2911 190670 261990 2.33 Flats/Maisonettes 1914 5.7 0.38 51.44479 -0.1475 51.43068 -0.12196 5519 7620
87 BR3 3 CR0 7 7153 4910 31.4 2243 214206 201870 2.02 Terraced 1940 7.8 0.73 51.3949 -0.03019 51.38447 -0.05469 4514 5143
88 NE8 3 NE6 2 1812 1248 31.1 564 35814 50760 2.38 Flats/Maisonettes 1986 1.5 0.5 54.95623 -1.58964 54.97313 -1.568 3727 4445
89 KT13 8 KT15 2 6738 4663 30.8 2075 150437 186750 1.98 Flats/Maisonettes 1971 3.7 1.25 51.37281 -0.45766 51.3727 -0.48684 3317 4145
90 W2 5 W10 4 13145 9102 30.8 4043 254709 363870 1.73 Flats/Maisonettes 1914 3.6 0.38 51.51783 -0.19337 51.52944 -0.21048 5062 3629
91 WC1A 1 W1T 1 23988 16609 30.8 7379 472256 664110 0.48 Flats/Maisonettes 2016 2.0 0.21 51.517 -0.1274 51.51779 -0.1344 241 584
92 EC1V 2 EC1V 8 13244 9196 30.6 4048 267168 364320 0.11 Flats/Maisonettes 2020 0.1 0.55 51.52885 -0.09564 51.52808 -0.09657 1056 1192
93 W2 4 W1H 5 15000 10428 30.5 4572 290322 411480 2.0 Flats/Maisonettes 1914 2.1 0.28 51.51248 -0.19214 51.5169 -0.16353 5441 1194
94 N15 5 N17 0 7488 5207 30.5 2281 141422 205290 2.58 Flats/Maisonettes 1940 2.6 0.47 51.58315 -0.08094 51.60306 -0.06115 3757 4447
95 W1K 4 W1G 6 22808 15856 30.5 6952 590920 625680 1.05 Flats/Maisonettes 1940 2.0 0.23 51.51197 -0.14832 51.52144 -0.15008 229 588
96 NW2 1 NW2 7 7494 5224 30.3 2270 164575 204300 1.95 Flats/Maisonettes 1958 1.4 0.52 51.56613 -0.21393 51.56404 -0.24245 3610 3368
97 SE4 2 SE6 2 7845 5474 30.2 2371 156486 213390 2.85 Flats/Maisonettes 1914 1.0 0.39 51.46145 -0.04025 51.44106 -0.01466 5138 5519
98 B18 4 B21 9 2042 1428 30.1 614 48506 55260 1.65 Terraced 1914 2.7 0.69 52.49286 -1.93966 52.50746 -1.93433 1927 3409
99 L8 8 L7 4 1950 1363 30.1 587 40796 52830 1.55 Terraced 1940 3.8 1.13 53.39004 -2.96384 53.39725 -2.94425 1868 401
100 NW10 5 NW10 2 10106 7078 30.0 3028 199848 272520 1.75 Flats/Maisonettes 1914 3.0 0.4 51.5329 -0.2278 51.54767 -0.23691 5023 3505
101 B19 3 B7 4 3417 2397 29.9 1020 69360 91800 1.88 Flats/Maisonettes 1958 2.0 0.47 52.49095 -1.90381 52.48861 -1.87637 1584 1469
102 W1U 7 WC1N 2 18854 13224 29.9 5630 375802 506700 2.67 Flats/Maisonettes 1914 1.0 0.44 51.51885 -0.15472 51.52321 -0.11611 216 514
103 L15 4 L7 4 1942 1363 29.8 579 39661 52110 1.33 Terraced 1914 3.2 0.52 53.40136 -2.9259 53.39725 -2.94425 2027 401
104 CH62 8 CH62 9 3363 2362 29.8 1001 81831 90090 0.74 Semi-Detached 1958 4.0 0.91 53.31456 -2.97287 53.30801 -2.97057 1775 1274
105 SE1 0 SE1 5 9712 6831 29.7 2881 167098 259290 2.32 Flats/Maisonettes 2005 5.6 0.41 51.5023 -0.10018 51.48984 -0.07272 2736 4478
106 SE28 8 DA18 4 4850 3409 29.7 1441 104112 129690 1.72 Terraced 1993 2.8 1.39 51.5066 0.11805 51.49419 0.1333 5033 1063
107 BD21 2 BD21 3 938 660 29.6 278 24186 25020 0.88 Terraced 1914 2.0 1.07 53.86885 -1.9178 53.87113 -1.90541 2303 1377
108 W7 1 UB1 3 7184 5063 29.5 2121 148470 190890 2.06 Flats/Maisonettes 1958 3.8 0.36 51.51927 -0.3343 51.51377 -0.36322 3765 2761
109 L16 7 L14 6 4026 2840 29.5 1186 117414 106740 1.88 Semi-Detached 1940 5.1 1.22 53.39586 -2.89157 53.41102 -2.87901 500 809
110 W1G 8 WC1N 2 18737 13224 29.4 5513 395557 496170 2.31 Flats/Maisonettes 1914 1.0 0.49 51.51913 -0.14944 51.52321 -0.11611 707 514
111 L6 1 L5 8 2573 1818 29.3 755 47376 67950 1.72 Flats/Maisonettes 2009 2.0 1.1 53.41331 -2.96373 53.42093 -2.98578 1468 800
112 W12 7 W12 0 11848 8378 29.3 3470 225550 312300 0.73 Flats/Maisonettes 1958 2.7 0.37 51.51063 -0.22867 51.51319 -0.23865 7409 5147
113 NW8 0 W2 2 13358 9441 29.3 3917 272231 352530 2.73 Flats/Maisonettes 1958 3.0 0.38 51.53784 -0.18192 51.51475 -0.1679 2625 4150
114 W1K 1 W1G 7 28754 20358 29.2 8396 812313 755640 1.52 Flats/Maisonettes 1940 2.0 0.49 51.50754 -0.15204 51.52096 -0.14696 288 280
115 WC1N 2 EC1N 7 13224 9362 29.2 3862 230754 347580 0.48 Flats/Maisonettes 1914 0.9 0.53 51.52321 -0.11611 51.52057 -0.11049 514 747
116 BB11 2 BB11 3 1455 1032 29.1 423 32042 38070 0.74 Terraced 1914 0.1 0.71 53.78136 -2.24549 53.78377 -2.23529 1925 2642
117 E11 3 E12 5 7858 5570 29.1 2288 156728 205920 2.78 Flats/Maisonettes 1940 2.9 0.65 51.56154 0.01383 51.55444 0.05315 5821 5288
118 L3 5 L3 2 2315 1642 29.1 673 38697 60570 0.98 Flats/Maisonettes 2004 1.5 0.34 53.40732 -2.97319 53.41155 -2.98583 1300 1341
119 IG1 2 IG11 8 5300 3768 28.9 1532 98814 137880 1.16 Flats/Maisonettes 1986 2.6 0.81 51.5513 0.07504 51.5409 0.07766 7114 4525
120 E8 3 E1 4 9141 6518 28.7 2623 174429 236070 2.67 Flats/Maisonettes 1999 6.7 0.32 51.54269 -0.0654 51.52181 -0.04552 4980 4193
121 E17 9 E10 7 9498 6778 28.6 2720 175440 244800 1.56 Flats/Maisonettes 1914 2.3 0.52 51.5812 -0.01156 51.56938 -0.02405 5616 4807
122 B20 3 B21 9 2000 1428 28.6 572 49764 51480 2.0 Terraced 1914 2.4 0.76 52.51116 -1.90551 52.50746 -1.93433 4683 3409
123 SW1Y 6 WC2H 9 19050 13619 28.5 5431 353015 488790 1.02 Flats/Maisonettes 1914 2.0 0.26 51.50775 -0.13677 51.51405 -0.12585 343 726
124 EC2Y 8 E1W 1 13481 9651 28.4 3830 268100 344700 2.4 Flats/Maisonettes 1971 0.0 0.2 51.52001 -0.09446 51.50581 -0.06777 2082 1353
125 L15 1 L7 4 1902 1363 28.3 539 35843 48510 0.76 Terraced 1914 4.0 0.84 53.39986 -2.93392 53.39725 -2.94425 1301 401
126 NW6 2 NW2 5 9924 7120 28.3 2804 165436 252360 2.23 Flats/Maisonettes 1940 1.5 0.27 51.54603 -0.19676 51.54856 -0.22936 3822 4370
127 NW3 6 NW11 8 10350 7423 28.3 2927 191718 263430 2.42 Flats/Maisonettes 1914 1.8 0.22 51.55188 -0.1812 51.57032 -0.2005 3182 2951
128 NE1 2 NE8 3 2524 1812 28.2 712 41296 64080 1.88 Flats/Maisonettes 2003 2.6 0.41 54.97209 -1.59959 54.95623 -1.58964 1713 3727
129 WC1R 4 EC1Y 8 12388 8894 28.2 3494 204399 314460 1.82 Flats/Maisonettes 1958 1.0 0.28 51.51938 -0.11755 51.5234 -0.09159 362 1020
130 B8 3 B8 1 2451 1761 28.2 690 55200 62100 0.95 Terraced 1914 3.9 1.07 52.48814 -1.84204 52.49094 -1.85524 4026 2834
131 TW12 2 TW16 5 8001 5764 28.0 2237 189585 201330 2.42 Flats/Maisonettes 1958 1.5 0.51 51.41708 -0.37008 51.41307 -0.4051 3364 2205
132 W4 1 W3 0 10328 7440 28.0 2888 254144 259920 2.74 Flats/Maisonettes 1914 2.3 0.41 51.49721 -0.25518 51.5193 -0.2734 3571 2316
133 B1 3 B16 8 3590 2588 27.9 1002 60120 90180 1.04 Flats/Maisonettes 2017 2.0 0.52 52.48455 -1.91433 52.47603 -1.92066 2657 5647
134 W1W 7 SW1P 4 15530 11191 27.9 4339 269018 390510 2.94 Flats/Maisonettes 1979 1.9 0.35 51.51856 -0.14057 51.49278 -0.1299 602 3443
135 WC1N 1 WC1H 8 12880 9294 27.8 3586 188265 322740 0.42 Flats/Maisonettes 1940 2.3 0.21 51.52432 -0.12359 51.5279 -0.12161 1191 815
136 SW8 1 SE5 0 9368 6774 27.7 2594 164719 233460 1.7 Flats/Maisonettes 1960 4.3 0.37 51.48059 -0.12115 51.47961 -0.09617 6064 3441
137 E20 1 E15 2 8586 6208 27.7 2378 160515 214020 0.88 Flats/Maisonettes 2017 3.9 0.28 51.54601 -0.00917 51.53827 -0.00621 6585 6157
138 EC1Y 1 EC1V 9 11663 8443 27.6 3220 214130 289800 0.26 Flats/Maisonettes 2010 1.0 0.1 51.52492 -0.08698 51.52596 -0.09038 155 960
139 N15 3 N17 0 7178 5207 27.5 1971 116289 177390 3.0 Flats/Maisonettes 1914 3.4 0.67 51.58598 -0.09545 51.60306 -0.06115 4481 4447
140 W1T 1 SW1Y 4 16609 12054 27.4 4555 247108 409950 1.01 Flats/Maisonettes 1993 2.0 0.2 51.51779 -0.1344 51.50869 -0.1327 584 176
141 N7 9 NW1 3 8179 5948 27.3 2231 142784 200790 2.66 Flats/Maisonettes 1971 3.7 0.39 51.54901 -0.12137 51.5281 -0.14076 4630 1949
142 L22 9 L22 4 2838 2067 27.2 771 74208 69390 0.51 Terraced 1914 1.4 0.47 53.47895 -3.02768 53.47998 -3.02039 567 716
143 HU1 2 HU2 8 2320 1690 27.2 630 34335 56700 0.77 Flats/Maisonettes 1979 1.1 0.49 53.74094 -0.34259 53.74792 -0.34183 1177 1146
144 SE1 3 SE16 3 9218 6721 27.1 2497 157311 224730 1.33 Flats/Maisonettes 1999 4.8 0.77 51.49842 -0.07986 51.49091 -0.06454 4683 4240
145 LE1 6 LE1 3 2159 1576 27.0 583 26526 52470 0.95 Flats/Maisonettes 2004 5.4 0.42 52.63073 -1.13046 52.63932 -1.13017 2093 1426
146 BB2 3 BB1 1 1608 1174 27.0 434 35154 39060 1.5 Terraced 1971 3.5 1.42 53.73342 -2.47718 53.74438 -2.4641 4798 3580
147 N1 2 N7 6 11472 8394 26.8 3078 192375 277020 2.21 Flats/Maisonettes 1940 3.2 0.32 51.54325 -0.09732 51.55896 -0.11742 4823 3705
148 L35 3 L34 6 2438 1786 26.7 652 48574 58680 2.38 Terraced 1971 0.8 0.61 53.41043 -2.7958 53.43186 -2.79945 3080 858
149 B33 0 B37 5 2704 1984 26.6 720 53640 64800 1.46 Terraced 1958 4.1 1.06 52.47541 -1.77066 52.47855 -1.74975 4058 3580
150 W1W 6 EC1N 7 12734 9362 26.5 3372 180402 303480 2.11 Flats/Maisonettes 1914 1.0 0.41 51.52013 -0.14163 51.52057 -0.11049 946 747
151 E2 6 E1 4 8850 6518 26.4 2332 148082 209880 1.38 Flats/Maisonettes 1986 5.7 0.41 51.52646 -0.06445 51.52181 -0.04552 3414 4193
152 SW17 8 SW16 2 8612 6351 26.3 2261 151487 203490 2.36 Flats/Maisonettes 1914 5.9 0.49 51.43266 -0.1565 51.43068 -0.12196 7040 7620
153 LE1 4 LE1 3 2136 1576 26.2 560 26600 50400 0.58 Flats/Maisonettes 2018 4.5 1.17 52.63787 -1.13834 52.63932 -1.13017 1436 1426
154 B15 2 B16 8 3508 2588 26.2 920 57960 82800 1.21 Flats/Maisonettes 2004 2.5 0.59 52.46686 -1.91097 52.47603 -1.92066 4678 5647
155 NE31 2 NE32 3 2197 1629 25.9 568 43452 51120 2.31 Semi-Detached 1958 1.7 1.14 54.96595 -1.50844 54.97725 -1.47983 4787 2350
156 W1T 2 EC4V 5 15137 11220 25.9 3917 230123 352530 2.37 Flats/Maisonettes 1914 1.5 0.15 51.51933 -0.13475 51.51284 -0.10141 334 208
157 SK6 6 SK6 3 4485 3329 25.8 1156 99416 104040 2.56 Semi-Detached 1958 1.0 0.5 53.3973 -2.07179 53.41157 -2.1014 2365 1915
158 SE1 1 SE16 3 9062 6721 25.8 2341 145142 210690 2.34 Flats/Maisonettes 1999 4.3 0.23 51.50157 -0.09427 51.49091 -0.06454 1565 4240
159 SW3 3 SW10 9 17851 13240 25.8 4611 262827 414990 1.42 Flats/Maisonettes 1940 4.1 0.55 51.49144 -0.1664 51.48648 -0.18567 3413 5825
160 E12 5 IG1 1 5570 4137 25.7 1433 99593 128970 1.98 Flats/Maisonettes 1940 1.6 0.73 51.55444 0.05315 51.55757 0.0819 5288 4839
161 WC1X 0 EC1A 4 13653 10144 25.7 3509 236857 315810 1.29 Flats/Maisonettes 2012 0.2 0.62 51.52581 -0.11375 51.51977 -0.09756 1688 188
162 B33 9 B37 5 2667 1984 25.6 683 49176 61470 2.98 Terraced 1958 3.0 0.74 52.48615 -1.79185 52.47855 -1.74975 3962 3580
163 W1B 1 W1G 9 24699 18377 25.6 6322 678034 568980 0.43 Flats/Maisonettes 1958 1.1 0.17 51.52197 -0.14618 51.51814 -0.14732 466 426
164 SW17 9 CR4 2 8157 6067 25.6 2090 145777 188100 1.15 Flats/Maisonettes 1914 5.0 0.44 51.42261 -0.16219 51.41288 -0.15632 6187 4538
165 EC1Y 0 EC1Y 8 11961 8894 25.6 3067 181719 276030 0.3 Flats/Maisonettes 1971 0.0 0.29 51.52254 -0.09582 51.5234 -0.09159 686 1020
166 IG8 7 IG6 2 7148 5325 25.5 1823 143105 164070 2.98 Terraced 1958 2.8 0.58 51.60562 0.03933 51.59914 0.08194 2965 4423
167 LS28 5 LS28 6 3598 2682 25.5 916 68242 82440 1.38 Terraced 1958 1.6 1.24 53.81629 -1.67775 53.80707 -1.66403 4799 1534
168 WC2E 7 SW1V 1 14910 11119 25.4 3791 242624 341190 2.53 Flats/Maisonettes 1986 2.0 0.26 51.51193 -0.1214 51.49297 -0.14234 337 1408
169 DN35 7 DN32 9 1278 954 25.4 324 27864 29160 1.8 Terraced 1914 1.2 0.9 53.56767 -0.04891 53.56012 -0.07242 4576 3714
170 SE22 9 SE23 1 10024 7474 25.4 2550 178500 229500 2.36 Flats/Maisonettes 1914 4.6 0.82 51.4571 -0.0718 51.446 -0.04209 3946 3974
171 N16 0 E5 9 10653 7944 25.4 2709 174730 243810 1.72 Flats/Maisonettes 1940 5.9 0.64 51.56195 -0.07997 51.56528 -0.0553 3007 5763
172 CH43 7 CH49 8 2680 2001 25.3 679 53301 61110 2.7 Terraced 1979 4.2 0.84 53.39925 -3.07298 53.37667 -3.08801 2619 1101
173 CH1 2 CH1 3 3642 2720 25.3 922 59008 82980 0.87 Flats/Maisonettes 1994 2.7 1.23 53.19006 -2.89451 53.19535 -2.88502 842 2725
174 N1C 4 N1 7 14255 10642 25.3 3613 251103 325170 2.36 Flats/Maisonettes 2018 2.5 0.6 51.53756 -0.12621 51.5324 -0.09254 1949 5352
175 NW2 2 NW2 7 6991 5224 25.3 1767 122806 159030 2.74 Flats/Maisonettes 1958 0.9 0.82 51.56242 -0.20213 51.56404 -0.24245 3603 3368
176 SW15 6 SW19 6 8335 6233 25.2 2102 145038 189180 1.89 Flats/Maisonettes 1958 4.0 0.64 51.4602 -0.22549 51.44413 -0.21583 4327 4199
177 HA0 4 NW10 0 6382 4776 25.2 1606 114026 144540 2.84 Flats/Maisonettes 1940 3.7 0.41 51.54606 -0.29796 51.55677 -0.25998 3934 3308
178 NE22 7 NE22 5 2220 1662 25.1 558 41292 50220 1.25 Semi-Detached 1958 2.8 0.9 55.1417 -1.56867 55.13372 -1.58167 1369 3178
179 B13 8 B13 9 3557 2669 25.0 888 59940 79920 1.35 Flats/Maisonettes 1958 3.3 0.65 52.44751 -1.89353 52.44314 -1.87493 3567 6454
180 L1 2 L3 8 2481 1863 24.9 618 35226 55620 0.97 Flats/Maisonettes 2006 1.0 0.33 53.40313 -2.97495 53.41171 -2.97208 531 1603
181 SE21 8 SW16 3 7738 5811 24.9 1927 150306 173430 2.71 Flats/Maisonettes 1940 5.2 0.47 51.4369 -0.09319 51.4182 -0.11905 4505 3133
182 SW7 1 SW1A 1 22459 16895 24.8 5564 730275 500760 1.99 Flats/Maisonettes 1940 2.9 0.46 51.50025 -0.16756 51.50602 -0.13973 1864 229
183 N3 2 N12 0 7373 5554 24.7 1819 123692 163710 1.22 Flats/Maisonettes 1940 3.2 0.52 51.60127 -0.18578 51.60872 -0.17254 3926 3282
184 W1H 1 W1H 4 18118 13634 24.7 4484 242136 403560 0.25 Flats/Maisonettes 1914 2.5 0.28 51.52019 -0.16119 51.51941 -0.16464 626 484
185 SW5 9 W6 8 13297 10011 24.7 3286 190588 295740 1.49 Flats/Maisonettes 1914 3.4 0.24 51.49114 -0.19565 51.48654 -0.21631 5808 3253
186 SE15 4 SE23 1 9910 7474 24.6 2436 171738 219240 2.95 Flats/Maisonettes 1914 3.4 0.46 51.46584 -0.07115 51.446 -0.04209 3545 3974
187 E8 2 E2 0 9330 7036 24.6 2294 144522 206460 2.8 Flats/Maisonettes 1940 6.8 0.39 51.55034 -0.0696 51.52807 -0.04998 4333 4330
188 W1U 6 W2 6 14569 10990 24.6 3579 238003 322110 1.81 Flats/Maisonettes 1914 1.0 0.23 51.52059 -0.15781 51.51704 -0.18386 1075 4210
189 SE16 6 E14 3 8241 6225 24.5 2016 139104 181440 2.21 Flats/Maisonettes 1993 1.7 0.54 51.50061 -0.04314 51.49193 -0.01389 1940 8179
190 SW13 9 W4 5 12220 9220 24.5 3000 231750 270000 2.61 Flats/Maisonettes 1940 2.5 0.93 51.48023 -0.24118 51.49745 -0.26758 2631 4935
191 L22 6 L22 7 3710 2800 24.5 910 73710 81900 0.4 Semi-Detached 1940 0.0 0.7 53.48144 -3.04085 53.47948 -3.03589 464 450
192 L3 0 L5 9 2906 2198 24.4 708 47790 63720 1.03 Flats/Maisonettes 2001 1.0 1.03 53.41642 -3.00063 53.42433 -2.99258 1085 525
193 WC2B 5 SW1V 1 14709 11119 24.4 3590 211810 323100 2.86 Flats/Maisonettes 1971 2.0 0.21 51.5155 -0.1218 51.49297 -0.14234 891 1408
194 W10 5 NW6 5 10968 8303 24.3 2665 163897 239850 1.66 Flats/Maisonettes 1971 4.0 0.55 51.5225 -0.21145 51.53337 -0.19455 5247 4901
195 L16 5 L14 6 3753 2840 24.3 913 79431 82170 1.4 Semi-Detached 1940 4.7 0.97 53.3991 -2.88607 53.41102 -2.87901 453 809
196 NE1 3 NE1 6 2754 2090 24.1 664 36520 59760 0.55 Flats/Maisonettes 2008 2.3 0.35 54.96753 -1.61131 54.972 -1.60784 903 712
197 W1T 3 SW1P 2 15879 12102 23.8 3777 247393 339930 2.58 Flats/Maisonettes 2015 1.9 0.3 51.51842 -0.13787 51.49529 -0.13253 827 2039
198 N1 8 NW1 9 12078 9213 23.7 2865 181927 257850 2.53 Flats/Maisonettes 1940 1.4 0.41 51.53533 -0.09877 51.544 -0.13333 3569 4459
199 SW1W 0 SW1E 5 16694 12733 23.7 3961 354509 356490 0.38 Flats/Maisonettes 1979 3.3 0.21 51.49667 -0.146 51.49843 -0.14124 595 272
200 L1 3 L2 0 2929 2234 23.7 695 33360 62550 0.5 Flats/Maisonettes 2009 2.0 0.39 53.40327 -2.98663 53.40563 -2.99288 365 609
201 KT2 5 KT2 7 8290 6333 23.6 1957 148732 176130 1.59 Flats/Maisonettes 1995 2.4 0.94 51.42071 -0.30084 51.41822 -0.27782 6209 3298
202 E1 2 E1 4 8529 6518 23.6 2011 120660 180990 1.1 Flats/Maisonettes 1999 6.2 0.3 51.51519 -0.0577 51.52181 -0.04552 3511 4193
203 SW1H 9 EC4V 3 14058 10757 23.5 3301 209613 297090 2.75 Flats/Maisonettes 1999 3.0 0.2 51.50018 -0.13284 51.51065 -0.09613 289 315
204 WV2 3 WV3 0 2407 1842 23.5 565 42375 50850 1.33 Terraced 1914 2.5 1.27 52.57102 -2.12564 52.57828 -2.14126 1725 3074
205 W1H 4 W1H 5 13634 10428 23.5 3206 201978 288540 0.29 Flats/Maisonettes 1940 3.3 0.27 51.51941 -0.16464 51.5169 -0.16353 484 1194
206 W4 5 TW8 9 9220 7062 23.4 2158 132717 194220 2.91 Flats/Maisonettes 1958 2.8 0.32 51.49745 -0.26758 51.49138 -0.30937 4935 2835
207 N15 4 N17 0 6797 5207 23.4 1590 94605 143100 2.03 Flats/Maisonettes 1958 2.4 0.48 51.58626 -0.07309 51.60306 -0.06115 5055 4447
208 B3 1 B16 8 3374 2588 23.3 786 44802 70740 1.35 Flats/Maisonettes 2009 2.0 0.31 52.48427 -1.90599 52.47603 -1.92066 3141 5647
209 BB9 8 BB9 7 1452 1115 23.2 337 27465 30330 1.05 Terraced 1940 1.4 1.36 53.84457 -2.20627 53.838 -2.21735 3407 2693
210 B4 6 B5 5 4045 3105 23.2 940 45590 84600 0.55 Flats/Maisonettes 2017 2.0 0.23 52.4845 -1.89523 52.48086 -1.88965 2116 434
211 EC1V 1 EC1V 8 11949 9196 23.0 2753 173439 247770 0.23 Flats/Maisonettes 2016 0.5 0.44 51.52897 -0.09357 51.52808 -0.09657 1360 1192
212 NW6 1 NW2 4 10139 7802 23.0 2337 157747 210330 1.63 Flats/Maisonettes 1914 1.4 0.42 51.55137 -0.19415 51.55025 -0.21803 5669 3614
213 NW5 1 N19 3 11360 8742 23.0 2618 175406 235620 1.65 Flats/Maisonettes 1914 4.4 0.39 51.55724 -0.14407 51.56879 -0.12869 3114 5518
214 L9 8 L20 9 1951 1502 23.0 449 40410 40410 1.84 Terraced 1940 2.4 0.44 53.46493 -2.9656 53.45061 -2.97938 1769 2306
215 SW17 7 SW16 6 8557 6603 22.8 1954 132872 175860 2.25 Flats/Maisonettes 1940 4.1 0.41 51.43851 -0.16223 51.42335 -0.14006 6006 6534
216 WC2H 9 SW1V 2 13619 10522 22.7 3097 170335 278730 2.74 Flats/Maisonettes 1940 2.0 0.18 51.51405 -0.12585 51.49031 -0.13734 726 3528
217 NE8 2 NE8 3 2343 1812 22.7 531 34780 47790 2.06 Flats/Maisonettes 2004 2.0 0.87 54.95767 -1.61988 54.95623 -1.58964 3437 3727
218 NE6 1 NE8 3 2345 1812 22.7 533 33845 47970 1.96 Flats/Maisonettes 1986 1.8 0.6 54.97331 -1.58183 54.95623 -1.58964 1688 3727
219 LE1 5 LE1 3 2034 1576 22.5 458 20381 41220 0.93 Flats/Maisonettes 1999 5.0 0.73 52.6316 -1.13556 52.63932 -1.13017 1626 1426
220 WC1N 3 WC1H 8 11955 9294 22.3 2661 154338 239490 0.73 Flats/Maisonettes 1940 1.0 0.38 51.52146 -0.11947 51.5279 -0.12161 917 815
221 E1 0 E1 4 8390 6518 22.3 1872 123552 168480 0.99 Flats/Maisonettes 1971 5.1 0.35 51.51313 -0.04908 51.52181 -0.04552 3486 4193
222 BB11 4 BB11 3 1328 1032 22.3 296 21090 26640 1.59 Terraced 1914 0.0 0.63 53.78411 -2.25864 53.78377 -2.23529 3293 2642
223 SE3 7 SE12 8 7893 6137 22.2 1756 126432 158040 2.72 Flats/Maisonettes 1940 3.6 0.65 51.47871 0.0152 51.45411 0.01431 3853 3365
224 B42 2 B20 1 3168 2464 22.2 704 54560 63360 2.14 Semi-Detached 1958 2.6 1.27 52.53357 -1.90811 52.52345 -1.93504 5931 2309
225 E17 7 E10 7 8712 6778 22.2 1934 126677 174060 1.45 Flats/Maisonettes 1940 2.0 0.28 51.58188 -0.03031 51.56938 -0.02405 6041 4807
226 W1G 7 W1G 6 20358 15856 22.1 4502 387172 405180 0.22 Flats/Maisonettes 1940 1.0 0.29 51.52096 -0.14696 51.52144 -0.15008 280 588
227 FY1 5 FY1 3 1190 927 22.1 263 20251 23670 1.43 Terraced 1940 0.0 0.71 53.80886 -3.04494 53.8218 -3.0435 3510 2642
228 L17 8 L8 3 2687 2096 22.0 591 37233 53190 0.78 Flats/Maisonettes 1914 5.7 0.79 53.3819 -2.94808 53.38744 -2.95519 2017 2200
229 SE10 0 E14 2 7544 5898 21.8 1646 116866 148140 1.89 Flats/Maisonettes 2016 2.0 0.58 51.49297 0.01055 51.50866 -0.00067 10015 844
230 E3 5 E3 4 8220 6430 21.8 1790 126195 161100 1.18 Flats/Maisonettes 1971 5.5 0.74 51.53199 -0.03426 51.52221 -0.02733 4925 6823
231 DL1 1 DL1 4 2015 1576 21.8 439 32486 39510 1.07 Semi-Detached 1986 2.2 0.97 54.52695 -1.53562 54.51731 -1.53379 3425 5446
232 NW5 3 NW1 1 11150 8731 21.7 2419 151187 217710 1.95 Flats/Maisonettes 1958 2.7 0.23 51.54742 -0.14738 51.53218 -0.13296 1597 2651
233 AL8 6 AL7 1 6072 4755 21.7 1317 90873 118530 1.96 Flats/Maisonettes 1958 2.0 0.84 51.79764 -0.21351 51.80767 -0.18975 2609 3209
234 N8 0 N22 6 8463 6637 21.6 1826 115038 164340 0.83 Flats/Maisonettes 1940 3.5 0.44 51.58777 -0.10664 51.59482 -0.10227 6469 4971
235 E14 5 E14 2 7510 5898 21.5 1612 114452 145080 0.85 Flats/Maisonettes 2000 0.8 0.37 51.50474 -0.0115 51.50866 -0.00067 712 844
236 N8 7 N22 8 7980 6261 21.5 1719 102280 154710 1.96 Flats/Maisonettes 1958 2.9 0.6 51.58797 -0.11977 51.6055 -0.11564 4703 4514
237 GU16 7 GU15 3 4558 3581 21.4 977 70832 87930 2.46 Flats/Maisonettes 1971 2.1 0.46 51.31468 -0.74357 51.33663 -0.7494 953 3911
238 E9 5 E15 4 7728 6072 21.4 1656 106812 149040 2.92 Flats/Maisonettes 1979 4.7 0.44 51.54638 -0.03302 51.54158 0.00934 4708 4460
239 B3 2 B16 8 3290 2588 21.3 702 38610 63180 1.43 Flats/Maisonettes 2014 2.0 0.23 52.48237 -1.90234 52.47603 -1.92066 178 5647
240 E1W 1 SE16 5 9651 7596 21.3 2055 146932 184950 1.93 Flats/Maisonettes 1998 1.4 0.57 51.50581 -0.06777 51.50438 -0.03943 1353 2901
241 L16 1 L14 6 3607 2840 21.3 767 61360 69030 1.18 Semi-Detached 1940 5.2 0.51 53.40267 -2.88984 53.41102 -2.87901 434 809
242 LA14 2 LA14 1 1252 985 21.3 267 18022 24030 0.81 Terraced 1914 1.1 1.37 54.10749 -3.22482 54.11479 -3.22584 3502 2195
243 B11 2 B11 1 2208 1741 21.2 467 36192 42030 1.57 Terraced 1914 2.2 0.64 52.4551 -1.84936 52.46166 -1.86989 1291 3202
244 L13 3 L13 2 2078 1637 21.2 441 34618 39690 0.48 Terraced 1914 4.1 1.34 53.41643 -2.91645 53.41304 -2.92082 1350 1340
245 E14 9 E14 3 7903 6225 21.2 1678 111587 151020 0.99 Flats/Maisonettes 2013 0.8 0.26 51.50073 -0.01626 51.49193 -0.01389 18659 8179
246 B25 8 B10 0 2476 1952 21.2 524 38514 47160 2.64 Terraced 1940 3.8 1.45 52.4666 -1.81995 52.46765 -1.85885 5605 2837
247 SW3 6 SW10 9 16803 13240 21.2 3563 260099 320670 0.84 Flats/Maisonettes 1914 4.3 0.67 51.48869 -0.17386 51.48648 -0.18567 1594 5825
248 WC1B 3 EC4V 5 14214 11220 21.1 2994 210328 269460 1.92 Flats/Maisonettes 1914 1.9 0.23 51.51799 -0.12848 51.51284 -0.10141 322 208
249 NW1 6 NW6 4 11154 8814 21.0 2340 136890 210600 2.62 Flats/Maisonettes 1914 3.1 0.23 51.5237 -0.16342 51.54102 -0.18969 3312 3641
250 EC1V 4 EC1Y 8 11255 8894 21.0 2361 151104 212490 0.83 Flats/Maisonettes 1999 0.0 0.43 51.52548 -0.10341 51.5234 -0.09159 897 1020
251 NW5 2 N19 3 11064 8742 21.0 2322 145125 208980 2.09 Flats/Maisonettes 1914 3.9 0.35 51.5506 -0.13698 51.56879 -0.12869 4088 5518
252 BB12 0 BB12 6 2222 1756 21.0 466 35882 41940 1.82 Terraced 1971 0.3 0.76 53.79775 -2.257 53.79231 -2.28235 3008 3957
253 L8 4 L7 6 2126 1682 20.9 444 30636 39960 2.4 Terraced 1958 3.5 0.69 53.3828 -2.9662 53.4 -2.94456 1582 885
254 N22 7 N11 2 8317 6576 20.9 1741 127093 156690 1.09 Flats/Maisonettes 1914 3.9 0.49 51.59943 -0.12437 51.60923 -0.12642 2841 3946
255 SW17 0 CR4 2 7672 6067 20.9 1605 111948 144450 2.42 Flats/Maisonettes 1958 5.0 0.6 51.43102 -0.17616 51.41288 -0.15632 7498 4538
256 TW7 7 TW7 4 6663 5276 20.8 1387 101944 124830 1.84 Flats/Maisonettes 1971 3.4 1.05 51.46225 -0.3339 51.47701 -0.34628 3353 3229
257 L16 2 L36 4 3306 2618 20.8 688 65016 61920 1.21 Semi-Detached 1958 3.5 1.21 53.40324 -2.87211 53.41162 -2.86068 807 2242
258 CH42 9 CH42 0 1726 1367 20.8 359 30694 32310 0.8 Terraced 1914 4.9 1.49 53.37733 -3.03646 53.38266 -3.02855 1821 1277
259 ST4 2 ST1 4 1611 1276 20.8 335 23785 30150 1.7 Terraced 1914 1.0 0.79 53.00742 -2.17135 53.01975 -2.18626 2587 1893
260 L36 9 L36 5 3227 2558 20.7 669 63555 60210 0.94 Semi-Detached 1986 2.4 0.35 53.41196 -2.84932 53.40572 -2.83991 824 1467
261 E2 9 E1 4 8218 6518 20.7 1700 105400 153000 1.34 Flats/Maisonettes 1971 6.3 0.31 51.53204 -0.05617 51.52181 -0.04552 3336 4193
262 N1 0 NW5 4 11018 8753 20.6 2265 144960 203850 2.96 Flats/Maisonettes 1958 1.6 0.54 51.53742 -0.11419 51.55049 -0.15229 4577 2697
263 NG7 7 NG7 6 2175 1730 20.5 445 31595 40050 0.71 Terraced 1914 4.1 0.56 52.97526 -1.16804 52.96909 -1.16531 2339 3559
264 NG7 2 NG2 1 2801 2228 20.5 573 42688 51570 1.76 Terraced 1958 4.4 0.58 52.94656 -1.17989 52.94167 -1.15523 3214 1056
265 NG1 7 NG1 1 2786 2216 20.5 570 30210 51300 0.49 Flats/Maisonettes 2010 3.4 0.2 52.94995 -1.14699 52.95301 -1.14178 945 2895
266 NR32 1 NR32 2 2311 1840 20.4 471 32734 42390 0.81 Terraced 1914 2.0 0.86 52.48169 1.75296 52.47962 1.74151 2532 4026
267 TW4 5 TW3 3 5776 4597 20.4 1179 76045 106110 1.08 Flats/Maisonettes 1979 5.5 1.24 51.45786 -0.3795 51.46635 -0.37157 3500 4518
268 L15 0 L7 4 1712 1363 20.4 349 23906 31410 0.75 Terraced 1914 4.8 1.06 53.39699 -2.93315 53.39725 -2.94425 1329 401
269 WF8 2 WF11 8 2705 2152 20.4 553 42304 49770 2.99 Semi-Detached 1958 1.8 0.8 53.6932 -1.29594 53.70935 -1.26056 7519 2858
270 CH42 2 CH42 0 1715 1367 20.3 348 28536 31320 1.85 Terraced 1940 2.9 0.32 53.37035 -3.01 53.38266 -3.02855 1267 1277
271 SW9 0 SE5 0 8502 6774 20.3 1728 112320 155520 1.46 Flats/Maisonettes 1958 3.7 0.46 51.47388 -0.11558 51.47961 -0.09617 4980 3441
272 LS6 3 LS6 2 3230 2577 20.2 653 51587 58770 1.69 Flats/Maisonettes 1940 1.6 0.68 53.82002 -1.58519 53.81733 -1.56075 4033 3873
273 SW20 0 KT3 4 8740 6972 20.2 1768 144092 159120 1.41 Flats/Maisonettes 1958 4.8 0.75 51.41269 -0.23796 51.40313 -0.25172 3673 2531
274 RM14 2 RM12 5 6360 5079 20.1 1281 111447 115290 2.99 Semi-Detached 1940 3.7 0.77 51.55277 0.24307 51.54507 0.20079 3026 3133
275 N7 8 N7 7 8998 7185 20.1 1813 110593 163170 1.11 Flats/Maisonettes 1979 3.2 0.28 51.54816 -0.11264 51.55818 -0.11109 4607 3539
276 B19 1 B21 9 1788 1428 20.1 360 30060 32400 1.69 Terraced 1914 3.6 1.2 52.50297 -1.91051 52.50746 -1.93433 3162 3409
277 L18 6 L15 6 3877 3098 20.1 779 88806 70110 1.54 Semi-Detached 1940 4.8 0.87 53.38188 -2.90414 53.39565 -2.90712 564 901
278 WV14 6 WV14 7 2774 2218 20.0 556 41700 50040 1.05 Semi-Detached 1958 3.5 0.74 52.57348 -2.0784 52.56866 -2.06512 2963 1517
279 DN35 8 DN35 7 1598 1278 20.0 320 26400 28800 1.7 Terraced 1914 0.7 0.65 53.55706 -0.03085 53.56767 -0.04891 3803 4576
280 NE4 5 NE8 1 1503 1202 20.0 301 21070 27090 2.84 Flats/Maisonettes 1914 2.0 0.96 54.97595 -1.63497 54.95588 -1.60896 3018 2310
281 B9 5 B10 9 2597 2079 19.9 518 44289 46620 0.95 Terraced 1940 5.5 1.05 52.47881 -1.84057 52.47059 -1.84439 5554 4180
282 SE11 4 SE5 0 8462 6774 19.9 1688 111408 151920 1.4 Flats/Maisonettes 1971 5.3 0.32 51.49044 -0.10679 51.47961 -0.09617 4439 3441
283 SE10 9 E14 3 7769 6225 19.9 1544 107308 138960 1.14 Flats/Maisonettes 2012 2.0 0.31 51.48279 -0.00612 51.49193 -0.01389 6113 8179
284 W2 6 NW6 4 10990 8814 19.8 2176 129472 195840 2.68 Flats/Maisonettes 1914 2.5 0.3 51.51704 -0.18386 51.54102 -0.18969 4210 3641
285 SE11 6 SE1 5 8493 6831 19.6 1662 103044 149580 2.81 Flats/Maisonettes 1971 4.7 0.58 51.49243 -0.11392 51.48984 -0.07272 2536 4478
286 TW10 6 SW14 7 11640 9375 19.5 2265 175537 203850 1.91 Flats/Maisonettes 1914 1.8 0.79 51.45695 -0.29727 51.46517 -0.27252 4157 2831
287 N2 0 NW11 0 8195 6594 19.5 1601 123277 144090 2.02 Flats/Maisonettes 1940 1.8 0.83 51.5874 -0.17339 51.58225 -0.2019 3685 2109
288 S1 2 S1 4 2568 2069 19.4 499 21207 44910 0.47 Flats/Maisonettes 2012 3.2 0.24 53.38187 -1.46947 53.37885 -1.47441 2182 5583
289 TW2 7 TW3 2 6971 5620 19.4 1351 113821 121590 1.0 Semi-Detached 1940 5.4 0.59 51.45266 -0.35393 51.4609 -0.36011 3377 2964
290 L15 7 L14 6 3518 2840 19.3 678 56274 61020 2.0 Semi-Detached 1940 5.1 0.83 53.40286 -2.90524 53.41102 -2.87901 1032 809
291 CH45 1 CH45 4 2213 1785 19.3 428 38948 38520 1.26 Semi-Detached 1914 1.7 0.83 53.43265 -3.03879 53.42321 -3.04925 1283 1465
292 N1 9 N1 6 10402 8392 19.3 2010 115575 180900 2.16 Flats/Maisonettes 1971 1.1 0.31 51.53273 -0.1152 51.52938 -0.08379 2833 3384
293 EC3R 8 E1 7 10309 8330 19.2 1979 101918 178110 0.99 Flats/Maisonettes 1986 0.0 0.19 51.50999 -0.08419 51.51629 -0.07389 221 2146
294 L18 9 L19 4 3921 3169 19.2 752 69184 67680 0.66 Semi-Detached 1958 4.2 0.67 53.36947 -2.89798 53.3647 -2.89212 1296 1311
295 BB9 0 BB9 7 1378 1115 19.1 263 22092 23670 1.05 Terraced 1914 1.0 0.84 53.82989 -2.20929 53.838 -2.21735 4211 2693
296 M25 9 M27 4 3607 2917 19.1 690 55890 62100 2.99 Semi-Detached 1958 1.5 1.33 53.52156 -2.2855 53.5097 -2.32514 2959 2295
297 E16 2 SE18 5 6146 4972 19.1 1174 78658 105660 1.26 Flats/Maisonettes 2017 3.1 0.33 51.502 0.04566 51.49158 0.05314 11558 3903
298 SK15 2 OL6 6 3024 2450 19.0 574 40754 51660 2.77 Terraced 1958 2.2 1.36 53.47937 -2.04467 53.48856 -2.08261 3742 2416
299 WV14 8 WS10 7 3186 2581 19.0 605 42652 54450 2.72 Semi-Detached 1958 1.7 0.62 52.54996 -2.06972 52.55734 -2.03155 5736 2725
300 TW12 3 KT8 1 7042 5708 18.9 1334 107053 120060 2.23 Terraced 1979 3.2 1.27 51.42588 -0.37796 51.40643 -0.36933 2527 1567
301 LS1 4 LS11 9 3513 2848 18.9 665 40897 59850 0.82 Flats/Maisonettes 2006 3.0 0.46 53.79487 -1.55334 53.78743 -1.55422 2171 2841
302 NE3 4 NE3 2 3298 2676 18.9 622 53803 55980 1.92 Semi-Detached 1958 4.9 1.25 55.00102 -1.63652 55.01655 -1.64912 3991 4588
303 CT19 5 CT19 4 3399 2761 18.8 638 53592 57420 2.06 Terraced 1940 3.0 0.77 51.08812 1.17314 51.08929 1.14286 4481 3250
304 L31 3 L10 2 3176 2578 18.8 598 56212 53820 2.77 Semi-Detached 1971 2.0 0.35 53.50739 -2.93196 53.48371 -2.94544 661 347
305 L9 0 L20 9 1849 1502 18.8 347 30189 31230 2.77 Terraced 1914 1.9 0.6 53.46914 -2.95196 53.45061 -2.97938 1477 2306
306 L30 7 L30 8 2650 2154 18.7 496 40176 44640 0.79 Semi-Detached 1979 3.0 1.02 53.49196 -2.96368 53.48815 -2.95383 790 425
307 BN2 0 BN2 4 5633 4582 18.7 1051 69366 94590 2.32 Flats/Maisonettes 1958 3.0 1.35 50.82353 -0.12438 50.84309 -0.11204 3130 5322
308 SW3 5 SW10 9 16271 13240 18.6 3031 207623 272790 1.09 Flats/Maisonettes 1914 4.8 1.05 51.48503 -0.16986 51.48648 -0.18567 3183 5825
309 IP4 1 IP1 1 2606 2120 18.6 486 28674 43740 0.79 Flats/Maisonettes 2004 2.1 0.95 52.05439 1.16289 52.05524 1.15128 2603 1100
310 NR33 0 NR32 2 2261 1840 18.6 421 30943 37890 1.62 Terraced 1914 2.0 1.29 52.46508 1.739 52.47962 1.74151 3979 4026
311 L9 2 L9 4 1807 1472 18.5 335 27637 30150 1.02 Terraced 1940 1.8 0.37 53.45663 -2.95873 53.46584 -2.95873 1133 551
312 B12 8 B11 1 2135 1741 18.5 394 30929 35460 0.88 Terraced 1914 1.8 1.16 52.45575 -1.87847 52.46166 -1.86989 2418 3202
313 NW3 5 NW3 6 12706 10350 18.5 2356 174344 212040 0.5 Flats/Maisonettes 1914 2.6 0.36 51.54962 -0.17479 51.55188 -0.1812 3173 3182
314 SW4 6 SE5 0 8312 6774 18.5 1538 100739 138420 2.69 Flats/Maisonettes 1958 3.4 0.27 51.46863 -0.13154 51.47961 -0.09617 4563 3441
315 L23 0 L21 9 3007 2450 18.5 557 49016 50130 1.48 Semi-Detached 1940 2.0 1.2 53.48494 -3.01832 53.47603 -3.00214 1547 1758
316 SW1W 8 SW1P 4 13714 11191 18.4 2523 166518 227070 1.55 Flats/Maisonettes 1993 3.8 0.47 51.48954 -0.15204 51.49278 -0.1299 3312 3443
317 TS4 3 TS4 2 1675 1367 18.4 308 23100 27720 1.7 Terraced 1958 1.6 1.03 54.548 -1.22043 54.56321 -1.22413 3973 4228
318 CR4 2 CR4 4 6067 4969 18.1 1098 76036 98820 2.29 Flats/Maisonettes 1940 4.4 0.48 51.41288 -0.15632 51.39323 -0.16681 4538 3840
319 PL1 3 PL4 0 2904 2378 18.1 526 35242 47340 1.79 Flats/Maisonettes 1986 2.3 1.41 50.36686 -4.15566 50.36895 -4.12957 4256 1753
320 E9 6 E1 4 7954 6518 18.1 1436 92622 129240 2.76 Flats/Maisonettes 1971 6.8 0.38 51.54675 -0.04816 51.52181 -0.04552 3947 4193
321 LS18 5 LS16 6 4110 3367 18.1 743 59068 66870 1.91 Semi-Detached 1958 2.8 1.03 53.84265 -1.63652 53.84651 -1.60911 3580 4155
322 E1 1 E14 7 7686 6306 18.0 1380 84180 124200 2.27 Flats/Maisonettes 2000 3.4 0.38 51.51507 -0.06529 51.51406 -0.03192 3545 5687
323 SW7 4 W14 8 14200 11646 18.0 2554 176226 229860 1.54 Flats/Maisonettes 1914 2.6 0.31 51.49492 -0.18518 51.49743 -0.20756 3430 7221
324 SW20 8 KT3 4 8500 6972 18.0 1528 116892 137520 2.23 Flats/Maisonettes 1940 4.6 0.46 51.41147 -0.22174 51.40313 -0.25172 5155 2531
325 L4 5 L20 2 1410 1158 17.9 252 19908 22680 1.18 Terraced 1914 2.0 0.99 53.44347 -2.96715 53.44207 -2.98445 2817 1300
326 SM6 7 SM6 0 5628 4620 17.9 1008 67032 90720 1.97 Flats/Maisonettes 1996 4.0 0.55 51.37513 -0.15286 51.35737 -0.15137 3418 3099
327 NW5 4 N7 7 8753 7185 17.9 1568 94864 141120 2.92 Flats/Maisonettes 1971 3.0 0.42 51.55049 -0.15229 51.55818 -0.11109 2697 3539
328 M3 7 M3 6 3816 3134 17.9 682 40579 61380 0.7 Flats/Maisonettes 2018 3.8 0.46 53.48713 -2.25085 53.4858 -2.26093 5941 3553
329 NE2 2 NE2 1 3146 2584 17.9 562 44960 50580 1.01 Flats/Maisonettes 1914 3.3 0.63 54.99126 -1.60136 54.98237 -1.5983 2656 3970
330 W4 2 W4 4 10174 8372 17.7 1802 128843 162180 1.1 Flats/Maisonettes 1914 1.8 0.69 51.48794 -0.25401 51.49057 -0.26957 4027 2732
331 SW4 0 SW2 1 9983 8214 17.7 1769 113216 159210 2.14 Flats/Maisonettes 1940 4.2 0.52 51.4646 -0.14415 51.45683 -0.11529 3931 3735
332 EC1M 5 EC1Y 8 10803 8894 17.7 1909 128857 171810 0.78 Flats/Maisonettes 1999 0.0 0.18 51.52179 -0.10282 51.5234 -0.09159 521 1020
333 L4 1 L20 7 2098 1728 17.6 370 27565 33300 1.2 Terraced 1979 2.3 0.49 53.43506 -2.98101 53.44391 -2.99118 1667 1243
334 EC3N 2 EC1V 9 10244 8443 17.6 1801 112562 162090 1.86 Flats/Maisonettes 1999 0.0 0.08 51.5113 -0.07707 51.52596 -0.09038 166 960
335 SE27 0 SW16 3 7049 5811 17.6 1238 95326 111420 1.5 Flats/Maisonettes 1940 5.6 0.53 51.42953 -0.10688 51.4182 -0.11905 5574 3133
336 B43 5 B20 1 2989 2464 17.6 525 43312 47250 1.8 Semi-Detached 1958 2.2 1.28 52.5392 -1.94182 52.52345 -1.93504 3455 2309
337 W6 0 W12 0 10152 8378 17.5 1774 116197 159660 1.9 Flats/Maisonettes 1940 3.2 0.33 51.49621 -0.23419 51.51319 -0.23865 6681 5147
338 UB3 4 UB7 9 6151 5076 17.5 1075 76325 96750 2.94 Flats/Maisonettes 2004 1.9 0.55 51.49951 -0.41996 51.50394 -0.46273 4607 4365
339 M7 1 M5 3 3274 2701 17.5 573 39823 51570 2.67 Flats/Maisonettes 2007 3.1 1.23 53.49476 -2.26295 53.47213 -2.27652 2375 5106
340 SW1V 4 SW1V 2 12739 10522 17.4 2217 116392 199530 0.47 Flats/Maisonettes 1890 3.0 0.61 51.48917 -0.14394 51.49031 -0.13734 3227 3528
341 HA7 2 HA3 0 6834 5646 17.4 1188 108108 106920 2.57 Semi-Detached 1940 2.9 1.31 51.60329 -0.31221 51.58069 -0.30359 2775 3122
342 EC1R 0 EC1R 4 12020 9931 17.4 2089 133696 188010 0.25 Flats/Maisonettes 1971 0.0 0.51 51.52501 -0.10599 51.52666 -0.10846 532 634
343 L19 7 L19 9 4230 3496 17.4 734 72666 66060 0.48 Semi-Detached 1940 4.0 0.53 53.36525 -2.90341 53.36383 -2.91015 446 1110
344 B90 2 B28 0 4200 3470 17.4 730 63145 65700 1.76 Semi-Detached 1958 1.9 0.99 52.40657 -1.83487 52.42046 -1.84765 4449 4603
345 SE8 5 E14 3 7524 6225 17.3 1299 88981 116910 1.7 Flats/Maisonettes 1986 1.6 0.67 51.48642 -0.03731 51.49193 -0.01389 5494 8179
346 SE10 8 E14 3 7531 6225 17.3 1306 90767 117540 1.98 Flats/Maisonettes 2001 1.0 0.36 51.47401 -0.01359 51.49193 -0.01389 5455 8179
347 BN7 1 BN7 2 6210 5138 17.3 1072 82544 96480 1.0 Terraced 1958 1.8 1.01 50.87346 -0.00155 50.87753 0.01166 3196 3571
348 NW10 4 NW10 9 7114 5893 17.2 1221 73260 109890 0.84 Flats/Maisonettes 1914 2.8 0.51 51.53703 -0.24549 51.5439 -0.25092 4233 3624
349 E14 0 E15 2 7497 6208 17.2 1289 83140 116010 2.9 Flats/Maisonettes 2016 2.5 0.36 51.51208 -0.0033 51.53827 -0.00621 8378 6157
350 UB5 4 UB5 5 5964 4938 17.2 1026 72333 92340 1.65 Flats/Maisonettes 1958 2.9 0.58 51.5526 -0.36148 51.54333 -0.38054 5251 4277
351 WC1X 9 WC1H 8 11211 9294 17.1 1917 106393 172530 0.45 Flats/Maisonettes 1940 0.5 0.41 51.52919 -0.11524 51.5279 -0.12161 1540 815
352 S6 4 S6 1 2988 2476 17.1 512 40192 46080 1.64 Terraced 1940 1.9 0.49 53.40639 -1.51241 53.42047 -1.50486 4963 4420
353 SO23 9 SO23 0 6311 5240 17.0 1071 79789 96390 1.31 Flats/Maisonettes 1971 3.2 1.28 51.05626 -1.31826 51.06197 -1.30127 2026 2127
354 L39 5 L39 6 3842 3187 17.0 655 74997 58950 1.36 Detached 1971 0.0 0.59 53.55169 -2.90318 53.53975 -2.90771 1515 821
355 SW11 8 SW11 7 13444 11155 17.0 2289 168241 206010 0.78 Flats/Maisonettes 2016 3.2 0.38 51.48159 -0.14506 51.48145 -0.13354 5298 4866
356 TS19 0 TS18 4 1750 1453 17.0 297 25096 26730 1.3 Semi-Detached 1958 2.3 1.23 54.57472 -1.33294 54.56297 -1.33273 4499 2209
357 CH63 3 CH63 2 3484 2896 16.9 588 51450 52920 1.01 Semi-Detached 1940 2.7 0.83 53.34542 -3.0082 53.3507 -3.02031 1436 1530
358 CH41 8 CH41 7 1806 1503 16.8 303 22270 27270 1.1 Terraced 1958 4.5 0.41 53.40007 -3.04816 53.40356 -3.06336 1683 1078
359 WS12 4 WS11 6 3130 2604 16.8 526 39450 47340 2.04 Semi-Detached 1986 2.9 1.48 52.71611 -2.0179 52.69769 -2.01587 6766 1607
360 SE24 0 SE5 9 9093 7561 16.8 1532 101112 137880 1.33 Flats/Maisonettes 1914 3.1 0.44 51.45863 -0.10307 51.47044 -0.09938 4116 4741
361 WF10 1 WF10 4 2506 2084 16.8 422 31861 37980 1.76 Terraced 1958 0.9 0.94 53.72625 -1.36697 53.72026 -1.34292 2055 4531
362 S20 7 S20 1 2922 2430 16.8 492 35424 44280 1.31 Semi-Detached 1979 0.2 0.39 53.33769 -1.35432 53.34676 -1.342 1346 2233
363 NW10 2 NW10 9 7078 5893 16.7 1185 71100 106650 1.04 Flats/Maisonettes 1940 2.2 0.56 51.54767 -0.23691 51.5439 -0.25092 3505 3624
364 L3 6 L5 3 2356 1962 16.7 394 24034 35460 1.21 Flats/Maisonettes 2003 1.5 0.64 53.41455 -2.99003 53.42081 -2.97545 1725 1454
365 L22 0 L22 1 2609 2175 16.6 434 28861 39060 0.66 Flats/Maisonettes 1914 1.3 0.24 53.47671 -3.02473 53.4711 -3.02158 710 617
366 SW4 9 SW2 5 10076 8400 16.6 1676 118158 150840 1.23 Flats/Maisonettes 1914 5.1 0.49 51.45624 -0.14204 51.45674 -0.1239 4010 5125
367 L9 3 L20 9 1802 1502 16.6 300 27450 27000 1.34 Terraced 1914 3.0 0.29 53.46011 -2.96714 53.45061 -2.97938 864 2306
368 BB2 2 BB2 1 1405 1172 16.6 233 16892 20970 0.83 Terraced 1940 4.7 0.56 53.73966 -2.50046 53.74659 -2.49571 2449 2156
369 NG8 5 NG8 6 2654 2216 16.5 438 29346 39420 1.54 Terraced 1940 3.4 1.11 52.97537 -1.19714 52.97907 -1.21893 5034 4450
370 KT1 4 KT2 7 7580 6333 16.5 1247 88537 112230 2.4 Flats/Maisonettes 1971 2.4 0.23 51.41458 -0.31269 51.41822 -0.27782 1490 3298
371 EC1N 8 E1 7 9979 8330 16.5 1649 96466 148410 2.35 Flats/Maisonettes 2002 0.8 0.21 51.52047 -0.10778 51.51629 -0.07389 573 2146
372 SE19 1 SW16 3 6947 5811 16.4 1136 87472 102240 2.43 Flats/Maisonettes 1958 4.9 0.37 51.42311 -0.08413 51.4182 -0.11905 4346 3133
373 TW1 2 TW1 1 9945 8322 16.3 1623 120913 146070 1.04 Flats/Maisonettes 1914 3.0 0.6 51.45459 -0.31202 51.45653 -0.32698 2753 3637
374 SW9 9 SW9 7 8416 7041 16.3 1375 89375 123750 1.0 Flats/Maisonettes 1940 2.8 0.36 51.46731 -0.123 51.46801 -0.10832 6031 3760
375 BN3 5 BN3 3 6741 5645 16.3 1096 74528 98640 1.24 Flats/Maisonettes 1914 5.6 0.44 50.83387 -0.18759 50.83132 -0.16973 5310 8935
376 L2 5 L1 6 1878 1574 16.2 304 14592 27360 0.16 Flats/Maisonettes 2006 1.0 0.19 53.40738 -2.98838 53.40797 -2.98616 369 881
377 L4 3 L20 2 1382 1158 16.2 224 18144 20160 0.68 Terraced 1914 2.2 0.47 53.44124 -2.97449 53.44207 -2.98445 1796 1300
378 L31 7 L10 3 3036 2545 16.2 491 44926 44190 2.43 Semi-Detached 1958 2.0 1.21 53.50819 -2.94793 53.48632 -2.94406 886 418
379 SM2 7 KT17 3 6904 5790 16.1 1114 173227 100260 2.55 Detached 1940 2.8 0.72 51.34792 -0.21697 51.32954 -0.23953 1934 1519
380 NW4 2 NW9 0 6465 5422 16.1 1043 75617 93870 2.92 Flats/Maisonettes 1940 5.2 0.69 51.58597 -0.21789 51.58914 -0.26066 3448 3514
381 BN1 3 BN2 3 6411 5387 16.0 1024 61952 92160 1.62 Flats/Maisonettes 1890 4.0 0.51 50.8283 -0.1474 50.83424 -0.12553 7052 5567
382 NW11 6 N2 0 9752 8195 16.0 1557 130788 140130 1.13 Flats/Maisonettes 1914 1.9 1.34 51.58492 -0.1896 51.5874 -0.17339 2269 3685
383 SN2 1 SN2 8 3293 2767 16.0 526 38924 47340 0.91 Terraced 1958 2.5 1.12 51.57453 -1.78706 51.57392 -1.77363 4720 1056
384 WA10 2 WA9 1 1701 1431 15.9 270 18225 24300 2.12 Terraced 1979 1.5 1.14 53.45722 -2.74475 53.4528 -2.71443 2814 3230
385 WD24 4 WD18 7 5679 4774 15.9 905 62445 81450 2.17 Flats/Maisonettes 1993 4.0 0.51 51.6669 -0.39275 51.65289 -0.41509 2014 4190
386 RM20 3 RM20 4 4648 3908 15.9 740 49580 66600 1.37 Terraced 2012 3.0 1.06 51.47874 0.27854 51.47707 0.29861 1330 1542
387 CH46 2 CH44 5 2334 1962 15.9 372 30504 33480 3.0 Terraced 1958 1.8 1.0 53.41644 -3.09066 53.41539 -3.04651 966 1407
388 SE15 3 SE15 1 8041 6766 15.9 1275 87337 114750 2.04 Flats/Maisonettes 1958 2.1 0.65 51.46242 -0.05615 51.48086 -0.05779 4646 3041
389 NW7 2 NW4 1 6545 5506 15.9 1039 81042 93510 1.94 Flats/Maisonettes 1958 5.9 0.88 51.60921 -0.23469 51.59454 -0.219 2863 3256
390 CH66 2 CH66 1 3004 2530 15.8 474 39342 42660 2.84 Semi-Detached 1971 1.3 1.4 53.26372 -2.92341 53.28903 -2.93023 4041 2959
391 PR8 3 PR8 4 2962 2498 15.7 464 43152 41760 2.97 Semi-Detached 1958 1.9 0.94 53.60196 -3.03175 53.6256 -3.01102 4601 3996
392 SM6 8 SM6 0 5483 4620 15.7 863 59547 77670 0.94 Flats/Maisonettes 1958 4.7 0.68 51.3632 -0.14127 51.35737 -0.15137 4470 3099
393 L32 5 L33 0 1870 1576 15.7 294 20874 26460 1.24 Terraced 1993 2.0 0.9 53.47912 -2.89684 53.4823 -2.8793 431 591
394 SW1P 1 SW1V 2 12483 10522 15.7 1961 126484 176490 0.55 Flats/Maisonettes 1914 3.0 0.35 51.49525 -0.13811 51.49031 -0.13734 1402 3528
395 M3 5 M5 4 4152 3503 15.6 649 39264 58410 1.18 Flats/Maisonettes 2020 2.9 0.3 53.48267 -2.25473 53.47828 -2.27064 4203 7855
396 SW1W 9 SW3 6 19900 16803 15.6 3097 277181 278730 1.68 Flats/Maisonettes 1914 4.0 0.38 51.49389 -0.15063 51.48869 -0.17386 1512 1594
397 L21 8 L20 5 1536 1297 15.6 239 19239 21510 0.75 Terraced 1940 2.0 0.76 53.46498 -2.99614 53.45858 -2.99252 1753 1206
398 NG1 5 NG7 3 2298 1941 15.5 357 21063 32130 1.12 Flats/Maisonettes 2004 4.1 0.35 52.95503 -1.15757 52.95802 -1.17335 1227 3268
399 PL31 1 PL31 2 2902 2451 15.5 451 35403 40590 0.68 Terraced 1979 1.0 0.91 50.46537 -4.72475 50.47146 -4.72295 3265 3650
400 N21 3 EN1 2 6539 5529 15.4 1010 85850 90900 2.0 Flats/Maisonettes 1940 2.4 0.67 51.62986 -0.09786 51.64171 -0.07559 2506 2804
401 NW3 2 NW3 6 12230 10350 15.4 1880 123140 169200 1.42 Flats/Maisonettes 1940 2.7 0.31 51.55255 -0.16025 51.55188 -0.1812 5663 3182
402 L16 3 L14 6 3353 2840 15.3 513 43348 46170 0.83 Semi-Detached 1958 5.4 0.79 53.4038 -2.8825 53.41102 -2.87901 646 809
403 ME7 2 ME7 5 3614 3061 15.3 553 42304 49770 1.62 Terraced 1940 3.0 1.29 51.38449 0.56629 51.38258 0.54265 4375 3928
404 B10 9 B8 1 2079 1761 15.3 318 26871 28620 2.37 Terraced 1914 4.9 1.28 52.47059 -1.84439 52.49094 -1.85524 4180 2834
405 SM5 2 SM6 0 5455 4620 15.3 835 55945 75150 2.06 Flats/Maisonettes 1971 4.6 0.63 51.37365 -0.16616 51.35737 -0.15137 4877 3099
406 S12 3 S12 4 2902 2461 15.2 441 33957 39690 1.88 Semi-Detached 1958 0.0 0.45 53.34167 -1.41358 53.34743 -1.38757 3092 4427
407 EC1V 7 EC1Y 8 10494 8894 15.2 1600 100800 144000 0.9 Flats/Maisonettes 1999 0.0 0.47 51.52856 -0.10175 51.5234 -0.09159 1368 1020
408 SW9 8 SW9 7 8306 7041 15.2 1265 81592 113850 0.69 Flats/Maisonettes 1979 3.1 0.37 51.46184 -0.1102 51.46801 -0.10832 3059 3760
409 N16 6 N15 6 6500 5515 15.2 985 65010 88650 1.01 Flats/Maisonettes 1914 3.7 0.51 51.5696 -0.06789 51.57766 -0.07499 4716 4196
410 EN2 6 EN1 1 6398 5424 15.2 974 67693 87660 1.32 Flats/Maisonettes 1958 4.5 0.42 51.65156 -0.08438 51.64635 -0.06695 1875 5034
411 SW11 6 SW18 3 10999 9326 15.2 1673 157262 150570 1.75 Flats/Maisonettes 1914 5.2 0.84 51.4556 -0.16199 51.44534 -0.18171 4237 5500
412 BB1 7 BB1 6 1764 1498 15.1 266 25935 23940 0.62 Terraced 1914 4.1 0.91 53.75417 -2.48301 53.75666 -2.47488 850 1175
413 WF6 2 WF6 1 2630 2234 15.1 396 30492 35640 1.36 Semi-Detached 1971 1.2 0.94 53.70552 -1.42098 53.69415 -1.41339 4466 4718
414 W9 2 W10 4 10718 9102 15.1 1616 103424 145440 1.32 Flats/Maisonettes 1914 4.1 0.48 51.52498 -0.19242 51.52944 -0.21048 5679 3629
415 NW11 7 NW2 2 8238 6991 15.1 1247 86978 112230 1.51 Flats/Maisonettes 1940 0.5 0.56 51.57475 -0.19246 51.56242 -0.20213 2668 3603
416 WR1 3 WR1 1 3131 2660 15.0 471 28731 42390 0.39 Flats/Maisonettes 1958 3.5 0.69 52.1988 -2.22707 52.19991 -2.22163 1651 2759

Binary file not shown.

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/br3-3-vs-cr0-7",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/br3-3-vs-cr0-7",
"title": "Beckenham vs Croydon: the same terraced house, about 31% cheaper per m\u00b2",
"hook": "\u00a3201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart",
"shocking_number": "31%",
"pricey": {
"sector": "BR3 3",
"name": "Beckenham",
"label": "Beckenham (BR3 3)",
"named": true,
"est_psqm": 7153,
"n": 4514
},
"twin": {
"sector": "CR0 7",
"name": "Croydon",
"label": "Croydon (CR0 7)",
"named": true,
"est_psqm": 4910,
"n": 5143
},
"stats": {
"gap_pct": 31.4,
"gap_per_sqm": 2243,
"gap_on_90sqm": 201870,
"gap_on_avg_home": 214206,
"dominant_type": "Terraced",
"build_year": 1940,
"good_secondary_catchments": 7.8,
"station_km": 0.73,
"distance_km": 2.02
},
"map_query": "lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/ha7-2-vs-ha3-0",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/ha7-2-vs-ha3-0",
"title": "Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m\u00b2",
"hook": "\u00a3106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart",
"shocking_number": "17%",
"pricey": {
"sector": "HA7 2",
"name": "Stanmore",
"label": "Stanmore (HA7 2)",
"named": true,
"est_psqm": 6834,
"n": 2775
},
"twin": {
"sector": "HA3 0",
"name": "Kenton",
"label": "Kenton (HA3 0)",
"named": true,
"est_psqm": 5646,
"n": 3122
},
"stats": {
"gap_pct": 17.4,
"gap_per_sqm": 1188,
"gap_on_90sqm": 106920,
"gap_on_avg_home": 108108,
"dominant_type": "Semi-Detached",
"build_year": 1940,
"good_secondary_catchments": 2.9,
"station_km": 1.31,
"distance_km": 2.57
},
"map_query": "lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/ig8-7-vs-ig6-2",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/ig8-7-vs-ig6-2",
"title": "Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m\u00b2",
"hook": "\u00a3164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart",
"shocking_number": "26%",
"pricey": {
"sector": "IG8 7",
"name": "Woodford Green",
"label": "Woodford Green (IG8 7)",
"named": true,
"est_psqm": 7148,
"n": 2965
},
"twin": {
"sector": "IG6 2",
"name": "Barkingside",
"label": "Barkingside (IG6 2)",
"named": true,
"est_psqm": 5325,
"n": 4423
},
"stats": {
"gap_pct": 25.5,
"gap_per_sqm": 1823,
"gap_on_90sqm": 164070,
"gap_on_avg_home": 143105,
"dominant_type": "Terraced",
"build_year": 1958,
"good_secondary_catchments": 2.8,
"station_km": 0.58,
"distance_km": 2.98
},
"map_query": "lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/l16-7-vs-l14-6",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/l16-7-vs-l14-6",
"title": "Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m\u00b2",
"hook": "\u00a3106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart",
"shocking_number": "30%",
"pricey": {
"sector": "L16 7",
"name": "Childwall",
"label": "Childwall (L16 7)",
"named": true,
"est_psqm": 4026,
"n": 500
},
"twin": {
"sector": "L14 6",
"name": "Broadgreen",
"label": "Broadgreen (L14 6)",
"named": true,
"est_psqm": 2840,
"n": 809
},
"stats": {
"gap_pct": 29.5,
"gap_per_sqm": 1186,
"gap_on_90sqm": 106740,
"gap_on_avg_home": 117414,
"dominant_type": "Semi-Detached",
"build_year": 1940,
"good_secondary_catchments": 5.1,
"station_km": 1.22,
"distance_km": 1.88
},
"map_query": "lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/m40-5-vs-m9-4",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/m40-5-vs-m9-4",
"title": "Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m\u00b2",
"hook": "\u00a3106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart",
"shocking_number": "42%",
"pricey": {
"sector": "M40 5",
"name": "Newton Heath",
"label": "Newton Heath (M40 5)",
"named": true,
"est_psqm": 2812,
"n": 1632
},
"twin": {
"sector": "M9 4",
"name": "Harpurhey",
"label": "Harpurhey (M9 4)",
"named": true,
"est_psqm": 1626,
"n": 3530
},
"stats": {
"gap_pct": 42.2,
"gap_per_sqm": 1186,
"gap_on_90sqm": 106740,
"gap_on_avg_home": 91915,
"dominant_type": "Terraced",
"build_year": 1958,
"good_secondary_catchments": 2.6,
"station_km": 0.72,
"distance_km": 1.18
},
"map_query": "lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/rm14-2-vs-rm12-5",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/rm14-2-vs-rm12-5",
"title": "Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m\u00b2",
"hook": "\u00a3115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart",
"shocking_number": "20%",
"pricey": {
"sector": "RM14 2",
"name": "Upminster",
"label": "Upminster (RM14 2)",
"named": true,
"est_psqm": 6360,
"n": 3026
},
"twin": {
"sector": "RM12 5",
"name": "Hornchurch",
"label": "Hornchurch (RM12 5)",
"named": true,
"est_psqm": 5079,
"n": 3133
},
"stats": {
"gap_pct": 20.1,
"gap_per_sqm": 1281,
"gap_on_90sqm": 115290,
"gap_on_avg_home": 111447,
"dominant_type": "Semi-Detached",
"build_year": 1940,
"good_secondary_catchments": 3.7,
"station_km": 0.77,
"distance_km": 2.99
},
"map_query": "lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/se28-8-vs-da18-4",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/se28-8-vs-da18-4",
"title": "SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m\u00b2",
"hook": "\u00a3129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart",
"shocking_number": "30%",
"pricey": {
"sector": "SE28 8",
"name": null,
"label": "SE28 8",
"named": false,
"est_psqm": 4850,
"n": 5033
},
"twin": {
"sector": "DA18 4",
"name": null,
"label": "DA18 4",
"named": false,
"est_psqm": 3409,
"n": 1063
},
"stats": {
"gap_pct": 29.7,
"gap_per_sqm": 1441,
"gap_on_90sqm": 129690,
"gap_on_avg_home": 104112,
"dominant_type": "Terraced",
"build_year": 1993,
"good_secondary_catchments": 2.8,
"station_km": 1.39,
"distance_km": 1.72
},
"map_query": "lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/sw1x-8-vs-sw7-2",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/sw1x-8-vs-sw7-2",
"title": "SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m\u00b2",
"hook": "\u00a31,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart",
"shocking_number": "42%",
"pricey": {
"sector": "SW1X 8",
"name": null,
"label": "SW1X 8",
"named": false,
"est_psqm": 26735,
"n": 1410
},
"twin": {
"sector": "SW7 2",
"name": null,
"label": "SW7 2",
"named": false,
"est_psqm": 15611,
"n": 1126
},
"stats": {
"gap_pct": 41.6,
"gap_per_sqm": 11124,
"gap_on_90sqm": 1001160,
"gap_on_avg_home": 1301508,
"dominant_type": "Flats/Maisonettes",
"build_year": 1890,
"good_secondary_catchments": 3.7,
"station_km": 0.43,
"distance_km": 1.31
},
"map_query": "lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/tw12-3-vs-kt8-1",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/tw12-3-vs-kt8-1",
"title": "Hampton vs East Molesey: the same terraced house, about 19% cheaper per m\u00b2",
"hook": "\u00a3120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart",
"shocking_number": "19%",
"pricey": {
"sector": "TW12 3",
"name": "Hampton",
"label": "Hampton (TW12 3)",
"named": true,
"est_psqm": 7042,
"n": 2527
},
"twin": {
"sector": "KT8 1",
"name": "East Molesey",
"label": "East Molesey (KT8 1)",
"named": true,
"est_psqm": 5708,
"n": 1567
},
"stats": {
"gap_pct": 18.9,
"gap_per_sqm": 1334,
"gap_on_90sqm": 120060,
"gap_on_avg_home": 107053,
"dominant_type": "Terraced",
"build_year": 1979,
"good_secondary_catchments": 3.2,
"station_km": 1.27,
"distance_km": 2.23
},
"map_query": "lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/tw2-7-vs-tw3-2",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/tw2-7-vs-tw3-2",
"title": "Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m\u00b2",
"hook": "\u00a3121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart",
"shocking_number": "19%",
"pricey": {
"sector": "TW2 7",
"name": "Twickenham",
"label": "Twickenham (TW2 7)",
"named": true,
"est_psqm": 6971,
"n": 3377
},
"twin": {
"sector": "TW3 2",
"name": "Hounslow",
"label": "Hounslow (TW3 2)",
"named": true,
"est_psqm": 5620,
"n": 2964
},
"stats": {
"gap_pct": 19.4,
"gap_per_sqm": 1351,
"gap_on_90sqm": 121590,
"gap_on_avg_home": 113821,
"dominant_type": "Semi-Detached",
"build_year": 1940,
"good_secondary_catchments": 5.4,
"station_km": 0.59,
"distance_km": 1.0
},
"map_query": "lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/w1j-7-vs-sw7-3",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/w1j-7-vs-sw7-3",
"title": "W1J 7 vs SW7 3: the same flat, about 41% cheaper per m\u00b2",
"hook": "\u00a31,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart",
"shocking_number": "41%",
"pricey": {
"sector": "W1J 7",
"name": "Mayfair",
"label": "Mayfair (W1J 7)",
"named": true,
"est_psqm": 32986,
"n": 724
},
"twin": {
"sector": "SW7 3",
"name": null,
"label": "SW7 3",
"named": false,
"est_psqm": 19392,
"n": 2581
},
"stats": {
"gap_pct": 41.2,
"gap_per_sqm": 13594,
"gap_on_90sqm": 1223460,
"gap_on_avg_home": 1077324,
"dominant_type": "Flats/Maisonettes",
"build_year": 1914,
"good_secondary_catchments": 2.0,
"station_km": 0.3,
"distance_km": 2.6
},
"map_query": "lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/w1j-8-vs-sw1a-2",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/w1j-8-vs-sw1a-2",
"title": "W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m\u00b2",
"hook": "\u00a3916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart",
"shocking_number": "37%",
"pricey": {
"sector": "W1J 8",
"name": "Mayfair",
"label": "Mayfair (W1J 8)",
"named": true,
"est_psqm": 27270,
"n": 295
},
"twin": {
"sector": "SW1A 2",
"name": null,
"label": "SW1A 2",
"named": false,
"est_psqm": 17088,
"n": 261
},
"stats": {
"gap_pct": 37.3,
"gap_per_sqm": 10182,
"gap_on_90sqm": 916380,
"gap_on_avg_home": 1089474,
"dominant_type": "Flats/Maisonettes",
"build_year": 2000,
"good_secondary_catchments": 2.0,
"station_km": 0.18,
"distance_km": 1.31
},
"map_query": "lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/w1k-2-vs-sw1x-0",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/w1k-2-vs-sw1x-0",
"title": "W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m\u00b2",
"hook": "\u00a3978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart",
"shocking_number": "32%",
"pricey": {
"sector": "W1K 2",
"name": "Mayfair",
"label": "Mayfair (W1K 2)",
"named": true,
"est_psqm": 34362,
"n": 591
},
"twin": {
"sector": "SW1X 0",
"name": null,
"label": "SW1X 0",
"named": false,
"est_psqm": 23489,
"n": 1606
},
"stats": {
"gap_pct": 31.6,
"gap_per_sqm": 10873,
"gap_on_90sqm": 978570,
"gap_on_avg_home": 1293887,
"dominant_type": "Flats/Maisonettes",
"build_year": 1914,
"good_secondary_catchments": 2.0,
"station_km": 0.5,
"distance_km": 1.62
},
"map_query": "lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/w1u-4-vs-nw1-4",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/w1u-4-vs-nw1-4",
"title": "Marylebone vs Camden: the same flat, about 43% cheaper per m\u00b2",
"hook": "\u00a3942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart",
"shocking_number": "43%",
"pricey": {
"sector": "W1U 4",
"name": "Marylebone",
"label": "Marylebone (W1U 4)",
"named": true,
"est_psqm": 24238,
"n": 984
},
"twin": {
"sector": "NW1 4",
"name": "Camden",
"label": "Camden (NW1 4)",
"named": true,
"est_psqm": 13766,
"n": 1340
},
"stats": {
"gap_pct": 43.2,
"gap_per_sqm": 10472,
"gap_on_90sqm": 942480,
"gap_on_avg_home": 759220,
"dominant_type": "Flats/Maisonettes",
"build_year": 1940,
"good_secondary_catchments": 1.0,
"station_km": 0.44,
"distance_km": 0.97
},
"map_query": "lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": false,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,42 @@
{
"slug": "cheaper-twin/wc2a-2-vs-ec2a-2",
"type": "cheaper_twin",
"page_path": "/cheaper-twin/wc2a-2-vs-ec2a-2",
"title": "WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m\u00b2",
"hook": "\u00a3981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart",
"shocking_number": "43%",
"pricey": {
"sector": "WC2A 2",
"name": null,
"label": "WC2A 2",
"named": false,
"est_psqm": 25482,
"n": 254
},
"twin": {
"sector": "EC2A 2",
"name": null,
"label": "EC2A 2",
"named": false,
"est_psqm": 14576,
"n": 772
},
"stats": {
"gap_pct": 42.8,
"gap_per_sqm": 10906,
"gap_on_90sqm": 981540,
"gap_on_avg_home": 834309,
"dominant_type": "Flats/Maisonettes",
"build_year": 2019,
"good_secondary_catchments": 2.0,
"station_km": 0.42,
"distance_km": 2.3
},
"map_query": "lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"map_url": "https://perfect-postcode.co.uk/?lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"og_image": "https://perfect-postcode.co.uk/api/screenshot?og=1&lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11",
"methodology": "Postcode sectors (e.g. N10 3) compared on estimated \u00a3/m\u00b2 of floor space. A pair is only called a 'twin' when the two sectors share the dominant property type, build era (\u00b130y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,28 @@
{
"slug": "square-metres-per-100k",
"type": "national_table",
"page_path": "/square-metres-per-100k",
"title": "How many square metres \u00a3100,000 buys across England",
"shocking_number": "152 m\u00b2 vs 3 m\u00b2",
"hook": "\u00a3100k buys ~152 m\u00b2 of floor space in BD21 3 but only ~3 m\u00b2 in Mayfair (W1K 2)",
"stats": {
"best": {
"sector": "BD21 3",
"est_psqm": 660,
"sqm_per_100k": 151.6,
"n": 1377
},
"dearest": {
"sector": "W1K 2",
"est_psqm": 34362,
"sqm_per_100k": 2.9,
"n": 591
},
"n_sectors": 7560
},
"map_query": "zoom=6&filter=Est.%20price%20per%20sqm:0:4000",
"methodology": "100000 \u00f7 median estimated \u00a3/m\u00b2, per England postcode sector with sufficient sales.",
"needs_name_check": true,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"sources": "HM Land Registry \u00b7 EPC (DLUHC) \u00b7 Ofsted \u00b7 DfT \u00b7 ONS \u00b7 Police.uk"
}

View file

@ -0,0 +1,120 @@
# Findings: review before publishing
16 findings generated from analysis/out/cheaper_twins.parquet.
**Check the place names** (⚠ = unnamed sector, needs a label in analysis/place_names.json) and spot-check a couple of numbers. Then these feed the page batch + video factory.
| ⚠ | Title | Hook number | Page path | Deep link |
|---|-------|-------------|-----------|-----------|
| ⚠ | W1J 7 vs SW7 3: the same flat, about 41% cheaper per m² | 41% | `/cheaper-twin/w1j-7-vs-sw7-3` | [map](https://perfect-postcode.co.uk/?lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m² | 42% | `/cheaper-twin/sw1x-8-vs-sw7-2` | [map](https://perfect-postcode.co.uk/?lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m² | 43% | `/cheaper-twin/wc2a-2-vs-ec2a-2` | [map](https://perfect-postcode.co.uk/?lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m² | 32% | `/cheaper-twin/w1k-2-vs-sw1x-0` | [map](https://perfect-postcode.co.uk/?lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Marylebone vs Camden: the same flat, about 43% cheaper per m² | 43% | `/cheaper-twin/w1u-4-vs-nw1-4` | [map](https://perfect-postcode.co.uk/?lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m² | 37% | `/cheaper-twin/w1j-8-vs-sw1a-2` | [map](https://perfect-postcode.co.uk/?lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Beckenham vs Croydon: the same terraced house, about 31% cheaper per m² | 31% | `/cheaper-twin/br3-3-vs-cr0-7` | [map](https://perfect-postcode.co.uk/?lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m² | 26% | `/cheaper-twin/ig8-7-vs-ig6-2` | [map](https://perfect-postcode.co.uk/?lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m² | 19% | `/cheaper-twin/tw2-7-vs-tw3-2` | [map](https://perfect-postcode.co.uk/?lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Hampton vs East Molesey: the same terraced house, about 19% cheaper per m² | 19% | `/cheaper-twin/tw12-3-vs-kt8-1` | [map](https://perfect-postcode.co.uk/?lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m² | 20% | `/cheaper-twin/rm14-2-vs-rm12-5` | [map](https://perfect-postcode.co.uk/?lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m² | 17% | `/cheaper-twin/ha7-2-vs-ha3-0` | [map](https://perfect-postcode.co.uk/?lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m² | 42% | `/cheaper-twin/m40-5-vs-m9-4` | [map](https://perfect-postcode.co.uk/?lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| | Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m² | 30% | `/cheaper-twin/l16-7-vs-l14-6` | [map](https://perfect-postcode.co.uk/?lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m² | 30% | `/cheaper-twin/se28-8-vs-da18-4` | [map](https://perfect-postcode.co.uk/?lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11) |
| ⚠ | How many square metres £100,000 buys across England | 152 m² vs 3 m² | `/square-metres-per-100k` | [map](https://perfect-postcode.co.uk/?zoom=6&filter=Est.%20price%20per%20sqm:0:4000) |
## Per-finding detail
### W1J 7 vs SW7 3: the same flat, about 41% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/w1j-7-vs-sw7-3`
- **Hook:** £1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart
- **Mayfair (W1J 7)** £32,986/m² (n=724) → **SW7 3** £19,392/m² (n=2,581) · gap 41.2% · Flats/Maisonettes, ~1914
- **OG card / deep link:** `lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11`
### SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/sw1x-8-vs-sw7-2`
- **Hook:** £1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart
- **SW1X 8** £26,735/m² (n=1,410) → **SW7 2** £15,611/m² (n=1,126) · gap 41.6% · Flats/Maisonettes, ~1890
- **OG card / deep link:** `lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11`
### WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/wc2a-2-vs-ec2a-2`
- **Hook:** £981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart
- **WC2A 2** £25,482/m² (n=254) → **EC2A 2** £14,576/m² (n=772) · gap 42.8% · Flats/Maisonettes, ~2019
- **OG card / deep link:** `lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11`
### W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/w1k-2-vs-sw1x-0`
- **Hook:** £978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart
- **Mayfair (W1K 2)** £34,362/m² (n=591) → **SW1X 0** £23,489/m² (n=1,606) · gap 31.6% · Flats/Maisonettes, ~1914
- **OG card / deep link:** `lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Marylebone vs Camden: the same flat, about 43% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/w1u-4-vs-nw1-4`
- **Hook:** £942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart
- **Marylebone (W1U 4)** £24,238/m² (n=984) → **Camden (NW1 4)** £13,766/m² (n=1,340) · gap 43.2% · Flats/Maisonettes, ~1940
- **OG card / deep link:** `lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11`
### W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/w1j-8-vs-sw1a-2`
- **Hook:** £916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart
- **Mayfair (W1J 8)** £27,270/m² (n=295) → **SW1A 2** £17,088/m² (n=261) · gap 37.3% · Flats/Maisonettes, ~2000
- **OG card / deep link:** `lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Beckenham vs Croydon: the same terraced house, about 31% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/br3-3-vs-cr0-7`
- **Hook:** £201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart
- **Beckenham (BR3 3)** £7,153/m² (n=4,514) → **Croydon (CR0 7)** £4,910/m² (n=5,143) · gap 31.4% · Terraced, ~1940
- **OG card / deep link:** `lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/ig8-7-vs-ig6-2`
- **Hook:** £164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart
- **Woodford Green (IG8 7)** £7,148/m² (n=2,965) → **Barkingside (IG6 2)** £5,325/m² (n=4,423) · gap 25.5% · Terraced, ~1958
- **OG card / deep link:** `lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/tw2-7-vs-tw3-2`
- **Hook:** £121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart
- **Twickenham (TW2 7)** £6,971/m² (n=3,377) → **Hounslow (TW3 2)** £5,620/m² (n=2,964) · gap 19.4% · Semi-Detached, ~1940
- **OG card / deep link:** `lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Hampton vs East Molesey: the same terraced house, about 19% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/tw12-3-vs-kt8-1`
- **Hook:** £120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart
- **Hampton (TW12 3)** £7,042/m² (n=2,527) → **East Molesey (KT8 1)** £5,708/m² (n=1,567) · gap 18.9% · Terraced, ~1979
- **OG card / deep link:** `lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/rm14-2-vs-rm12-5`
- **Hook:** £115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart
- **Upminster (RM14 2)** £6,360/m² (n=3,026) → **Hornchurch (RM12 5)** £5,079/m² (n=3,133) · gap 20.1% · Semi-Detached, ~1940
- **OG card / deep link:** `lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/ha7-2-vs-ha3-0`
- **Hook:** £106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart
- **Stanmore (HA7 2)** £6,834/m² (n=2,775) → **Kenton (HA3 0)** £5,646/m² (n=3,122) · gap 17.4% · Semi-Detached, ~1940
- **OG card / deep link:** `lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/m40-5-vs-m9-4`
- **Hook:** £106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart
- **Newton Heath (M40 5)** £2,812/m² (n=1,632) → **Harpurhey (M9 4)** £1,626/m² (n=3,530) · gap 42.2% · Terraced, ~1958
- **OG card / deep link:** `lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11`
### Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/l16-7-vs-l14-6`
- **Hook:** £106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart
- **Childwall (L16 7)** £4,026/m² (n=500) → **Broadgreen (L14 6)** £2,840/m² (n=809) · gap 29.5% · Semi-Detached, ~1940
- **OG card / deep link:** `lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11`
### SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m²
- **Type:** cheaper_twin · **Page:** `/cheaper-twin/se28-8-vs-da18-4`
- **Hook:** £129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart
- **SE28 8** £4,850/m² (n=5,033) → **DA18 4** £3,409/m² (n=1,063) · gap 29.7% · Terraced, ~1993
- **OG card / deep link:** `lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11`
### How many square metres £100,000 buys across England
- **Type:** national_table · **Page:** `/square-metres-per-100k`
- **Hook:** £100k buys ~152 m² of floor space in BD21 3 but only ~3 m² in Mayfair (W1K 2)
- **OG card / deep link:** `zoom=6&filter=Est.%20price%20per%20sqm:0:4000`

View file

@ -0,0 +1,268 @@
{
"generated_with": "analysis/cheaper_twins.py",
"params": {
"min_props": 150,
"min_recorded": 40,
"max_km": 3.0,
"min_gap": 0.15,
"max_gap": 0.45,
"build_band": 30,
"school_tol": 1.5,
"station_max": 1.5,
"station_tol": 0.9,
"crime_ratio": 1.5,
"owner_tol": 22,
"degree_tol": 22,
"age_tol": 12,
"floor_ratio": 0.72,
"min_abs_gap": 20000
},
"n_sectors": 7560,
"n_twin_pairs": 415,
"attribution": "Contains HM Land Registry data \u00a9 Crown copyright and database right. Licensed under the Open Government Licence v3.0.",
"best_value_sector": {
"sector": "BD21 3",
"est_psqm": 660,
"sqm_per_100k": 151.6,
"n": 1377
},
"dearest_sector": {
"sector": "W1K 2",
"est_psqm": 34362,
"sqm_per_100k": 2.9,
"n": 591
},
"biggest_twin_gap": {
"pricey_sector": "W1U 3",
"twin_sector": "EC1N 7",
"pricey_psqm": 16948,
"twin_psqm": 9362,
"gap_pct": 44.8,
"gap_per_sqm": 7586,
"gap_on_avg_home": 504469,
"gap_on_90sqm": 682740,
"dist_km": 2.89,
"dominant_type": "Flats/Maisonettes",
"build_year": 1940,
"good_secondary": 1.3,
"station_km": 0.41,
"pricey_lat": 51.51712,
"pricey_lon": -0.15271,
"twin_lat": 51.52057,
"twin_lon": -0.11049,
"pricey_n": 302,
"twin_n": 747
},
"top_twins": [
{
"pricey_sector": "W1U 3",
"twin_sector": "EC1N 7",
"pricey_psqm": 16948,
"twin_psqm": 9362,
"gap_pct": 44.8,
"gap_per_sqm": 7586,
"gap_on_avg_home": 504469,
"gap_on_90sqm": 682740,
"dist_km": 2.89,
"dominant_type": "Flats/Maisonettes",
"build_year": 1940,
"good_secondary": 1.3,
"station_km": 0.41,
"pricey_lat": 51.51712,
"pricey_lon": -0.15271,
"twin_lat": 51.52057,
"twin_lon": -0.11049,
"pricey_n": 302,
"twin_n": 747
},
{
"pricey_sector": "WC2R 1",
"twin_sector": "SW1V 1",
"pricey_psqm": 19997,
"twin_psqm": 11119,
"gap_pct": 44.4,
"gap_per_sqm": 8878,
"gap_on_avg_home": 665850,
"gap_on_90sqm": 799020,
"dist_km": 2.82,
"dominant_type": "Flats/Maisonettes",
"build_year": 2017,
"good_secondary": 2.0,
"station_km": 0.21,
"pricey_lat": 51.51228,
"pricey_lon": -0.11525,
"twin_lat": 51.49297,
"twin_lon": -0.14234,
"pricey_n": 316,
"twin_n": 1408
},
{
"pricey_sector": "L8 7",
"twin_sector": "L7 0",
"pricey_psqm": 2757,
"twin_psqm": 1541,
"gap_pct": 44.1,
"gap_per_sqm": 1216,
"gap_on_avg_home": 78432,
"gap_on_90sqm": 109440,
"dist_km": 2.36,
"dominant_type": "Flats/Maisonettes",
"build_year": 1914,
"good_secondary": 2.3,
"station_km": 1.04,
"pricey_lat": 53.39791,
"pricey_lon": -2.96459,
"twin_lat": 53.41174,
"twin_lon": -2.93813,
"pricey_n": 2054,
"twin_n": 2208
},
{
"pricey_sector": "L3 2",
"twin_sector": "L5 5",
"pricey_psqm": 1642,
"twin_psqm": 920,
"gap_pct": 44.0,
"gap_per_sqm": 722,
"gap_on_avg_home": 47291,
"gap_on_90sqm": 64980,
"dist_km": 1.61,
"dominant_type": "Flats/Maisonettes",
"build_year": 2004,
"good_secondary": 1.5,
"station_km": 0.47,
"pricey_lat": 53.41155,
"pricey_lon": -2.98583,
"twin_lat": 53.42544,
"twin_lon": -2.97852,
"pricey_n": 1341,
"twin_n": 706
},
{
"pricey_sector": "S2 4",
"twin_sector": "S3 9",
"pricey_psqm": 2468,
"twin_psqm": 1402,
"gap_pct": 43.2,
"gap_per_sqm": 1066,
"gap_on_avg_home": 73554,
"gap_on_90sqm": 95940,
"dist_km": 2.82,
"dominant_type": "Flats/Maisonettes",
"build_year": 1986,
"good_secondary": 2.6,
"station_km": 0.72,
"pricey_lat": 53.36993,
"pricey_lon": -1.46978,
"twin_lat": 53.39521,
"twin_lon": -1.46478,
"pricey_n": 2423,
"twin_n": 1778
},
{
"pricey_sector": "W1U 4",
"twin_sector": "NW1 4",
"pricey_psqm": 24238,
"twin_psqm": 13766,
"gap_pct": 43.2,
"gap_per_sqm": 10472,
"gap_on_avg_home": 759220,
"gap_on_90sqm": 942480,
"dist_km": 0.97,
"dominant_type": "Flats/Maisonettes",
"build_year": 1940,
"good_secondary": 1.0,
"station_km": 0.44,
"pricey_lat": 51.51958,
"pricey_lon": -0.15295,
"twin_lat": 51.52803,
"twin_lon": -0.14886,
"pricey_n": 984,
"twin_n": 1340
},
{
"pricey_sector": "WC2A 2",
"twin_sector": "EC2A 2",
"pricey_psqm": 25482,
"twin_psqm": 14576,
"gap_pct": 42.8,
"gap_per_sqm": 10906,
"gap_on_avg_home": 834309,
"gap_on_90sqm": 981540,
"dist_km": 2.3,
"dominant_type": "Flats/Maisonettes",
"build_year": 2019,
"good_secondary": 2.0,
"station_km": 0.42,
"pricey_lat": 51.51496,
"pricey_lon": -0.11456,
"twin_lat": 51.52119,
"twin_lon": -0.08217,
"pricey_n": 254,
"twin_n": 772
},
{
"pricey_sector": "M40 5",
"twin_sector": "M9 4",
"pricey_psqm": 2812,
"twin_psqm": 1626,
"gap_pct": 42.2,
"gap_per_sqm": 1186,
"gap_on_avg_home": 91915,
"gap_on_90sqm": 106740,
"dist_km": 1.18,
"dominant_type": "Terraced",
"build_year": 1958,
"good_secondary": 2.6,
"station_km": 0.72,
"pricey_lat": 53.51372,
"pricey_lon": -2.18713,
"twin_lat": 53.51214,
"twin_lon": -2.20436,
"pricey_n": 1632,
"twin_n": 3530
},
{
"pricey_sector": "W11 2",
"twin_sector": "NW1 6",
"pricey_psqm": 19262,
"twin_psqm": 11154,
"gap_pct": 42.1,
"gap_per_sqm": 8108,
"gap_on_avg_home": 482426,
"gap_on_90sqm": 729720,
"dist_km": 2.94,
"dominant_type": "Flats/Maisonettes",
"build_year": 1890,
"good_secondary": 4.0,
"station_km": 0.53,
"pricey_lat": 51.51407,
"pricey_lon": -0.20373,
"twin_lat": 51.5237,
"twin_lon": -0.16342,
"pricey_n": 4082,
"twin_n": 3312
},
{
"pricey_sector": "N10 3",
"twin_sector": "N12 0",
"pricey_psqm": 9590,
"twin_psqm": 5554,
"gap_pct": 42.1,
"gap_per_sqm": 4036,
"gap_on_avg_home": 296646,
"gap_on_90sqm": 363240,
"dist_km": 2.97,
"dominant_type": "Flats/Maisonettes",
"build_year": 1914,
"good_secondary": 3.9,
"station_km": 1.16,
"pricey_lat": 51.58839,
"pricey_lon": -0.14404,
"twin_lat": 51.60872,
"twin_lon": -0.17254,
"pricey_n": 3984,
"twin_n": 3282
}
]
}

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,23 @@
# Video kits: film one per 12 weeks
Each kit is a complete, payoff-first faceless video you can screen-record off the live map. Pick one, open its Map URL, record, read the narration (human voice), export one clean cut + a 9:16 Short.
**Priority order (relatable family-home twins first, since they convert better than prime London):**
| Kit | Hook | File |
|-----|------|------|
| Beckenham → Croydon | 31% / £201,870 | `br3-3-vs-cr0-7.md` |
| Woodford Green → Barkingside | 26% / £164,070 | `ig8-7-vs-ig6-2.md` |
| SE28 8 → DA18 4 | 30% / £129,690 | `se28-8-vs-da18-4.md` |
| Twickenham → Hounslow | 19% / £121,590 | `tw2-7-vs-tw3-2.md` |
| Hampton → East Molesey | 19% / £120,060 | `tw12-3-vs-kt8-1.md` |
| Upminster → Hornchurch | 20% / £115,290 | `rm14-2-vs-rm12-5.md` |
| Stanmore → Kenton | 17% / £106,920 | `ha7-2-vs-ha3-0.md` |
| Childwall → Broadgreen | 30% / £106,740 | `l16-7-vs-l14-6.md` |
| Newton Heath → Harpurhey | 42% / £106,740 | `m40-5-vs-m9-4.md` |
| Mayfair → SW7 3 | 41% / £1,223,460 | `w1j-7-vs-sw7-3.md` |
| SW1X 8 → SW7 2 | 42% / £1,001,160 | `sw1x-8-vs-sw7-2.md` |
| WC2A 2 → EC2A 2 | 43% / £981,540 | `wc2a-2-vs-ec2a-2.md` |
| Mayfair → SW1X 0 | 32% / £978,570 | `w1k-2-vs-sw1x-0.md` |
| Marylebone → Camden | 43% / £942,480 | `w1u-4-vs-nw1-4.md` |
| Mayfair → SW1A 2 | 37% / £916,380 | `w1j-8-vs-sw1a-2.md` |

View file

@ -0,0 +1,79 @@
# Video kit: Beckenham vs Croydon
**Page:** https://perfect-postcode.co.uk/cheaper-twin/br3-3-vs-cr0-7 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£201,870 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£201,870 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (terraced houses) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Beckenham £7,153 vs Croydon £4,910. | Caption: '31% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Beckenham. And this is Croydon, right next door. Same station. Same secondary school catchment. The same kind of home: terraced houses built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Beckenham: £7,153. Croydon: £4,910. That's 31% cheaper, about £201,870 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Beckenham vs Croydon
- Same station. Same schools.
- £201,870 cheaper
- Same terraced, ~1940
- 31% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Beckenham vs Croydon: the same terraced house, about 31% cheaper per m²
2. Beckenham vs Croydon: same station, same schools, £201,870 cheaper
3. Why Croydon is the smart-money version of Beckenham (31% less per m²)
**Thumbnail text:** big number `£201,870 cheaper` + the two names `Beckenham → Croydon`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.38969&lon=-0.04244&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5200&filter=Good%2B%20secondary%20school%20catchments:1:11
Beckenham and Croydon share a station, a school catchment and the same era of housing, but Croydon costs about 31% less per square metre (£201,870 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Beckenham & Croydon)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Beckenham £7,153 → Croydon £4,910) + caption '31% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-br3-3-vs-cr0-7",
"city": "london",
"promptText": "Best value terraceds near Beckenham: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
5200
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Croydon: same life, 31% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Stanmore vs Kenton
**Page:** https://perfect-postcode.co.uk/cheaper-twin/ha7-2-vs-ha3-0 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£106,920 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£106,920 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (semi-detached houses) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Stanmore £6,834 vs Kenton £5,646. | Caption: '17% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Stanmore. And this is Kenton, right next door. Same station. Same secondary school catchment. The same kind of home: semi-detached houses built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Stanmore: £6,834. Kenton: £5,646. That's 17% cheaper, about £106,920 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Stanmore vs Kenton
- Same station. Same schools.
- £106,920 cheaper
- Same semi-detached, ~1940
- 17% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m²
2. Stanmore vs Kenton: same station, same schools, £106,920 cheaper
3. Why Kenton is the smart-money version of Stanmore (17% less per m²)
**Thumbnail text:** big number `£106,920 cheaper` + the two names `Stanmore → Kenton`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.59199&lon=-0.3079&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11
Stanmore and Kenton share a station, a school catchment and the same era of housing, but Kenton costs about 17% less per square metre (£106,920 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Stanmore & Kenton)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Stanmore £6,834 → Kenton £5,646) + caption '17% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-ha7-2-vs-ha3-0",
"city": "london",
"promptText": "Best value semi-detacheds near Stanmore: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
5900
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Kenton: same life, 17% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Woodford Green vs Barkingside
**Page:** https://perfect-postcode.co.uk/cheaper-twin/ig8-7-vs-ig6-2 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£164,070 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£164,070 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (terraced houses) and build era (~1958). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Woodford Green £7,148 vs Barkingside £5,325. | Caption: '26% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Woodford Green. And this is Barkingside, right next door. Same station. Same secondary school catchment. The same kind of home: terraced houses built around 1958. On every measure that moves price, they're twins. But watch the price per square metre. Woodford Green: £7,148. Barkingside: £5,325. That's 26% cheaper, about £164,070 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Woodford Green vs Barkingside
- Same station. Same schools.
- £164,070 cheaper
- Same terraced, ~1958
- 26% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m²
2. Woodford Green vs Barkingside: same station, same schools, £164,070 cheaper
3. Why Barkingside is the smart-money version of Woodford Green (26% less per m²)
**Thumbnail text:** big number `£164,070 cheaper` + the two names `Woodford Green → Barkingside`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.60238&lon=0.06063&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5600&filter=Good%2B%20secondary%20school%20catchments:1:11
Woodford Green and Barkingside share a station, a school catchment and the same era of housing, but Barkingside costs about 26% less per square metre (£164,070 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Woodford Green & Barkingside)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Woodford Green £7,148 → Barkingside £5,325) + caption '26% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-ig8-7-vs-ig6-2",
"city": "london",
"promptText": "Best value terraceds near Woodford Green: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
5600
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Barkingside: same life, 26% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Childwall vs Broadgreen
**Page:** https://perfect-postcode.co.uk/cheaper-twin/l16-7-vs-l14-6 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£106,740 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£106,740 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (semi-detached houses) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Childwall £4,026 vs Broadgreen £2,840. | Caption: '30% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Childwall. And this is Broadgreen, right next door. Same station. Same secondary school catchment. The same kind of home: semi-detached houses built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Childwall: £4,026. Broadgreen: £2,840. That's 30% cheaper, about £106,740 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Childwall vs Broadgreen
- Same station. Same schools.
- £106,740 cheaper
- Same semi-detached, ~1940
- 30% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m²
2. Childwall vs Broadgreen: same station, same schools, £106,740 cheaper
3. Why Broadgreen is the smart-money version of Childwall (30% less per m²)
**Thumbnail text:** big number `£106,740 cheaper` + the two names `Childwall → Broadgreen`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=53.40344&lon=-2.88529&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3000&filter=Good%2B%20secondary%20school%20catchments:1:11
Childwall and Broadgreen share a station, a school catchment and the same era of housing, but Broadgreen costs about 30% less per square metre (£106,740 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Childwall & Broadgreen)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Childwall £4,026 → Broadgreen £2,840) + caption '30% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-l16-7-vs-l14-6",
"city": "london",
"promptText": "Best value semi-detacheds near Childwall: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
3000
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Broadgreen: same life, 30% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Newton Heath vs Harpurhey
**Page:** https://perfect-postcode.co.uk/cheaper-twin/m40-5-vs-m9-4 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£106,740 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£106,740 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (terraced houses) and build era (~1958). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Newton Heath £2,812 vs Harpurhey £1,626. | Caption: '42% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Newton Heath. And this is Harpurhey, right next door. Same station. Same secondary school catchment. The same kind of home: terraced houses built around 1958. On every measure that moves price, they're twins. But watch the price per square metre. Newton Heath: £2,812. Harpurhey: £1,626. That's 42% cheaper, about £106,740 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Newton Heath vs Harpurhey
- Same station. Same schools.
- £106,740 cheaper
- Same terraced, ~1958
- 42% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m²
2. Newton Heath vs Harpurhey: same station, same schools, £106,740 cheaper
3. Why Harpurhey is the smart-money version of Newton Heath (42% less per m²)
**Thumbnail text:** big number `£106,740 cheaper` + the two names `Newton Heath → Harpurhey`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=53.51293&lon=-2.19574&zoom=12.5&filter=Est.%20price%20per%20sqm:0:1700&filter=Good%2B%20secondary%20school%20catchments:1:11
Newton Heath and Harpurhey share a station, a school catchment and the same era of housing, but Harpurhey costs about 42% less per square metre (£106,740 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Newton Heath & Harpurhey)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Newton Heath £2,812 → Harpurhey £1,626) + caption '42% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-m40-5-vs-m9-4",
"city": "manchester",
"promptText": "Best value terraceds near Newton Heath: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
1700
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Harpurhey: same life, 42% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Upminster vs Hornchurch
**Page:** https://perfect-postcode.co.uk/cheaper-twin/rm14-2-vs-rm12-5 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£115,290 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£115,290 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (semi-detached houses) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Upminster £6,360 vs Hornchurch £5,079. | Caption: '20% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Upminster. And this is Hornchurch, right next door. Same station. Same secondary school catchment. The same kind of home: semi-detached houses built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Upminster: £6,360. Hornchurch: £5,079. That's 20% cheaper, about £115,290 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Upminster vs Hornchurch
- Same station. Same schools.
- £115,290 cheaper
- Same semi-detached, ~1940
- 20% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m²
2. Upminster vs Hornchurch: same station, same schools, £115,290 cheaper
3. Why Hornchurch is the smart-money version of Upminster (20% less per m²)
**Thumbnail text:** big number `£115,290 cheaper` + the two names `Upminster → Hornchurch`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.54892&lon=0.22193&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5300&filter=Good%2B%20secondary%20school%20catchments:1:11
Upminster and Hornchurch share a station, a school catchment and the same era of housing, but Hornchurch costs about 20% less per square metre (£115,290 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Upminster & Hornchurch)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Upminster £6,360 → Hornchurch £5,079) + caption '20% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-rm14-2-vs-rm12-5",
"city": "london",
"promptText": "Best value semi-detacheds near Upminster: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
5300
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Hornchurch: same life, 20% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: SE28 8 vs DA18 4
**Page:** https://perfect-postcode.co.uk/cheaper-twin/se28-8-vs-da18-4 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£129,690 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£129,690 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (terraced houses) and build era (~1993). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: SE28 8 £4,850 vs DA18 4 £3,409. | Caption: '30% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is SE28 8. And this is DA18 4, right next door. Same station. Same secondary school catchment. The same kind of home: terraced houses built around 1993. On every measure that moves price, they're twins. But watch the price per square metre. SE28 8: £4,850. DA18 4: £3,409. That's 30% cheaper, about £129,690 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- SE28 8 vs DA18 4
- Same station. Same schools.
- £129,690 cheaper
- Same terraced, ~1993
- 30% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m²
2. SE28 8 vs DA18 4: same station, same schools, £129,690 cheaper
3. Why DA18 4 is the smart-money version of SE28 8 (30% less per m²)
**Thumbnail text:** big number `£129,690 cheaper` + the two names `SE28 8 → DA18 4`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.50039&lon=0.12568&zoom=12.5&filter=Est.%20price%20per%20sqm:0:3600&filter=Good%2B%20secondary%20school%20catchments:1:11
SE28 8 and DA18 4 share a station, a school catchment and the same era of housing, but DA18 4 costs about 30% less per square metre (£129,690 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (SE28 8 & DA18 4)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (SE28 8 £4,850 → DA18 4 £3,409) + caption '30% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-se28-8-vs-da18-4",
"city": "london",
"promptText": "Best value terraceds near SE28 8: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
3600
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "DA18 4: same life, 30% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: SW1X 8 vs SW7 2
**Page:** https://perfect-postcode.co.uk/cheaper-twin/sw1x-8-vs-sw7-2 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£1,001,160 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£1,001,160 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~1890). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: SW1X 8 £26,735 vs SW7 2 £15,611. | Caption: '42% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is SW1X 8. And this is SW7 2, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 1890. On every measure that moves price, they're twins. But watch the price per square metre. SW1X 8: £26,735. SW7 2: £15,611. That's 42% cheaper, about £1,001,160 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- SW1X 8 vs SW7 2
- Same station. Same schools.
- £1,001,160 cheaper
- Same flats/maisonettes, ~1890
- 42% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m²
2. SW1X 8 vs SW7 2: same station, same schools, £1,001,160 cheaper
3. Why SW7 2 is the smart-money version of SW1X 8 (42% less per m²)
**Thumbnail text:** big number `£1,001,160 cheaper` + the two names `SW1X 8 → SW7 2`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.49758&lon=-0.16439&zoom=12.5&filter=Est.%20price%20per%20sqm:0:16400&filter=Good%2B%20secondary%20school%20catchments:1:11
SW1X 8 and SW7 2 share a station, a school catchment and the same era of housing, but SW7 2 costs about 42% less per square metre (£1,001,160 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (SW1X 8 & SW7 2)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (SW1X 8 £26,735 → SW7 2 £15,611) + caption '42% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-sw1x-8-vs-sw7-2",
"city": "london",
"promptText": "Best value flats/maisonettess near SW1X 8: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
16400
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "SW7 2: same life, 42% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Hampton vs East Molesey
**Page:** https://perfect-postcode.co.uk/cheaper-twin/tw12-3-vs-kt8-1 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£120,060 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£120,060 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (terraced houses) and build era (~1979). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Hampton £7,042 vs East Molesey £5,708. | Caption: '19% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Hampton. And this is East Molesey, right next door. Same station. Same secondary school catchment. The same kind of home: terraced houses built around 1979. On every measure that moves price, they're twins. But watch the price per square metre. Hampton: £7,042. East Molesey: £5,708. That's 19% cheaper, about £120,060 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Hampton vs East Molesey
- Same station. Same schools.
- £120,060 cheaper
- Same terraced, ~1979
- 19% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Hampton vs East Molesey: the same terraced house, about 19% cheaper per m²
2. Hampton vs East Molesey: same station, same schools, £120,060 cheaper
3. Why East Molesey is the smart-money version of Hampton (19% less per m²)
**Thumbnail text:** big number `£120,060 cheaper` + the two names `Hampton → East Molesey`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.41616&lon=-0.37365&zoom=12.5&filter=Est.%20price%20per%20sqm:0:6000&filter=Good%2B%20secondary%20school%20catchments:1:11
Hampton and East Molesey share a station, a school catchment and the same era of housing, but East Molesey costs about 19% less per square metre (£120,060 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Hampton & East Molesey)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Hampton £7,042 → East Molesey £5,708) + caption '19% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-tw12-3-vs-kt8-1",
"city": "london",
"promptText": "Best value terraceds near Hampton: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
6000
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "East Molesey: same life, 19% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Twickenham vs Hounslow
**Page:** https://perfect-postcode.co.uk/cheaper-twin/tw2-7-vs-tw3-2 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£121,590 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£121,590 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (semi-detached houses) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Twickenham £6,971 vs Hounslow £5,620. | Caption: '19% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Twickenham. And this is Hounslow, right next door. Same station. Same secondary school catchment. The same kind of home: semi-detached houses built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Twickenham: £6,971. Hounslow: £5,620. That's 19% cheaper, about £121,590 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Twickenham vs Hounslow
- Same station. Same schools.
- £121,590 cheaper
- Same semi-detached, ~1940
- 19% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m²
2. Twickenham vs Hounslow: same station, same schools, £121,590 cheaper
3. Why Hounslow is the smart-money version of Twickenham (19% less per m²)
**Thumbnail text:** big number `£121,590 cheaper` + the two names `Twickenham → Hounslow`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.45678&lon=-0.35702&zoom=12.5&filter=Est.%20price%20per%20sqm:0:5900&filter=Good%2B%20secondary%20school%20catchments:1:11
Twickenham and Hounslow share a station, a school catchment and the same era of housing, but Hounslow costs about 19% less per square metre (£121,590 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Twickenham & Hounslow)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Twickenham £6,971 → Hounslow £5,620) + caption '19% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-tw2-7-vs-tw3-2",
"city": "london",
"promptText": "Best value semi-detacheds near Twickenham: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
5900
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Hounslow: same life, 19% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Mayfair vs SW7 3
**Page:** https://perfect-postcode.co.uk/cheaper-twin/w1j-7-vs-sw7-3 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£1,223,460 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£1,223,460 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~1914). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Mayfair £32,986 vs SW7 3 £19,392. | Caption: '41% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Mayfair. And this is SW7 3, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 1914. On every measure that moves price, they're twins. But watch the price per square metre. Mayfair: £32,986. SW7 3: £19,392. That's 41% cheaper, about £1,223,460 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Mayfair vs SW7 3
- Same station. Same schools.
- £1,223,460 cheaper
- Same flats/maisonettes, ~1914
- 41% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. W1J 7 vs SW7 3: the same flat, about 41% cheaper per m²
2. Mayfair vs SW7 3: same station, same schools, £1,223,460 cheaper
3. Why SW7 3 is the smart-money version of Mayfair (41% less per m²)
**Thumbnail text:** big number `£1,223,460 cheaper` + the two names `Mayfair → SW7 3`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.49856&lon=-0.16253&zoom=12.5&filter=Est.%20price%20per%20sqm:0:20400&filter=Good%2B%20secondary%20school%20catchments:1:11
Mayfair and SW7 3 share a station, a school catchment and the same era of housing, but SW7 3 costs about 41% less per square metre (£1,223,460 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Mayfair & SW7 3)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Mayfair £32,986 → SW7 3 £19,392) + caption '41% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-w1j-7-vs-sw7-3",
"city": "london",
"promptText": "Best value flats/maisonettess near Mayfair: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
20400
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "SW7 3: same life, 41% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Mayfair vs SW1A 2
**Page:** https://perfect-postcode.co.uk/cheaper-twin/w1j-8-vs-sw1a-2 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£916,380 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£916,380 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~2000). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Mayfair £27,270 vs SW1A 2 £17,088. | Caption: '37% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Mayfair. And this is SW1A 2, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 2000. On every measure that moves price, they're twins. But watch the price per square metre. Mayfair: £27,270. SW1A 2: £17,088. That's 37% cheaper, about £916,380 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Mayfair vs SW1A 2
- Same station. Same schools.
- £916,380 cheaper
- Same flats/maisonettes, ~2000
- 37% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m²
2. Mayfair vs SW1A 2: same station, same schools, £916,380 cheaper
3. Why SW1A 2 is the smart-money version of Mayfair (37% less per m²)
**Thumbnail text:** big number `£916,380 cheaper` + the two names `Mayfair → SW1A 2`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.50663&lon=-0.13494&zoom=12.5&filter=Est.%20price%20per%20sqm:0:17900&filter=Good%2B%20secondary%20school%20catchments:1:11
Mayfair and SW1A 2 share a station, a school catchment and the same era of housing, but SW1A 2 costs about 37% less per square metre (£916,380 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Mayfair & SW1A 2)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Mayfair £27,270 → SW1A 2 £17,088) + caption '37% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-w1j-8-vs-sw1a-2",
"city": "london",
"promptText": "Best value flats/maisonettess near Mayfair: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
17900
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "SW1A 2: same life, 37% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Mayfair vs SW1X 0
**Page:** https://perfect-postcode.co.uk/cheaper-twin/w1k-2-vs-sw1x-0 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£978,570 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£978,570 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~1914). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Mayfair £34,362 vs SW1X 0 £23,489. | Caption: '32% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Mayfair. And this is SW1X 0, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 1914. On every measure that moves price, they're twins. But watch the price per square metre. Mayfair: £34,362. SW1X 0: £23,489. That's 32% cheaper, about £978,570 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Mayfair vs SW1X 0
- Same station. Same schools.
- £978,570 cheaper
- Same flats/maisonettes, ~1914
- 32% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m²
2. Mayfair vs SW1X 0: same station, same schools, £978,570 cheaper
3. Why SW1X 0 is the smart-money version of Mayfair (32% less per m²)
**Thumbnail text:** big number `£978,570 cheaper` + the two names `Mayfair → SW1X 0`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.50311&lon=-0.15673&zoom=12.5&filter=Est.%20price%20per%20sqm:0:24700&filter=Good%2B%20secondary%20school%20catchments:1:11
Mayfair and SW1X 0 share a station, a school catchment and the same era of housing, but SW1X 0 costs about 32% less per square metre (£978,570 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Mayfair & SW1X 0)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Mayfair £34,362 → SW1X 0 £23,489) + caption '32% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-w1k-2-vs-sw1x-0",
"city": "london",
"promptText": "Best value flats/maisonettess near Mayfair: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
24700
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "SW1X 0: same life, 32% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: Marylebone vs Camden
**Page:** https://perfect-postcode.co.uk/cheaper-twin/w1u-4-vs-nw1-4 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£942,480 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£942,480 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~1940). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: Marylebone £24,238 vs Camden £13,766. | Caption: '43% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is Marylebone. And this is Camden, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 1940. On every measure that moves price, they're twins. But watch the price per square metre. Marylebone: £24,238. Camden: £13,766. That's 43% cheaper, about £942,480 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- Marylebone vs Camden
- Same station. Same schools.
- £942,480 cheaper
- Same flats/maisonettes, ~1940
- 43% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. Marylebone vs Camden: the same flat, about 43% cheaper per m²
2. Marylebone vs Camden: same station, same schools, £942,480 cheaper
3. Why Camden is the smart-money version of Marylebone (43% less per m²)
**Thumbnail text:** big number `£942,480 cheaper` + the two names `Marylebone → Camden`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.5238&lon=-0.15091&zoom=12.5&filter=Est.%20price%20per%20sqm:0:14500&filter=Good%2B%20secondary%20school%20catchments:1:11
Marylebone and Camden share a station, a school catchment and the same era of housing, but Camden costs about 43% less per square metre (£942,480 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (Marylebone & Camden)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (Marylebone £24,238 → Camden £13,766) + caption '43% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-w1u-4-vs-nw1-4",
"city": "london",
"promptText": "Best value flats/maisonettess near Marylebone: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
14500
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "Camden: same life, 43% cheaper."
}
```

View file

@ -0,0 +1,79 @@
# Video kit: WC2A 2 vs EC2A 2
**Page:** https://perfect-postcode.co.uk/cheaper-twin/wc2a-2-vs-ec2a-2 · **Format:** faceless screen-record, ~4560s long + a 9:16 Short cut
## 🎬 Map URL to record (open this, hit record)
`https://perfect-postcode.co.uk/?lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11`
*(filters are pre-applied so the value is on screen immediately)*
## Hook (first 2 seconds, on screen + said)
**"£981,540 cheaper. Same station. Same schools."**
## Shot list
| Time | Beat | What to show | On-screen |
|------|------|--------------|-----------|
| 0:000:06 | COLD OPEN: payoff first | Open on the map already showing both areas with the £/m² gap visible. Caption: '£981,540 cheaper'. Say the hook. | Land on the map URL below (filters pre-applied). |
| 0:060:18 | Same station | Pan/zoom to show both areas sit by the same line/station. Toggle the commute context if you want. | Caption: 'Same station.' |
| 0:180:28 | Same schools | Show the Good+ secondary catchment covering both. | Caption: 'Same school catchment.' |
| 0:280:38 | Same homes | Note the dominant type (flats) and build era (~2019). | Caption: 'Same homes.' |
| 0:380:52 | THE REVEAL | Show the £/m² side by side: WC2A 2 £25,482 vs EC2A 2 £14,576. | Caption: '43% less per m²'. |
| 0:521:00 | CTA | End on the map; invite them to find their own cheaper twin. | Caption: 'Free. No signup.' |
## Narration (human voiceover, never raw TTS for a property audience)
> This is WC2A 2. And this is EC2A 2, right next door. Same station. Same secondary school catchment. The same kind of home: flats built around 2019. On every measure that moves price, they're twins. But watch the price per square metre. WC2A 2: £25,482. EC2A 2: £14,576. That's 43% cheaper, about £981,540 on a typical 90-square-metre home, for the same life, one postcode over. You're not paying for the house. You're paying for the name. You can find the cheaper twin of any postcode in England on the map for free, no signup.
## Captions (≤6 words, sound-off)
- WC2A 2 vs EC2A 2
- Same station. Same schools.
- £981,540 cheaper
- Same flats/maisonettes, ~2019
- 43% less per m²
- Find your cheaper twin, free
## YouTube
**Title options:**
1. WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m²
2. WC2A 2 vs EC2A 2: same station, same schools, £981,540 cheaper
3. Why EC2A 2 is the smart-money version of WC2A 2 (43% less per m²)
**Thumbnail text:** big number `£981,540 cheaper` + the two names `WC2A 2 → EC2A 2`
**Description (paste as-is):**
```
https://perfect-postcode.co.uk/?lat=51.51807&lon=-0.09837&zoom=12.5&filter=Est.%20price%20per%20sqm:0:15300&filter=Good%2B%20secondary%20school%20catchments:1:11
WC2A 2 and EC2A 2 share a station, a school catchment and the same era of housing, but EC2A 2 costs about 43% less per square metre (£981,540 on a 90 m² home). I built a map that ranks every postcode in England by what each pound actually buys, from official open data (Land Registry, EPC, Ofsted, DfT, Police.uk). Find the cheaper twin of any area, free and with no signup, at https://perfect-postcode.co.uk.
0:00 The two postcodes (WC2A 2 & EC2A 2)
0:08 Same station
0:18 Same school catchment
0:28 Same kind of home
0:38 The price-per-m² reveal
0:52 Find your own cheaper twin (free map)
Data: Contains HM Land Registry data © Crown copyright and database right, OGL v3.0. Figures are estimates aggregated to postcode sector, not valuations.
```
## 9:16 Short (cut from the same recording)
First 3 seconds: the £/m² reveal (WC2A 2 £25,482 → EC2A 2 £14,576) + caption '43% less'. End card: 'Find your cheaper twin, free, no signup.'
## Optional auto-render spec (video/src/storyboard.ts AD_CONFIGS)
Add this as a `DemoAdStoryboardConfig` and run `video/render.sh --prod` (needs login creds + the live stack). Filter names must match live `/api/features` or preflight fails.
```json
{
"name": "twin-wc2a-2-vs-ec2a-2",
"city": "london",
"promptText": "Best value flats/maisonettess near WC2A 2: same schools and station, lower price",
"initialFilters": {
"Est. price per sqm": [
0,
15300
],
"Good+ secondary school catchments": [
1,
11
]
},
"outroLine": "EC2A 2: same life, 43% cheaper."
}
```

23
analysis/place_names.json Normal file
View file

@ -0,0 +1,23 @@
{
"_comment": "Outward-code -> approximate neighbourhood label, used by generate_findings.py. APPROXIMATE (an outward code can span more than one area). VERIFY before publishing a page/video. Add entries to name more sectors; unknown codes fall back to the bare sector code and are flagged NEEDS-NAME in findings_review.md.",
"W11": "Notting Hill", "W12": "Shepherd's Bush", "W4": "Chiswick", "W3": "Acton",
"W1H": "Marylebone", "W1U": "Marylebone", "W1K": "Mayfair", "W1J": "Mayfair",
"SW3": "Chelsea", "SW5": "Earl's Court", "SW1V": "Pimlico", "SW13": "Barnes", "SW1H": "Westminster",
"N1": "Islington", "N7": "Holloway", "N10": "Muswell Hill", "N12": "North Finchley", "N16": "Stoke Newington",
"NW1": "Camden", "NW6": "West Hampstead", "NW2": "Cricklewood", "NW10": "Willesden",
"E2": "Bethnal Green", "E5": "Clapton", "E8": "Hackney",
"EC1V": "Clerkenwell", "EC1Y": "Old Street", "EC1N": "Farringdon", "EC4V": "Blackfriars",
"WC1N": "Bloomsbury", "WC1B": "Bloomsbury", "WC2R": "Covent Garden", "WC1E": "Bloomsbury",
"SE1": "Bermondsey",
"BR3": "Beckenham", "CR0": "Croydon",
"HA7": "Stanmore", "HA3": "Kenton",
"IG8": "Woodford Green", "IG6": "Barkingside",
"TW2": "Twickenham", "TW3": "Hounslow", "TW12": "Hampton", "KT8": "East Molesey",
"RM14": "Upminster", "RM12": "Hornchurch",
"SM2": "Sutton", "KT17": "Ewell",
"B11": "Sparkhill",
"M40": "Newton Heath", "M9": "Harpurhey",
"L16": "Childwall", "L14": "Broadgreen", "L8": "Toxteth", "L7": "Kensington (L'pool)",
"SK6": "Romiley",
"BN7": "Lewes"
}

354
analysis/weekly_readout.py Normal file
View file

@ -0,0 +1,354 @@
#!/usr/bin/env python3
"""Perfect Postcode: weekly growth-metrics readout.
Assembles a one-screen, dated markdown block (SEO + YouTube + product funnel)
suitable for pasting into a weekly note. Every section degrades gracefully: if a
credential is missing the section prints "⚠ set X to enable" instead of crashing.
USAGE
python3 analysis/weekly_readout.py # prints the readout to stdout
DATA SOURCES & CREDENTIALS (all read from the environment)
SEO: Google Search Console (Search Analytics API)
GSC_SITE_URL Property as registered in GSC, e.g.
"sc-domain:perfect-postcode.co.uk" or
"https://perfect-postcode.co.uk/".
GOOGLE_APPLICATION_CREDENTIALS (or GSC_CREDENTIALS)
Path to a service-account JSON key. Create it in
Google Cloud IAM Service Accounts, enable the
"Google Search Console API", then in Search Console
Settings Users add the service-account email as a
(restricted) user on the property.
YouTube: view counts (Data API v3, API key) + impressions/CTR/retention
(Analytics API, founder OAuth)
YT_API_KEY Public Data API v3 key (Cloud console Credentials).
Gives per-video view/like/comment counts.
YT_CHANNEL_ID Channel id (starts "UC..."). See
youtube.com/account_advanced.
YT_OAUTH_TOKEN (optional) Path to an authorized_user OAuth token
JSON for the channel owner. ONLY this unlocks
impressions, CTR and average view duration, which are
private and need the founder's Google login + scope
yt-analytics.readonly. Without it we show public views
only.
Funnel: Plausible (self-hosted) Stats API v2
PLAUSIBLE_API_KEY Stats API key (Plausible Settings API Keys).
PLAUSIBLE_SITE_ID (default perfect-postcode.co.uk)
PLAUSIBLE_HOST (default https://stats.schmelczer.dev)
KILL / KEEP rule (printed at the bottom): keep going if BOTH search impressions
AND map opens grew month-over-month; otherwise reconsider.
"""
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import date, timedelta
from google.auth.transport.requests import Request
from google.oauth2 import service_account
from google.oauth2.credentials import Credentials
TODAY = date.today()
WARN = ""
# Comparison windows (yesterday-anchored). GSC data lags ~2-3 days, so the most
# recent days of the SEO section may read low, which is expected.
def window(end_offset: int, length: int) -> tuple[str, str]:
end = TODAY - timedelta(days=end_offset)
return (end - timedelta(days=length - 1)).isoformat(), end.isoformat()
WK_CUR, WK_PREV = window(1, 7), window(8, 7) # weekly readout
MO_CUR, MO_PREV = window(1, 28), window(29, 28) # monthly kill/keep gate
def delta(cur: float, prev: float) -> str:
if prev == 0:
return " (new)" if cur else ""
pct = (cur - prev) / prev * 100
return f" {'' if pct >= 0 else ''}{pct:+.0f}%"
def http_json(url, *, method="GET", headers=None, body=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method, headers=headers or {})
if data is not None:
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
def google_token(creds_path, scopes, *, authorized_user):
"""Mint an OAuth bearer token from a Google credential file."""
if authorized_user:
creds = Credentials.from_authorized_user_file(creds_path, scopes)
else:
creds = service_account.Credentials.from_service_account_file(
creds_path, scopes=scopes
)
creds.refresh(Request())
return creds.token
# --- shared GSC + Plausible query helpers (reused by sections and kill/keep) ---
def gsc_creds():
return os.environ.get("GSC_SITE_URL"), (
os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
or os.environ.get("GSC_CREDENTIALS")
)
_GSC_TOKEN = {} # cache: creds_path -> token
def gsc_query(start, end, dimensions=None, limit=1):
"""Search Analytics query. Caller must have verified gsc_creds() first."""
site, creds = gsc_creds()
if creds not in _GSC_TOKEN:
_GSC_TOKEN[creds] = google_token(
creds,
["https://www.googleapis.com/auth/webmasters.readonly"],
authorized_user=False,
)
body = {"startDate": start, "endDate": end, "rowLimit": limit}
if dimensions:
body["dimensions"] = dimensions
url = (
"https://searchconsole.googleapis.com/webmasters/v3/sites/"
+ urllib.parse.quote(site, safe="")
+ "/searchAnalytics/query"
)
headers = {"Authorization": f"Bearer {_GSC_TOKEN[creds]}"}
return http_json(url, method="POST", headers=headers, body=body).get("rows", [])
def plausible_query(metrics, date_range, filters=None):
"""Stats API v2 aggregate query -> list of metric values (aligned to
`metrics`). Caller must have verified PLAUSIBLE_API_KEY first."""
key = os.environ["PLAUSIBLE_API_KEY"]
site = os.environ.get("PLAUSIBLE_SITE_ID", "perfect-postcode.co.uk")
host = os.environ.get("PLAUSIBLE_HOST", "https://stats.schmelczer.dev").rstrip("/")
body = {"site_id": site, "metrics": metrics, "date_range": list(date_range)}
if filters:
body["filters"] = filters
res = http_json(
host + "/api/v2/query",
method="POST",
headers={"Authorization": f"Bearer {key}"},
body=body,
).get("results", [])
return res[0]["metrics"] if res else [0] * len(metrics)
# Real Plausible events (frontend/src/lib/analytics.ts + MapPage.tsx):
# pageview /dashboard -> map opens (no dedicated "Map Open" event exists)
# "Filter Add" -> a filter was applied (props.feature = feature name)
# "Upgrade Modal Shown" -> the 3-filter demo cap (DEMO_MAX_FILTERS) was hit
MAP_FILTER = [["is", "event:page", ["/dashboard"]]]
ADD_FILTER = [["is", "event:name", ["Filter Add"]]]
CAP_FILTER = [["is", "event:name", ["Upgrade Modal Shown"]]]
# ---------------------------------------------------------------------------
# 1. SEO: Google Search Console
# ---------------------------------------------------------------------------
def section_seo() -> None:
print("## SEO: Google Search Console")
site, creds = gsc_creds()
if not site or not creds:
print(f"{WARN} set GSC_SITE_URL + GOOGLE_APPLICATION_CREDENTIALS to enable\n")
return
try:
cur = gsc_query(*WK_CUR)
prev = gsc_query(*WK_PREV)
except Exception as exc: # noqa: BLE001
print(f"{WARN} GSC failed: {exc}\n")
return
c, p = (cur[0] if cur else {}), (prev[0] if prev else {})
ci, cc = c.get("impressions", 0), c.get("clicks", 0)
pi, pc = p.get("impressions", 0), p.get("clicks", 0)
print(f" impressions {ci:>8,.0f}{delta(ci, pi)} (prior {pi:,.0f})")
print(f" clicks {cc:>8,.0f}{delta(cc, pc)} (prior {pc:,.0f})")
print(f" CTR {(cc / ci * 100 if ci else 0):>7.2f}%")
rows = sorted(
gsc_query(*WK_CUR, dimensions=["page"], limit=1000),
key=lambda r: r.get("impressions", 0),
reverse=True,
)
watch = ("twin", "postcode", "dashboard", "property-search", "cheaper")
print(" top pages (impressions / clicks):")
for r in rows[:8]:
page = r["keys"][0]
star = "" if any(w in page.lower() for w in watch) else ""
path = page.replace("https://perfect-postcode.co.uk", "") or "/"
print(
f" {r.get('impressions', 0):>6,.0f} / {r.get('clicks', 0):>4,.0f}"
f" {path}{star}"
)
print()
# ---------------------------------------------------------------------------
# 2. YouTube
# ---------------------------------------------------------------------------
def section_youtube() -> None:
print("## YouTube")
api_key, channel = os.environ.get("YT_API_KEY"), os.environ.get("YT_CHANNEL_ID")
if not api_key or not channel:
print(f"{WARN} set YT_API_KEY + YT_CHANNEL_ID to enable\n")
return
try:
search = http_json(
"https://www.googleapis.com/youtube/v3/search?"
f"key={api_key}&channelId={channel}&part=id&type=video"
"&order=date&maxResults=15"
)
ids = [
it["id"]["videoId"]
for it in search.get("items", [])
if it.get("id", {}).get("videoId")
]
if not ids:
print(" (no public videos found)\n")
return
stats = http_json(
"https://www.googleapis.com/youtube/v3/videos?"
f"key={api_key}&part=snippet,statistics&id={','.join(ids)}"
)
print(" public views (Data API v3):")
for it in stats.get("items", []):
views = int(it.get("statistics", {}).get("viewCount", 0))
print(f" {views:>8,} {it['snippet']['title'][:48]}")
except Exception as exc: # noqa: BLE001
print(f"{WARN} YouTube Data API failed: {exc}")
oauth = os.environ.get("YT_OAUTH_TOKEN") # private metrics need founder OAuth
if not oauth:
print(
f" {WARN} set YT_OAUTH_TOKEN (founder OAuth) to add impressions, CTR "
"and avg view duration\n"
)
return
try:
token = google_token(
oauth,
["https://www.googleapis.com/auth/yt-analytics.readonly"],
authorized_user=True,
)
rep = http_json(
"https://youtubeanalytics.googleapis.com/v2/reports?ids=channel==MINE"
f"&startDate={WK_CUR[0]}&endDate={WK_CUR[1]}"
"&metrics=impressions,impressionsClickThroughRate,averageViewDuration,views"
"&dimensions=video&sort=-impressions&maxResults=15",
headers={"Authorization": f"Bearer {token}"},
)
print(" impressions / CTR% / avg-dur(s) (Analytics API):")
for vid, impr, ctr, avgdur, _views in rep.get("rows", []):
print(f" {impr:>7,} CTR {ctr:>5.1f}% {avgdur:>4.0f}s {vid}")
except Exception as exc: # noqa: BLE001
print(f" {WARN} YouTube Analytics failed: {exc}")
print()
# ---------------------------------------------------------------------------
# 3. Funnel: Plausible (self-hosted)
# ---------------------------------------------------------------------------
def section_funnel():
print("## Funnel: Plausible")
if not os.environ.get("PLAUSIBLE_API_KEY"):
site = os.environ.get("PLAUSIBLE_SITE_ID", "perfect-postcode.co.uk")
print(f"{WARN} set PLAUSIBLE_API_KEY to enable (site={site})\n")
return None
def snapshot(rng):
return (
plausible_query(["visitors"], rng)[0],
plausible_query(["pageviews"], rng, MAP_FILTER)[0],
plausible_query(["visitors"], rng, ADD_FILTER)[0],
plausible_query(["visitors"], rng, CAP_FILTER)[0],
)
try:
cv, cm, cf, cc = snapshot(WK_CUR)
pv, pm, _pf, _pc = snapshot(WK_PREV)
except urllib.error.HTTPError as exc:
print(f"{WARN} Plausible query failed ({exc.code}); check key/site_id\n")
return None
except Exception as exc: # noqa: BLE001
print(f"{WARN} Plausible query failed: {exc}\n")
return None
print(f" visitors {cv:>7,}{delta(cv, pv)} (prior {pv:,})")
print(f" map opens {cm:>7,}{delta(cm, pm)} (prior {pm:,})")
print(
f" ≥1 filter applied {cf:>7,} = {(cf / cv * 100 if cv else 0):.0f}% of visitors"
)
print(
f" 3-filter cap hit {cc:>7,} = {(cc / cm * 100 if cm else 0):.0f}% of map opens"
)
print()
return cm # weekly map opens (unused below, but handy for callers)
# ---------------------------------------------------------------------------
# Kill / keep: month-over-month gate
# ---------------------------------------------------------------------------
def kill_keep(_map_opens_weekly) -> None:
print("## Kill / Keep")
impr_up = map_up = None
site, creds = gsc_creds()
if site and creds:
try:
cur = gsc_query(*MO_CUR)
prev = gsc_query(*MO_PREV)
impr_up = (cur[0]["impressions"] if cur else 0) > (
prev[0]["impressions"] if prev else 0
)
except Exception: # noqa: BLE001
impr_up = None
if os.environ.get("PLAUSIBLE_API_KEY"):
try:
map_up = (
plausible_query(["pageviews"], MO_CUR, MAP_FILTER)[0]
> plausible_query(["pageviews"], MO_PREV, MAP_FILTER)[0]
)
except Exception: # noqa: BLE001
map_up = None
fmt = lambda v: "?" if v is None else ("up" if v else "down") # noqa: E731
if impr_up and map_up:
verdict = "KEEP: impressions ↑ and map opens ↑ MoM"
elif impr_up is None or map_up is None:
verdict = "INCONCLUSIVE: enable GSC + Plausible to decide"
else:
verdict = "REVIEW: impressions and/or map opens did not grow MoM"
print(f" impressions MoM: {fmt(impr_up)} | map opens MoM: {fmt(map_up)}")
print(f"{verdict}\n")
def main() -> int:
print(f"# Perfect Postcode: weekly readout ({TODAY.isoformat()})")
print(f"week {WK_CUR[0]}{WK_CUR[1]} vs prior {WK_PREV[0]}{WK_PREV[1]}\n")
section_seo()
section_youtube()
map_opens_weekly = section_funnel()
kill_keep(map_opens_weekly)
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -30,7 +30,7 @@ services:
- cargo-home:/usr/local/cargo
- cargo-target:/app/server-rs/target
environment:
# Fallback only the binary uses jemalloc as its global allocator
# Fallback only: the binary uses jemalloc as its global allocator
# (tuned via a baked-in malloc_conf). Caps glibc to 2 arenas.
MALLOC_ARENA_MAX: "2"
# Dev only: spill the large property arrays (feature matrix +

View file

@ -1,9 +1,9 @@
# Finder property listing scraper
# Finder: property listing scraper
Scrapes Greater-London sale listings from **Rightmove**, **OnTheMarket**, and
**Zoopla**, recovers each property's true full postcode, and writes a single
parquet (`data/online_listings_buy.parquet`) that the rest of the app consumes
(after a separate enrich step see [Output](#output)).
(after a separate enrich step, see [Output](#output)).
`main.py` is the only entry point; everything else is library code.
@ -12,7 +12,7 @@ parquet (`data/online_listings_buy.parquet`) that the rest of the app consumes
## How it works (and why it's careful about postcodes)
Every portal's **search** API exposes only an *outcode*-level address (e.g.
`"…, London, SW9"`) plus map coordinates never the full unit postcode. The
`"…, London, SW9"`) plus map coordinates, never the full unit postcode. The
full postcode lives on each listing's **detail page**, so the scraper fetches
detail pages to recover it, and only trusts a detail postcode when its outcode
agrees with the coordinate-nearest postcode (so a stale/wrong value can never
@ -22,7 +22,7 @@ falls back to the coordinate-nearest postcode. See the module docstrings in
Detail fetching is the dominant cost, so it is:
- **cached across runs** `data/detail_cache/{source}.json` maps listing id →
- **cached across runs**: `data/detail_cache/{source}.json` maps listing id →
recovered postcode; a re-run only fetches *newly-appeared* listings;
- **fetched concurrently** for the HTTP portals (Rightmove, OnTheMarket), bounded
by a shared global rate limiter so the VPN egress stays polite;
@ -51,7 +51,7 @@ Also required: the ARCGIS postcode parquet at `../property-data/arcgis_data.parq
## Running
### Docker Compose (recommended the only way that does Zoopla)
### Docker Compose (recommended, the only way that does Zoopla)
`finder/docker-compose.yml` brings up the scraper plus **FlareSolverr** (which
solves Zoopla's Cloudflare challenge), both sharing `media_gluetun`'s netns. This
@ -106,18 +106,18 @@ GLUETUN_PROXY="" .venv/bin/python main.py --source onthemarket --outcodes SW9 \
| Flag | Default | Meaning |
|------|---------|---------|
| `--source rightmove,onthemarket` | `all` | Comma-separated portal(s): any of `rightmove`, `onthemarket`, `zoopla`, or `all`. |
| `--outcodes SW9,E14,BR1` | | Specific outcodes (must be Greater-London-ish). Otherwise the full London set is loaded from ARCGIS. |
| `--limit-outcodes N` | | Cap the number of outcodes (quick smoke). |
| `--max-properties-per-source N` | | Stop each source after N transformed listings. |
| `--outcodes SW9,E14,BR1` | none | Specific outcodes (must be Greater-London-ish). Otherwise the full London set is loaded from ARCGIS. |
| `--limit-outcodes N` | none | Cap the number of outcodes (quick smoke). |
| `--max-properties-per-source N` | none | Stop each source after N transformed listings. |
| `--output-dir DIR` | `data/` | Where the parquet (and `detail_cache/`) are written. |
| `--test` | off | ~10 likely-London outcodes, ≤100 listings/source, writes to `data/test/`. |
> **Always pass `--output-dir /tmp/...` for testing** the default `data/` holds
> **Always pass `--output-dir /tmp/...` for testing**: the default `data/` holds
> the real listings the app consumes.
### Stopping a run
`Ctrl+C` (SIGINT) — or `docker stop` (SIGTERM) — triggers a **graceful
`Ctrl+C` (SIGINT), or `docker stop` (SIGTERM), triggers a **graceful
shutdown**: every source stops at its next outcode boundary, in-flight delays
and retry backoffs wake immediately, and the run still persists the detail
caches and writes the listings collected so far before exiting (code `130`).
@ -147,7 +147,7 @@ A **separate enrich step** (outside `finder/`) turns that into
`online_listings_buy_enriched.parquet`, which is what the Rust backend actually
loads (`--actual-listings-path …/online_listings_buy_enriched.parquet` in the
top-level `docker-compose.yml`). That enrich/scheduling pipeline is **not**
documented here — only the raw scrape is.
documented here. Only the raw scrape is documented.
The top-level `docker-compose.yml` (Rust `server`, `frontend`, `pocketbase`,
`screenshot`) is the **web app**; it is downstream of the scrape and is **not**

View file

@ -26,14 +26,14 @@ RETRY_BASE_DELAY = 2.0
DETAIL_FETCH_CONCURRENCY = int(os.environ.get("DETAIL_FETCH_CONCURRENCY", "8"))
REQUESTS_PER_SECOND = float(os.environ.get("REQUESTS_PER_SECOND", "10"))
GRID_CELL_SIZE = 0.01 # degrees for postcode spatial index
MAX_BEDROOMS = 20 # sanity cap values above this are almost certainly parsing errors
MAX_BEDROOMS = 20 # sanity cap: values above this are almost certainly parsing errors
TYPEAHEAD_URL = "https://los.rightmove.co.uk/typeahead"
SEARCH_URL = "https://www.rightmove.co.uk/api/property-search/listing/search"
RIGHTMOVE_BASE = "https://www.rightmove.co.uk"
# Detail page (plain HTTPS GET, no Cloudflare). Its window.__PAGE_MODEL embeds
# propertyData.address.{outcode,incode}, which together form the property's TRUE
# full postcode — the search API only exposes the outcode. {id} is the numeric
# full postcode. The search API only exposes the outcode. {id} is the numeric
# listing id from the search response.
RIGHTMOVE_DETAIL_URL = "https://www.rightmove.co.uk/properties/{id}"
@ -53,7 +53,7 @@ RIGHTMOVE_MAX_DETAILS_PER_OUTCODE = 4000 # max detail-page fetches per outcode
# keep on APPROXIMATE pins (new-builds/developments) where Rightmove
# deliberately fuzzes the coordinates. Degrades safely: when `pinType` is absent
# from the search payload, nothing is skipped (behaviour is unchanged), so this
# is only a speed-up to the extent the field is present — verify against a live
# is only a speed-up to the extent the field is present. Verify against a live
# search response before relying on the saving.
RIGHTMOVE_SKIP_DETAILS_FOR_ACCURATE_PINS = (
os.environ.get("RIGHTMOVE_SKIP_DETAILS_FOR_ACCURATE_PINS", "1") != "0"
@ -94,7 +94,7 @@ GLUETUN_API_KEY = "My8AbvnKhfyFdRhpTVfoTfa5DkAMmg8K"
GLUETUN_MAX_ROTATIONS = 0 # max egress-IP rotations per Cloudflare challenge
# Zoopla fetcher: "flaresolverr" (default) solves Cloudflare via the FlareSolverr
# sidecar (docker-compose.yml) and needs no display/VNC verified to return the
# sidecar (docker-compose.yml) and needs no display/VNC, verified to return the
# RSC flight stream with postcode + coordinates; "camoufox" drives a local
# anti-fingerprint browser (needs an interactive solve on datacenter IPs).
ZOOPLA_FETCHER = os.environ.get("ZOOPLA_FETCHER", "flaresolverr")
@ -214,3 +214,16 @@ PROPERTY_TYPE_MAP = {
CHANNELS = [
{"channel": "BUY", "transactionType": "BUY", "sortType": "2"},
]
# A second search pass that restricts the BUY channel to new-build developments
# via Rightmove's `mustHave=newHome` filter, so new homes (which can rank low in
# the default resale sort) are captured thoroughly. The API still echoes
# `?channel=RES_BUY` in every listing URL regardless of this filter, so new
# builds are identified by the per-listing `development` flag in
# `transform_property`, which re-stamps the URL channel as RES_NEW.
NEW_HOMES_CHANNEL = {
"channel": "BUY",
"transactionType": "BUY",
"sortType": "2",
"extra_params": {"mustHave": "newHome"},
}

View file

@ -1,4 +1,4 @@
"""FlareSolverr client fetch Cloudflare-protected pages as rendered HTML.
"""FlareSolverr client: fetch Cloudflare-protected pages as rendered HTML.
FlareSolverr (https://github.com/FlareSolverr/FlareSolverr) drives an
undetected browser to pass Cloudflare's challenge and returns the fully
@ -6,7 +6,7 @@ rendered HTML. It runs as a sidecar service (see docker-compose.yml) sharing
the Gluetun VPN network namespace, so its browser egresses through the VPN.
Verified working against Zoopla's managed Turnstile on a datacenter VPN IP,
provided a reused session and a generous maxTimeout (~120s) the first
provided a reused session and a generous maxTimeout (~120s): the first
challenge solve is slow, subsequent requests on the warm session are fast.
"""

View file

@ -23,7 +23,7 @@ class RateLimiter:
Detail-page fetches run concurrently across many worker threads (and across
providers), but a single shared limiter caps their COMBINED rate so the VPN
egress IP stays polite. Each ``acquire()`` reserves the next free time slot
under a lock, then sleeps (outside the lock) until that slot so N threads
under a lock, then sleeps (outside the lock) until that slot, so N threads
calling concurrently are spaced ``1/rate_per_second`` apart rather than all
firing at once. ``rate_per_second <= 0`` disables limiting."""

View file

@ -132,7 +132,7 @@ def main() -> int:
configure_standalone_runtime()
configure_logging()
# Ctrl+C (and SIGTERM, e.g. `docker stop`) asks the scrapers to wind down
# gracefully — each source stops at its next outcode boundary and the run
# gracefully. Each source stops at its next outcode boundary and the run
# still persists detail caches and writes the listings collected so far.
shutdown.install_signal_handlers()

View file

@ -1,4 +1,4 @@
"""OnTheMarket (onthemarket.com) scraper sale properties.
"""OnTheMarket (onthemarket.com) scraper: sale properties.
OnTheMarket serves a Next.js app with the full search-results payload embedded
as JSON in a `__NEXT_DATA__` script tag. No JS execution or browser needed:
@ -15,19 +15,19 @@ Postcodes
---------
The search card exposes only an *outcode*-level address (e.g. "Padfield Road,
London, SE5") and a map pin, so the old behaviour derived the postcode from the
nearest postcode to that pin a guess that frequently lands on a neighbouring
nearest postcode to that pin, a guess that frequently lands on a neighbouring
unit (the pin can sit on the wrong side of a street boundary).
Each *detail* page (`/details/{id}/`) is a plain HTTPS GET whose `__NEXT_DATA__`
embeds the property's analytics dataLayer at
`props.initialReduxState.metadata.dataLayer`, which carries the property's own
`postcode` (full unit postcode, e.g. "SE5 9AA") keyed to this listing by
`property-id`. Crucially this is NOT the agent's office postcode — that lives
`property-id`. Crucially this is NOT the agent's office postcode. That lives
separately at `property.agent.postcode` ("SE5 8RS" for the same listing) and
is the classic trap when blindly scanning the page for a postcode. We read the
dataLayer postcode, verify `property-id` matches the listing, and accept it only
when its outcode agrees with the coordinate-nearest postcode (via
``resolve_listing_postcode``) exactly the trust rule the other scrapers use.
``resolve_listing_postcode``), exactly the trust rule the other scrapers use.
Measured over a sample of real listings this yields a trustworthy, usually
exact-unit postcode for ~11/12 listings; the rest safely fall back to the
coordinate-nearest postcode.
@ -68,8 +68,8 @@ from transform import (
log = logging.getLogger("rightmove")
# Detail-page postcode recovery (see module docstring). When enabled, each
# listing's detail page is fetched so its analytics dataLayer postcode the
# property's own full unit postcode can replace the coordinate-nearest guess.
# listing's detail page is fetched so its analytics dataLayer postcode (the
# property's own full unit postcode) can replace the coordinate-nearest guess.
# Bounded per outcode so a large outcode can't balloon into unbounded extra
# HTTPS GETs. Kept at parity with the Rightmove/Zoopla detail caps (400) so a
# typical outcode's listings all get their real postcode rather than a
@ -145,7 +145,7 @@ def _fetch_page_json(client: httpx.Client, outcode: str, page_num: int) -> dict
if 300 <= resp.status_code < 400:
log.debug(
"OnTheMarket %s page %d redirected (%d) end of results",
"OnTheMarket %s page %d redirected (%d): end of results",
outcode, page_num, resp.status_code,
)
return None
@ -189,7 +189,7 @@ def parse_detail_postcode(html: str, listing_id: str | None = None) -> str | Non
``props.initialReduxState.metadata.dataLayer.postcode`` and is the
property's own unit postcode (e.g. "SE5 9AA"). It is deliberately NOT the
agent's office postcode, which sits separately at
``property.agent.postcode`` the trap when scanning a detail page for "a"
``property.agent.postcode``, the trap when scanning a detail page for "a"
postcode. When ``listing_id`` is given, the dataLayer's ``property-id`` must
match it, guaranteeing we read this listing's postcode and not a stray one.
@ -235,7 +235,7 @@ def _fetch_detail_postcode(
Results (including failures) are cached by listing id so a listing that
reappears across overlapping outcode searches is fetched at most once. Plain
HTTPS GET OnTheMarket detail pages have no Cloudflare challenge. Network /
HTTPS GET: OnTheMarket detail pages have no Cloudflare challenge. Network /
parse errors degrade gracefully to None so the caller falls back to the
coordinate-nearest postcode. Safe to call concurrently: distinct listing ids
write distinct cache keys, and the shared RATE_LIMITER spaces the GETs.
@ -452,7 +452,7 @@ def _prime_detail_postcodes(
) -> None:
"""Fill ``_detail_postcode_cache`` for the listings that need a detail page.
Picks the fresh (uncached) listings up to ``detail_cap`` per outcode then
Picks the fresh (uncached) listings, up to ``detail_cap`` per outcode, then
fetches their detail pages CONCURRENTLY, bounded by
``DETAIL_FETCH_CONCURRENCY`` (the shared RATE_LIMITER keeps the combined
request rate polite). Cached listings cost neither a slot nor a GET. The

View file

@ -2,7 +2,7 @@
Each portal recovers a listing's true postcode (Rightmove/OnTheMarket) or full
geo dict (Zoopla) from its detail page. That value never changes for a given
listing id, yet the in-memory caches are discarded at the end of every run so
listing id, yet the in-memory caches are discarded at the end of every run, so
each run re-fetches every listing's detail page from scratch. Persisting the
cache to disk means a steady-state run only fetches NEWLY-appeared listings,
typically a small fraction of the market, which is the single biggest saving
@ -27,7 +27,7 @@ log = logging.getLogger("rightmove")
def load_cache(path: str | Path) -> dict:
"""Load a persisted detail cache. Returns ``{}`` when absent or unreadable.
A corrupt or non-object file is treated as empty rather than fatal a bad
A corrupt or non-object file is treated as empty rather than fatal: a bad
cache must never block a scrape; the worst case is re-fetching details."""
p = Path(path)
if not p.exists():

View file

@ -36,7 +36,7 @@ _MAX_INDEX = 1008
# ---------------------------------------------------------------------------
#
# The search API (_paginate) only returns an outcode-level `displayAddress`
# (e.g. "Akerman Road, Brixton, London, SW9") never the full postcode. Each
# (e.g. "Akerman Road, Brixton, London, SW9"), never the full postcode. Each
# listing's detail page, however, embeds the property's OWN full postcode in a
# `window.__PAGE_MODEL` script as `propertyData.address.{outcode, incode}`
# (e.g. outcode "SW9" + incode "0HD" → "SW9 0HD"), independently corroborated by
@ -51,8 +51,8 @@ _MAX_INDEX = 1008
# __PAGE_MODEL is a "devalue"-style flattened object graph: its `data` field is
# a JSON STRING holding a flat array where every integer inside a container is
# an index reference into that same array (so the graph can dedupe). We
# brace-match the (large, deeply-nested) object literal a non-greedy regex
# cannot then rehydrate the reference graph before reading the address.
# brace-match the (large, deeply-nested) object literal (a non-greedy regex
# cannot), then rehydrate the reference graph before reading the address.
_PAGE_MODEL_RE = re.compile(r"window\.__PAGE_MODEL\s*=\s*")
@ -128,7 +128,7 @@ def parse_detail_postcode(html: str) -> str | None:
Pure and network-free so it is unit-testable: callers pass the page HTML.
Reads ``propertyData.address.outcode`` + ``.incode`` from window.__PAGE_MODEL
and returns a normalised full postcode (e.g. "SW9 0HD"), or None when the
page has no parseable address (the property location wrapper can be empty
page has no parseable address (the property location wrapper can be empty;
the caller then keeps the coordinate fallback). The returned outcode is
re-validated against the joined postcode so a malformed incode is dropped.
"""
@ -193,7 +193,7 @@ def _fetch_detail_postcode(client: httpx.Client, property_id: str) -> str | None
"""GET a listing detail page and return its true full postcode (or None).
Results (including failures) are cached by listing id. The detail page is a
plain HTML GET no Cloudflare, unlike Zoopla so a single httpx call
plain HTML GET (no Cloudflare, unlike Zoopla), so a single httpx call
suffices; any error degrades gracefully to the coordinate fallback. Safe to
call concurrently: distinct listing ids write distinct cache keys, and the
shared RATE_LIMITER spaces the GETs."""
@ -245,7 +245,7 @@ def _needs_detail_fetch(prop: dict) -> bool:
Skips listings the search already pins precisely: an "ACCURATE_POINT"
``pinType`` means rooftop-exact coordinates, so the coordinate-nearest
postcode is trustworthy and the detail page would only confirm it. Listings
with an approximate pin or no ``pinType`` field at all still get fetched,
with an approximate pin (or no ``pinType`` field at all) still get fetched,
so this degrades safely to the previous behaviour when the search payload
omits ``pinType``."""
if not RIGHTMOVE_SKIP_DETAILS_FOR_ACCURATE_PINS:
@ -262,8 +262,8 @@ def _prime_detail_postcodes(
) -> None:
"""Fill ``_detail_postcode_cache`` for the listings that need a detail page.
Picks the fresh (uncached, not-skipped) listings up to ``detail_cap`` per
outcode then fetches their detail pages CONCURRENTLY, bounded by
Picks the fresh (uncached, not-skipped) listings, up to ``detail_cap`` per
outcode, then fetches their detail pages CONCURRENTLY, bounded by
``DETAIL_FETCH_CONCURRENCY`` (the shared RATE_LIMITER keeps the combined
request rate polite). Cached listings cost neither a slot nor a GET. The
worklist is deduplicated, so distinct ids write distinct cache keys and the
@ -305,8 +305,8 @@ def _collect_search_props(
) -> tuple[list[dict], int]:
"""Paginate the search API for one outcode+channel, collecting raw results.
Returns ``(raw_props, result_count)``. Pagination stays serial each page
reveals the next but is cheap relative to detail fetching, and the
Returns ``(raw_props, result_count)``. Pagination stays serial (each page
reveals the next) but is cheap relative to detail fetching, and the
RATE_LIMITER spaces the page GETs. Collection stops at ``max_properties`` raw
listings, the end of results, or Rightmove's ``_MAX_INDEX`` page cap."""
raw_props: list[dict] = []
@ -324,6 +324,8 @@ def _collect_search_props(
"channel": channel_cfg["channel"],
"transactionType": channel_cfg["transactionType"],
}
# Optional per-channel filters, e.g. `mustHave=newHome` for the new-homes pass.
params.update(channel_cfg.get("extra_params", {}))
data = fetch_with_retry(client, SEARCH_URL, params)
if not data:
log.warning(
@ -372,7 +374,7 @@ def _paginate(
) -> tuple[list[dict], int]:
"""Collect search results, recover true postcodes, and transform them.
Search pages are paginated serially; then when ``fetch_details`` is set
Search pages are paginated serially; then, when ``fetch_details`` is set,
up to ``detail_cap`` listings per outcode have their detail page fetched
CONCURRENTLY for the property's TRUE full postcode (see
``parse_detail_postcode``), with listings the search already pins precisely

View file

@ -14,6 +14,7 @@ import polars as pl
from constants import (
ARCGIS_PATH,
CHANNELS,
NEW_HOMES_CHANNEL,
DATA_DIR,
DELAY_BETWEEN_OUTCODES,
LONDON_OUTCODE_PREFIXES,
@ -368,6 +369,34 @@ def _scrape_rightmove(
except Exception as exc:
_record_error(errors, "rightmove", outcode, exc)
# Second pass: new-build developments (mustHave=newHome). These can
# rank low in the default resale sort, so a dedicated pass ensures
# they are captured; transform_property stamps their URL as RES_NEW.
# Overlap with the resale pass is removed by id in _merge_properties.
# Skipped if a stop was requested mid-outcode (don't start a new pass).
remaining = _source_remaining(
results, "rightmove", max_properties_per_source
)
if not shutdown.stop_requested() and remaining != 0:
try:
new_props = rightmove_search_outcode(
client,
outcode_id,
outcode,
NEW_HOMES_CHANNEL,
pc_index,
max_properties=remaining,
)
added_new = _store_properties(
results,
"rightmove",
new_props,
max_properties_per_source,
)
log.info("Rightmove %s new-homes: +%d", outcode, added_new)
except Exception as exc:
_record_error(errors, "rightmove", outcode, exc)
shutdown.sleep(DELAY_BETWEEN_OUTCODES)
finally:
client.close()
@ -378,7 +407,7 @@ class OutcodeTimeout(BaseException):
Inherits BaseException (not Exception) so the SIGALRM-triggered raise can't
be silently swallowed by any of the broad `except Exception:` handlers
inside zoopla.py the signal may fire at any bytecode boundary, including
inside zoopla.py: the signal may fire at any bytecode boundary, including
inside those handlers."""
@ -431,7 +460,7 @@ def _wall_clock_timeout(seconds: int, label: str):
Interrupts a hung Playwright IPC by delivering SIGALRM to the main thread;
socket waits return EINTR and the handler raises into the caller. The
browser is presumed unhealthy afterwards caller must relaunch it."""
browser is presumed unhealthy afterwards. Caller must relaunch it."""
if seconds <= 0:
yield
return

View file

@ -4,8 +4,8 @@ A single :class:`threading.Event` is set the first time the process receives
SIGINT (Ctrl+C) or SIGTERM. Every scrape loop polls :func:`stop_requested` at
its outcode/page boundaries and every blocking delay goes through :func:`sleep`,
which wakes the instant a stop is requested. So Ctrl+C makes each source stop
*starting* new work and unwind through its normal ``finally`` blocks detail
caches are persisted and whatever has been collected so far is still written
*starting* new work and unwind through its normal ``finally`` blocks (detail
caches are persisted and whatever has been collected so far is still written)
instead of hanging until the worker threads happen to finish (the orchestrator's
``ThreadPoolExecutor`` used to block the exit waiting on them) or losing the run
outright.
@ -34,7 +34,7 @@ def request_stop() -> None:
def reset() -> None:
"""Clear the flag — for tests and repeated in-process runs."""
"""Clear the flag (for tests and repeated in-process runs)."""
_STOP.clear()

View file

@ -16,7 +16,7 @@ def write_parquet(properties: list[dict], path: Path) -> None:
log.warning("No properties to write to %s", path)
return
# Sanitize bedroom/bathroom counts values above MAX_BEDROOMS are
# Sanitize bedroom/bathroom counts: values above MAX_BEDROOMS are
# almost certainly prices or other numeric fields mis-parsed as bedrooms.
bad_count = 0
for p in properties:
@ -91,7 +91,7 @@ def write_parquet(properties: list[dict], path: Path) -> None:
else:
listing_dates.append(None)
# Zero prices indicate parsing failures or POA/auction listings treat as null
# Zero prices indicate parsing failures or POA/auction listings: treat as null
asking_prices = [p["price"] if p["price"] > 0 else None for p in properties]
listing_statuses = ["For sale"] * len(properties)

View file

@ -73,7 +73,7 @@ def test_zero_rate_disables_limiting(monkeypatch):
def test_concurrent_acquires_are_all_spaced(monkeypatch):
# Real clock, tiny rate: N threads hitting acquire() at once must be
# serialised so the total wall time is at least (N-1) * interval.
rl = RateLimiter(200) # 5ms interval fast but measurable
rl = RateLimiter(200) # 5ms interval, fast but measurable
barrier = threading.Barrier(8)
def worker():

View file

@ -6,7 +6,7 @@ import main
# ---------------------------------------------------------------------------
# selected_sources comma-separated --source values
# selected_sources: comma-separated --source values
# ---------------------------------------------------------------------------

View file

@ -5,7 +5,7 @@ or None), so these tests use a trimmed but faithful copy of a real OnTheMarket
detail page's `__NEXT_DATA__` payload. The fixture mirrors the live structure:
the property's own postcode lives in the analytics dataLayer
(`props.initialReduxState.metadata.dataLayer.postcode`) while the agent's office
postcode sits separately under `property.agent.postcode` the trap we must not
postcode sits separately under `property.agent.postcode`, the trap we must not
fall into.
"""
@ -49,7 +49,7 @@ def _detail_html(
"property": {
"displayAddress": "Padfield Road, London, SE5",
"location": {"lon": -0.100233, "lat": 51.466129},
# The agent block carries the AGENT'S office postcode the
# The agent block carries the AGENT'S office postcode, the
# trap. parse_detail_postcode must not return this.
"agent": {
"address": "29 Denmark Hill, Camberwell\nLondon\nSE5 8RS",
@ -125,7 +125,7 @@ def test_parse_handles_missing_datalayer():
# ---------------------------------------------------------------------------
# transform_property detail postcode wiring + trust rule
# transform_property: detail postcode wiring + trust rule
# ---------------------------------------------------------------------------
@ -176,7 +176,7 @@ def test_transform_without_detail_postcode_uses_coordinates():
def test_transform_detail_postcode_via_search_address_outcode():
# When the card address already carries a full postcode that agrees with the
# coordinates, the existing "address" source still wins absent a detail
# postcode — detail recovery never regresses that path.
# postcode. Detail recovery never regresses that path.
raw = dict(_RAW_LISTING, address="Padfield Road, London, SE5 1AA")
index = _StubIndex("SE5 1AA")
out = transform_property(raw, index, detail_postcode=None)

View file

@ -0,0 +1,33 @@
"""Tests for the new-homes search pass (mustHave=newHome) channel wiring."""
import rightmove
from constants import CHANNELS, NEW_HOMES_CHANNEL
from rightmove import _collect_search_props
def _capture_params(monkeypatch):
captured: list[dict] = []
def fake_fetch(client, url, params):
captured.append(dict(params))
return {"properties": [], "resultCount": "0"}
monkeypatch.setattr(rightmove, "fetch_with_retry", fake_fetch)
return captured
def test_new_homes_channel_sends_must_have_new_home(monkeypatch):
captured = _capture_params(monkeypatch)
_collect_search_props(None, "749", "E14", NEW_HOMES_CHANNEL)
assert captured, "no search request was issued"
assert captured[0].get("mustHave") == "newHome"
# New homes are still requested on the BUY channel; only the filter differs.
assert captured[0]["channel"] == "BUY"
assert captured[0]["transactionType"] == "BUY"
def test_resale_channel_sends_no_extra_filters(monkeypatch):
captured = _capture_params(monkeypatch)
_collect_search_props(None, "749", "E14", CHANNELS[0])
assert captured, "no search request was issued"
assert "mustHave" not in captured[0]

View file

@ -14,7 +14,7 @@ def _prop(pid, pin=None):
# ---------------------------------------------------------------------------
# _needs_detail_fetch accurate-pin skip
# _needs_detail_fetch: accurate-pin skip
# ---------------------------------------------------------------------------
@ -32,7 +32,7 @@ def test_needs_detail_fetch_disabled_always_fetches(monkeypatch):
# ---------------------------------------------------------------------------
# _prime_detail_postcodes worklist selection + concurrent fetch
# _prime_detail_postcodes: worklist selection + concurrent fetch
# ---------------------------------------------------------------------------
@ -97,7 +97,7 @@ def test_prime_is_a_noop_when_disabled_or_cap_zero(monkeypatch):
# ---------------------------------------------------------------------------
# _paginate end-to-end (network stubbed): accurate pins fall back to
# _paginate end-to-end (network stubbed): accurate pins fall back to
# coordinates, approximate pins use the detail postcode.
# ---------------------------------------------------------------------------

View file

@ -9,7 +9,7 @@ import zoopla
# ---------------------------------------------------------------------------
# _run_sources Zoopla inline, others in threads, failures isolated
# _run_sources: Zoopla inline, others in threads, failures isolated
# ---------------------------------------------------------------------------
@ -85,13 +85,13 @@ def test_seed_and_save_detail_caches_round_trip(tmp_path):
def test_seed_detail_caches_tolerates_missing_files(tmp_path):
rightmove._detail_postcode_cache.clear()
# No file written yet seeding must not raise and must leave cache empty.
# No file written yet: seeding must not raise and must leave cache empty.
scraper._seed_detail_caches(["rightmove"], tmp_path)
assert rightmove._detail_postcode_cache == {}
# ---------------------------------------------------------------------------
# run_scrape full orchestration wiring (sources stubbed, no network)
# run_scrape: full orchestration wiring (sources stubbed, no network)
# ---------------------------------------------------------------------------

View file

@ -1,4 +1,5 @@
from transform import (
build_listing_url,
build_register_address,
clean_listing_address,
extract_full_postcode,
@ -173,3 +174,33 @@ def test_rightmove_transform_without_detail_keeps_coordinate_logic() -> None:
assert result is not None
assert result["Postcode"] == "SW9 7AA"
assert result["Postcode source"] == "coordinates"
def test_build_listing_url_stamps_channel_from_new_build_flag() -> None:
# Resale gets RES_BUY; new builds get RES_NEW.
assert build_listing_url("/properties/200", False) == (
"https://www.rightmove.co.uk/properties/200#/?channel=RES_BUY"
)
assert build_listing_url("/properties/200", True) == (
"https://www.rightmove.co.uk/properties/200#/?channel=RES_NEW"
)
# An existing channel/fragment on the source URL is stripped and re-stamped.
assert build_listing_url("/properties/200#/?channel=RES_BUY", True) == (
"https://www.rightmove.co.uk/properties/200#/?channel=RES_NEW"
)
# Missing URL stays empty.
assert build_listing_url("", True) == ""
def test_rightmove_transform_tags_new_builds_res_new() -> None:
# The Rightmove search response marks new-build developments with
# development=True; transform_property must stamp the listing URL RES_NEW.
new_build = {**_rightmove_prop(), "development": True}
result = transform_property(new_build, "SW9", StubPostcodeIndex("SW9 7AA"))
assert result is not None
assert result["Listing URL"].endswith("#/?channel=RES_NEW")
# Ordinary resale (development absent/false) stays RES_BUY.
resale = transform_property(_rightmove_prop(), "SW9", StubPostcodeIndex("SW9 7AA"))
assert resale is not None
assert resale["Listing URL"].endswith("#/?channel=RES_BUY")

View file

@ -103,7 +103,7 @@ def test_parse_detail_geo_merges_location_uprn_with_address_full_address() -> No
def test_parse_detail_geo_does_not_borrow_comparable_full_address() -> None:
# The only `address` twin on the page belongs to a different uprn (a
# comparable listing). With a uprn to match on, an unrelated twin is never
# borrowed full_address stays None rather than grabbing the wrong street.
# borrowed: full_address stays None rather than grabbing the wrong street.
html = (
'"location":{"outcode":"NR29",'
'"coordinates":{"latitude":52.716014,"longitude":1.614495},'
@ -185,7 +185,7 @@ def test_parse_detail_geo_returns_none_for_garbage() -> None:
assert parse_detail_geo("<html><body>no data here</body></html>") is None
assert parse_detail_geo("") is None
# Coordinates that are not inside a property location/address wrapper (e.g.
# only an unwrapped POI) yield nothing safe degradation to the outcode.
# only an unwrapped POI) yield nothing, safe degradation to the outcode.
assert parse_detail_geo('"name":"X","coordinates":{"latitude":51.5,"longitude":-0.1}') is None

View file

@ -149,7 +149,7 @@ def map_property_type(sub_type: str | None) -> str:
return "Terraced"
if "house" in lower or "cottage" in lower:
return "Detached"
log.warning("Unknown propertySubType: %r mapping to Other", sub_type)
log.warning("Unknown propertySubType: %r, mapping to Other", sub_type)
return "Other"
@ -267,7 +267,7 @@ def build_register_address(
property's own number or name (e.g. Zoopla detail pages expose
``propertyNumberOrName`` = "12" or "Martham Mill"), prepend it so the address
carries the house identifier that the EPC/Price-Paid register addresses also
use turning a fuzzy street match into a near-exact one. Falls back to the
use, turning a fuzzy street match into a near-exact one. Falls back to the
plain cleaned address when no number/name is available.
"""
cleaned = clean_listing_address(raw_address)
@ -282,6 +282,23 @@ def build_register_address(
return f"{number_or_name}, {cleaned}" if cleaned else number_or_name
def build_listing_url(property_url: str | None, is_new_build: bool) -> str:
"""Build the canonical Rightmove listing URL with an explicit channel marker.
The search API always echoes ``?channel=RES_BUY`` in ``propertyUrl`` even for
new-build developments (the request channel stays BUY), so the channel is
re-stamped here from the per-listing ``development`` flag: ``RES_NEW`` for new
builds, ``RES_BUY`` for ordinary resale. The map UI reads this marker to split
new vs non-new listings. Any channel/fragment already on ``propertyUrl`` is
stripped first so the result is deterministic.
"""
if not property_url:
return ""
base = property_url.split("#", 1)[0].split("?", 1)[0]
channel = "RES_NEW" if is_new_build else "RES_BUY"
return f"{RIGHTMOVE_BASE}{base}#/?channel={channel}"
def transform_property(
prop: dict,
outcode: str,
@ -337,7 +354,7 @@ def transform_property(
inferred_postcode = pc_index.nearest(lat, lng)
if not inferred_postcode:
log.debug("No England postcode for property at %.4f, %.4f skipping", lat, lng)
log.debug("No England postcode for property at %.4f, %.4f; skipping", lat, lng)
return None
raw_address = prop.get("displayAddress", "") or ""
extracted_postcode = extract_full_postcode(raw_address)
@ -391,7 +408,7 @@ def transform_property(
"price_frequency": "",
"Price qualifier": price_qualifier,
"Total floor area (sqm)": parse_display_size(prop.get("displaySize")),
"Listing URL": RIGHTMOVE_BASE + property_url if property_url else "",
"Listing URL": build_listing_url(property_url, bool(prop.get("development"))),
"Listing features": key_features,
"first_visible_date": prop.get("firstVisibleDate", ""),
}

View file

@ -1,4 +1,4 @@
"""Zoopla (zoopla.co.uk) scraper sale properties.
"""Zoopla (zoopla.co.uk) scraper: sale properties.
Zoopla is behind Cloudflare Turnstile (managed interactive challenge), which
blocks non-browser HTTP clients and even Playwright with stealth patches. Only
@ -522,7 +522,7 @@ def _gluetun_set_vpn_status(client: httpx.Client, status: str) -> bool:
return False
if resp.status_code == 401:
log.warning(
"Gluetun vpn/status %s: 401 Unauthorized — the API key must be "
"Gluetun vpn/status %s: 401 Unauthorized. The API key must be "
"authorised for 'PUT /v1/vpn/status' in Gluetun's auth config.toml",
status,
)
@ -593,7 +593,7 @@ def _rotate_and_retry_challenge(page, max_rotations: int) -> bool:
"""Rotate IP and reload until the challenge clears. Returns True on success."""
for attempt in range(1, max_rotations + 1):
log.warning(
"Cloudflare Turnstile challenge rotating Gluetun IP (attempt %d/%d)",
"Cloudflare Turnstile challenge, rotating Gluetun IP (attempt %d/%d)",
attempt, max_rotations,
)
if not _rotate_gluetun_ip():
@ -637,7 +637,7 @@ def _wait_for_turnstile(page, headless_mode: bool | str) -> None:
if not _is_turnstile_challenge(page):
return
# Try Gluetun IP rotation first works in any mode and is the only option
# Try Gluetun IP rotation first: works in any mode and is the only option
# in headless/unattended runs where no human can click the challenge.
max_rotations = _gluetun_max_rotations()
if max_rotations > 0 and _rotate_and_retry_challenge(page, max_rotations):
@ -654,7 +654,7 @@ def _wait_for_turnstile(page, headless_mode: bool | str) -> None:
timeout = _challenge_timeout_seconds()
log.warning(
"Gluetun rotation insufficient — falling back to interactive solve. "
"Gluetun rotation insufficient. Falling back to interactive solve. "
"Complete the Cloudflare challenge in the Zoopla browser window; "
"waiting up to %ds. Profile: %s",
timeout,
@ -727,7 +727,7 @@ def launch_browser():
page.goto(f"{ZOOPLA_BASE}/", wait_until="domcontentloaded", timeout=60000)
_wait_for_turnstile(page, headless_mode)
log.info("Zoopla browser ready title: %s", page.title())
log.info("Zoopla browser ready, title: %s", page.title())
time.sleep(2)
# Dismiss cookie consent
@ -1117,14 +1117,14 @@ def _extract_outcode(text: str) -> str | None:
# "outcode":...,"postcode":...,"uprn":...} feeding the map widgets.
# Nearby points of interest (stations, schools, EV chargers) and comparable
# listings carry their own "coordinates" too, but never inside the property's
# own "location" / "address":{"fullAddress" wrapper so the wrapper, not a
# own "location" / "address":{"fullAddress" wrapper, so the wrapper, not a
# loose coordinates object, is what we anchor on (see parse_detail_geo).
# listingId -> parsed detail dict (or None). Failures are cached too, so a
# broken listing is not re-fetched within a run (the same listing reappears
# across overlapping outcode searches). Seeded from / dumped to a persistent
# on-disk cache by the orchestrator (see postcode_cache.py) so a recurring
# scrape only re-fetches newly-listed properties the biggest saving for
# scrape only re-fetches newly-listed properties, the biggest saving for
# Zoopla, whose detail fetch drives a real browser tab.
_detail_cache: dict[str, dict | None] = {}
@ -1144,7 +1144,7 @@ _LISTING_ID_RE = re.compile(r"/details/(\d+)/?")
# The property's own location is carried by a `"location":{...}` wrapper and a
# twin `"address":{"fullAddress":...}` widget object. We anchor on those
# wrappers (and capture their full object body, which contains exactly one
# nested object `coordinates`) rather than scanning for loose coordinate
# nested object, `coordinates`) rather than scanning for loose coordinate
# objects: nearby points of interest (stations/schools/EV chargers) and
# comparable/"similar" listings also embed coordinates, but never inside the
# property's own `"location"` / `"address":{"fullAddress"` wrapper, so the
@ -1225,7 +1225,7 @@ def parse_detail_geo(html: str, search_outcode: str | None = None) -> dict | Non
# twin, not the `location` wrapper we anchor coordinates on. Pull it from
# the twin that shares this property's uprn; when there is no uprn to
# disambiguate, fall back to the first twin (document order = primary
# listing), but never guess a twin when a uprn exists and none matches
# listing), but never guess a twin when a uprn exists and none matches:
# that would risk grabbing a comparable listing's address.
if result is None or result.get("full_address"):
return result
@ -1245,7 +1245,7 @@ def parse_detail_geo(html: str, search_outcode: str | None = None) -> dict | Non
result["full_address"] = first
return result
# Strategy 1 the property's own `location` wrapper (authoritative). Take
# Strategy 1: the property's own `location` wrapper (authoritative). Take
# the first match (the primary listing precedes any comparables in the
# flight stream), but prefer one whose outcode matches the searched outcode.
first_location = None
@ -1269,7 +1269,7 @@ def parse_detail_geo(html: str, search_outcode: str | None = None) -> dict | Non
if first_location is not None:
return attach_full_address(first_location)
# Strategy 2 the `address` map-widget twin (same coordinates, backup).
# Strategy 2: the `address` map-widget twin (same coordinates, backup).
for match in _DETAIL_ADDRESS_RE.finditer(buf):
full_address = match.group(1) or None
body = match.group(2)
@ -1284,7 +1284,7 @@ def parse_detail_geo(html: str, search_outcode: str | None = None) -> dict | Non
def _detail_cache_key(listing_url: str) -> str:
"""Cache key for a listing detail page its numeric id when present."""
"""Cache key for a listing detail page: its numeric id when present."""
id_match = _LISTING_ID_RE.search(listing_url)
return id_match.group(1) if id_match else listing_url
@ -1388,7 +1388,7 @@ def transform_property(
location comes from the listing's detail page (see ``parse_detail_geo`` /
``_fetch_listing_detail``), passed in as ``detail``. When detail-page
coordinates are available we resolve the nearest postcode via the spatial
index mirroring rightmove/onthemarket and only fall back to the coarse
index (mirroring rightmove/onthemarket) and only fall back to the coarse
outcode centroid when no detail location could be obtained."""
price = parse_int_value(raw.get("price")) or 0
@ -1563,7 +1563,7 @@ def search_outcode(
if detail_budget_seconds is not None:
detail_deadline = time.monotonic() + detail_budget_seconds
# Always try extraction even if result count is 0 the count regex may
# Always try extraction even if result count is 0: the count regex may
# not match Zoopla's current text format, but listings may still be in DOM
raw_listings = _paginate(
page,
@ -1577,7 +1577,7 @@ def search_outcode(
if not raw_listings:
if total_results > 0:
log.debug(
"Zoopla %s %s: page claims %d results but extraction found 0 "
"Zoopla %s %s: page claims %d results but extraction found 0; "
"DOM selectors may need updating",
outcode, "BUY", total_results,
)

View file

@ -1,7 +1,7 @@
"""Zoopla scraping via FlareSolverr (no browser/VNC needed).
FlareSolverr solves Zoopla's Cloudflare and returns the rendered HTML, which
still contains the React Server Components flight stream so the existing pure
still contains the React Server Components flight stream, so the existing pure
parsers work unchanged:
- the search page yields the outcode's listing detail URLs, and
- each detail page's flight stream carries the property's location object

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Beckenham vs Croydon: the same terraced house, about 31% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/br3-3-vs-cr0-7" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Beckenham vs Croydon", "item": "https://perfect-postcode.co.uk/cheaper-twin/br3-3-vs-cr0-7"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Beckenham vs Croydon: the same terraced house, about 31% cheaper per m²</h1>
<div class="big">31% cheaper / m²</div>
<p class="hook">£201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.38969&amp;lon=-0.04244&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:5200&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Beckenham (BR3 3)</th><th>Croydon (CR0 7)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£7,153</td><td class='val'>£4,910</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£643,770</td><td class='val'><span class="cheaper">£441,900</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Terraced</td><td class='val'>Terraced</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>7.8</td><td class='val'>7.8</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.73 km</td><td class='val'>~0.73 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>4,514</td><td class='val'>5,143</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Beckenham (BR3 3) and Croydon (CR0 7) sit about 2.02 km apart, share the same dominant housing (terraced, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>31% (about £201,870 on a 90 m² property) cheaper in Croydon</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/ha7-2-vs-ha3-0"><b>Stanmore vs Kenton</b><br>£106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart</a>
<a href="/cheaper-twin/ig8-7-vs-ig6-2"><b>Woodford Green vs Barkingside</b><br>£164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart</a>
<a href="/cheaper-twin/l16-7-vs-l14-6"><b>Childwall vs Broadgreen</b><br>£106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/ha7-2-vs-ha3-0" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Stanmore vs Kenton", "item": "https://perfect-postcode.co.uk/cheaper-twin/ha7-2-vs-ha3-0"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Stanmore vs Kenton: the same semi-detached house, about 17% cheaper per m²</h1>
<div class="big">17% cheaper / m²</div>
<p class="hook">£106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.59199&amp;lon=-0.3079&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:5900&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Stanmore (HA7 2)</th><th>Kenton (HA3 0)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£6,834</td><td class='val'>£5,646</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£615,060</td><td class='val'><span class="cheaper">£508,140</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Semi-Detached</td><td class='val'>Semi-Detached</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.9</td><td class='val'>2.9</td></tr>
<tr><td>Nearest station</td><td class='val'>~1.31 km</td><td class='val'>~1.31 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>2,775</td><td class='val'>3,122</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Stanmore (HA7 2) and Kenton (HA3 0) sit about 2.57 km apart, share the same dominant housing (semi-detached, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>17% (about £106,920 on a 90 m² property) cheaper in Kenton</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/ig8-7-vs-ig6-2"><b>Woodford Green vs Barkingside</b><br>£164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart</a>
<a href="/cheaper-twin/l16-7-vs-l14-6"><b>Childwall vs Broadgreen</b><br>£106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart</a>
<a href="/cheaper-twin/m40-5-vs-m9-4"><b>Newton Heath vs Harpurhey</b><br>£106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/ig8-7-vs-ig6-2" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Woodford Green vs Barkingside", "item": "https://perfect-postcode.co.uk/cheaper-twin/ig8-7-vs-ig6-2"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Woodford Green vs Barkingside: the same terraced house, about 26% cheaper per m²</h1>
<div class="big">26% cheaper / m²</div>
<p class="hook">£164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.60238&amp;lon=0.06063&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:5600&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Woodford Green (IG8 7)</th><th>Barkingside (IG6 2)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£7,148</td><td class='val'>£5,325</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£643,320</td><td class='val'><span class="cheaper">£479,250</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Terraced</td><td class='val'>Terraced</td></tr>
<tr><td>Typical build era</td><td class='val'>~1958</td><td class='val'>~1958</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.8</td><td class='val'>2.8</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.58 km</td><td class='val'>~0.58 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>2,965</td><td class='val'>4,423</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Woodford Green (IG8 7) and Barkingside (IG6 2) sit about 2.98 km apart, share the same dominant housing (terraced, typically built around 1958), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>26% (about £164,070 on a 90 m² property) cheaper in Barkingside</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/l16-7-vs-l14-6"><b>Childwall vs Broadgreen</b><br>£106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart</a>
<a href="/cheaper-twin/m40-5-vs-m9-4"><b>Newton Heath vs Harpurhey</b><br>£106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart</a>
<a href="/cheaper-twin/rm14-2-vs-rm12-5"><b>Upminster vs Hornchurch</b><br>£115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/l16-7-vs-l14-6" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Childwall vs Broadgreen", "item": "https://perfect-postcode.co.uk/cheaper-twin/l16-7-vs-l14-6"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Childwall vs Broadgreen: the same semi-detached house, about 30% cheaper per m²</h1>
<div class="big">30% cheaper / m²</div>
<p class="hook">£106,740 less for an equivalent semi-detached house: same station, similar schools, ~1.88km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=53.40344&amp;lon=-2.88529&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:3000&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Childwall (L16 7)</th><th>Broadgreen (L14 6)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£4,026</td><td class='val'>£2,840</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£362,340</td><td class='val'><span class="cheaper">£255,600</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Semi-Detached</td><td class='val'>Semi-Detached</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>5.1</td><td class='val'>5.1</td></tr>
<tr><td>Nearest station</td><td class='val'>~1.22 km</td><td class='val'>~1.22 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>500</td><td class='val'>809</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Childwall (L16 7) and Broadgreen (L14 6) sit about 1.88 km apart, share the same dominant housing (semi-detached, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>30% (about £106,740 on a 90 m² property) cheaper in Broadgreen</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/m40-5-vs-m9-4"><b>Newton Heath vs Harpurhey</b><br>£106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart</a>
<a href="/cheaper-twin/rm14-2-vs-rm12-5"><b>Upminster vs Hornchurch</b><br>£115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart</a>
<a href="/cheaper-twin/se28-8-vs-da18-4"><b>SE28 8 vs DA18 4</b><br>£129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/m40-5-vs-m9-4" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Newton Heath vs Harpurhey", "item": "https://perfect-postcode.co.uk/cheaper-twin/m40-5-vs-m9-4"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Newton Heath vs Harpurhey: the same terraced house, about 42% cheaper per m²</h1>
<div class="big">42% cheaper / m²</div>
<p class="hook">£106,740 less for an equivalent terraced house: same station, similar schools, ~1.18km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=53.51293&amp;lon=-2.19574&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:1700&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Newton Heath (M40 5)</th><th>Harpurhey (M9 4)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£2,812</td><td class='val'>£1,626</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£253,080</td><td class='val'><span class="cheaper">£146,340</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Terraced</td><td class='val'>Terraced</td></tr>
<tr><td>Typical build era</td><td class='val'>~1958</td><td class='val'>~1958</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.6</td><td class='val'>2.6</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.72 km</td><td class='val'>~0.72 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>1,632</td><td class='val'>3,530</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Newton Heath (M40 5) and Harpurhey (M9 4) sit about 1.18 km apart, share the same dominant housing (terraced, typically built around 1958), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>42% (about £106,740 on a 90 m² property) cheaper in Harpurhey</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/rm14-2-vs-rm12-5"><b>Upminster vs Hornchurch</b><br>£115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart</a>
<a href="/cheaper-twin/se28-8-vs-da18-4"><b>SE28 8 vs DA18 4</b><br>£129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart</a>
<a href="/cheaper-twin/sw1x-8-vs-sw7-2"><b>SW1X 8 vs SW7 2</b><br>£1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/rm14-2-vs-rm12-5" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Upminster vs Hornchurch", "item": "https://perfect-postcode.co.uk/cheaper-twin/rm14-2-vs-rm12-5"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Upminster vs Hornchurch: the same semi-detached house, about 20% cheaper per m²</h1>
<div class="big">20% cheaper / m²</div>
<p class="hook">£115,290 less for an equivalent semi-detached house: same station, similar schools, ~2.99km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.54892&amp;lon=0.22193&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:5300&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Upminster (RM14 2)</th><th>Hornchurch (RM12 5)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£6,360</td><td class='val'>£5,079</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£572,400</td><td class='val'><span class="cheaper">£457,110</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Semi-Detached</td><td class='val'>Semi-Detached</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>3.7</td><td class='val'>3.7</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.77 km</td><td class='val'>~0.77 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>3,026</td><td class='val'>3,133</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Upminster (RM14 2) and Hornchurch (RM12 5) sit about 2.99 km apart, share the same dominant housing (semi-detached, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>20% (about £115,290 on a 90 m² property) cheaper in Hornchurch</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/se28-8-vs-da18-4"><b>SE28 8 vs DA18 4</b><br>£129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart</a>
<a href="/cheaper-twin/sw1x-8-vs-sw7-2"><b>SW1X 8 vs SW7 2</b><br>£1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart</a>
<a href="/cheaper-twin/tw12-3-vs-kt8-1"><b>Hampton vs East Molesey</b><br>£120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/se28-8-vs-da18-4" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "SE28 8 vs DA18 4", "item": "https://perfect-postcode.co.uk/cheaper-twin/se28-8-vs-da18-4"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>SE28 8 vs DA18 4: the same terraced house, about 30% cheaper per m²</h1>
<div class="big">30% cheaper / m²</div>
<p class="hook">£129,690 less for an equivalent terraced house: same station, similar schools, ~1.72km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.50039&amp;lon=0.12568&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:3600&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>SE28 8</th><th>DA18 4</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£4,850</td><td class='val'>£3,409</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£436,500</td><td class='val'><span class="cheaper">£306,810</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Terraced</td><td class='val'>Terraced</td></tr>
<tr><td>Typical build era</td><td class='val'>~1993</td><td class='val'>~1993</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.8</td><td class='val'>2.8</td></tr>
<tr><td>Nearest station</td><td class='val'>~1.39 km</td><td class='val'>~1.39 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>5,033</td><td class='val'>1,063</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>SE28 8 and DA18 4 sit about 1.72 km apart, share the same dominant housing (terraced, typically built around 1993), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>30% (about £129,690 on a 90 m² property) cheaper in DA18 4</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/sw1x-8-vs-sw7-2"><b>SW1X 8 vs SW7 2</b><br>£1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart</a>
<a href="/cheaper-twin/tw12-3-vs-kt8-1"><b>Hampton vs East Molesey</b><br>£120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart</a>
<a href="/cheaper-twin/tw2-7-vs-tw3-2"><b>Twickenham vs Hounslow</b><br>£121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/sw1x-8-vs-sw7-2" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "SW1X 8 vs SW7 2", "item": "https://perfect-postcode.co.uk/cheaper-twin/sw1x-8-vs-sw7-2"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>SW1X 8 vs SW7 2: the same flat, about 42% cheaper per m²</h1>
<div class="big">42% cheaper / m²</div>
<p class="hook">£1,001,160 less for an equivalent flat: same station, similar schools, ~1.31km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.49758&amp;lon=-0.16439&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:16400&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>SW1X 8</th><th>SW7 2</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£26,735</td><td class='val'>£15,611</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£2,406,150</td><td class='val'><span class="cheaper">£1,404,990</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~1890</td><td class='val'>~1890</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>3.7</td><td class='val'>3.7</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.43 km</td><td class='val'>~0.43 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>1,410</td><td class='val'>1,126</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>SW1X 8 and SW7 2 sit about 1.31 km apart, share the same dominant housing (flats/maisonettes, typically built around 1890), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>42% (about £1,001,160 on a 90 m² property) cheaper in SW7 2</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/tw12-3-vs-kt8-1"><b>Hampton vs East Molesey</b><br>£120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart</a>
<a href="/cheaper-twin/tw2-7-vs-tw3-2"><b>Twickenham vs Hounslow</b><br>£121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart</a>
<a href="/cheaper-twin/w1j-7-vs-sw7-3"><b>W1J 7 vs SW7 3</b><br>£1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hampton vs East Molesey: the same terraced house, about 19% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/tw12-3-vs-kt8-1" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Hampton vs East Molesey", "item": "https://perfect-postcode.co.uk/cheaper-twin/tw12-3-vs-kt8-1"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Hampton vs East Molesey: the same terraced house, about 19% cheaper per m²</h1>
<div class="big">19% cheaper / m²</div>
<p class="hook">£120,060 less for an equivalent terraced house: same station, similar schools, ~2.23km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.41616&amp;lon=-0.37365&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:6000&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Hampton (TW12 3)</th><th>East Molesey (KT8 1)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£7,042</td><td class='val'>£5,708</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£633,780</td><td class='val'><span class="cheaper">£513,720</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Terraced</td><td class='val'>Terraced</td></tr>
<tr><td>Typical build era</td><td class='val'>~1979</td><td class='val'>~1979</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>3.2</td><td class='val'>3.2</td></tr>
<tr><td>Nearest station</td><td class='val'>~1.27 km</td><td class='val'>~1.27 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>2,527</td><td class='val'>1,567</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Hampton (TW12 3) and East Molesey (KT8 1) sit about 2.23 km apart, share the same dominant housing (terraced, typically built around 1979), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>19% (about £120,060 on a 90 m² property) cheaper in East Molesey</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/tw2-7-vs-tw3-2"><b>Twickenham vs Hounslow</b><br>£121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart</a>
<a href="/cheaper-twin/w1j-7-vs-sw7-3"><b>W1J 7 vs SW7 3</b><br>£1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart</a>
<a href="/cheaper-twin/w1j-8-vs-sw1a-2"><b>W1J 8 vs SW1A 2</b><br>£916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/tw2-7-vs-tw3-2" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Twickenham vs Hounslow", "item": "https://perfect-postcode.co.uk/cheaper-twin/tw2-7-vs-tw3-2"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Twickenham vs Hounslow: the same semi-detached house, about 19% cheaper per m²</h1>
<div class="big">19% cheaper / m²</div>
<p class="hook">£121,590 less for an equivalent semi-detached house: same station, similar schools, ~1.0km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.45678&amp;lon=-0.35702&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:5900&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Twickenham (TW2 7)</th><th>Hounslow (TW3 2)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£6,971</td><td class='val'>£5,620</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£627,390</td><td class='val'><span class="cheaper">£505,800</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Semi-Detached</td><td class='val'>Semi-Detached</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>5.4</td><td class='val'>5.4</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.59 km</td><td class='val'>~0.59 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>3,377</td><td class='val'>2,964</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Twickenham (TW2 7) and Hounslow (TW3 2) sit about 1.0 km apart, share the same dominant housing (semi-detached, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>19% (about £121,590 on a 90 m² property) cheaper in Hounslow</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/w1j-7-vs-sw7-3"><b>W1J 7 vs SW7 3</b><br>£1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart</a>
<a href="/cheaper-twin/w1j-8-vs-sw1a-2"><b>W1J 8 vs SW1A 2</b><br>£916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart</a>
<a href="/cheaper-twin/w1k-2-vs-sw1x-0"><b>W1K 2 vs SW1X 0</b><br>£978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>W1J 7 vs SW7 3: the same flat, about 41% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/w1j-7-vs-sw7-3" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "W1J 7 vs SW7 3", "item": "https://perfect-postcode.co.uk/cheaper-twin/w1j-7-vs-sw7-3"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>W1J 7 vs SW7 3: the same flat, about 41% cheaper per m²</h1>
<div class="big">41% cheaper / m²</div>
<p class="hook">£1,223,460 less for an equivalent flat: same station, similar schools, ~2.6km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.49856&amp;lon=-0.16253&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:20400&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Mayfair (W1J 7)</th><th>SW7 3</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£32,986</td><td class='val'>£19,392</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£2,968,740</td><td class='val'><span class="cheaper">£1,745,280</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~1914</td><td class='val'>~1914</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.0</td><td class='val'>2.0</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.3 km</td><td class='val'>~0.3 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>724</td><td class='val'>2,581</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Mayfair (W1J 7) and SW7 3 sit about 2.6 km apart, share the same dominant housing (flats/maisonettes, typically built around 1914), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>41% (about £1,223,460 on a 90 m² property) cheaper in SW7 3</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/w1j-8-vs-sw1a-2"><b>W1J 8 vs SW1A 2</b><br>£916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart</a>
<a href="/cheaper-twin/w1k-2-vs-sw1x-0"><b>W1K 2 vs SW1X 0</b><br>£978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart</a>
<a href="/cheaper-twin/w1u-4-vs-nw1-4"><b>Marylebone vs Camden</b><br>£942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/w1j-8-vs-sw1a-2" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "W1J 8 vs SW1A 2", "item": "https://perfect-postcode.co.uk/cheaper-twin/w1j-8-vs-sw1a-2"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>W1J 8 vs SW1A 2: the same flat, about 37% cheaper per m²</h1>
<div class="big">37% cheaper / m²</div>
<p class="hook">£916,380 less for an equivalent flat: same station, similar schools, ~1.31km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.50663&amp;lon=-0.13494&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:17900&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Mayfair (W1J 8)</th><th>SW1A 2</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£27,270</td><td class='val'>£17,088</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£2,454,300</td><td class='val'><span class="cheaper">£1,537,920</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~2000</td><td class='val'>~2000</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.0</td><td class='val'>2.0</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.18 km</td><td class='val'>~0.18 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>295</td><td class='val'>261</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Mayfair (W1J 8) and SW1A 2 sit about 1.31 km apart, share the same dominant housing (flats/maisonettes, typically built around 2000), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>37% (about £916,380 on a 90 m² property) cheaper in SW1A 2</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/w1k-2-vs-sw1x-0"><b>W1K 2 vs SW1X 0</b><br>£978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart</a>
<a href="/cheaper-twin/w1u-4-vs-nw1-4"><b>Marylebone vs Camden</b><br>£942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart</a>
<a href="/cheaper-twin/wc2a-2-vs-ec2a-2"><b>WC2A 2 vs EC2A 2</b><br>£981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/w1k-2-vs-sw1x-0" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "W1K 2 vs SW1X 0", "item": "https://perfect-postcode.co.uk/cheaper-twin/w1k-2-vs-sw1x-0"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>W1K 2 vs SW1X 0: the same flat, about 32% cheaper per m²</h1>
<div class="big">32% cheaper / m²</div>
<p class="hook">£978,570 less for an equivalent flat: same station, similar schools, ~1.62km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.50311&amp;lon=-0.15673&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:24700&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Mayfair (W1K 2)</th><th>SW1X 0</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£34,362</td><td class='val'>£23,489</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£3,092,580</td><td class='val'><span class="cheaper">£2,114,010</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~1914</td><td class='val'>~1914</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.0</td><td class='val'>2.0</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.5 km</td><td class='val'>~0.5 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>591</td><td class='val'>1,606</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Mayfair (W1K 2) and SW1X 0 sit about 1.62 km apart, share the same dominant housing (flats/maisonettes, typically built around 1914), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>32% (about £978,570 on a 90 m² property) cheaper in SW1X 0</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/w1u-4-vs-nw1-4"><b>Marylebone vs Camden</b><br>£942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart</a>
<a href="/cheaper-twin/wc2a-2-vs-ec2a-2"><b>WC2A 2 vs EC2A 2</b><br>£981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart</a>
<a href="/cheaper-twin/br3-3-vs-cr0-7"><b>Beckenham vs Croydon</b><br>£201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Marylebone vs Camden: the same flat, about 43% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/w1u-4-vs-nw1-4" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "Marylebone vs Camden", "item": "https://perfect-postcode.co.uk/cheaper-twin/w1u-4-vs-nw1-4"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>Marylebone vs Camden: the same flat, about 43% cheaper per m²</h1>
<div class="big">43% cheaper / m²</div>
<p class="hook">£942,480 less for an equivalent flat: same station, similar schools, ~0.97km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.5238&amp;lon=-0.15091&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:14500&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>Marylebone (W1U 4)</th><th>Camden (NW1 4)</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£24,238</td><td class='val'>£13,766</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£2,181,420</td><td class='val'><span class="cheaper">£1,238,940</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~1940</td><td class='val'>~1940</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>1.0</td><td class='val'>1.0</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.44 km</td><td class='val'>~0.44 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>984</td><td class='val'>1,340</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>Marylebone (W1U 4) and Camden (NW1 4) sit about 0.97 km apart, share the same dominant housing (flats/maisonettes, typically built around 1940), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>43% (about £942,480 on a 90 m² property) cheaper in Camden</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/wc2a-2-vs-ec2a-2"><b>WC2A 2 vs EC2A 2</b><br>£981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart</a>
<a href="/cheaper-twin/br3-3-vs-cr0-7"><b>Beckenham vs Croydon</b><br>£201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart</a>
<a href="/cheaper-twin/ha7-2-vs-ha3-0"><b>Stanmore vs Kenton</b><br>£106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m² | Perfect Postcode</title>
<meta name="description" content="£981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart" />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twin/wc2a-2-vs-ec2a-2" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "WC2A 2 vs EC2A 2", "item": "https://perfect-postcode.co.uk/cheaper-twin/wc2a-2-vs-ec2a-2"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">Cheaper twin · England</p>
<h1>WC2A 2 vs EC2A 2: the same flat, about 43% cheaper per m²</h1>
<div class="big">43% cheaper / m²</div>
<p class="hook">£981,540 less for an equivalent flat: same station, similar schools, ~2.3km apart</p>
<a class="cta" href="https://perfect-postcode.co.uk/?lat=51.51807&amp;lon=-0.09837&amp;zoom=12.5&amp;filter=Est.%20price%20per%20sqm:0:15300&amp;filter=Good%2B%20secondary%20school%20catchments:1:11">See both areas on the live map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th></th><th>WC2A 2</th><th>EC2A 2</th></tr></thead>
<tbody><tr><td>Estimated £/m²</td><td class='val'>£25,482</td><td class='val'>£14,576</td></tr>
<tr><td>On a 90 m² home</td><td class='val'>£2,293,380</td><td class='val'><span class="cheaper">£1,311,840</span></td></tr>
<tr><td>Dominant property type</td><td class='val'>Flats/Maisonettes</td><td class='val'>Flats/Maisonettes</td></tr>
<tr><td>Typical build era</td><td class='val'>~2019</td><td class='val'>~2019</td></tr>
<tr><td>Good+ secondary catchments</td><td class='val'>2.0</td><td class='val'>2.0</td></tr>
<tr><td>Nearest station</td><td class='val'>~0.42 km</td><td class='val'>~0.42 km</td></tr>
<tr><td>Sales in sample (N)</td><td class='val'>254</td><td class='val'>772</td></tr></tbody></table>
<section><h2>The same life, one postcode cheaper</h2><p>WC2A 2 and EC2A 2 sit about 2.3 km apart, share the same dominant housing (flats/maisonettes, typically built around 2019), comparable good-school catchments and the same level of station access. Yet an equivalent home works out roughly <b>43% (about £981,540 on a 90 m² property) cheaper in EC2A 2</b>. On the measures that move price they are near-identical; the gap is mostly the premium attached to the better-known name.</p></section>
<section><h2>How we worked this out</h2><p>Postcode sectors (e.g. N10 3) compared on estimated £/m² of floor space. A pair is only called a &#x27;twin&#x27; when the two sectors share the dominant property type, build era (±30y), good-school catchment provision, station access, deprivation/tenure, education, age and home size, so the price gap reflects a name premium, not a different kind of area. Estimates, not valuations; aggregated to sector, never address-level.</p></section>
<section><h2>Compare more areas</h2>
<div class="links">
<a href="/cheaper-twins"><b>All cheaper twins →</b><br>Browse every England name-premium pair we found.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Prices, crime, schools, broadband for any postcode.</a>
<a href="/property-price-map"><b>Property price map →</b><br>Rank England by what each £ buys.</a>
</div>
<div class="links"><a href="/cheaper-twin/br3-3-vs-cr0-7"><b>Beckenham vs Croydon</b><br>£201,870 less for an equivalent terraced house: same station, similar schools, ~2.02km apart</a>
<a href="/cheaper-twin/ha7-2-vs-ha3-0"><b>Stanmore vs Kenton</b><br>£106,920 less for an equivalent semi-detached house: same station, similar schools, ~2.57km apart</a>
<a href="/cheaper-twin/ig8-7-vs-ig6-2"><b>Woodford Green vs Barkingside</b><br>£164,070 less for an equivalent terraced house: same station, similar schools, ~2.98km apart</a></div>
</section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0. Figures are estimates derived from recorded sales and EPC floor areas, aggregated to postcode sector, not valuations, and not address-level.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,82 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cheaper twin postcodes in England | Perfect Postcode</title>
<meta name="description" content="Neighbouring England postcodes priced apart for the name, not the home. Find the cheaper twin of a pricier area." />
<link rel="canonical" href="https://perfect-postcode.co.uk/cheaper-twins" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "CollectionPage", "name": "Cheaper twins", "url": "https://perfect-postcode.co.uk/cheaper-twins"}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">England</p>
<h1>Cheaper twins: pay for the home, not the name</h1>
<p class="hook">Pairs of neighbouring England postcodes that share a station, school catchment and build era,
but sell thousands apart because one name got bid up. Built from 15 verified pairs.</p>
<a class="cta" href="/?ref=twins-hub">Find your cheaper twin on the map →</a>
</div></div>
<div class="wrap">
<section><div class="cards"><a class="card" href="/cheaper-twin/br3-3-vs-cr0-7"><div class="n">31%</div><div>Beckenham vs Croydon</div></a>
<a class="card" href="/cheaper-twin/ha7-2-vs-ha3-0"><div class="n">17%</div><div>Stanmore vs Kenton</div></a>
<a class="card" href="/cheaper-twin/ig8-7-vs-ig6-2"><div class="n">26%</div><div>Woodford Green vs Barkingside</div></a>
<a class="card" href="/cheaper-twin/l16-7-vs-l14-6"><div class="n">30%</div><div>Childwall vs Broadgreen</div></a>
<a class="card" href="/cheaper-twin/m40-5-vs-m9-4"><div class="n">42%</div><div>Newton Heath vs Harpurhey</div></a>
<a class="card" href="/cheaper-twin/rm14-2-vs-rm12-5"><div class="n">20%</div><div>Upminster vs Hornchurch</div></a>
<a class="card" href="/cheaper-twin/se28-8-vs-da18-4"><div class="n">30%</div><div>SE28 8 vs DA18 4</div></a>
<a class="card" href="/cheaper-twin/sw1x-8-vs-sw7-2"><div class="n">42%</div><div>SW1X 8 vs SW7 2</div></a>
<a class="card" href="/cheaper-twin/tw12-3-vs-kt8-1"><div class="n">19%</div><div>Hampton vs East Molesey</div></a>
<a class="card" href="/cheaper-twin/tw2-7-vs-tw3-2"><div class="n">19%</div><div>Twickenham vs Hounslow</div></a>
<a class="card" href="/cheaper-twin/w1j-7-vs-sw7-3"><div class="n">41%</div><div>W1J 7 vs SW7 3</div></a>
<a class="card" href="/cheaper-twin/w1j-8-vs-sw1a-2"><div class="n">37%</div><div>W1J 8 vs SW1A 2</div></a>
<a class="card" href="/cheaper-twin/w1k-2-vs-sw1x-0"><div class="n">32%</div><div>W1K 2 vs SW1X 0</div></a>
<a class="card" href="/cheaper-twin/w1u-4-vs-nw1-4"><div class="n">43%</div><div>Marylebone vs Camden</div></a>
<a class="card" href="/cheaper-twin/wc2a-2-vs-ec2a-2"><div class="n">43%</div><div>WC2A 2 vs EC2A 2</div></a></div></section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -80,4 +80,91 @@
<changefreq>yearly</changefreq>
<priority>0.3</priority>
</url>
<!-- DATA_PAGES_START -->
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/br3-3-vs-cr0-7</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/ha7-2-vs-ha3-0</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/ig8-7-vs-ig6-2</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/l16-7-vs-l14-6</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/m40-5-vs-m9-4</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/rm14-2-vs-rm12-5</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/se28-8-vs-da18-4</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/sw1x-8-vs-sw7-2</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/tw12-3-vs-kt8-1</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/tw2-7-vs-tw3-2</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/w1j-7-vs-sw7-3</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/w1j-8-vs-sw1a-2</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/w1k-2-vs-sw1x-0</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/w1u-4-vs-nw1-4</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twin/wc2a-2-vs-ec2a-2</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/square-metres-per-100k</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://perfect-postcode.co.uk/cheaper-twins</loc>
<changefreq>monthly</changefreq>
<priority>0.7</priority>
</url>
<!-- DATA_PAGES_END -->
</urlset>

View file

@ -0,0 +1,76 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How many square metres £100,000 buys across England | Perfect Postcode</title>
<meta name="description" content="£100k buys ~152 m² of floor space in BD21 3 but only ~3 m² in Mayfair (W1K 2)" />
<link rel="canonical" href="https://perfect-postcode.co.uk/square-metres-per-100k" />
<style>
:root{color-scheme:light dark}
*{box-sizing:border-box}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:#0b1220;background:#f7f5f0;line-height:1.6}
a{color:#0d9488}
.topbar{background:#0b1220;color:#e7ecf3;padding:.7rem 1.25rem;display:flex;justify-content:space-between;align-items:center}
.topbar a{color:#2dd4bf;text-decoration:none;font-weight:700}
.wrap{max-width:54rem;margin:0 auto;padding:0 1.25rem}
.hero{background:linear-gradient(#0b1220,#111a2e);color:#fff;padding:3rem 0 2.5rem}
.eyebrow{color:#2dd4bf;font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:.8rem;margin:0 0 .5rem}
h1{font-size:2rem;line-height:1.15;margin:.2rem 0 .6rem}
.hook{color:#cbd5e1;font-size:1.15rem;margin:.5rem 0 1.4rem;max-width:42rem}
.big{font-size:3rem;font-weight:800;color:#2dd4bf;margin:.3rem 0}
.cta{display:inline-block;margin-top:.4rem;padding:.8rem 1.4rem;border-radius:.6rem;background:#f09a22;color:#0b1220;
font-weight:700;text-decoration:none;box-shadow:0 6px 20px rgba(122,57,5,.35)}
.cta:hover{background:#df8614}
table{width:100%;border-collapse:collapse;margin:1.5rem 0;background:#fff;border-radius:.6rem;overflow:hidden;
box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:.7rem .9rem;text-align:left;border-bottom:1px solid #ece8e0;font-size:.95rem}
thead th{background:#0b1220;color:#fff}
tbody tr:last-child td{border-bottom:0}
.val{font-variant-numeric:tabular-nums;font-weight:600}
.cheaper{color:#0d9488}
section{margin:2rem 0}
h2{font-size:1.3rem;margin:0 0 .6rem}
.note{font-size:.82rem;color:#6b7280;border-top:1px solid #e5e1d8;padding-top:1rem;margin-top:2rem}
.links{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));margin:1rem 0}
.links a{display:block;background:#fff;border:1px solid #ece8e0;border-radius:.5rem;padding:.8rem 1rem;text-decoration:none;color:#0b1220}
.links a:hover{border-color:#5eead4}
.links b{color:#0d9488}
.cards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))}
.card{background:#fff;border:1px solid #ece8e0;border-radius:.6rem;padding:1.1rem;text-decoration:none;color:#0b1220}
.card:hover{border-color:#5eead4}
.card .n{color:#0d9488;font-weight:800;font-size:1.4rem}
footer{color:#6b7280;font-size:.8rem;padding:2rem 0 3rem}
@media(prefers-color-scheme:dark){body{background:#0b1220;color:#e7ecf3}table{background:#13203a}
th,td{border-color:#223153}.card,.links a{background:#13203a;border-color:#223153;color:#e7ecf3}
.note{border-color:#223153;color:#9fb0c3}}
</style>
<script type="application/ld+json">{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Home", "item": "https://perfect-postcode.co.uk/"}, {"@type": "ListItem", "position": 2, "name": "Cheaper twins", "item": "https://perfect-postcode.co.uk/cheaper-twins"}, {"@type": "ListItem", "position": 3, "name": "How many square metres \u00a3100,000 buys across England", "item": "https://perfect-postcode.co.uk/square-metres-per-100k"}]}</script>
</head>
<body>
<div class="topbar"><a href="/">Perfect Postcode</a><a href="/?ref=twin">Open the map →</a></div>
<div class="hero"><div class="wrap">
<p class="eyebrow">England · value index</p>
<h1>How many square metres £100,000 buys across England</h1>
<div class="big">152 m² vs 3 m²</div>
<p class="hook">£100k buys ~152 m² of floor space in BD21 3 but only ~3 m² in Mayfair (W1K 2)</p>
<a class="cta" href="https://perfect-postcode.co.uk/?zoom=6&amp;filter=Est.%20price%20per%20sqm:0:4000">Explore the value map →</a>
</div></div>
<div class="wrap">
<table><thead><tr><th>Sector</th><th>Est. £/m²</th><th>m² for £100k</th><th>N</th></tr></thead><tbody>
<tr><td>BD21 3 (best value)</td><td class='val'>£660</td><td class='val cheaper'>152 m²</td><td class='val'>1,377</td></tr>
<tr><td>W1K 2 (dearest)</td><td class='val'>£34,362</td><td class='val'>3 m²</td><td class='val'>591</td></tr>
</tbody></table>
<section><h2>How we worked this out</h2><p>100000 ÷ median estimated £/m², per England postcode sector with sufficient sales.</p></section>
<section><h2>More</h2><div class="links">
<a href="/cheaper-twins"><b>Cheaper twins →</b><br>Pairs of areas priced apart for the name, not the home.</a>
<a href="/postcode-checker"><b>Postcode checker →</b><br>Everything known about any postcode.</a>
</div></section>
<p class="note">Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</p>
</div>
<footer><div class="wrap">Sources: HM Land Registry · EPC (DLUHC) · Ofsted · DfT · ONS · Police.uk. Contains HM Land Registry data © Crown copyright and database right. Licensed under the Open Government Licence v3.0.</div></footer>
</body>
</html>

View file

@ -0,0 +1,14 @@
WEBVTT
00:00:00.203 --> 00:00:06.283
Beckenham and Croydon sit side by side: same trains, same school catchment.
00:00:06.683 --> 00:00:10.523
Rank them by what each pound of floor space actually buys.
00:00:11.823 --> 00:00:16.943
One postcode over, the same home quietly costs about a third less.
00:00:18.093 --> 00:00:22.893
Beckenham's cheaper twin is on this map. Find yours, free.

View file

@ -0,0 +1,14 @@
WEBVTT
00:00:00.202 --> 00:00:05.882
Stanmore and Kenton sit right next door, with the same schools and transport links.
00:00:06.282 --> 00:00:10.842
Rank every postcode by what each pound of floor space actually buys.
00:00:12.142 --> 00:00:17.422
One postcode over, the same home quietly costs about a sixth less.
00:00:18.572 --> 00:00:22.652
Stanmore's cheaper twin is on this map. Find yours, free.

View file

@ -0,0 +1,14 @@
WEBVTT
00:00:00.203 --> 00:00:06.843
Childwall and Broadgreen sit right next door, with the same schools and transport links.
00:00:07.243 --> 00:00:11.803
Rank every postcode by what each pound of floor space actually buys.
00:00:13.103 --> 00:00:18.223
One postcode over, the same home quietly costs about a third less.
00:00:19.373 --> 00:00:24.493
Childwall's cheaper twin is on this map. Find yours, free.

Some files were not shown because too many files have changed in this diff Show more