Clean up
This commit is contained in:
parent
b94cf17d75
commit
0c6d207967
41 changed files with 1809 additions and 1204 deletions
|
|
@ -83,7 +83,10 @@ impl OutcodeData {
|
|||
})
|
||||
.collect();
|
||||
|
||||
info!(outcodes = names.len(), "Outcode data derived from postcodes");
|
||||
info!(
|
||||
outcodes = names.len(),
|
||||
"Outcode data derived from postcodes"
|
||||
);
|
||||
|
||||
OutcodeData {
|
||||
names,
|
||||
|
|
|
|||
|
|
@ -1129,7 +1129,6 @@ pub static FEATURE_GROUPS: &[FeatureGroup] = &[
|
|||
},
|
||||
];
|
||||
|
||||
|
||||
/// Flat ordered list of all numeric feature names (follows group order).
|
||||
pub fn all_numeric_feature_names() -> Vec<&'static str> {
|
||||
FEATURE_GROUPS
|
||||
|
|
|
|||
|
|
@ -243,8 +243,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
"Postcode boundaries loaded"
|
||||
);
|
||||
|
||||
let outcode_data =
|
||||
data::OutcodeData::from_postcode_and_place_data(&postcode_data, &place_data);
|
||||
let outcode_data = data::OutcodeData::from_postcode_and_place_data(&postcode_data, &place_data);
|
||||
|
||||
// Initialize tile reader
|
||||
let tiles_path = &cli.tiles;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ mod filters;
|
|||
mod h3;
|
||||
|
||||
pub use bounds::{bounds_intersect, h3_cell_bounds, parse_bounds, require_bounds};
|
||||
pub use fields::{parse_field_indices, parse_field_set};
|
||||
pub use fields::{parse_enum_dist, parse_field_indices, parse_field_set};
|
||||
pub use filters::{
|
||||
count_filter_impacts, parse_filters, row_passes_filters, ParsedEnumFilter, ParsedFilter,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
mod ai_filters;
|
||||
mod checkout;
|
||||
mod export;
|
||||
mod filter_counts;
|
||||
mod features;
|
||||
mod filter_counts;
|
||||
mod hexagon_stats;
|
||||
pub(crate) mod hexagons;
|
||||
mod invites;
|
||||
|
|
@ -32,8 +32,8 @@ pub(crate) mod travel_time;
|
|||
pub use ai_filters::{build_system_prompt, post_ai_filters};
|
||||
pub use checkout::post_checkout;
|
||||
pub use export::get_export;
|
||||
pub use filter_counts::get_filter_counts;
|
||||
pub use features::{build_features_response, get_features, FeatureInfo, FeaturesResponse};
|
||||
pub use filter_counts::get_filter_counts;
|
||||
pub use hexagon_stats::get_hexagon_stats;
|
||||
pub use hexagons::get_hexagons;
|
||||
pub use invites::{get_invite, get_invites, post_invites, post_redeem_invite};
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{Map, Value};
|
||||
use tracing::info;
|
||||
|
||||
use crate::aggregation::Aggregator;
|
||||
use crate::aggregation::{Aggregator, EnumDistConfig};
|
||||
use crate::auth::OptionalUser;
|
||||
use crate::consts::MAX_CELLS_PER_REQUEST;
|
||||
use crate::data::travel_time::TravelData;
|
||||
use crate::licensing::check_license_bounds;
|
||||
use crate::parsing::{
|
||||
cell_for_row_cached, needs_parent, parse_field_indices, parse_filters, require_bounds,
|
||||
row_passes_filters, validate_h3_resolution,
|
||||
cell_for_row_cached, needs_parent, parse_enum_dist, parse_field_indices, parse_filters,
|
||||
require_bounds, row_passes_filters, validate_h3_resolution,
|
||||
};
|
||||
use crate::routes::travel_time::{parse_optional_travel, TravelTimeAgg};
|
||||
use crate::state::SharedState;
|
||||
|
|
@ -65,6 +65,9 @@ pub struct HexagonParams {
|
|||
/// Each entry requests travel time aggregation for that mode+destination.
|
||||
/// Optional min:max applies as a filter (exclude properties outside range).
|
||||
travel: Option<String>,
|
||||
/// Feature name for enum distribution counting (pie chart visualization).
|
||||
/// When set, each cell includes `dist_{name}: [count_val0, count_val1, ...]`.
|
||||
enum_dist: Option<String>,
|
||||
}
|
||||
|
||||
/// Build feature maps from aggregated cell data, filtering to only cells whose
|
||||
|
|
@ -83,6 +86,7 @@ fn build_feature_maps(
|
|||
resolution: h3o::Resolution,
|
||||
travel_aggs: &[FxHashMap<u64, TravelTimeAgg>],
|
||||
travel_field_keys: &[String],
|
||||
enum_dist_key: Option<&str>,
|
||||
) -> Vec<Map<String, Value>> {
|
||||
let mut features = Vec::with_capacity(groups.len());
|
||||
let (q_south, q_west, q_north, q_east) = query_bounds;
|
||||
|
|
@ -175,6 +179,12 @@ fn build_feature_maps(
|
|||
}
|
||||
}
|
||||
|
||||
// Add enum distribution array (for pie chart visualization)
|
||||
if let (Some(key), Some(ref ed)) = (enum_dist_key, &aggregation.enum_dist) {
|
||||
let arr: Vec<Value> = ed.counts.iter().map(|&c| Value::from(c)).collect();
|
||||
map.insert(key.to_string(), Value::Array(arr));
|
||||
}
|
||||
|
||||
features.push(map);
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +222,19 @@ pub async fn get_hexagons(
|
|||
let travel_entries = parse_optional_travel(params.travel.as_deref())
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?;
|
||||
|
||||
let enum_dist_config: EnumDistConfig = parse_enum_dist(
|
||||
params.enum_dist.as_deref(),
|
||||
&state.feature_name_to_index,
|
||||
&state.data.enum_values,
|
||||
)
|
||||
.map_err(|err| (err.0, err.1).into_response())?;
|
||||
|
||||
// Pre-compute the dist_ key name (e.g. "dist_Property type") outside spawn_blocking
|
||||
let enum_dist_key: Option<String> = params
|
||||
.enum_dist
|
||||
.as_ref()
|
||||
.map(|name| format!("dist_{}", name.trim()));
|
||||
|
||||
let response = tokio::task::spawn_blocking(move || -> Result<HexagonsResponse, String> {
|
||||
let t0 = std::time::Instant::now();
|
||||
|
||||
|
|
@ -325,7 +348,7 @@ pub async fn get_hexagons(
|
|||
|
||||
let agg = local_groups
|
||||
.entry(cell_id)
|
||||
.or_insert_with(|| Aggregator::new(num_features));
|
||||
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config));
|
||||
if let Some(sel_indices) = field_indices.as_deref() {
|
||||
agg.add_row_selective(
|
||||
feature_data,
|
||||
|
|
@ -357,7 +380,7 @@ pub async fn get_hexagons(
|
|||
for (cell_id, local_agg) in local_groups {
|
||||
groups
|
||||
.entry(cell_id)
|
||||
.or_insert_with(|| Aggregator::new(num_features))
|
||||
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config))
|
||||
.merge(&local_agg);
|
||||
}
|
||||
for (ti, local_ta) in local_travel.into_iter().enumerate() {
|
||||
|
|
@ -417,7 +440,7 @@ pub async fn get_hexagons(
|
|||
|
||||
let aggregation = groups
|
||||
.entry(cell_id)
|
||||
.or_insert_with(|| Aggregator::new(num_features));
|
||||
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config));
|
||||
if let Some(sel_indices) = field_indices.as_deref() {
|
||||
aggregation.add_row_selective(
|
||||
feature_data,
|
||||
|
|
@ -454,6 +477,7 @@ pub async fn get_hexagons(
|
|||
h3_res,
|
||||
&travel_aggs,
|
||||
&travel_field_keys,
|
||||
enum_dist_key.as_deref(),
|
||||
);
|
||||
|
||||
let truncated = features.len() > MAX_CELLS_PER_REQUEST;
|
||||
|
|
|
|||
|
|
@ -142,9 +142,7 @@ pub async fn get_places(
|
|||
outcode_results.sort_unstable_by(|a, b| {
|
||||
let a_exact = a.name.eq_ignore_ascii_case(&query_upper);
|
||||
let b_exact = b.name.eq_ignore_ascii_case(&query_upper);
|
||||
b_exact
|
||||
.cmp(&a_exact)
|
||||
.then(a.name.len().cmp(&b.name.len()))
|
||||
b_exact.cmp(&a_exact).then(a.name.len().cmp(&b.name.len()))
|
||||
});
|
||||
|
||||
// Prepend outcode results (up to 3) before place results, keeping total ≤ limit
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::{Map, Value};
|
||||
use tracing::info;
|
||||
|
||||
use crate::aggregation::Aggregator;
|
||||
use crate::aggregation::{Aggregator, EnumDistConfig};
|
||||
use crate::auth::OptionalUser;
|
||||
use crate::consts::MAX_CELLS_PER_REQUEST;
|
||||
use crate::data::travel_time::TravelData;
|
||||
use crate::licensing::check_license_bounds;
|
||||
use crate::parsing::{
|
||||
bounds_intersect, parse_field_indices, parse_filters, require_bounds, row_passes_filters,
|
||||
bounds_intersect, parse_enum_dist, parse_field_indices, parse_filters, require_bounds,
|
||||
row_passes_filters,
|
||||
};
|
||||
use crate::routes::travel_time::{parse_optional_travel, TravelTimeAgg};
|
||||
use crate::state::SharedState;
|
||||
|
|
@ -43,6 +44,8 @@ pub struct PostcodeParams {
|
|||
fields: Option<String>,
|
||||
/// Pipe-separated travel time entries: `mode:slug|mode:slug:min:max`
|
||||
travel: Option<String>,
|
||||
/// Feature name for enum distribution counting (pie chart visualization).
|
||||
enum_dist: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_postcodes(
|
||||
|
|
@ -73,6 +76,18 @@ pub async fn get_postcodes(
|
|||
let travel_entries = parse_optional_travel(params.travel.as_deref())
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, err).into_response())?;
|
||||
|
||||
let enum_dist_config: EnumDistConfig = parse_enum_dist(
|
||||
params.enum_dist.as_deref(),
|
||||
&state.feature_name_to_index,
|
||||
&state.data.enum_values,
|
||||
)
|
||||
.map_err(|err| (err.0, err.1).into_response())?;
|
||||
|
||||
let enum_dist_key: Option<String> = params
|
||||
.enum_dist
|
||||
.as_ref()
|
||||
.map(|name| format!("dist_{}", name.trim()));
|
||||
|
||||
let response = tokio::task::spawn_blocking(move || -> Result<PostcodesResponse, String> {
|
||||
let postcode_data = &state.postcode_data;
|
||||
let t0 = std::time::Instant::now();
|
||||
|
|
@ -129,7 +144,7 @@ pub async fn get_postcodes(
|
|||
if let Some(&pc_idx) = postcode_data.postcode_to_idx.get(postcode) {
|
||||
let agg = postcode_aggs
|
||||
.entry(pc_idx)
|
||||
.or_insert_with(|| Aggregator::new(num_features));
|
||||
.or_insert_with(|| Aggregator::new(num_features, enum_dist_config));
|
||||
if has_selective {
|
||||
agg.add_row_selective(feature_data, row, num_features, sel_indices, &quant);
|
||||
} else {
|
||||
|
|
@ -272,6 +287,12 @@ pub async fn get_postcodes(
|
|||
}
|
||||
}
|
||||
|
||||
// Add enum distribution array (for pie chart visualization)
|
||||
if let (Some(ref key), Some(ref ed)) = (&enum_dist_key, &aggregation.enum_dist) {
|
||||
let arr: Vec<Value> = ed.counts.iter().map(|&c| Value::from(c)).collect();
|
||||
props.insert(key.clone(), Value::Array(arr));
|
||||
}
|
||||
|
||||
// Build GeoJSON Feature
|
||||
let mut feature = Map::new();
|
||||
feature.insert("type".into(), Value::String("Feature".into()));
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ pub struct Property {
|
|||
pub listing_url: Option<String>,
|
||||
pub property_sub_type: Option<String>,
|
||||
pub price_qualifier: Option<String>,
|
||||
pub former_council_house: Option<String>,
|
||||
|
||||
// Numeric fields
|
||||
pub lat: f32,
|
||||
|
|
@ -168,6 +169,13 @@ pub fn build_property(
|
|||
listing_url: state.data.listing_url(row).map(String::from),
|
||||
property_sub_type: state.data.property_sub_type(row).map(String::from),
|
||||
price_qualifier: state.data.price_qualifier(row).map(String::from),
|
||||
former_council_house: lookup_enum_value(
|
||||
feature_name_to_index,
|
||||
&state.data,
|
||||
enum_values,
|
||||
row,
|
||||
"Former council house",
|
||||
),
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue