diff --git a/Dockerfile b/Dockerfile index f460db4..59ec2e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,4 +47,4 @@ EXPOSE 8001 HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=3 \ CMD curl -f http://localhost:8001/health || exit 1 ENTRYPOINT ["./property-map-server"] -CMD ["--properties", "/app/data/properties.parquet", "--postcode-features", "/app/data/postcode.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--places", "/app/data/places.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/postcode_boundaries", "--travel-times", "/app/data/travel-times", "--dist", "/app/frontend/dist"] +CMD ["--properties", "/app/data/properties.parquet", "--postcode-features", "/app/data/postcode.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--places", "/app/data/places.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/postcode_boundaries", "--travel-times", "/app/data/travel-times", "--satellite-tiles", "/app/data/satellite.pmtiles", "--satellite-highres-tiles", "/app/data/satellite_highres.pmtiles", "--noise-overlay-tiles", "/app/data/noise_lden_10m.pmtiles", "--crime-hotspot-tiles", "/app/data/crime_hotspots.pmtiles", "--tree-overlay-tiles", "/app/data/trees_outside_woodlands.pmtiles", "--property-border-tiles", "/app/data/property_borders.pmtiles", "--dist", "/app/frontend/dist"] diff --git a/docker-compose.yml b/docker-compose.yml index 40f3445..f10af75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: command: > bash -c " cargo install cargo-watch && - cargo watch --poll -i logs/ -x 'run -- --properties /app/property-data4/properties.parquet --postcode-features /app/property-data4/postcode.parquet --pois /app/property-data4/filtered_uk_pois.parquet --places /app/property-data4/places.parquet --tiles /app/property-data4/uk.pmtiles --postcodes /app/property-data4/postcode_boundaries --travel-times /app/property-data4/travel-times' + cargo watch --poll -i logs/ -x 'run -- --properties /app/property-data4/properties.parquet --postcode-features /app/property-data4/postcode.parquet --pois /app/property-data4/filtered_uk_pois.parquet --places /app/property-data4/places.parquet --tiles /app/property-data4/uk.pmtiles --postcodes /app/property-data4/postcode_boundaries --travel-times /app/property-data4/travel-times --satellite-tiles /app/property-data4/satellite.pmtiles --satellite-highres-tiles /app/property-data4/satellite_highres.pmtiles --noise-overlay-tiles /app/property-data4/noise_lden_10m.pmtiles --crime-hotspot-tiles /app/property-data4/crime_hotspots.pmtiles --tree-overlay-tiles /app/property-data4/trees_outside_woodlands.pmtiles --property-border-tiles /app/property-data4/property_borders.pmtiles' " ports: - "8001:8001" @@ -51,8 +51,6 @@ services: BUGSINK_ENVIRONMENT: ${BUGSINK_ENVIRONMENT:-development} BUGSINK_RELEASE: ${BUGSINK_RELEASE:-} BUGSINK_SEND_DEFAULT_PII: ${BUGSINK_SEND_DEFAULT_PII:-false} - ACTUAL_LISTINGS_PATH: /app/finder/data/online_listings_buy_enriched.parquet - CRIME_BY_YEAR_PATH: /app/property-data4/crime_by_postcode_by_year.parquet depends_on: screenshot: condition: service_healthy diff --git a/server-rs/src/bugsink.rs b/server-rs/src/bugsink.rs index 38d97fe..018d9c2 100644 --- a/server-rs/src/bugsink.rs +++ b/server-rs/src/bugsink.rs @@ -21,10 +21,6 @@ pub struct FrontendConfig { pub send_default_pii: bool, } -pub fn env_nonempty(name: &str) -> Option { - std::env::var(name).ok().and_then(nonempty) -} - pub fn nonempty(value: String) -> Option { let trimmed = value.trim(); (!trimmed.is_empty()).then(|| trimmed.to_owned()) diff --git a/server-rs/src/data/crime_by_year.rs b/server-rs/src/data/crime_by_year.rs index cba947f..ceff34a 100644 --- a/server-rs/src/data/crime_by_year.rs +++ b/server-rs/src/data/crime_by_year.rs @@ -40,14 +40,6 @@ pub struct CrimeByYearData { } impl CrimeByYearData { - pub fn empty() -> Self { - Self { - crime_types: Vec::new(), - years_by_type: Vec::new(), - series_by_postcode: FxHashMap::default(), - } - } - pub fn load(path: &Path) -> anyhow::Result { run_polars_io(|| Self::load_inner(path)) } diff --git a/server-rs/src/main.rs b/server-rs/src/main.rs index 05a1e3e..5722f16 100644 --- a/server-rs/src/main.rs +++ b/server-rs/src/main.rs @@ -251,29 +251,29 @@ struct Cli { #[arg(long)] tiles: PathBuf, - /// Optional PMTiles raster basemap for satellite imagery. + /// PMTiles raster basemap for satellite imagery. #[arg(long, env = "SATELLITE_TILES")] - satellite_tiles: Option, + satellite_tiles: PathBuf, - /// Optional PMTiles raster overlay for high-resolution EA aerial photography. + /// PMTiles raster overlay for high-resolution EA aerial photography. #[arg(long, env = "SATELLITE_HIGHRES_TILES")] - satellite_highres_tiles: Option, + satellite_highres_tiles: PathBuf, - /// Optional PMTiles raster overlay for high-resolution strategic noise. + /// PMTiles raster overlay for high-resolution strategic noise. #[arg(long, env = "NOISE_OVERLAY_TILES")] - noise_overlay_tiles: Option, + noise_overlay_tiles: PathBuf, - /// Optional PMTiles vector overlay for crime heatmap points. + /// PMTiles vector overlay for crime heatmap points. #[arg(long, env = "CRIME_HOTSPOT_TILES")] - crime_hotspot_tiles: Option, + crime_hotspot_tiles: PathBuf, - /// Optional PMTiles vector overlay for Trees Outside Woodland polygons. + /// PMTiles vector overlay for Trees Outside Woodland polygons. #[arg(long, env = "TREE_OVERLAY_TILES")] - tree_overlay_tiles: Option, + tree_overlay_tiles: PathBuf, - /// Optional PMTiles vector overlay for INSPIRE property-border polygons. + /// PMTiles vector overlay for INSPIRE property-border polygons. #[arg(long, env = "PROPERTY_BORDER_TILES")] - property_border_tiles: Option, + property_border_tiles: PathBuf, /// Path to the frontend dist directory (optional; disables static serving and OG injection when omitted) #[arg(long)] @@ -311,13 +311,13 @@ struct Cli { #[arg(long, env = "TRAVEL_TIMES")] travel_times: PathBuf, - /// Optional path to a parquet of live online listings (Rightmove etc.) to overlay on the map. + /// Path to a parquet of live online listings (Rightmove etc.) to overlay on the map. #[arg(long, env = "ACTUAL_LISTINGS_PATH")] - actual_listings_path: Option, + actual_listings_path: PathBuf, - /// Optional path to the per-LSOA per-year crime parquet (display-only side table for the right pane). + /// Path to the per-LSOA per-year crime parquet (display-only side table for the right pane). #[arg(long, env = "CRIME_BY_YEAR_PATH")] - crime_by_year_path: Option, + crime_by_year_path: PathBuf, /// Google Maps API key for Street View metadata lookups #[arg(long, env = "GOOGLE_MAPS_API_KEY")] @@ -345,22 +345,22 @@ struct Cli { /// Bugsink DSN for backend error reporting #[arg(long, env = "BUGSINK_DSN", hide_env_values = true)] - bugsink_dsn: Option, + bugsink_dsn: String, - /// Bugsink DSN injected into the browser app; falls back to BUGSINK_DSN when omitted + /// Bugsink DSN injected into the browser app #[arg(long, env = "FRONTEND_BUGSINK_DSN", hide_env_values = true)] - frontend_bugsink_dsn: Option, + frontend_bugsink_dsn: String, /// Bugsink/Sentry environment name #[arg(long, env = "BUGSINK_ENVIRONMENT")] - bugsink_environment: Option, + bugsink_environment: String, /// Bugsink/Sentry release name #[arg(long, env = "BUGSINK_RELEASE")] - bugsink_release: Option, + bugsink_release: String, /// Include default PII in Bugsink events - #[arg(long, env = "BUGSINK_SEND_DEFAULT_PII", default_value_t = false)] + #[arg(long, env = "BUGSINK_SEND_DEFAULT_PII", action = clap::ArgAction::Set)] bugsink_send_default_pii: bool, } @@ -404,49 +404,19 @@ async fn init_required_tile_reader( Ok(Arc::new(routes::init_tile_reader(path).await?)) } -fn configured_or_default_overlay_path( - configured: &Option, - tiles_path: &Path, - file_name: &str, -) -> PathBuf { - if let Some(path) = configured { - return path.clone(); - } - - tiles_path - .parent() - .map(|parent| parent.join(file_name)) - .unwrap_or_else(|| PathBuf::from(file_name)) -} - #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let bugsink_environment = cli - .bugsink_environment - .clone() - .or_else(|| bugsink::env_nonempty("SENTRY_ENVIRONMENT")); - let bugsink_release = cli - .bugsink_release - .clone() - .or_else(|| bugsink::env_nonempty("SENTRY_RELEASE")); - let backend_bugsink_dsn = cli - .bugsink_dsn - .clone() - .or_else(|| bugsink::env_nonempty("SENTRY_DSN")); let _bugsink_guard = bugsink::init_backend(&bugsink::BackendConfig { - dsn: backend_bugsink_dsn.clone(), - environment: bugsink_environment.clone(), - release: bugsink_release.clone(), + dsn: Some(cli.bugsink_dsn.clone()), + environment: Some(cli.bugsink_environment.clone()), + release: Some(cli.bugsink_release.clone()), send_default_pii: cli.bugsink_send_default_pii, }); let bugsink_frontend_config = bugsink::frontend_config( - cli.frontend_bugsink_dsn - .clone() - .or_else(|| bugsink::env_nonempty("PUBLIC_BUGSINK_DSN")) - .or(backend_bugsink_dsn), - bugsink_environment.clone(), - bugsink_release.clone(), + Some(cli.frontend_bugsink_dsn.clone()), + Some(cli.bugsink_environment.clone()), + Some(cli.bugsink_release.clone()), cli.bugsink_send_default_pii, ); @@ -569,44 +539,17 @@ async fn main() -> anyhow::Result<()> { let tile_reader = Arc::new(routes::init_tile_reader(tiles_path).await?); info!("PMTiles loaded successfully"); - let noise_overlay_tiles = configured_or_default_overlay_path( - &cli.noise_overlay_tiles, - tiles_path, - "noise_lden_10m.pmtiles", - ); - let satellite_tiles = - configured_or_default_overlay_path(&cli.satellite_tiles, tiles_path, "satellite.pmtiles"); - let satellite_highres_tiles = configured_or_default_overlay_path( - &cli.satellite_highres_tiles, - tiles_path, - "satellite_highres.pmtiles", - ); - let crime_hotspot_tiles = configured_or_default_overlay_path( - &cli.crime_hotspot_tiles, - tiles_path, - "crime_hotspots.pmtiles", - ); - let tree_overlay_tiles = configured_or_default_overlay_path( - &cli.tree_overlay_tiles, - tiles_path, - "trees_outside_woodlands.pmtiles", - ); - let property_border_tiles = configured_or_default_overlay_path( - &cli.property_border_tiles, - tiles_path, - "property_borders.pmtiles", - ); - - let noise_overlay_reader = init_required_tile_reader("Noise", &noise_overlay_tiles).await?; - let satellite_reader = init_required_tile_reader("Satellite", &satellite_tiles).await?; + let noise_overlay_reader = + init_required_tile_reader("Noise", &cli.noise_overlay_tiles).await?; + let satellite_reader = init_required_tile_reader("Satellite", &cli.satellite_tiles).await?; let satellite_highres_reader = - init_required_tile_reader("Satellite high-res", &satellite_highres_tiles).await?; + init_required_tile_reader("Satellite high-res", &cli.satellite_highres_tiles).await?; let crime_hotspot_reader = - init_required_tile_reader("Crime hotspots", &crime_hotspot_tiles).await?; + init_required_tile_reader("Crime hotspots", &cli.crime_hotspot_tiles).await?; let tree_overlay_reader = - init_required_tile_reader("Trees outside woodland", &tree_overlay_tiles).await?; + init_required_tile_reader("Trees outside woodland", &cli.tree_overlay_tiles).await?; let property_border_reader = - init_required_tile_reader("Property borders", &property_border_tiles).await?; + init_required_tile_reader("Property borders", &cli.property_border_tiles).await?; let feature_name_to_index: rustc_hash::FxHashMap = property_data .feature_names @@ -720,7 +663,8 @@ async fn main() -> anyhow::Result<()> { let superuser_token_cache = Arc::new(pocketbase::SuperuserTokenCache::new()); let share_cache = Arc::new(licensing::ShareBoundsCache::new()); - let actual_listings = if let Some(path) = cli.actual_listings_path.as_ref() { + let actual_listings = { + let path = &cli.actual_listings_path; if !path.exists() { bail!("Actual listings parquet not found: {}", path.display()); } @@ -728,22 +672,17 @@ async fn main() -> anyhow::Result<()> { let listings = data::ActualListingData::load(path, &property_data)?; trim_allocator("actual listings load"); info!(rows = listings.lat.len(), "Actual listings loaded"); - Some(Arc::new(listings)) - } else { - info!("ACTUAL_LISTINGS_PATH not set; live listings overlay disabled"); - None + Arc::new(listings) }; - let crime_by_year = if let Some(path) = cli.crime_by_year_path.as_ref() { + let crime_by_year = { + let path = &cli.crime_by_year_path; if !path.exists() { bail!("Crime-by-year parquet not found: {}", path.display()); } let data = data::CrimeByYearData::load(path)?; trim_allocator("crime-by-year load"); Arc::new(data) - } else { - info!("CRIME_BY_YEAR_PATH not set; crime-over-time chart disabled"); - Arc::new(data::CrimeByYearData::empty()) }; let app_state = AppState { diff --git a/server-rs/src/routes/actual_listings.rs b/server-rs/src/routes/actual_listings.rs index 9723cec..51cea5d 100644 --- a/server-rs/src/routes/actual_listings.rs +++ b/server-rs/src/routes/actual_listings.rs @@ -65,14 +65,7 @@ pub async fn get_actual_listings( ); } - let Some(actual_listings) = state.actual_listings.clone() else { - return Ok(Json(ActualListingsResponse { - listings: Vec::new(), - total: 0, - offset, - truncated: false, - })); - }; + let actual_listings = state.actual_listings.clone(); let (south, west, north, east) = require_bounds(params.bounds).map_err(IntoResponse::into_response)?; diff --git a/server-rs/src/state.rs b/server-rs/src/state.rs index 27a9a85..edd8ebb 100644 --- a/server-rs/src/state.rs +++ b/server-rs/src/state.rs @@ -44,10 +44,9 @@ pub struct AppState { pub poi_category_groups: Arc>, /// Precomputed travel time data store pub travel_time_store: Arc, - /// Optional real-world listings (e.g. Rightmove / Zoopla data) loaded from ACTUAL_LISTINGS_PATH. - pub actual_listings: Option>, + /// Real-world listings (e.g. Rightmove / Zoopla data) loaded from ACTUAL_LISTINGS_PATH. + pub actual_listings: Arc, /// Per-LSOA per-year crime counts used by the right pane to plot trends. - /// Empty when the side parquet was not supplied. pub crime_by_year: Arc, /// Token validation cache (60s TTL) pub token_cache: Arc,