No description
Find a file
Andras Schmelczer 6ea544a0f6
Some checks failed
CI / Check (push) Failing after 6m52s
Build and publish Docker image / build-and-push (push) Failing after 16m5s
fmt
2026-05-17 19:48:55 +01:00
.forgejo/workflows fmt 2026-05-17 19:48:55 +01:00
.vscode lgtm 2026-05-12 22:00:56 +01:00
analyses seems alright 2026-05-17 13:52:11 +01:00
finder fmt 2026-05-17 19:48:55 +01:00
frontend fmt 2026-05-17 19:48:55 +01:00
manual-data gitignore 2026-05-12 06:45:21 +01:00
pipeline all is well 2026-05-17 17:20:19 +01:00
property-data Changes 2026-02-08 18:40:17 +00:00
property-data2 all is well 2026-05-17 17:20:19 +01:00
r5-java all good 2026-05-17 10:16:30 +01:00
screenshot lgtm 2 2026-05-14 22:39:41 +01:00
scripts Remove finder 2026-04-04 22:59:28 +01:00
server-rs fmt 2026-05-17 19:48:55 +01:00
video all good 2026-05-17 10:16:30 +01:00
.dockerignore LGTM 2026-05-11 21:38:26 +01:00
.gitignore all good 2026-05-17 10:16:30 +01:00
.python-version initial 2026-01-23 20:58:41 +00:00
check.sh lgtm 2026-05-12 22:13:07 +01:00
docker-compose.yml Fix crime & add actual listings 2026-05-17 11:12:25 +01:00
Dockerfile all good 2026-05-17 10:16:30 +01:00
grafana-dashboard.json Fun changes 2026-04-04 22:59:44 +01:00
Makefile.data Fix crime & add actual listings 2026-05-17 11:12:25 +01:00
Pasted image 20260515211038.png all is well 2026-05-17 17:20:19 +01:00
pyproject.toml seems alright 2026-05-17 13:52:11 +01:00
README.md all good 2026-05-17 10:16:30 +01:00
uv.lock seems alright 2026-05-17 13:52:11 +01:00

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=...

# Optional Bugsink/Sentry-compatible error reporting
export BUGSINK_DSN=...
export FRONTEND_BUGSINK_DSN=...
export BUGSINK_ENVIRONMENT=development
export BUGSINK_RELEASE=...
export BUGSINK_SEND_DEFAULT_PII=false
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.

Bugsink

Bugsink is wired through the Sentry-compatible SDKs. Set BUGSINK_DSN for the Rust API and FRONTEND_BUGSINK_DSN for the browser app. If the frontend DSN is omitted, the server falls back to BUGSINK_DSN when injecting runtime config into served HTML.

The frontend build also accepts FRONTEND_BUGSINK_DSN, BUGSINK_ENVIRONMENT, BUGSINK_RELEASE, and BUGSINK_SEND_DEFAULT_PII as build-time values. Runtime HTML injection is preferred for Docker deployments because the DSN can be set with environment variables when the container starts.

Production Webpack builds emit hidden source maps. Upload them to Bugsink after building if you want browser stack traces to resolve to source:

cd frontend
npx sentry-cli sourcemaps inject dist
SENTRY_AUTH_TOKEN=... npx sentry-cli --url https://your-bugsink-instance \
  sourcemaps --org bugsinkhasnoorgs --project ignoredfornow upload dist