fmt
This commit is contained in:
parent
30d36a33d5
commit
7bc591be2b
10 changed files with 64 additions and 51 deletions
|
|
@ -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]);
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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.',
|
||||||
|
|
|
||||||
|
|
@ -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', () => {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue