Updates
Some checks failed
CI / Frontend (lint + typecheck) (push) Failing after 3m45s
CI / Rust (lint + test) (push) Failing after 5m15s
CI / Python (lint + test) (push) Failing after 5m17s
Build and publish Docker image / build-and-push (push) Failing after 7m15s

This commit is contained in:
Andras Schmelczer 2026-03-28 12:00:15 +00:00
parent 7591e5fc05
commit 89a85e9a0c
22 changed files with 1006 additions and 899 deletions

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@ pub fn parse_field_indices(
return Ok(Some(vec![]));
}
let mut indices = Vec::new();
for name in fields_str.split(',') {
for name in fields_str.split(";;") {
let name = name.trim();
if name.is_empty() {
continue;
@ -38,7 +38,7 @@ pub fn parse_field_set(fields: Option<&str>) -> (bool, HashSet<String>) {
let field_set: HashSet<String> = fields
.map(|fields_str| {
fields_str
.split(',')
.split(";;")
.map(|field| field.trim().to_string())
.filter(|field| !field.is_empty())
.collect()

View file

@ -1022,7 +1022,7 @@ pub async fn post_ai_filters(
"No properties match these filters. Try relaxing some constraints.".to_string()
} else {
format!(
"{}. No properties match — try relaxing some constraints.",
"{}. No properties match. Try relaxing some constraints.",
notes
)
};

View file

@ -6,7 +6,7 @@ use serde::Serialize;
use tracing::info;
use crate::data::{Histogram, PropertyData};
use crate::features::{ENUM_FEATURE_GROUPS, FEATURE_GROUPS};
use crate::features::{Feature, FEATURE_GROUPS};
use crate::state::SharedState;
fn is_empty(val: &str) -> bool {
@ -69,74 +69,53 @@ pub struct FeaturesResponse {
}
/// Build the features response at startup. Called once and cached in AppState.
/// Feature order in each group follows the array order in FEATURE_GROUPS.
pub fn build_features_response(data: &PropertyData) -> FeaturesResponse {
// Collect all group names in order, merging numeric and enum groups with the same name
let mut group_names: Vec<&str> = Vec::new();
for feature_group in FEATURE_GROUPS {
if !group_names.contains(&feature_group.name) {
group_names.push(feature_group.name);
}
}
for enum_group in ENUM_FEATURE_GROUPS {
if !group_names.contains(&enum_group.name) {
group_names.push(enum_group.name);
}
}
let mut groups: Vec<FeatureGroupResponse> = Vec::new();
for &group_name in &group_names {
for feature_group in FEATURE_GROUPS {
let mut features: Vec<FeatureInfo> = Vec::new();
// Add numeric features for this group
for feature_group in FEATURE_GROUPS {
if feature_group.name == group_name {
for feature_config in feature_group.features {
for feature in feature_group.features {
match feature {
Feature::Numeric(config) => {
if let Some(feat_idx) = data
.feature_names
.iter()
.position(|feat_name| feat_name == feature_config.name)
.position(|name| name == config.name)
{
let stats = &data.feature_stats[feat_idx];
features.push(FeatureInfo::Numeric {
name: feature_config.name.to_string(),
name: config.name.to_string(),
min: stats.slider_min,
max: stats.slider_max,
step: feature_config.step,
step: config.step,
histogram: stats.histogram.clone(),
description: feature_config.description,
detail: feature_config.detail,
source: feature_config.source,
prefix: feature_config.prefix,
suffix: feature_config.suffix,
raw: feature_config.raw,
absolute: feature_config.absolute,
modes: feature_config.modes,
linked: feature_config.linked,
description: config.description,
detail: config.detail,
source: config.source,
prefix: config.prefix,
suffix: config.suffix,
raw: config.raw,
absolute: config.absolute,
modes: config.modes,
linked: config.linked,
});
}
}
}
}
// Add enum features for this group
for enum_group in ENUM_FEATURE_GROUPS {
if enum_group.name == group_name {
for enum_config in enum_group.features {
// Find the feature index by name
Feature::Enum(config) => {
if let Some(feat_idx) = data
.feature_names
.iter()
.position(|name| name == enum_config.name)
.position(|name| name == config.name)
{
// Check if this feature has enum values
if let Some(values) = data.enum_values.get(&feat_idx) {
features.push(FeatureInfo::Enum {
name: enum_config.name.to_string(),
name: config.name.to_string(),
values: values.clone(),
description: enum_config.description,
detail: enum_config.detail,
source: enum_config.source,
description: config.description,
detail: config.detail,
source: config.source,
});
}
}
@ -146,7 +125,7 @@ pub fn build_features_response(data: &PropertyData) -> FeaturesResponse {
if !features.is_empty() {
groups.push(FeatureGroupResponse {
name: group_name.to_string(),
name: feature_group.name.to_string(),
features,
});
}

View file

@ -142,7 +142,7 @@ pub async fn get_short_url(
let redirect_url = format!("/dashboard?{params}");
let og_image_url = format!("{}/api/screenshot?og=1&{params}", state.public_url);
let og_url = format!("{}/s/{code}", state.public_url);
let og_title = "Perfect Postcode \u{2014} Every neighbourhood in England";
let og_title = "Perfect Postcode | Every neighbourhood in England";
let og_description = "Explore property prices, energy ratings, crime stats, school ratings, and more across England on one interactive map.";
let html = format!(