# 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`: - [Home](https://perfect-postcode.co.uk/) - `/` - [Learn](https://perfect-postcode.co.uk/learn) - `/learn` - [Pricing](https://perfect-postcode.co.uk/pricing) - `/pricing` - [Property price map](https://perfect-postcode.co.uk/property-price-map) - `/property-price-map` - [Postcode property search](https://perfect-postcode.co.uk/postcode-property-search) - `/postcode-property-search` - [Commute property search](https://perfect-postcode.co.uk/commute-property-search) - `/commute-property-search` - [School property search](https://perfect-postcode.co.uk/school-property-search) - `/school-property-search` - [Postcode checker](https://perfect-postcode.co.uk/postcode-checker) - `/postcode-checker` - [Birmingham property search](https://perfect-postcode.co.uk/property-search/birmingham) - `/property-search/birmingham` - [Manchester property search](https://perfect-postcode.co.uk/property-search/manchester) - `/property-search/manchester` - [Bristol property search](https://perfect-postcode.co.uk/property-search/bristol) - `/property-search/bristol` - [Data sources](https://perfect-postcode.co.uk/data-sources) - `/data-sources` - [Methodology](https://perfect-postcode.co.uk/methodology) - `/methodology` - [Privacy and security](https://perfect-postcode.co.uk/privacy-security) - `/privacy-security` ## 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: ```text 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: ```bash 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: ```bash make -f Makefile.data generate-travel-times ``` For a quick R5 smoke test: ```bash ./r5-java/run.sh --demo ``` ## Local Development With the required files in `property-data/`, the full stack can be started with Docker Compose: ```bash docker compose up --build ``` Services: - frontend: http://localhost:3001 - API: http://localhost:8001 - PocketBase: http://localhost:8090 - screenshot service: http://localhost:8002 The frontend dev server proxies `/api` and `/s` to the Rust API and `/pb` to PocketBase. To run pieces directly: ```bash cd frontend npm install npm run dev ``` Export the server's service configuration first: ```bash 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 ``` ```bash 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: ```bash ./check.sh ``` It runs Python lint/tests, frontend lint/format/typecheck/tests, screenshot service tests, and Rust clippy/format/tests. Useful focused commands: ```bash 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. ```bash 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: ```bash 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 ```