From 0c6283d2fa9fd65822fd8101f5e1ad69eb80bc3c Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Mon, 26 Jan 2026 19:57:08 +0000 Subject: [PATCH] Fix proxying --- CLAUDE.md | 69 ++++++++++++++++++++++++++++++++++++++ frontend/src/App.tsx | 27 ++++++++++++++- frontend/webpack.config.js | 3 ++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9a7302a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,69 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Property Map is a full-stack geospatial web application that visualizes UK property price data aggregated by H3 hexagonal spatial indices. It combines Land Registry price data with postcode geolocation to create an interactive map for exploring property markets. + +## Commands + +All commands use [Task](https://taskfile.dev) runner. Install with: `curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | sudo -E bash` + +```bash +# Initial setup (downloads ~GB of data, runs pipeline) +task prepare + +# Development (run in separate terminals) +task server # FastAPI backend on :8001 +task frontend # Webpack dev server on :3030 (proxies /api to :8001) + +# Code quality +task lint # Lint Python (ruff) + TypeScript (ESLint + Prettier) +task format # Auto-fix formatting +task typecheck # TypeScript type checking +task check # All checks (lint + typecheck + build) + +# Production +task build # Build frontend +task prod # Serve built frontend via FastAPI +``` + +## Architecture + +``` +frontend/ React + TypeScript SPA (deck.gl/MapLibre for visualization) + src/App.tsx Main component with filters and map state + src/components/ Map.tsx (deck.gl H3HexagonLayer), Filters UI + +server/ FastAPI backend + main.py App setup, CORS, static file mounting + routes/hexagons.py GET /api/hexagons - returns aggregated price data + +pipeline/ Data processing (Polars + H3) + config.py Central config (H3 resolutions 6-11, year/price ranges) + sources/ Postcode loading, property price joins + processors/ H3 aggregation (count, avg/median/min/max by cell+year) + +tfl_journey_client/ Generated TFL API client (local package) +``` + +## Data Flow + +1. **Download**: Land Registry prices + ArcGIS postcode→lat/lon mappings → `data_sources/` +2. **Pipeline**: Join data, compute H3 indices, aggregate stats → `data_sources/processed/aggregates/*.parquet` +3. **Serve**: Load parquet files into memory, filter by bounds/year/price, return as GeoJSON-like response +4. **Visualize**: Frontend fetches on viewport change, renders hexagons colored by average price + +## Tech Stack + +- **Frontend**: React 18, TypeScript, Webpack, TailwindCSS, deck.gl, MapLibre GL +- **Backend**: Python 3.12, FastAPI, Polars, H3 +- **Package managers**: `uv` (Python), `npm` (frontend) + +## Key Implementation Details + +- Backend caches dataframes in memory and uses LRU cache on queries +- Bounds rounded to 0.01° precision to improve cache hits +- Results capped at 50,000 hexagons per request (truncated flag in response) +- Frontend debounces API calls on map movement diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index acdb3c6..3a1f10e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,6 +12,31 @@ import type { const DEBOUNCE_MS = 150; +// Detect if running through VS Code web proxy and construct API base URL +function getApiBaseUrl(): string { + const { hostname, pathname, href } = window.location; + + // Check pathname for /proxy/PORT pattern + const pathMatch = pathname.match(/^(\/proxy\/)(\d+)/); + if (pathMatch) { + return `${pathMatch[1]}8001`; + } + + // Check full href in case proxy rewrites pathname + const hrefMatch = href.match(/(\/proxy\/)\d+/); + if (hrefMatch) { + return `${hrefMatch[1]}8001`; + } + + // If not localhost, assume we're behind a proxy and need explicit backend port + if (hostname !== 'localhost' && hostname !== '127.0.0.1') { + return '/proxy/8001'; + } + + // Local development - webpack proxies /api to :8001 + return ''; +} + export default function App() { const [filters, setFilters] = useState(DEFAULT_FILTERS); const [data, setData] = useState([]); @@ -49,7 +74,7 @@ export default function App() { max_price: filters.maxPrice.toString(), bounds: boundsStr, }); - const res = await fetch(`/api/hexagons?${params}`, { + const res = await fetch(`${getApiBaseUrl()}/api/hexagons?${params}`, { signal: abortControllerRef.current.signal, }); const json: ApiResponse = await res.json(); diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index f175910..a2a1872 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -7,6 +7,7 @@ module.exports = { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', clean: true, + publicPath: './', }, resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'], @@ -31,6 +32,8 @@ module.exports = { ], devServer: { port: 3000, + allowedHosts: 'all', + historyApiFallback: true, proxy: [ { context: ['/api'],