1use aria_core::{Engine, EngineConfig, Signal, Scoreable};
8use aria_core::item::Item;
9use aria_core::factor::{ChallengeFactor, SpacingFactor, CoverageFactor};
10use aria_core::serialiser::Serialiser;
11
12fn main() {
13 println!("=== aria-core: Learning Domain Simulation ===\n");
14
15 let mut engine = Engine::new(EngineConfig {
17 exploration_rate: 0.0, alpha: 0.1, });
20
21 engine.add_factor(Box::new(ChallengeFactor::new(0.2)));
23 engine.add_factor(Box::new(SpacingFactor::new(10))); engine.add_factor(Box::new(CoverageFactor));
25 engine.seed_rng(42);
26
27 engine.add_items(vec![
29 Item::new("counting", 0.05, "math"),
31 Item::new("addition", 0.15, "math"),
32 Item::new("multiplication", 0.30, "math"),
33 Item::new("fractions", 0.45, "math"),
34 Item::new("algebra_basics", 0.60, "math"),
35 Item::new("quadratics", 0.75, "math")
36 .with_prereqs(vec!["algebra_basics".into()]),
37 Item::new("calculus_intro", 0.90, "math")
38 .with_prereqs(vec!["quadratics".into()]),
39
40 Item::new("sci_observation", 0.10, "science"),
42 Item::new("sci_hypothesis", 0.25, "science"),
43 Item::new("sci_experiment", 0.50, "science"),
44 Item::new("sci_analysis", 0.70, "science"),
45 Item::new("sci_research", 0.90, "science"),
46 ]).unwrap();
47
48 println!("Items registered: {}", engine.item_count());
49 println!("Factors registered: {}\n", engine.factor_count());
50
51 println!("{:<5} {:<22} {:<10} {:<8} {:<8} {:<10}",
52 "Step", "Item", "Category", "Success", "Effort", "Skill");
53 println!("{}", "-".repeat(70));
54
55 let interactions: Vec<(bool, f32)> = vec![
58 (true, 0.8), (true, 0.3), (true, 0.2), (false, 0.9), (true, 0.5), (true, 0.4), (true, 0.2), (true, 0.3), (false, 0.7), (true, 0.5), (true, 0.4), (true, 0.3), (true, 0.2), (true, 0.5), (true, 0.3), ];
74
75 for (step, (success, effort)) in interactions.iter().enumerate() {
76 let item = engine.suggest("alice").unwrap();
77 let item_id = item.id().to_string();
78 let category = item.category().to_string();
79
80 engine.feedback("alice", &item_id, Signal::new(*success, *effort)).unwrap();
81
82 let state = engine.get_state("alice").unwrap();
83 println!("{:<5} {:<22} {:<10} {:<8} {:<8.2} {:<.4}",
84 step + 1,
85 item_id,
86 category,
87 if *success { "✓" } else { "✗" },
88 effort,
89 state.skill,
90 );
91 }
92
93 let state = engine.get_state("alice").unwrap();
95 println!("\n=== Final State ===");
96 println!("Skill: {:.4}", state.skill);
97 println!("Optimism bias: {:.4}", state.optimism_bias);
98 println!("Target: {:.4}", state.target());
99 println!("Interactions: {}", state.interaction_count);
100 println!("Resolved items: {:?}", state.resolved_set);
101
102 println!("\n=== Category Coverage ===");
103 let mut cats: Vec<_> = state.category_count.iter().collect();
104 cats.sort_by_key(|(k, _)| k.as_str());
105 for (cat, count) in cats {
106 println!(" {:<12} → {} interactions", cat, count);
107 }
108
109 println!("\n=== Serialisation Round-Trip ===");
111 let encoded = Serialiser::encode(state);
112 println!("Encoded keys: {}", encoded.len());
113 let decoded = Serialiser::decode(&encoded).unwrap();
114 assert!((decoded.skill - state.skill).abs() < 1e-5, "skill mismatch after round-trip");
115 assert_eq!(decoded.interaction_count, state.interaction_count);
116 println!("Round-trip: ✓ skill={:.4} interactions={}", decoded.skill, decoded.interaction_count);
117
118 println!("\n=== Prerequisite Gating ===");
120 println!("calculus_intro requires: quadratics → algebra_basics");
121 let can_see_calculus = state.resolved_set.contains("quadratics");
122 println!("User resolved quadratics: {}", can_see_calculus);
123 println!("(calculus_intro will unlock once quadratics is resolved)");
124
125 println!("\n=== Custom Domain Example (ecommerce) ===");
126 demo_ecommerce();
127}
128
129fn demo_ecommerce() {
131 use aria_core::factor::ChallengeFactor;
132
133 struct BudgetFactor;
134 impl aria_core::factor::Factor for BudgetFactor {
135 fn name(&self) -> &str { "budget" }
136 fn score(&self, item: &dyn Scoreable, state: &aria_core::ProfileState, _now: u64) -> f32 {
137 let target = state.target();
140 let diff = item.score_proxy() - target;
141 (-diff * diff / 0.08).exp() }
143 }
144
145 let mut engine = Engine::new(EngineConfig {
146 exploration_rate: 0.0,
147 alpha: 0.1,
148 });
149 engine.add_factor(Box::new(BudgetFactor));
150 engine.add_factor(Box::new(ChallengeFactor::new(0.25))); engine.seed_rng(1);
152
153 engine.add_items(vec![
154 Item::new("budget_headphones", 0.1, "audio"),
155 Item::new("mid_headphones", 0.4, "audio"),
156 Item::new("premium_headphones", 0.9, "audio"),
157 Item::new("budget_speaker", 0.15, "audio"),
158 Item::new("smartwatch_basic", 0.3, "wearables"),
159 Item::new("smartwatch_pro", 0.8, "wearables"),
160 ]).unwrap();
161
162 let mut state = aria_core::ProfileState::new();
164 state.skill = 0.35; engine.load_state("shopper", state);
166
167 print!("Suggestions for mid-budget shopper: ");
168 for _ in 0..3 {
169 let item = engine.suggest("shopper").unwrap();
170 let id = item.id().to_string();
171 print!("{} ", id);
172 engine.feedback("shopper", &id, Signal::new(true, 0.5)).unwrap();
173 }
174 println!();
175}