807 lines
40 KiB
Rust
807 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,
|
|
}
|
|
|
|
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",
|
|
];
|
|
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
FeatureConfig {
|
|
name: "Approximate 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",
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Transport",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "public_transport_easy_minutes",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 2.0,
|
|
description: "Quickest public transport journey to central London (easy route)",
|
|
detail: "Journey time in minutes by public transport to central London destinations, using TfL's Journey Planner API. The 'easy' route minimises changes and walking. Calculated for weekday morning commute times.",
|
|
source: "tfl-journey-times",
|
|
},
|
|
FeatureConfig {
|
|
name: "public_transport_quick_minutes",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 2.0,
|
|
description: "Fastest public transport journey to central London",
|
|
detail: "Journey time in minutes by public transport to central London destinations, using TfL's Journey Planner API. The 'quick' route optimises for shortest total time regardless of changes. Calculated for weekday morning commute times.",
|
|
source: "tfl-journey-times",
|
|
},
|
|
FeatureConfig {
|
|
name: "cycling_minutes",
|
|
bounds: Bounds::Fixed {
|
|
min: 0.0,
|
|
max: 180.0,
|
|
},
|
|
step: 1.0,
|
|
description: "Cycling time to central London via TfL routing",
|
|
detail: "Cycling journey time in minutes to central London destinations, as calculated by the TfL Journey Planner API. Uses TfL's default cycling speed and route preferences.",
|
|
source: "tfl-journey-times",
|
|
},
|
|
FeatureConfig {
|
|
name: "Public transport 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",
|
|
},
|
|
],
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Deprivation",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Index of Multiple Deprivation (IMD) Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "Overall deprivation score combining all domains",
|
|
detail: "The Index of Multiple Deprivation is the official measure of relative deprivation in England. It combines seven weighted domains: Income (22.5%), Employment (22.5%), Education (13.5%), Health (13.5%), Crime (9.3%), Barriers to Housing & Services (9.3%), and Living Environment (9.3%). Higher scores indicate greater deprivation. Measured at LSOA level (~1,500 people).",
|
|
source: "iod",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
FeatureConfig {
|
|
name: "Crime Score",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 0.1,
|
|
description: "IoD crime deprivation score measuring personal risk",
|
|
detail: "From the English Indices of Deprivation. Measures the risk of personal and material victimisation at local level. Derived from recorded rates of violence, burglary, theft, and criminal damage. Higher scores indicate higher crime-related deprivation.",
|
|
source: "iod",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Amenities",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "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",
|
|
},
|
|
FeatureConfig {
|
|
name: "Groceries 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",
|
|
},
|
|
FeatureConfig {
|
|
name: "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",
|
|
},
|
|
],
|
|
},
|
|
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",
|
|
},
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
FeatureGroup {
|
|
name: "Council Tax",
|
|
features: &[
|
|
FeatureConfig {
|
|
name: "Council tax (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Exact annual council tax based on the property's actual band",
|
|
detail: "Annual council tax for this property based on its actual VOA council tax band and the local authority's 2025-26 rates. Only available where the property's band was successfully matched from the VOA valuation list. For a dwelling occupied by two adults, including adult social care and parish precepts.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band A (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band A property (2-adult dwelling)",
|
|
detail: "Council tax for a Band A dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band A covers properties valued up to £40,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 6/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band B (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band B property (2-adult dwelling)",
|
|
detail: "Council tax for a Band B dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band B covers properties valued £40,001-£52,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 7/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band C (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band C property (2-adult dwelling)",
|
|
detail: "Council tax for a Band C dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band C covers properties valued £52,001-£68,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 8/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band D (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band D property (2-adult dwelling)",
|
|
detail: "Council tax for a Band D dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band D covers properties valued £68,001-£88,000 at 1 April 1991 and is the standard reference band used for national comparisons. Includes adult social care and parish precepts.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band E (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band E property (2-adult dwelling)",
|
|
detail: "Council tax for a Band E dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band E covers properties valued £88,001-£120,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 11/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band F (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band F property (2-adult dwelling)",
|
|
detail: "Council tax for a Band F dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band F covers properties valued £120,001-£160,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 13/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band G (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band G property (2-adult dwelling)",
|
|
detail: "Council tax for a Band G dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band G covers properties valued £160,001-£320,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 15/9.",
|
|
source: "council-tax",
|
|
},
|
|
FeatureConfig {
|
|
name: "Council tax Band H (£/yr)",
|
|
bounds: Bounds::Percentile {
|
|
low: 2.0,
|
|
high: 98.0,
|
|
},
|
|
step: 10.0,
|
|
description: "Annual council tax for a Band H property (2-adult dwelling)",
|
|
detail: "Council tax for a Band H dwelling occupied by two adults in the local authority area, for financial year 2025-26. Band H covers properties valued above £320,000 at 1 April 1991. Includes adult social care and parish precepts. The ratio to Band D is 18/9 (double Band D).",
|
|
source: "council-tax",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
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: "Current energy rating",
|
|
order: Some(&["A", "B", "C", "D", "E", "F", "G"]),
|
|
description: "Current EPC energy efficiency rating (A-G)",
|
|
detail: "The current energy efficiency rating from the Energy Performance Certificate, graded A (most efficient) to G (least efficient). Based on the energy costs per square metre of floor area for heating, hot water, lighting, and ventilation.",
|
|
source: "epc",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Potential energy rating",
|
|
order: Some(&["A", "B", "C", "D", "E", "F", "G"]),
|
|
description: "Achievable EPC rating after recommended improvements",
|
|
detail: "The potential energy efficiency rating that could be achieved if all cost-effective improvements recommended in the EPC were carried out. Graded A (most efficient) to G (least efficient).",
|
|
source: "epc",
|
|
},
|
|
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",
|
|
},
|
|
EnumFeatureConfig {
|
|
name: "Property type/built form",
|
|
order: None,
|
|
description: "Detailed property type and built form from the EPC",
|
|
detail: "A more detailed classification from the Energy Performance Certificate combining property type and built form. Examples include 'Semi-Detached House', 'Mid-Terrace House', 'Ground-Floor Flat', 'Detached Bungalow', etc.",
|
|
source: "epc",
|
|
},
|
|
],
|
|
},
|
|
EnumFeatureGroup {
|
|
name: "Council Tax",
|
|
features: &[
|
|
EnumFeatureConfig {
|
|
name: "Council tax band",
|
|
order: Some(&["A", "B", "C", "D", "E", "F", "G", "H"]),
|
|
description: "VOA council tax valuation band (A-H)",
|
|
detail: "The council tax band assigned by the Valuation Office Agency based on the estimated open market value of the property at 1 April 1991. Band A (up to £40k) to Band H (over £320k). Matched from the VOA valuation list by address within postcode.",
|
|
source: "council-tax",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
/// 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)
|
|
}
|
|
|
|
/// 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",
|
|
"Amenity",
|
|
"Building",
|
|
"Craft",
|
|
"Healthcare",
|
|
"Leisure",
|
|
"Office",
|
|
"Shop",
|
|
"Tourism",
|
|
];
|