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