perfect-postcode/README.md
2026-05-11 21:38:26 +01:00

6 KiB

Property Map

Interactive UK property intelligence map. The app combines transaction, EPC, postcode, neighbourhood, transport, POI, and travel-time data into local parquet files, serves fast geospatial aggregations from Rust, and renders the result as a React/deck.gl map.

The public product is branded as Perfect Postcodes, while this repository is still named property-map.

Public SEO Pages

The indexable public pages are listed in frontend/public/sitemap.xml and prerendered by frontend/scripts/prerender.mjs:

What Is In Here

  • frontend/ - React 18, TypeScript, Tailwind, MapLibre, and deck.gl. The app has a landing page, map dashboard, saved searches/properties, account pages, pricing, invites, and shareable URLs.
  • server-rs/ - Rust Axum API. It loads the generated parquet data into memory, builds spatial indexes, serves H3/postcode aggregations, proxies PocketBase, serves PMTiles, handles AI filter parsing, screenshots, exports, checkout, and telemetry.
  • pipeline/ - Python/Polars download and transform pipeline. Makefile.data orchestrates the data DAG.
  • r5-java/ - Batch travel-time generator using Conveyal R5. It writes sparse per-destination parquet files for car, bicycle, walking, and transit.
  • screenshot/ - Playwright/Express service used by the Rust API for map screenshots and Open Graph images.
  • property-data/ and manual-data/ - Local generated/downloaded data. These are runtime inputs, not source code.

Runtime Data

The Rust server expects these files or directories to exist:

property-data/properties.parquet
property-data/postcode.parquet
property-data/filtered_uk_pois.parquet
property-data/places.parquet
property-data/uk.pmtiles
property-data/postcode_boundaries/
property-data/travel-times/

Most data, including police.uk crime archives, can be downloaded or generated through Makefile.data. Some inputs are deliberately manual:

  • manual-data/domestic-csv.zip from the EPC register
  • postcode boundaries, generated from OA boundaries, INSPIRE polygons, and UPRN lookup data

Build the main property datasets with:

uv sync
make -f Makefile.data prepare
make -f Makefile.data tiles
make -f Makefile.data download-places
make -f Makefile.data generate-postcode-boundaries

generate-postcode-boundaries writes to manual-data/postcode_boundaries/. The running server expects the same structure under property-data/postcode_boundaries/; copy or symlink it if needed.

Travel times are built separately because they are expensive:

make -f Makefile.data generate-travel-times

For a quick R5 smoke test:

./r5-java/run.sh --demo

Local Development

With the required files in property-data/, the full stack can be started with Docker Compose:

docker compose up --build

Services:

The frontend dev server proxies /api and /s to the Rust API and /pb to PocketBase.

To run pieces directly:

cd frontend
npm install
npm run dev

Export the server's service configuration first:

export SCREENSHOT_URL=http://localhost:8002
export PUBLIC_URL=http://localhost:3001
export POCKETBASE_URL=http://localhost:8090
export POCKETBASE_ADMIN_EMAIL=...
export POCKETBASE_ADMIN_PASSWORD=...
export GEMINI_API_KEY=...
export GEMINI_MODEL=...
export GOOGLE_MAPS_API_KEY=...
export STRIPE_SECRET_KEY=...
export STRIPE_WEBHOOK_SECRET=...
export STRIPE_REFERRAL_COUPON_ID=...
export GOOGLE_OAUTH_CLIENT_ID=...
export GOOGLE_OAUTH_CLIENT_SECRET=...
cd server-rs
cargo run -- \
  --properties ../property-data/properties.parquet \
  --postcode-features ../property-data/postcode.parquet \
  --pois ../property-data/filtered_uk_pois.parquet \
  --places ../property-data/places.parquet \
  --tiles ../property-data/uk.pmtiles \
  --postcodes ../property-data/postcode_boundaries \
  --travel-times ../property-data/travel-times

Checks

Run the combined local check script:

./check.sh

It runs Python lint/tests, frontend lint/format/typecheck/tests, screenshot service tests, and Rust clippy/format/tests.

Useful focused commands:

uv run ruff check .
uv run pytest

cd frontend
npm run lint
npm run typecheck
npm run test
npm run build

cd ../server-rs
cargo clippy --all-targets -- -D warnings
cargo fmt --all --check
cargo test

Production Build

The root Dockerfile builds the frontend and Rust server into a runtime image. Data is mounted at /app/data; it is not baked into the image.

docker build -t property-map .

The container entrypoint runs property-map-server with the expected data paths under /app/data and serves frontend/dist when --dist is present.