use aria_core::{Engine, EngineConfig, Signal, Scoreable};
use aria_core::item::Item;
use aria_core::factor::{ChallengeFactor, SpacingFactor, CoverageFactor};
use aria_core::serialiser::Serialiser;
fn main() {
println!("=== aria-core: Learning Domain Simulation ===\n");
let mut engine = Engine::new(EngineConfig {
exploration_rate: 0.0, alpha: 0.1, });
engine.add_factor(Box::new(ChallengeFactor::new(0.2)));
engine.add_factor(Box::new(SpacingFactor::new(10))); engine.add_factor(Box::new(CoverageFactor));
engine.seed_rng(42);
engine.add_items(vec![
Item::new("counting", 0.05, "math"),
Item::new("addition", 0.15, "math"),
Item::new("multiplication", 0.30, "math"),
Item::new("fractions", 0.45, "math"),
Item::new("algebra_basics", 0.60, "math"),
Item::new("quadratics", 0.75, "math")
.with_prereqs(vec!["algebra_basics".into()]),
Item::new("calculus_intro", 0.90, "math")
.with_prereqs(vec!["quadratics".into()]),
Item::new("sci_observation", 0.10, "science"),
Item::new("sci_hypothesis", 0.25, "science"),
Item::new("sci_experiment", 0.50, "science"),
Item::new("sci_analysis", 0.70, "science"),
Item::new("sci_research", 0.90, "science"),
]).unwrap();
println!("Items registered: {}", engine.item_count());
println!("Factors registered: {}\n", engine.factor_count());
println!("{:<5} {:<22} {:<10} {:<8} {:<8} {:<10}",
"Step", "Item", "Category", "Success", "Effort", "Skill");
println!("{}", "-".repeat(70));
let interactions: Vec<(bool, f32)> = vec![
(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), ];
for (step, (success, effort)) in interactions.iter().enumerate() {
let item = engine.suggest("alice").unwrap();
let item_id = item.id().to_string();
let category = item.category().to_string();
engine.feedback("alice", &item_id, Signal::new(*success, *effort)).unwrap();
let state = engine.get_state("alice").unwrap();
println!("{:<5} {:<22} {:<10} {:<8} {:<8.2} {:<.4}",
step + 1,
item_id,
category,
if *success { "✓" } else { "✗" },
effort,
state.skill,
);
}
let state = engine.get_state("alice").unwrap();
println!("\n=== Final State ===");
println!("Skill: {:.4}", state.skill);
println!("Optimism bias: {:.4}", state.optimism_bias);
println!("Target: {:.4}", state.target());
println!("Interactions: {}", state.interaction_count);
println!("Resolved items: {:?}", state.resolved_set);
println!("\n=== Category Coverage ===");
let mut cats: Vec<_> = state.category_count.iter().collect();
cats.sort_by_key(|(k, _)| k.as_str());
for (cat, count) in cats {
println!(" {:<12} → {} interactions", cat, count);
}
println!("\n=== Serialisation Round-Trip ===");
let encoded = Serialiser::encode(state);
println!("Encoded keys: {}", encoded.len());
let decoded = Serialiser::decode(&encoded).unwrap();
assert!((decoded.skill - state.skill).abs() < 1e-5, "skill mismatch after round-trip");
assert_eq!(decoded.interaction_count, state.interaction_count);
println!("Round-trip: ✓ skill={:.4} interactions={}", decoded.skill, decoded.interaction_count);
println!("\n=== Prerequisite Gating ===");
println!("calculus_intro requires: quadratics → algebra_basics");
let can_see_calculus = state.resolved_set.contains("quadratics");
println!("User resolved quadratics: {}", can_see_calculus);
println!("(calculus_intro will unlock once quadratics is resolved)");
println!("\n=== Custom Domain Example (ecommerce) ===");
demo_ecommerce();
}
fn demo_ecommerce() {
use aria_core::factor::ChallengeFactor;
struct BudgetFactor;
impl aria_core::factor::Factor for BudgetFactor {
fn name(&self) -> &str { "budget" }
fn score(&self, item: &dyn Scoreable, state: &aria_core::ProfileState, _now: u64) -> f32 {
let target = state.target();
let diff = item.score_proxy() - target;
(-diff * diff / 0.08).exp() }
}
let mut engine = Engine::new(EngineConfig {
exploration_rate: 0.0,
alpha: 0.1,
});
engine.add_factor(Box::new(BudgetFactor));
engine.add_factor(Box::new(ChallengeFactor::new(0.25))); engine.seed_rng(1);
engine.add_items(vec![
Item::new("budget_headphones", 0.1, "audio"),
Item::new("mid_headphones", 0.4, "audio"),
Item::new("premium_headphones", 0.9, "audio"),
Item::new("budget_speaker", 0.15, "audio"),
Item::new("smartwatch_basic", 0.3, "wearables"),
Item::new("smartwatch_pro", 0.8, "wearables"),
]).unwrap();
let mut state = aria_core::ProfileState::new();
state.skill = 0.35; engine.load_state("shopper", state);
print!("Suggestions for mid-budget shopper: ");
for _ in 0..3 {
let item = engine.suggest("shopper").unwrap();
let id = item.id().to_string();
print!("{} ", id);
engine.feedback("shopper", &id, Signal::new(true, 0.5)).unwrap();
}
println!();
}