fmt
All checks were successful
Build and publish Docker image / build-and-push (push) Successful in 6m31s
CI / Check (push) Successful in 10m26s

This commit is contained in:
Andras Schmelczer 2026-06-26 19:57:59 +01:00
parent 30d36a33d5
commit 7bc591be2b
10 changed files with 64 additions and 51 deletions

View file

@ -707,10 +707,7 @@ export default function MapPage({
// link adds are blocked regardless of count, so don't auto-clear there — the user // link adds are blocked regardless of count, so don't auto-clear there — the user
// dismisses it themselves. Prevents a stale flag from resurfacing spuriously. // dismisses it themselves. Prevents a stale flag from resurfacing spuriously.
useEffect(() => { useEffect(() => {
if ( if (filtersUnlimited || (!lockFilterAdds && Object.keys(filters).length < effectiveFilterCap)) {
filtersUnlimited ||
(!lockFilterAdds && Object.keys(filters).length < effectiveFilterCap)
) {
setFilterLimitHit(false); setFilterLimitHit(false);
} }
}, [filtersUnlimited, lockFilterAdds, effectiveFilterCap, filters]); }, [filtersUnlimited, lockFilterAdds, effectiveFilterCap, filters]);

View file

@ -682,8 +682,7 @@ const en = {
addFilter: 'Add filters', addFilter: 'Add filters',
findingPerfectPostcode: 'Finding the Perfect Postcode', findingPerfectPostcode: 'Finding the Perfect Postcode',
addFiltersHint: 'Add filters below to narrow the map to areas that match your criteria', addFiltersHint: 'Add filters below to narrow the map to areas that match your criteria',
upgradePrompt: upgradePrompt: 'Go lifetime to stack unlimited filters across every postcode in England.',
'Go lifetime to stack unlimited filters across every postcode in England.',
oneTimeLifetime: 'One-time payment, lifetime access.', oneTimeLifetime: 'One-time payment, lifetime access.',
upgradeToFullMap: 'Upgrade to full map', upgradeToFullMap: 'Upgrade to full map',
registerPrompt: registerPrompt:

View file

@ -674,8 +674,7 @@ const hi: Translations = {
addFilter: 'फ़िल्टर जोड़ें', addFilter: 'फ़िल्टर जोड़ें',
findingPerfectPostcode: 'Perfect Postcode खोजा जा रहा है', findingPerfectPostcode: 'Perfect Postcode खोजा जा रहा है',
addFiltersHint: 'अपनी शर्तों से मेल खाने वाले क्षेत्र पाने के लिए नीचे फ़िल्टर जोड़ें', addFiltersHint: 'अपनी शर्तों से मेल खाने वाले क्षेत्र पाने के लिए नीचे फ़िल्टर जोड़ें',
upgradePrompt: upgradePrompt: 'इंग्लैंड के हर पोस्टकोड में असीमित फ़िल्टर लगाने के लिए आजीवन पहुँच लें.',
'इंग्लैंड के हर पोस्टकोड में असीमित फ़िल्टर लगाने के लिए आजीवन पहुँच लें.',
oneTimeLifetime: 'एक बार भुगतान, आजीवन पहुँच.', oneTimeLifetime: 'एक बार भुगतान, आजीवन पहुँच.',
upgradeToFullMap: 'पूरे मानचित्र पर अपग्रेड करें', upgradeToFullMap: 'पूरे मानचित्र पर अपग्रेड करें',
registerPrompt: registerPrompt:

View file

@ -702,7 +702,8 @@ const hu: Translations = {
upgradeToFullMap: 'Teljes térkép megnyitása', upgradeToFullMap: 'Teljes térkép megnyitása',
registerPrompt: registerPrompt:
'Hozz létre egy ingyenes fiókot, hogy akár 5 szűrőt kombinálhass, és menthess, megoszthass és exportálhass kereséseket.', 'Hozz létre egy ingyenes fiókot, hogy akár 5 szűrőt kombinálhass, és menthess, megoszthass és exportálhass kereséseket.',
registerSubPrompt: 'Ingyenes kártya nélkül. Korlátlan szűrők élethosszig tartó hozzáféréssel.', registerSubPrompt:
'Ingyenes kártya nélkül. Korlátlan szűrők élethosszig tartó hozzáféréssel.',
registerCta: 'Ingyenes fiók létrehozása', registerCta: 'Ingyenes fiók létrehozása',
chooseFilters: chooseFilters:
'Kattints a Hozzáadásra a szűréshez. A kis gombok adatokat mutatnak vagy színezik a térképet.', 'Kattints a Hozzáadásra a szűréshez. A kis gombok adatokat mutatnak vagy színezik a térképet.',

View file

@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it } from 'vitest';
import type { FeatureMeta } from '../types'; import type { FeatureMeta } from '../types';
import { parseUrlState, stateToParams } from './url-state'; import { parseUrlState, stateToParams } from './url-state';
import { DEFAULT_OVERLAY_IDS } from './overlays';
import { INITIAL_VIEW_STATE } from './consts'; import { INITIAL_VIEW_STATE } from './consts';
import { createSchoolFilterKey } from './school-filter'; import { createSchoolFilterKey } from './school-filter';
import { createSpecificCrimeFilterKey } from './crime-filter'; import { createSpecificCrimeFilterKey } from './crime-filter';
@ -199,10 +200,10 @@ describe('url-state', () => {
expect(state.overlays).toEqual(new Set(['noise', 'crime-hotspots'])); expect(state.overlays).toEqual(new Set(['noise', 'crime-hotspots']));
}); });
it('enables crime + trees overlays by default on a fresh visit', () => { it('enables the default overlays on a fresh visit', () => {
const state = parseUrlState(); const state = parseUrlState();
expect(state.overlays).toEqual(new Set(['crime-hotspots', 'trees-outside-woodlands'])); expect(state.overlays).toEqual(new Set(DEFAULT_OVERLAY_IDS));
}); });
it('round-trips an explicit "all overlays off" via the __none sentinel', () => { it('round-trips an explicit "all overlays off" via the __none sentinel', () => {

View file

@ -158,7 +158,10 @@ impl CrimeRecords {
ParquetReader::new(file) ParquetReader::new(file)
.get_metadata() .get_metadata()
.with_context(|| { .with_context(|| {
format!("Failed to read crime-records parquet metadata at {}", path.display()) format!(
"Failed to read crime-records parquet metadata at {}",
path.display()
)
})? })?
.clone() .clone()
}; };
@ -193,7 +196,15 @@ impl CrimeRecords {
// the column builders' write order. // the column builders' write order.
let mut global_row: u32 = 0; let mut global_row: u32 = 0;
let columns: Vec<String> = ["postcode", "month_index", "crime_type", "location", "outcome", "lat", "lon"] let columns: Vec<String> = [
"postcode",
"month_index",
"crime_type",
"location",
"outcome",
"lat",
"lon",
]
.iter() .iter()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect(); .collect();
@ -337,7 +348,10 @@ impl CrimeRecords {
if let Some(prev) = cur_pc.take() { if let Some(prev) = cur_pc.take() {
by_postcode.insert(prev, (cur_start, global_row - cur_start)); by_postcode.insert(prev, (cur_start, global_row - cur_start));
} }
debug_assert_eq!(global_row as usize, n, "streamed fewer rows than the parquet declares"); debug_assert_eq!(
global_row as usize, n,
"streamed fewer rows than the parquet declares"
);
let records = Self { let records = Self {
month: month.finish()?, month: month.finish()?,
@ -518,7 +532,10 @@ mod tests {
rss_after, rss_after,
); );
assert!(!recs.by_postcode.is_empty(), "expected at least one postcode"); assert!(
!recs.by_postcode.is_empty(),
"expected at least one postcode"
);
assert!(total > 0, "expected at least one record"); assert!(total > 0, "expected at least one record");
// The old `.collect()` decoded all rows' string columns at once (tens of // The old `.collect()` decoded all rows' string columns at once (tens of
// GB). Streaming must keep the peak growth far below that; a generous 20GiB // GB). Streaming must keep the peak growth far below that; a generous 20GiB

View file

@ -82,10 +82,7 @@ impl PostcodePopulation {
bail!("population parquet at {} produced no rows", path.display()); bail!("population parquet at {} produced no rows", path.display());
} }
info!( info!(postcodes = by_postcode.len(), "Postcode population loaded");
postcodes = by_postcode.len(),
"Postcode population loaded"
);
Ok(Self { by_postcode }) Ok(Self { by_postcode })
} }

View file

@ -152,7 +152,10 @@ pub struct SpillVecBuilder<T: SpillElem> {
} }
enum Builder<T: SpillElem> { enum Builder<T: SpillElem> {
Owned { values: Vec<T>, len: usize }, Owned {
values: Vec<T>,
len: usize,
},
Mapped { Mapped {
map: MmapMut, map: MmapMut,
len: usize, len: usize,
@ -462,7 +465,9 @@ mod tests {
#[test] #[test]
fn builder_streams_identically_owned_and_mapped() { fn builder_streams_identically_owned_and_mapped() {
let values: Vec<u32> = (0..50_000u32).map(|n| n.wrapping_mul(2_246_822_519)).collect(); let values: Vec<u32> = (0..50_000u32)
.map(|n| n.wrapping_mul(2_246_822_519))
.collect();
// Owned path (no spill dir): pushes accumulate into a heap Vec. // Owned path (no spill dir): pushes accumulate into a heap Vec.
let mut owned = SpillVecBuilder::<u32>::with_len(values.len(), None, "u32_owned").unwrap(); let mut owned = SpillVecBuilder::<u32>::with_len(values.len(), None, "u32_owned").unwrap();
@ -497,7 +502,8 @@ mod tests {
let dir = TempDir::new("builder-spur"); let dir = TempDir::new("builder-spur");
let mut builder = let mut builder =
SpillVecBuilder::<lasso::Spur>::with_len(keys.len(), Some(dir.path()), "spurs").unwrap(); SpillVecBuilder::<lasso::Spur>::with_len(keys.len(), Some(dir.path()), "spurs")
.unwrap();
for &k in &keys { for &k in &keys {
builder.push(k); builder.push(k);
} }
@ -536,7 +542,8 @@ mod tests {
#[should_panic(expected = "overflow")] #[should_panic(expected = "overflow")]
fn builder_overfill_panics() { fn builder_overfill_panics() {
let dir = TempDir::new("builder-overfill"); let dir = TempDir::new("builder-overfill");
let mut builder = SpillVecBuilder::<u32>::with_len(2, Some(dir.path()), "overfill").unwrap(); let mut builder =
SpillVecBuilder::<u32>::with_len(2, Some(dir.path()), "overfill").unwrap();
builder.push(1); builder.push(1);
builder.push(2); builder.push(2);
builder.push(3); // one past the declared length builder.push(3); // one past the declared length

View file

@ -99,7 +99,11 @@ pub async fn get_crime_records(
.postcode_to_idx .postcode_to_idx
.get(&normalized) .get(&normalized)
.ok_or_else(|| { .ok_or_else(|| {
(StatusCode::NOT_FOUND, format!("Postcode not found: {normalized}")).into_response() (
StatusCode::NOT_FOUND,
format!("Postcode not found: {normalized}"),
)
.into_response()
})?; })?;
Selection::Postcode(normalized) Selection::Postcode(normalized)
} else { } else {
@ -120,12 +124,9 @@ pub async fn get_crime_records(
let mut h3_cache: FxHashMap<u64, u64> = FxHashMap::default(); let mut h3_cache: FxHashMap<u64, u64> = FxHashMap::default();
let mut seen: FxHashSet<&str> = FxHashSet::default(); let mut seen: FxHashSet<&str> = FxHashSet::default();
let mut out: Vec<String> = Vec::new(); let mut out: Vec<String> = Vec::new();
state.grid.for_each_in_bounds( state
min_lat, .grid
min_lon, .for_each_in_bounds(min_lat, min_lon, max_lat, max_lon, |row_idx| {
max_lat,
max_lon,
|row_idx| {
let row = row_idx as usize; let row = row_idx as usize;
if cell_for_row_cached( if cell_for_row_cached(
row, row,
@ -140,8 +141,7 @@ pub async fn get_crime_records(
out.push(pc.to_string()); out.push(pc.to_string());
} }
} }
}, });
);
out out
} }
}; };
@ -177,6 +177,10 @@ pub async fn get_crime_records(
.map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response())? .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response())?
.map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error).into_response())?; .map_err(|error| (StatusCode::INTERNAL_SERVER_ERROR, error).into_response())?;
info!(total = result.total, returned = result.records.len(), "GET /api/crime-records"); info!(
total = result.total,
returned = result.records.len(),
"GET /api/crime-records"
);
Ok(Json(result)) Ok(Json(result))
} }

View file

@ -11,8 +11,8 @@ use crate::data::{FeatureStats, PostcodePoiMetrics, PropertyData};
use crate::utils::{postcode_outcode, postcode_sector}; use crate::utils::{postcode_outcode, postcode_sector};
use super::hexagon_stats::{ use super::hexagon_stats::{
CrimeAreaAverage, CrimeYearPoint, CrimeYearStats, EnumFeatureStats, CrimeAreaAverage, CrimeYearPoint, CrimeYearStats, EnumFeatureStats, HistogramStats,
HistogramStats, NumericFeatureStats, PricePoint, NumericFeatureStats, PricePoint,
}; };
/// Extract price history (year, price) pairs from matching rows, downsampled if needed. /// Extract price history (year, price) pairs from matching rows, downsampled if needed.
@ -398,7 +398,6 @@ pub fn compute_crime_by_year(
out out
} }
/// Latest year present anywhere in the by-year crime dataset. The client /// Latest year present anywhere in the by-year crime dataset. The client
/// compares each selection's last charted year against this to caption /// compares each selection's last charted year against this to caption
/// force-level publication gaps (e.g. Greater Manchester ends mid-2019) as /// force-level publication gaps (e.g. Greater Manchester ends mid-2019) as
@ -615,18 +614,12 @@ mod tests {
assert_eq!(sector.as_deref(), Some("E14 2")); assert_eq!(sector.as_deref(), Some("E14 2"));
assert_eq!(out.len(), 2); assert_eq!(out.len(), 2);
let burglary = out let burglary = out.iter().find(|c| c.name == "Burglary (/yr, 7y)").unwrap();
.iter()
.find(|c| c.name == "Burglary (/yr, 7y)")
.unwrap();
assert_eq!(burglary.national, Some(8.0)); assert_eq!(burglary.national, Some(8.0));
assert_eq!(burglary.outcode, Some(10.0)); assert_eq!(burglary.outcode, Some(10.0));
assert_eq!(burglary.sector, Some(5.0)); assert_eq!(burglary.sector, Some(5.0));
let robbery = out let robbery = out.iter().find(|c| c.name == "Robbery (/yr, 7y)").unwrap();
.iter()
.find(|c| c.name == "Robbery (/yr, 7y)")
.unwrap();
assert_eq!(robbery.national, Some(6.0)); assert_eq!(robbery.national, Some(6.0));
// The outcode value was NaN — dropped to None; the sector value is finite. // The outcode value was NaN — dropped to None; the sector value is finite.
assert_eq!(robbery.outcode, None); assert_eq!(robbery.outcode, None);
@ -637,9 +630,7 @@ mod tests {
fn area_crime_averages_respect_fields_filter() { fn area_crime_averages_respect_fields_filter() {
let avgs = sample_averages(); let avgs = sample_averages();
// Area averages are keyed by the full crime-feature name. // Area averages are keyed by the full crime-feature name.
let fields: HashSet<String> = ["Burglary (/yr, 7y)".to_string()] let fields: HashSet<String> = ["Burglary (/yr, 7y)".to_string()].into_iter().collect();
.into_iter()
.collect();
let (_, _, out) = area_crime_averages_for(Some("E14 2DG"), &avgs, true, &fields); let (_, _, out) = area_crime_averages_for(Some("E14 2DG"), &avgs, true, &fields);
assert_eq!(out.len(), 1); assert_eq!(out.len(), 1);
assert_eq!(out[0].name, "Burglary (/yr, 7y)"); assert_eq!(out[0].name, "Burglary (/yr, 7y)");