lgtm 2
Some checks failed
Build and publish Docker image / build-and-push (push) Failing after 2m43s
CI / Check (push) Failing after 3m7s

This commit is contained in:
Andras Schmelczer 2026-05-14 22:39:41 +01:00
parent a8de0a614d
commit 3fa95819e3
30 changed files with 907 additions and 205 deletions

View file

@ -10,13 +10,13 @@ use tracing::{info, warn};
use crate::auth::OptionalUser;
use crate::consts::POSTCODE_SEARCH_OFFSET;
use crate::licensing::{check_license_point, resolve_share_code};
use crate::parsing::{
parse_field_set, parse_filters_with_poi, row_passes_filters, row_passes_poi_filters,
};
use crate::parsing::{parse_filters_with_poi, row_passes_filters, row_passes_poi_filters};
use crate::state::SharedState;
use crate::utils::normalize_postcode;
use super::hexagon_stats::HexagonStatsResponse;
use super::hexagon_stats::{
parse_area_stats_field_set, top_filter_exclusions, HexagonStatsResponse,
};
use super::stats;
use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_travel_filters};
@ -24,8 +24,9 @@ use super::travel_time::{load_travel_data, parse_optional_travel, row_passes_tra
pub struct PostcodeStatsParams {
pub postcode: String,
pub filters: Option<String>,
/// Comma-separated feature names to include in stats response.
/// Only listed features are computed; if absent or empty, no features are returned.
/// `;;`-separated feature names to include in stats response.
/// Only listed features are computed. If absent, area stats default to
/// displayable groups; if empty, no feature stats are returned.
pub fields: Option<String>,
/// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`.
/// Optional min:max applies as a filter (exclude properties outside range).
@ -80,7 +81,7 @@ pub async fn get_postcode_stats(
let filters_str = params.filters;
let has_poi_filters = !parsed_poi_filters.is_empty();
let (fields_specified, field_set) = parse_field_set(params.fields.as_deref());
let (fields_specified, field_set) = parse_area_stats_field_set(params.fields.as_deref());
let travel_entries = parse_optional_travel(params.travel.as_deref())
.map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?;
@ -100,26 +101,26 @@ pub async fn get_postcode_stats(
let min_lon = centroid_lon as f64 - offset;
let max_lon = centroid_lon as f64 + offset;
let mut area_rows: Vec<usize> = Vec::new();
let mut matching_rows: Vec<usize> = Vec::new();
state
.grid
.for_each_in_bounds(min_lat, min_lon, max_lat, max_lon, |row_idx| {
let row = row_idx as usize;
let row_postcode = state.data.postcode(row);
if row_postcode == postcode_str
&& row_passes_filters(
row,
&parsed_filters,
&parsed_enum_filters,
feature_data,
num_features,
)
&& (!has_poi_filters
|| row_passes_poi_filters(
row,
&parsed_poi_filters,
&state.data.poi_metrics,
))
if row_postcode != postcode_str {
return;
}
area_rows.push(row);
if row_passes_filters(
row,
&parsed_filters,
&parsed_enum_filters,
feature_data,
num_features,
) && (!has_poi_filters
|| row_passes_poi_filters(row, &parsed_poi_filters, &state.data.poi_metrics))
{
if has_travel
&& !row_passes_travel_filters(row_postcode, &travel_entries, &travel_data)
@ -131,6 +132,19 @@ pub async fn get_postcode_stats(
});
let total_count = matching_rows.len();
let filter_exclusions = if total_count == 0 {
top_filter_exclusions(
&area_rows,
&parsed_filters,
&parsed_enum_filters,
&parsed_poi_filters,
&travel_entries,
&travel_data,
&state.data,
)
} else {
Vec::new()
};
let price_history =
stats::extract_price_history(&matching_rows, &state.data, &state.feature_name_to_index);
@ -168,6 +182,7 @@ pub async fn get_postcode_stats(
enum_features: enum_features_out,
price_history,
central_postcode: None,
filter_exclusions,
})
})
.await