pub struct Item {
pub id: String,
pub score_proxy: f32,
pub category: String,
pub prerequisites: Vec<String>,
pub metadata: HashMap<String, String>,
}Expand description
Concrete generic item — callers can use this directly
or implement Scoreable on their own type.
Fields§
§id: String§score_proxy: f32§category: String§prerequisites: Vec<String>§metadata: HashMap<String, String>Implementations§
Source§impl Item
impl Item
Sourcepub fn new(
id: impl Into<String>,
score_proxy: f32,
category: impl Into<String>,
) -> Self
pub fn new( id: impl Into<String>, score_proxy: f32, category: impl Into<String>, ) -> Self
Examples found in repository?
examples/learning_simulation.rs (line 30)
12fn main() {
13 println!("=== aria-core: Learning Domain Simulation ===\n");
14
15 // --- Build engine ---
16 let mut engine = Engine::new(EngineConfig {
17 exploration_rate: 0.0, // deterministic for demo
18 alpha: 0.1, // faster skill movement so visible in 15 steps
19 });
20
21 // Register factors — caller's choice
22 engine.add_factor(Box::new(ChallengeFactor::new(0.2)));
23 engine.add_factor(Box::new(SpacingFactor::new(10))); // 10s interval for demo
24 engine.add_factor(Box::new(CoverageFactor));
25 engine.seed_rng(42);
26
27 // Register items — caller's curriculum
28 engine.add_items(vec![
29 // Math track
30 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 // Science track (no prereqs — runs in parallel)
41 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 // --- Simulate 15 interactions ---
56 // Alternates easy successes and a few failures to show adaptation
57 let interactions: Vec<(bool, f32)> = vec![
58 (true, 0.8), // 1 hard success
59 (true, 0.3), // 2 easy success → optimism up
60 (true, 0.2), // 3 easy
61 (false, 0.9), // 4 failure → optimism eases
62 (true, 0.5), // 5
63 (true, 0.4), // 6
64 (true, 0.2), // 7 easy → optimism climbs
65 (true, 0.3), // 8
66 (false, 0.7), // 9 failure
67 (true, 0.5), // 10
68 (true, 0.4), // 11
69 (true, 0.3), // 12
70 (true, 0.2), // 13
71 (true, 0.5), // 14
72 (true, 0.3), // 15
73 ];
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 // --- Show final state ---
94 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 // --- Serialise / deserialise round-trip ---
110 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 // --- Prereq demonstration ---
119 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
129/// Quick demo showing the same engine used for a completely different domain.
130fn 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 // score_proxy = price_ratio (0=free, 1=most expensive)
138 // ProfileState.skill = budget_willingness
139 let target = state.target();
140 let diff = item.score_proxy() - target;
141 (-diff * diff / 0.08).exp() // bandwidth=0.2
142 }
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))); // reused as price-fit
151 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 // User with mid budget
163 let mut state = aria_core::ProfileState::new();
164 state.skill = 0.35; // budget willingness
165 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}Sourcepub fn with_prereqs(self, prereqs: Vec<String>) -> Self
pub fn with_prereqs(self, prereqs: Vec<String>) -> Self
Examples found in repository?
examples/learning_simulation.rs (line 36)
12fn main() {
13 println!("=== aria-core: Learning Domain Simulation ===\n");
14
15 // --- Build engine ---
16 let mut engine = Engine::new(EngineConfig {
17 exploration_rate: 0.0, // deterministic for demo
18 alpha: 0.1, // faster skill movement so visible in 15 steps
19 });
20
21 // Register factors — caller's choice
22 engine.add_factor(Box::new(ChallengeFactor::new(0.2)));
23 engine.add_factor(Box::new(SpacingFactor::new(10))); // 10s interval for demo
24 engine.add_factor(Box::new(CoverageFactor));
25 engine.seed_rng(42);
26
27 // Register items — caller's curriculum
28 engine.add_items(vec![
29 // Math track
30 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 // Science track (no prereqs — runs in parallel)
41 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 // --- Simulate 15 interactions ---
56 // Alternates easy successes and a few failures to show adaptation
57 let interactions: Vec<(bool, f32)> = vec![
58 (true, 0.8), // 1 hard success
59 (true, 0.3), // 2 easy success → optimism up
60 (true, 0.2), // 3 easy
61 (false, 0.9), // 4 failure → optimism eases
62 (true, 0.5), // 5
63 (true, 0.4), // 6
64 (true, 0.2), // 7 easy → optimism climbs
65 (true, 0.3), // 8
66 (false, 0.7), // 9 failure
67 (true, 0.5), // 10
68 (true, 0.4), // 11
69 (true, 0.3), // 12
70 (true, 0.2), // 13
71 (true, 0.5), // 14
72 (true, 0.3), // 15
73 ];
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 // --- Show final state ---
94 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 // --- Serialise / deserialise round-trip ---
110 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 // --- Prereq demonstration ---
119 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}pub fn with_metadata(self, metadata: HashMap<String, String>) -> Self
Trait Implementations§
Source§impl Scoreable for Item
impl Scoreable for Item
fn id(&self) -> &str
Source§fn score_proxy(&self) -> f32
fn score_proxy(&self) -> f32
Normalised difficulty/complexity proxy. Range [0.0, 1.0].
Caller defines what this means in their domain.
Source§fn category(&self) -> &str
fn category(&self) -> &str
Category label used for coverage balancing.
e.g. “algebra”, “electronics”, “beach”, “thriller”
Source§fn prerequisites(&self) -> &[String]
fn prerequisites(&self) -> &[String]
Prerequisite item IDs. Engine will not suggest this item
until all prereqs appear in the user’s
resolved_set.Auto Trait Implementations§
impl Freeze for Item
impl RefUnwindSafe for Item
impl Send for Item
impl Sync for Item
impl Unpin for Item
impl UnsafeUnpin for Item
impl UnwindSafe for Item
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more