u_schedule/models/
resource.rs1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13use super::Calendar;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Resource {
18 pub id: String,
20 pub name: String,
22 pub resource_type: ResourceType,
24 pub capacity: i32,
26 pub efficiency: f64,
28 pub calendar: Option<Calendar>,
30 pub skills: Vec<Skill>,
32 pub cost_per_hour: Option<f64>,
34 pub attributes: HashMap<String, String>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub enum ResourceType {
44 Primary,
46 Secondary,
48 Human,
50 Consumable,
52 Custom(String),
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Skill {
59 pub name: String,
61 pub level: f64,
63}
64
65impl Resource {
66 pub fn new(id: impl Into<String>, resource_type: ResourceType) -> Self {
68 Self {
69 id: id.into(),
70 name: String::new(),
71 resource_type,
72 capacity: 1,
73 efficiency: 1.0,
74 calendar: None,
75 skills: Vec::new(),
76 cost_per_hour: None,
77 attributes: HashMap::new(),
78 }
79 }
80
81 pub fn primary(id: impl Into<String>) -> Self {
83 Self::new(id, ResourceType::Primary)
84 }
85
86 pub fn human(id: impl Into<String>) -> Self {
88 Self::new(id, ResourceType::Human)
89 }
90
91 pub fn secondary(id: impl Into<String>) -> Self {
93 Self::new(id, ResourceType::Secondary)
94 }
95
96 pub fn with_name(mut self, name: impl Into<String>) -> Self {
98 self.name = name.into();
99 self
100 }
101
102 pub fn with_capacity(mut self, capacity: i32) -> Self {
104 self.capacity = capacity;
105 self
106 }
107
108 pub fn with_efficiency(mut self, efficiency: f64) -> Self {
110 self.efficiency = efficiency;
111 self
112 }
113
114 pub fn with_calendar(mut self, calendar: Calendar) -> Self {
116 self.calendar = Some(calendar);
117 self
118 }
119
120 pub fn with_skill(mut self, name: impl Into<String>, level: f64) -> Self {
122 self.skills.push(Skill {
123 name: name.into(),
124 level: level.clamp(0.0, 1.0),
125 });
126 self
127 }
128
129 pub fn with_cost(mut self, cost_per_hour: f64) -> Self {
131 self.cost_per_hour = Some(cost_per_hour);
132 self
133 }
134
135 pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
137 self.attributes.insert(key.into(), value.into());
138 self
139 }
140
141 pub fn has_skill(&self, name: &str) -> bool {
143 self.skills.iter().any(|s| s.name == name)
144 }
145
146 pub fn skill_level(&self, name: &str) -> f64 {
148 self.skills
149 .iter()
150 .find(|s| s.name == name)
151 .map(|s| s.level)
152 .unwrap_or(0.0)
153 }
154
155 pub fn is_available_at(&self, time_ms: i64) -> bool {
160 match &self.calendar {
161 None => true,
162 Some(cal) => cal.is_working_time(time_ms),
163 }
164 }
165}
166
167impl Skill {
168 pub fn new(name: impl Into<String>, level: f64) -> Self {
170 Self {
171 name: name.into(),
172 level: level.clamp(0.0, 1.0),
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_resource_builder() {
183 let r = Resource::primary("M1")
184 .with_name("CNC Machine 1")
185 .with_capacity(1)
186 .with_efficiency(1.2)
187 .with_skill("milling", 0.9)
188 .with_skill("drilling", 0.7)
189 .with_cost(50.0)
190 .with_attribute("location", "Shop Floor A");
191
192 assert_eq!(r.id, "M1");
193 assert_eq!(r.name, "CNC Machine 1");
194 assert_eq!(r.resource_type, ResourceType::Primary);
195 assert_eq!(r.capacity, 1);
196 assert!((r.efficiency - 1.2).abs() < 1e-10);
197 assert!(r.has_skill("milling"));
198 assert!(!r.has_skill("welding"));
199 assert!((r.skill_level("milling") - 0.9).abs() < 1e-10);
200 assert!((r.skill_level("unknown") - 0.0).abs() < 1e-10);
201 assert_eq!(r.cost_per_hour, Some(50.0));
202 }
203
204 #[test]
205 fn test_resource_types() {
206 let m = Resource::primary("M1");
207 assert_eq!(m.resource_type, ResourceType::Primary);
208
209 let w = Resource::human("W1");
210 assert_eq!(w.resource_type, ResourceType::Human);
211
212 let t = Resource::secondary("T1");
213 assert_eq!(t.resource_type, ResourceType::Secondary);
214 }
215
216 #[test]
217 fn test_resource_availability_no_calendar() {
218 let r = Resource::primary("M1");
219 assert!(r.is_available_at(0));
220 assert!(r.is_available_at(1_000_000));
221 }
222
223 #[test]
224 fn test_skill_clamping() {
225 let r = Resource::primary("M1")
226 .with_skill("over", 1.5)
227 .with_skill("under", -0.5);
228
229 assert!((r.skill_level("over") - 1.0).abs() < 1e-10);
230 assert!((r.skill_level("under") - 0.0).abs() < 1e-10);
231 }
232}