mecha10_sim/sim/
selector.rs1use anyhow::Result;
4use std::collections::HashSet;
5
6use super::catalog::{Environment, EnvironmentCatalog};
7use super::profile::RobotProfile;
8
9#[derive(Debug, Clone)]
10pub struct EnvironmentMatch {
11 pub environment: Environment,
12 pub score: i32,
13 pub reasons: Vec<String>,
14}
15
16pub struct EnvironmentSelector {
17 catalog: EnvironmentCatalog,
18}
19
20impl EnvironmentSelector {
21 pub fn new() -> Result<Self> {
23 let catalog = EnvironmentCatalog::load_default()?;
24 Ok(Self { catalog })
25 }
26
27 pub fn with_catalog(catalog: EnvironmentCatalog) -> Self {
29 Self { catalog }
30 }
31
32 pub fn select_environments(&self, profile: &RobotProfile, max_envs: usize) -> Result<Vec<EnvironmentMatch>> {
34 let mut matches: Vec<EnvironmentMatch> = self
35 .catalog
36 .environments
37 .iter()
38 .map(|env| self.score_environment(env, profile))
39 .filter(|m| m.score > 0) .collect();
41
42 matches.sort_by_key(|m| -m.score);
44
45 Ok(matches.into_iter().take(max_envs).collect())
47 }
48
49 fn score_environment(&self, env: &Environment, profile: &RobotProfile) -> EnvironmentMatch {
51 let mut score = 0;
52 let mut reasons = Vec::new();
53
54 for task_node in &profile.task_nodes {
56 if env.task_nodes.contains(&task_node.name) {
57 score += 20;
58 reasons.push(format!("Task node match: {}", task_node.name));
59 }
60 }
61
62 let node_tags: HashSet<String> = profile.task_nodes.iter().flat_map(|n| n.tags.iter().cloned()).collect();
64
65 for tag in &node_tags {
66 if env.tags.contains(tag) {
67 score += 10;
68 reasons.push(format!("Tag match: {}", tag));
69 }
70 }
71
72 let has_required_sensors = env.is_compatible_with_sensors(&profile.sensors);
74
75 if has_required_sensors {
76 score += 5;
77 reasons.push("All required sensors present".to_string());
78 } else {
79 score -= 50; let missing: Vec<_> = env
81 .required_sensors
82 .iter()
83 .filter(|s| !profile.sensors.contains(s))
84 .collect();
85 reasons.push(format!(
86 "Missing required sensors: {}",
87 missing.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", ")
88 ));
89 }
90
91 if env.is_compatible_with_platform(&profile.platform) {
93 score += 3;
94 reasons.push(format!("Platform match: {}", profile.platform));
95 }
96
97 if env.difficulty == profile.difficulty_preference {
99 score += 2;
100 reasons.push(format!("Difficulty match: {}", env.difficulty));
101 }
102
103 for capability in &profile.capabilities {
105 if env.tags.contains(capability) {
106 score += 1;
107 reasons.push(format!("Capability match: {}", capability));
108 }
109 }
110
111 EnvironmentMatch {
112 environment: env.clone(),
113 score,
114 reasons,
115 }
116 }
117
118 pub fn catalog(&self) -> &EnvironmentCatalog {
120 &self.catalog
121 }
122}