1use super::learned_component::{LearnedDepGraph, LearnedExploration, LearnedStrategy};
16use super::offline::{
17 ActionOrderSource, LearnedActionOrder, OfflineModel, OptimalParameters, StrategyConfig,
18};
19use super::scenario_profile::ScenarioProfile;
20
21pub fn profile_to_offline_model(profile: &ScenarioProfile) -> OfflineModel {
30 let mut model = OfflineModel::default();
31
32 if let Some(exploration) = &profile.exploration {
34 model.parameters = OptimalParameters {
35 ucb1_c: exploration.ucb1_c,
36 learning_weight: exploration.learning_weight,
37 ngram_weight: exploration.ngram_weight,
38 };
39 }
40
41 if let Some(strategy) = &profile.strategy {
43 model.strategy_config = StrategyConfig {
44 initial_strategy: strategy.initial_strategy.clone(),
45 maturity_threshold: strategy.maturity_threshold as u32,
46 error_rate_threshold: strategy.error_rate_threshold,
47 };
48 }
49
50 if let Some(dep_graph) = &profile.dep_graph {
52 if !dep_graph.action_order.is_empty() {
54 use std::collections::hash_map::DefaultHasher;
56 use std::hash::{Hash, Hasher};
57 let mut hasher = DefaultHasher::new();
58 dep_graph.action_order.hash(&mut hasher);
59 let action_set_hash = hasher.finish();
60
61 model.action_order = Some(LearnedActionOrder {
62 discover: dep_graph.action_order.clone(),
63 not_discover: Vec::new(), action_set_hash,
65 source: ActionOrderSource::Manual, });
67 }
68
69 model.recommended_paths = dep_graph.recommended_paths.clone();
71 }
72
73 model.updated_at = profile.updated_at;
75 model.analyzed_sessions = profile
76 .dep_graph
77 .as_ref()
78 .map(|d| d.learned_from.len())
79 .unwrap_or(0);
80
81 model
82}
83
84pub fn offline_model_to_components(
92 model: &OfflineModel,
93) -> (
94 Option<LearnedDepGraph>,
95 Option<LearnedExploration>,
96 Option<LearnedStrategy>,
97) {
98 let exploration = Some(LearnedExploration {
100 ucb1_c: model.parameters.ucb1_c,
101 learning_weight: model.parameters.learning_weight,
102 ngram_weight: model.parameters.ngram_weight,
103 confidence: 0.8, session_count: model.analyzed_sessions,
105 updated_at: model.updated_at,
106 });
107
108 let strategy = Some(LearnedStrategy {
110 initial_strategy: model.strategy_config.initial_strategy.clone(),
111 maturity_threshold: model.strategy_config.maturity_threshold as usize,
112 error_rate_threshold: model.strategy_config.error_rate_threshold,
113 confidence: 0.8,
114 session_count: model.analyzed_sessions,
115 updated_at: model.updated_at,
116 });
117
118 let dep_graph = model.action_order.as_ref().map(|order| {
120 use crate::exploration::DependencyGraph;
121
122 LearnedDepGraph {
123 graph: DependencyGraph::new(), action_order: order.discover.clone(),
125 recommended_paths: model.recommended_paths.clone(),
126 confidence: 0.8, learned_from: Vec::new(),
128 updated_at: model.updated_at,
129 }
130 });
131
132 (dep_graph, exploration, strategy)
133}
134
135pub fn migrate_offline_model_to_profile(
137 profile_id: impl Into<String>,
138 scenario_path: impl Into<std::path::PathBuf>,
139 model: &OfflineModel,
140) -> ScenarioProfile {
141 use super::scenario_profile::{ProfileState, ScenarioSource};
142
143 let (dep_graph, exploration, strategy) = offline_model_to_components(model);
144
145 let mut profile =
146 ScenarioProfile::new(profile_id, ScenarioSource::from_path(scenario_path.into()));
147
148 profile.dep_graph = dep_graph;
149 profile.exploration = exploration;
150 profile.strategy = strategy;
151 profile.state = ProfileState::Active; profile.updated_at = model.updated_at;
153
154 profile
155}
156
157pub trait ProfileToOfflineModel {
163 fn to_offline_model(&self) -> OfflineModel;
165}
166
167impl ProfileToOfflineModel for ScenarioProfile {
168 fn to_offline_model(&self) -> OfflineModel {
169 profile_to_offline_model(self)
170 }
171}
172
173#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::exploration::DependencyGraph;
181 use crate::learn::scenario_profile::ScenarioSource;
182
183 #[test]
184 fn test_profile_to_offline_model_empty() {
185 let profile = ScenarioProfile::new("test", ScenarioSource::from_path("/test.toml"));
186 let model = profile_to_offline_model(&profile);
187
188 assert!(model.parameters.ucb1_c > 0.0);
190 }
191
192 #[test]
193 fn test_profile_to_offline_model_with_components() {
194 let mut profile = ScenarioProfile::new("test", ScenarioSource::from_path("/test.toml"));
195
196 profile.exploration = Some(LearnedExploration {
197 ucb1_c: 2.5,
198 learning_weight: 0.4,
199 ngram_weight: 1.2,
200 confidence: 0.9,
201 session_count: 10,
202 updated_at: 12345,
203 });
204
205 profile.strategy = Some(LearnedStrategy {
206 initial_strategy: "greedy".to_string(),
207 maturity_threshold: 10,
208 error_rate_threshold: 0.3,
209 confidence: 0.85,
210 session_count: 10,
211 updated_at: 12345,
212 });
213
214 profile.dep_graph = Some(
215 LearnedDepGraph::new(
216 DependencyGraph::new(),
217 vec!["A".to_string(), "B".to_string()],
218 )
219 .with_confidence(0.95),
220 );
221
222 let model = profile_to_offline_model(&profile);
223
224 assert_eq!(model.parameters.ucb1_c, 2.5);
225 assert_eq!(model.parameters.learning_weight, 0.4);
226 assert_eq!(model.strategy_config.initial_strategy, "greedy");
227 assert_eq!(model.strategy_config.maturity_threshold, 10);
228 assert!(model.action_order.is_some());
229 assert_eq!(model.action_order.as_ref().unwrap().discover.len(), 2);
230 }
231
232 #[test]
233 fn test_offline_model_to_components() {
234 use super::ActionOrderSource;
235
236 let mut model = OfflineModel::default();
237 model.parameters.ucb1_c = 1.8;
238 model.strategy_config.initial_strategy = "ucb1".to_string();
239 model.action_order = Some(LearnedActionOrder {
240 discover: vec!["X".to_string(), "Y".to_string()],
241 not_discover: vec![],
242 action_set_hash: 12345,
243 source: ActionOrderSource::Manual,
244 });
245 model.analyzed_sessions = 5;
246
247 let (dep_graph, exploration, strategy) = offline_model_to_components(&model);
248
249 assert!(dep_graph.is_some());
250 assert!(exploration.is_some());
251 assert!(strategy.is_some());
252
253 let exploration = exploration.unwrap();
254 assert_eq!(exploration.ucb1_c, 1.8);
255
256 let strategy = strategy.unwrap();
257 assert_eq!(strategy.initial_strategy, "ucb1");
258
259 let dep_graph = dep_graph.unwrap();
260 assert_eq!(dep_graph.action_order.len(), 2);
261 }
262
263 #[test]
264 fn test_migrate_offline_model_to_profile() {
265 let mut model = OfflineModel::default();
266 model.parameters.ucb1_c = 2.0;
267 model.analyzed_sessions = 10;
268
269 let profile =
270 migrate_offline_model_to_profile("test-profile", "/path/to/scenario.toml", &model);
271
272 assert_eq!(profile.id.0, "test-profile");
273 assert!(profile.exploration.is_some());
274 assert_eq!(profile.exploration.as_ref().unwrap().ucb1_c, 2.0);
275 }
276
277 #[test]
278 fn test_profile_to_offline_model_trait() {
279 let profile = ScenarioProfile::new("test", ScenarioSource::from_path("/test.toml"));
280 let model = profile.to_offline_model();
281
282 assert!(model.parameters.ucb1_c > 0.0);
283 }
284}