Checkpoint all changes
This commit is contained in:
parent
65877acf95
commit
66c2a25457
28 changed files with 3035 additions and 621 deletions
|
|
@ -5,6 +5,7 @@ use serde::Serialize;
|
|||
use tracing::info;
|
||||
|
||||
use crate::data::Histogram;
|
||||
use crate::features::{ENUM_FEATURE_GROUPS, FEATURE_GROUPS};
|
||||
use crate::state::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
@ -13,75 +14,123 @@ pub enum FeatureInfo {
|
|||
#[serde(rename = "numeric")]
|
||||
Numeric {
|
||||
name: String,
|
||||
label: String,
|
||||
min: f64,
|
||||
max: f64,
|
||||
step: f64,
|
||||
histogram: Histogram,
|
||||
description: &'static str,
|
||||
detail: &'static str,
|
||||
source: &'static str,
|
||||
},
|
||||
#[serde(rename = "enum")]
|
||||
Enum {
|
||||
name: String,
|
||||
label: String,
|
||||
values: Vec<String>,
|
||||
description: &'static str,
|
||||
detail: &'static str,
|
||||
source: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FeaturesResponse {
|
||||
pub struct FeatureGroupResponse {
|
||||
name: String,
|
||||
features: Vec<FeatureInfo>,
|
||||
}
|
||||
|
||||
fn snake_to_label(name: &str) -> String {
|
||||
// If name contains '/' or uppercase, assume it's already human-readable
|
||||
if name.contains('/') || name.chars().any(|c| c.is_uppercase()) {
|
||||
return name.to_string();
|
||||
}
|
||||
name.split('_')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(c) => {
|
||||
let mut s = c.to_uppercase().to_string();
|
||||
s.extend(chars);
|
||||
s
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
#[derive(Serialize)]
|
||||
pub struct FeaturesResponse {
|
||||
groups: Vec<FeatureGroupResponse>,
|
||||
}
|
||||
|
||||
pub async fn get_features(state: Arc<AppState>) -> Json<FeaturesResponse> {
|
||||
let mut features: Vec<FeatureInfo> = state
|
||||
.data
|
||||
.feature_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, name): (usize, &String)| {
|
||||
let stats = &state.data.feature_stats[i];
|
||||
FeatureInfo::Numeric {
|
||||
name: name.clone(),
|
||||
label: snake_to_label(name),
|
||||
min: stats.p_low,
|
||||
max: stats.p_high,
|
||||
histogram: stats.histogram.clone(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for ef in &state.data.enum_features {
|
||||
features.push(FeatureInfo::Enum {
|
||||
name: ef.name.clone(),
|
||||
label: snake_to_label(&ef.name),
|
||||
values: ef.values.clone(),
|
||||
});
|
||||
// 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 {
|
||||
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 {
|
||||
if let Some(feat_idx) =
|
||||
state.data.feature_names.iter().position(|feat_name| feat_name == feature_config.name)
|
||||
{
|
||||
let stats = &state.data.feature_stats[feat_idx];
|
||||
features.push(FeatureInfo::Numeric {
|
||||
name: feature_config.name.to_string(),
|
||||
min: stats.slider_min,
|
||||
max: stats.slider_max,
|
||||
step: feature_config.step,
|
||||
histogram: stats.histogram.clone(),
|
||||
description: feature_config.description,
|
||||
detail: feature_config.detail,
|
||||
source: feature_config.source,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if let Some(enum_feature) = state
|
||||
.data
|
||||
.enum_features
|
||||
.iter()
|
||||
.find(|enum_feat| enum_feat.name == enum_config.name)
|
||||
{
|
||||
features.push(FeatureInfo::Enum {
|
||||
name: enum_config.name.to_string(),
|
||||
values: enum_feature.values.clone(),
|
||||
description: enum_config.description,
|
||||
detail: enum_config.detail,
|
||||
source: enum_config.source,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !features.is_empty() {
|
||||
groups.push(FeatureGroupResponse {
|
||||
name: group_name.to_string(),
|
||||
features,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let num_numeric: usize = groups
|
||||
.iter()
|
||||
.flat_map(|group| &group.features)
|
||||
.filter(|feature| matches!(feature, FeatureInfo::Numeric { .. }))
|
||||
.count();
|
||||
let num_enum: usize = groups
|
||||
.iter()
|
||||
.flat_map(|group| &group.features)
|
||||
.filter(|feature| matches!(feature, FeatureInfo::Enum { .. }))
|
||||
.count();
|
||||
|
||||
info!(
|
||||
numeric = features.iter().filter(|f| matches!(f, FeatureInfo::Numeric { .. })).count(),
|
||||
enums = features.iter().filter(|f| matches!(f, FeatureInfo::Enum { .. })).count(),
|
||||
numeric = num_numeric,
|
||||
enums = num_enum,
|
||||
groups = groups.len(),
|
||||
"GET /api/features"
|
||||
);
|
||||
Json(FeaturesResponse { features })
|
||||
Json(FeaturesResponse { groups })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue