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
// dismisses it themselves. Prevents a stale flag from resurfacing spuriously.
useEffect(() => {
if (
filtersUnlimited ||
(!lockFilterAdds && Object.keys(filters).length < effectiveFilterCap)
) {
if (filtersUnlimited || (!lockFilterAdds && Object.keys(filters).length < effectiveFilterCap)) {
setFilterLimitHit(false);
}
}, [filtersUnlimited, lockFilterAdds, effectiveFilterCap, filters]);

View file

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

View file

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

View file

@ -702,7 +702,8 @@ const hu: Translations = {
upgradeToFullMap: 'Teljes térkép megnyitása',
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.',
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',
chooseFilters:
'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 { parseUrlState, stateToParams } from './url-state';
import { DEFAULT_OVERLAY_IDS } from './overlays';
import { INITIAL_VIEW_STATE } from './consts';
import { createSchoolFilterKey } from './school-filter';
import { createSpecificCrimeFilterKey } from './crime-filter';
@ -199,10 +200,10 @@ describe('url-state', () => {
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();
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', () => {

View file

@ -158,7 +158,10 @@ impl CrimeRecords {
ParquetReader::new(file)
.get_metadata()
.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()
};
@ -193,10 +196,18 @@ impl CrimeRecords {
// the column builders' write order.
let mut global_row: u32 = 0;
let columns: Vec<String> = ["postcode", "month_index", "crime_type", "location", "outcome", "lat", "lon"]
.iter()
.map(|s| s.to_string())
.collect();
let columns: Vec<String> = [
"postcode",
"month_index",
"crime_type",
"location",
"outcome",
"lat",
"lon",
]
.iter()
.map(|s| s.to_string())
.collect();
let mut offset = 0usize;
while offset < n {
@ -337,7 +348,10 @@ impl CrimeRecords {
if let Some(prev) = cur_pc.take() {
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 {
month: month.finish()?,
@ -413,7 +427,7 @@ mod tests {
.unwrap();
assert_eq!(robbery.outcome, Some("Court result"));
assert_eq!(robbery.location, None); // null location → None
// Two records have a null outcome (an AA1 Burglary and the BB2 Drugs).
// Two records have a null outcome (an AA1 Burglary and the BB2 Drugs).
let null_outcomes = all
.iter()
.map(|&i| recs.view(i))
@ -518,7 +532,10 @@ mod tests {
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");
// 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

View file

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

View file

@ -152,7 +152,10 @@ pub struct SpillVecBuilder<T: SpillElem> {
}
enum Builder<T: SpillElem> {
Owned { values: Vec<T>, len: usize },
Owned {
values: Vec<T>,
len: usize,
},
Mapped {
map: MmapMut,
len: usize,
@ -462,7 +465,9 @@ mod tests {
#[test]
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.
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 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 {
builder.push(k);
}
@ -536,7 +542,8 @@ mod tests {
#[should_panic(expected = "overflow")]
fn builder_overfill_panics() {
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(2);
builder.push(3); // one past the declared length

View file

@ -99,7 +99,11 @@ pub async fn get_crime_records(
.postcode_to_idx
.get(&normalized)
.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)
} else {
@ -120,12 +124,9 @@ pub async fn get_crime_records(
let mut h3_cache: FxHashMap<u64, u64> = FxHashMap::default();
let mut seen: FxHashSet<&str> = FxHashSet::default();
let mut out: Vec<String> = Vec::new();
state.grid.for_each_in_bounds(
min_lat,
min_lon,
max_lat,
max_lon,
|row_idx| {
state
.grid
.for_each_in_bounds(min_lat, min_lon, max_lat, max_lon, |row_idx| {
let row = row_idx as usize;
if cell_for_row_cached(
row,
@ -140,8 +141,7 @@ pub async fn get_crime_records(
out.push(pc.to_string());
}
}
},
);
});
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).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))
}

View file

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