895 lines
40 KiB
Rust
895 lines
40 KiB
Rust
//! Static feature configuration. Every numeric and enum column in wide.parquet
|
|
//! must be declared here. Unknown columns cause a startup panic.
|
|
|
|
pub enum Bounds {
|
|
/// Fixed min/max values for the slider
|
|
Fixed { min: f32, max: f32 },
|
|
/// Compute percentile from data at startup
|
|
Percentile { low: f64, high: f64 },
|
|
}
|
|
|
|
pub struct FeatureConfig {
|
|
/// Must match parquet column name exactly (also used as display label)
|
|
pub name: &'static str,
|
|
pub bounds: Bounds,
|
|
/// Slider step size. Controls the granularity of the range slider in the UI.
|
|
pub step: f32,
|
|
/// Short one-line description shown in the filter sidebar
|
|
pub description: &'static str,
|
|
/// Longer description explaining methodology, data source, and caveats
|
|
pub detail: &'static str,
|
|
/// Data source slug for linking to /data-sources#<slug>
|
|
pub source: &'static str,
|
|
/// Display prefix (e.g. "£")
|
|
pub prefix: &'static str,
|
|
/// Display suffix (e.g. " mins")
|
|
pub suffix: &'static str,
|
|
/// If true, show full integer (no k/M abbreviation)
|
|
pub raw: bool,
|
|
}
|
|
|
|
/// Features whose histogram bins should be exactly 1 unit wide (one per integer).
|
|
/// p1/p99 are snapped to integer boundaries before binning.
|
|
pub const INTEGER_BIN_FEATURES: &[&str] = &["Number of bedrooms & living rooms"];
|
|
|
|
pub struct FeatureGroup {
|
|
pub name: &'static str,
|
|
pub features: &'static [FeatureConfig],
|
|
}
|
|
|
|
pub struct EnumFeatureConfig {
|
|
pub name: &'static str,
|
|
/// If set, values are presented in this order instead of alphabetical.
|
|
/// Values not listed are appended alphabetically after the ordered ones.
|
|
pub order: Option<&'static [&'static str]>,
|
|
/// Short one-line description shown in the filter sidebar
|
|
pub description: &'static str,
|
|
/// Longer description explaining methodology, data source, and caveats
|
|
pub detail: &'static str,
|
|
/// Data source slug for linking to /data-sources#<slug>
|
|
pub source: &'static str,
|
|
}
|
|
|
|
pub struct EnumFeatureGroup {
|
|
pub name: &'static str,
|
|
pub features: &'static [EnumFeatureConfig],
|
|
}
|
|
|
|
/// Columns in parquet that are not filterable
|
|
pub const IGNORED_COLUMNS: &[&str] = &[
|
|
"lat",
|
|
"lon",
|
|
"Address per Property Register",
|
|
"Address per EPC",
|
|
"Postcode",
|
|
"historical_prices",
|
|
"Is construction date approximate",
|
|
"Current energy rating",
|
|
"Potential energy rating",
|
|
];
|
|
|
|
pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|
FeatureGroup {
|
|
name: "Property",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Last known price",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 2_000_000.0,
|
|
},
|
|
step: 10000.0,
|
|
description: "Most recent sale price from the Land Registry",
|
|
detail: "The last recorded sale price for this property from HM Land Registry Price Paid data. Covers residential sales in England and Wales. May be years old if the property hasn't sold recently.",
|
|
source: "price-paid",
|
|
prefix: "£",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Estimated current price",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 2_000_000.0,
|
|
},
|
|
step: 10000.0,
|
|
description: "Inflation-adjusted estimate of the current property value",
|
|
detail: "Estimated by applying a repeat-sales price index to the last known sale price. The index tracks price changes within each postcode sector and property type. Properties sold recently will have estimates close to their sale price; older sales are adjusted more. Coverage depends on having enough repeat sales in the local area to build the index.",
|
|
source: "price-paid",
|
|
prefix: "£",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Price per sqm",
|
|
bounds: Bounds::Percentile {
|
|
low: 0.0,
|
|
high: 98.0,
|
|
},
|
|
step: 100.0,
|
|
description: "Sale price divided by total floor area",
|
|
detail: "Calculated by dividing the last known sale price by the total floor area from the EPC certificate. Useful for comparing value across different-sized properties. Only available where both price and floor area data exist.",
|
|
source: "price-paid",
|
|
prefix: "£",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Total floor area (sqm)",
|
|
bounds: Bounds::Percentile {
|
|
low: 0.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Internal floor area from the EPC survey",
|
|
detail: "Total useful floor area in square metres as measured during the Energy Performance Certificate assessment. Includes all habitable rooms but excludes garages, outbuildings, and external areas.",
|
|
source: "epc",
|
|
prefix: "",
|
|
suffix: " sqm",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Number of bedrooms & living rooms",
|
|
bounds: Bounds::Fixed {
|
|
min: 1.0,
|
|
max: 10.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Count of habitable rooms from the EPC survey",
|
|
detail: "Total number of habitable rooms (bedrooms plus living rooms) as recorded in the Energy Performance Certificate. Kitchens and bathrooms are typically excluded unless they are large enough to count as habitable rooms.",
|
|
source: "epc",
|
|
prefix: "",
|
|
suffix: " rooms",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Date of last transaction",
|
|
bounds: Bounds::Fixed {
|
|
min: 1995.0,
|
|
max: 2026.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Date of the most recent sale from the Land Registry",
|
|
detail: "The date of the most recent recorded sale for this property from HM Land Registry Price Paid data. Stored as a datetime in the data; converted to fractional year for filtering and charting.",
|
|
source: "price-paid",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: true,
|
|
},
|
|
FeatureConfig {
|
|
name: "Construction age",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 2026.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Estimated year of construction from the EPC",
|
|
detail: "The approximate year of construction as recorded in the Energy Performance Certificate. Derived from the construction age band (e.g. '1930-1949') by taking the midpoint. May be approximate, especially for older buildings.",
|
|
source: "epc",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: true,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Transport",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Public transport to Bank (mins)",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 2.0,
|
|
description: "Public transport journey time to Bank station",
|
|
detail: "Journey time in minutes by public transport to Bank station in the City of London, using TfL's Journey Planner API. Calculated for weekday morning commute times.",
|
|
source: "tfl-journey-times",
|
|
prefix: "",
|
|
suffix: " mins",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Public transport to Fitzrovia (mins)",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 2.0,
|
|
description: "Public transport journey time to Fitzrovia",
|
|
detail: "Journey time in minutes by public transport to Fitzrovia in central London, using TfL's Journey Planner API. Calculated for weekday morning commute times.",
|
|
source: "tfl-journey-times",
|
|
prefix: "",
|
|
suffix: " mins",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Cycling to Bank (mins)",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Cycling time to Bank station",
|
|
detail: "Cycling journey time in minutes to Bank station, as calculated by the TfL Journey Planner API. Uses TfL's default cycling speed and route preferences.",
|
|
source: "tfl-journey-times",
|
|
prefix: "",
|
|
suffix: " mins",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Cycling to Fitzrovia (mins)",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Cycling time to Fitzrovia",
|
|
detail: "Cycling journey time in minutes to Fitzrovia, as calculated by the TfL Journey Planner API. Uses TfL's default cycling speed and route preferences.",
|
|
source: "tfl-journey-times",
|
|
prefix: "",
|
|
suffix: " mins",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Number of public transport stations within 2km",
|
|
bounds: Bounds::Percentile {
|
|
low: 5.0,
|
|
high: 95.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Number of public transport stops within 2km",
|
|
detail: "Count of bus stops, rail stations, tube stations, tram stops, and other public transport access points within a 2km radius of the property's postcode. Derived from the NaPTAN (National Public Transport Access Nodes) dataset.",
|
|
source: "naptan",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Education",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Education, Skills and Training Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "IoD education deprivation score for the local area",
|
|
detail: "From the English Indices of Deprivation. Measures deprivation in education, skills and training in the local area (LSOA). Higher scores indicate greater deprivation. Combines children/young people sub-domain (school attainment, entry to higher education) and adult skills sub-domain (adult qualifications, English language proficiency).",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Good+ primary schools within 5km",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 30.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Primary schools rated Good or Outstanding by Ofsted nearby",
|
|
detail: "Number of state-funded primary schools within 5km that have a current Ofsted rating of Good or Outstanding. Based on the latest inspection outcomes dataset. Schools that have not yet been inspected are excluded.",
|
|
source: "ofsted",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Good+ secondary schools within 5km",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 15.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Secondary schools rated Good or Outstanding by Ofsted nearby",
|
|
detail: "Number of state-funded secondary schools within 5km that have a current Ofsted rating of Good or Outstanding. Based on the latest inspection outcomes dataset. Schools that have not yet been inspected are excluded.",
|
|
source: "ofsted",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Deprivation",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Income Score (rate)",
|
|
bounds: Bounds::Fixed { min: 0.0, max: 0.6 },
|
|
step: 0.01,
|
|
description: "Proportion of the population experiencing income deprivation",
|
|
detail: "From the English Indices of Deprivation. The proportion of the local population experiencing deprivation relating to low income. Includes people on Income Support, income-based Jobseeker's Allowance, income-based Employment and Support Allowance, Pension Credit, Working Tax Credit and Child Tax Credit, Universal Credit, and asylum seekers.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Employment Score (rate)",
|
|
bounds: Bounds::Fixed { min: 0.0, max: 0.4 },
|
|
step: 0.01,
|
|
description: "Proportion of the working-age population involuntarily excluded from work",
|
|
detail: "From the English Indices of Deprivation. The proportion of the working-age population involuntarily excluded from the labour market. Includes claimants of Jobseeker's Allowance, Employment and Support Allowance, Incapacity Benefit, Severe Disablement Allowance, Carer's Allowance, and relevant Universal Credit claimants.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Health Deprivation and Disability Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "Risk of premature death and quality of life impairment",
|
|
detail: "From the English Indices of Deprivation. Measures the risk of premature death and impairment of quality of life through poor physical or mental health. Derived from years of potential life lost, comparative illness and disability ratio, acute morbidity, and mood and anxiety disorders.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Living Environment Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "Quality of the local indoor and outdoor environment",
|
|
detail: "From the English Indices of Deprivation. Measures deprivation in the quality of the local environment. Combines the Indoors sub-domain (housing quality, central heating, housing conditions) and Outdoors sub-domain (air quality, road traffic accidents). Higher scores indicate poorer living environments.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Indoors Sub-domain Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "Housing quality and conditions in the local area",
|
|
detail: "From the English Indices of Deprivation, Living Environment domain. Measures the quality of housing stock: houses without central heating, housing in poor condition, and houses failing Decent Homes standards. Higher scores indicate worse housing conditions.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Outdoors Sub-domain Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "Air quality and road safety in the local area",
|
|
detail: "From the English Indices of Deprivation, Living Environment domain. Measures the outdoor living environment quality through air quality indicators and road traffic accident casualties involving pedestrians and cyclists. Higher scores indicate poorer outdoor environments.",
|
|
source: "iod",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Crime",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Anti-social behaviour (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly anti-social behaviour incidents in the area",
|
|
detail: "Average number of anti-social behaviour incidents per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes nuisance, environmental, and personal anti-social behaviour.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Violence and sexual offences (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly violent and sexual offences in the area",
|
|
detail: "Average number of violence and sexual offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes assault, harassment, and sexual offences.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Criminal damage and arson (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly criminal damage and arson in the area",
|
|
detail: "Average number of criminal damage and arson incidents per year in the LSOA, from police.uk street-level crime data (2023-2025).",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Burglary (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly burglary offences in the area",
|
|
detail: "Average number of burglary offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes residential and commercial burglary.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Vehicle crime (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly vehicle crime in the area",
|
|
detail: "Average number of vehicle crime incidents per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes theft of and from vehicles.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Robbery (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly robbery offences in the area",
|
|
detail: "Average number of robbery offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Robbery involves theft with force or threat of force.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Other theft (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly other theft offences in the area",
|
|
detail: "Average number of 'other theft' offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes theft not classified under burglary, vehicle crime, shoplifting, or bicycle theft.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Shoplifting (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly shoplifting offences in the area",
|
|
detail: "Average number of shoplifting offences per year in the LSOA, from police.uk street-level crime data (2023-2025).",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Drugs (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly drug offences in the area",
|
|
detail: "Average number of drug offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes possession and trafficking offences.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Possession of weapons (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly weapons possession offences in the area",
|
|
detail: "Average number of possession of weapons offences per year in the LSOA, from police.uk street-level crime data (2023-2025).",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Public order (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly public order offences in the area",
|
|
detail: "Average number of public order offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes causing fear, alarm, or distress.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Bicycle theft (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly bicycle theft in the area",
|
|
detail: "Average number of bicycle theft offences per year in the LSOA, from police.uk street-level crime data (2023-2025).",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Theft from the person (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly theft from the person in the area",
|
|
detail: "Average number of theft from the person offences per year in the LSOA, from police.uk street-level crime data (2023-2025). Includes pickpocketing and bag snatching without force.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Other crime (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Average yearly other crime in the area",
|
|
detail: "Average number of other crime offences per year in the LSOA, from police.uk street-level crime data (2023-2025). A catch-all category for offences not classified elsewhere.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Serious crime (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Aggregate of serious crime categories per year",
|
|
detail: "Sum of violence, robbery, burglary, and weapons possession per year in the LSOA, from police.uk street-level crime data (2023-2025). Provides a single serious crime metric.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Minor crime (avg/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Aggregate of minor crime categories per year",
|
|
detail: "Sum of anti-social behaviour, shoplifting, bicycle theft, and other lower-severity crime per year in the LSOA, from police.uk street-level crime data (2023-2025). Provides a single minor crime metric.",
|
|
source: "crime",
|
|
prefix: "",
|
|
suffix: "/yr",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Demographics",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "% White",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 100.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Percentage of population identifying as White",
|
|
detail: "From the 2021 Census. Percentage of the local authority population identifying as White (English, Welsh, Scottish, Northern Irish, British, Irish, Gypsy or Irish Traveller, Roma, or any other White background).",
|
|
source: "ethnicity",
|
|
prefix: "",
|
|
suffix: "%",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "% Asian",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 100.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Percentage of population identifying as Asian",
|
|
detail: "From the 2021 Census. Percentage of the local authority population identifying as Asian or Asian British (Indian, Pakistani, Bangladeshi, Chinese, or any other Asian background).",
|
|
source: "ethnicity",
|
|
prefix: "",
|
|
suffix: "%",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "% Black",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 100.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Percentage of population identifying as Black",
|
|
detail: "From the 2021 Census. Percentage of the local authority population identifying as Black, Black British, Caribbean, or African.",
|
|
source: "ethnicity",
|
|
prefix: "",
|
|
suffix: "%",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "% Mixed",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 100.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Percentage of population identifying as Mixed or Multiple ethnic groups",
|
|
detail: "From the 2021 Census. Percentage of the local authority population identifying as Mixed or Multiple ethnic groups (White and Black Caribbean, White and Black African, White and Asian, or any other Mixed or Multiple background).",
|
|
source: "ethnicity",
|
|
prefix: "",
|
|
suffix: "%",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "% Other",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 100.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Percentage of population identifying as Other ethnic group",
|
|
detail: "From the 2021 Census. Percentage of the local authority population identifying as Other ethnic group (Arab or any other ethnic group not covered by the main categories).",
|
|
source: "ethnicity",
|
|
prefix: "",
|
|
suffix: "%",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Amenities",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Number of restaurants within 2km",
|
|
bounds: Bounds::Percentile {
|
|
low: 5.0,
|
|
high: 95.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Number of restaurants and cafes within 2km",
|
|
detail: "Count of restaurants, cafes, and food establishments within a 2km radius of the property's postcode centroid. Derived from OpenStreetMap POI data using haversine distance calculation with a 0.05° spatial grid for candidate reduction.",
|
|
source: "osm-pois",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Number of grocery shops and supermarkets within 2km",
|
|
bounds: Bounds::Percentile {
|
|
low: 5.0,
|
|
high: 95.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Number of grocery shops and supermarkets within 2km",
|
|
detail: "Count of supermarkets, convenience stores, and other grocery shops within a 2km radius of the property's postcode centroid. Derived from OpenStreetMap POI data.",
|
|
source: "osm-pois",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Number of parks within 2km",
|
|
bounds: Bounds::Percentile {
|
|
low: 5.0,
|
|
high: 95.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Number of parks and green spaces within 2km",
|
|
detail: "Count of parks, gardens, nature reserves, and other green spaces within a 2km radius of the property's postcode centroid. Derived from OpenStreetMap POI data.",
|
|
source: "osm-pois",
|
|
prefix: "",
|
|
suffix: "",
|
|
raw: false,
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Environment",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Noise (dB)",
|
|
bounds: Bounds::Fixed {
|
|
min: 50.0,
|
|
max: 80.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Road noise level at the postcode in decibels (Lden)",
|
|
detail: "Road noise level in decibels (Lden — day-evening-night 24-hour weighted average) from Defra's Strategic Noise Mapping Round 4 (2022). Modelled at 4m above ground on a 10m grid. Sampled at postcode centroids via WCS GeoTIFF tiles. Values above ~55 dB are generally considered noticeable; above ~70 dB can affect health.",
|
|
source: "noise",
|
|
prefix: "",
|
|
suffix: " dB",
|
|
raw: false,
|
|
},
|
|
FeatureConfig {
|
|
name: "Max available download speed (Mbps)",
|
|
bounds: Bounds::Percentile {
|
|
low: 5.0,
|
|
high: 95.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Maximum broadband download speed available at the postcode",
|
|
detail: "Maximum available fixed broadband download speed in Megabits per second, from Ofcom's Connected Nations 2025 report. Measured at Output Area level and represents the maximum speed available from any provider, not actual achieved speeds.",
|
|
source: "broadband",
|
|
prefix: "",
|
|
suffix: " Mbps",
|
|
raw: true,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
pub static ENUM_FEATURE_GROUPS: &[EnumFeatureGroup] = &[
|
|
EnumFeatureGroup {
|
|
name: "Property",
|
|
features: &[
|
|
EnumFeatureConfig {
|
|
name: "Leashold/Freehold",
|
|
order: Some(&["Freehold", "Leasehold"]),
|
|
description: "Whether the property is leasehold or freehold",
|
|
detail: "From HM Land Registry Price Paid data. Freehold means you own the building and the land it stands on. Leasehold means you own the building but not the land — you have a lease from the freeholder for a set number of years.",
|
|
source: "price-paid",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Property type",
|
|
order: Some(&["Detached", "Semi-Detached", "Terraced", "Flat"]),
|
|
description: "Type of property: detached, semi-detached, terraced, or flat",
|
|
detail: "From HM Land Registry Price Paid data. The broad property type classification: Detached, Semi-Detached, Terraced, or Flat/Maisonette.",
|
|
source: "price-paid",
|
|
},
|
|
],
|
|
},
|
|
EnumFeatureGroup {
|
|
name: "Environment",
|
|
features: &[
|
|
EnumFeatureConfig {
|
|
name: "Environmental risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Highest ground stability risk across all six hazard types",
|
|
detail: "Overall ground stability risk for the area, taken as the maximum across all six GeoSure hazard categories (collapsible deposits, compressible ground, landslides, running sand, shrink-swell, and soluble rocks). From Ordnance Survey GeoSure data on a 5km hex grid.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Collapsible deposits risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of ground collapse from natural underground cavities",
|
|
detail: "From OS GeoSure. Indicates the likelihood of ground collapse due to natural cavities formed by dissolution of soluble rocks or the collapse of old mines and natural pipes. Rated on a 5km hex grid across Great Britain.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Compressible ground risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of ground compression causing subsidence",
|
|
detail: "From OS GeoSure. Indicates the potential for ground to compress under loading, which can cause gradual settlement or subsidence of buildings and infrastructure. Typically associated with soft clay, silt, or peat deposits.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Landslide risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of landslide or slope instability",
|
|
detail: "From OS GeoSure. Indicates the susceptibility of the ground to landslides and slope instability. Based on slope angle, geology, and historical landslide records.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Running sand risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of sand becoming fluid when saturated",
|
|
detail: "From OS GeoSure. Indicates the potential for fine-grained sand to behave like a fluid when saturated with water, which can affect excavations and foundations.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Shrink-swell risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of clay shrinking and swelling with moisture changes",
|
|
detail: "From OS GeoSure. Indicates the potential for clay-rich soils to shrink when dry and swell when wet, causing ground movement that can damage buildings and infrastructure. One of the most common causes of subsidence in the UK.",
|
|
source: "geosure",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Soluble rocks risk",
|
|
order: Some(&["Low", "Moderate", "Significant"]),
|
|
description: "Risk of sinkholes from dissolution of soluble rocks",
|
|
detail: "From OS GeoSure. Indicates the potential for soluble rocks (limestone, chalk, gypsum) to dissolve, creating underground voids that can lead to sinkholes and ground subsidence.",
|
|
source: "geosure",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
/// Flat ordered list of all numeric feature names (follows group order).
|
|
pub fn all_numeric_feature_names() -> Vec<&'static str> {
|
|
FEATURE_GROUPS
|
|
.iter()
|
|
.flat_map(|group| group.features.iter().map(|feature| feature.name))
|
|
.collect()
|
|
}
|
|
|
|
/// Flat ordered list of all enum feature names (follows group order).
|
|
pub fn all_enum_feature_names() -> Vec<&'static str> {
|
|
ENUM_FEATURE_GROUPS
|
|
.iter()
|
|
.flat_map(|group| group.features.iter().map(|feature| feature.name))
|
|
.collect()
|
|
}
|
|
|
|
/// Look up the configured value order for an enum feature by name.
|
|
pub fn order_for(name: &str) -> Option<&'static [&'static str]> {
|
|
ENUM_FEATURE_GROUPS
|
|
.iter()
|
|
.flat_map(|group| group.features.iter())
|
|
.find(|feature| feature.name == name)
|
|
.and_then(|feature| feature.order)
|
|
}
|
|
|
|
/// Whether this feature should use integer-width histogram bins.
|
|
pub fn has_integer_bins(name: &str) -> bool {
|
|
INTEGER_BIN_FEATURES.contains(&name)
|
|
}
|
|
|
|
/// Look up the Bounds config for a numeric feature by name.
|
|
pub fn bounds_for(name: &str) -> Option<&'static Bounds> {
|
|
FEATURE_GROUPS
|
|
.iter()
|
|
.flat_map(|group| group.features.iter())
|
|
.find(|feature| feature.name == name)
|
|
.map(|feature| &feature.bounds)
|
|
}
|
|
|
|
/// Canonical display order for POI category groups.
|
|
/// The server will panic at startup if the data contains groups not in this list or vice versa.
|
|
pub const POI_GROUP_ORDER: &[&str] = &[
|
|
"Public Transport",
|
|
"Leisure",
|
|
"Education",
|
|
"Health",
|
|
"Emergency Services",
|
|
"Other",
|
|
"Groceries",
|
|
"Local Businesses",
|
|
"Culture",
|
|
"Services",
|
|
"Shops",
|
|
];
|