solverforge_core/domain/
annotations.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(tag = "type")]
5pub enum PlanningAnnotation {
6 PlanningId,
7 PlanningEntity,
8 PlanningSolution,
9 PlanningVariable {
10 #[serde(default)]
11 value_range_provider_refs: Vec<String>,
12 #[serde(default)]
13 allows_unassigned: bool,
14 },
15 PlanningListVariable {
16 #[serde(default)]
17 value_range_provider_refs: Vec<String>,
18 },
19 PlanningScore {
20 #[serde(default)]
21 bendable_hard_levels: Option<usize>,
22 #[serde(default)]
23 bendable_soft_levels: Option<usize>,
24 },
25 ValueRangeProvider {
26 #[serde(default)]
27 id: Option<String>,
28 },
29 ProblemFactProperty,
30 ProblemFactCollectionProperty,
31 PlanningEntityProperty,
32 PlanningEntityCollectionProperty,
33 PlanningPin,
34 InverseRelationShadowVariable {
35 source_variable_name: String,
36 },
37}
38
39impl PlanningAnnotation {
40 pub fn planning_variable(value_range_provider_refs: Vec<String>) -> Self {
41 PlanningAnnotation::PlanningVariable {
42 value_range_provider_refs,
43 allows_unassigned: false,
44 }
45 }
46
47 pub fn planning_variable_unassigned(value_range_provider_refs: Vec<String>) -> Self {
48 PlanningAnnotation::PlanningVariable {
49 value_range_provider_refs,
50 allows_unassigned: true,
51 }
52 }
53
54 pub fn planning_list_variable(value_range_provider_refs: Vec<String>) -> Self {
55 PlanningAnnotation::PlanningListVariable {
56 value_range_provider_refs,
57 }
58 }
59
60 pub fn planning_score() -> Self {
61 PlanningAnnotation::PlanningScore {
62 bendable_hard_levels: None,
63 bendable_soft_levels: None,
64 }
65 }
66
67 pub fn planning_score_bendable(hard_levels: usize, soft_levels: usize) -> Self {
68 PlanningAnnotation::PlanningScore {
69 bendable_hard_levels: Some(hard_levels),
70 bendable_soft_levels: Some(soft_levels),
71 }
72 }
73
74 pub fn value_range_provider(id: impl Into<String>) -> Self {
75 PlanningAnnotation::ValueRangeProvider {
76 id: Some(id.into()),
77 }
78 }
79
80 pub fn inverse_relation_shadow(source_variable_name: impl Into<String>) -> Self {
81 PlanningAnnotation::InverseRelationShadowVariable {
82 source_variable_name: source_variable_name.into(),
83 }
84 }
85
86 pub fn is_planning_variable(&self) -> bool {
87 matches!(self, PlanningAnnotation::PlanningVariable { .. })
88 }
89
90 pub fn is_planning_list_variable(&self) -> bool {
91 matches!(self, PlanningAnnotation::PlanningListVariable { .. })
92 }
93
94 pub fn is_any_variable(&self) -> bool {
95 self.is_planning_variable() || self.is_planning_list_variable()
96 }
97
98 pub fn is_shadow_variable(&self) -> bool {
99 matches!(
100 self,
101 PlanningAnnotation::InverseRelationShadowVariable { .. }
102 )
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_planning_id() {
112 let ann = PlanningAnnotation::PlanningId;
113 assert_eq!(ann, PlanningAnnotation::PlanningId);
114 }
115
116 #[test]
117 fn test_planning_variable() {
118 let ann = PlanningAnnotation::planning_variable(vec!["rooms".to_string()]);
119 match ann {
120 PlanningAnnotation::PlanningVariable {
121 value_range_provider_refs,
122 allows_unassigned,
123 } => {
124 assert_eq!(value_range_provider_refs, vec!["rooms"]);
125 assert!(!allows_unassigned);
126 }
127 _ => panic!("Expected PlanningVariable"),
128 }
129 }
130
131 #[test]
132 fn test_planning_variable_unassigned() {
133 let ann = PlanningAnnotation::planning_variable_unassigned(vec!["slots".to_string()]);
134 match ann {
135 PlanningAnnotation::PlanningVariable {
136 value_range_provider_refs,
137 allows_unassigned,
138 } => {
139 assert_eq!(value_range_provider_refs, vec!["slots"]);
140 assert!(allows_unassigned);
141 }
142 _ => panic!("Expected PlanningVariable"),
143 }
144 }
145
146 #[test]
147 fn test_planning_list_variable() {
148 let ann = PlanningAnnotation::planning_list_variable(vec!["tasks".to_string()]);
149 match ann {
150 PlanningAnnotation::PlanningListVariable {
151 value_range_provider_refs,
152 } => {
153 assert_eq!(value_range_provider_refs, vec!["tasks"]);
154 }
155 _ => panic!("Expected PlanningListVariable"),
156 }
157 }
158
159 #[test]
160 fn test_planning_score() {
161 let ann = PlanningAnnotation::planning_score();
162 match ann {
163 PlanningAnnotation::PlanningScore {
164 bendable_hard_levels,
165 bendable_soft_levels,
166 } => {
167 assert!(bendable_hard_levels.is_none());
168 assert!(bendable_soft_levels.is_none());
169 }
170 _ => panic!("Expected PlanningScore"),
171 }
172 }
173
174 #[test]
175 fn test_planning_score_bendable() {
176 let ann = PlanningAnnotation::planning_score_bendable(2, 3);
177 match ann {
178 PlanningAnnotation::PlanningScore {
179 bendable_hard_levels,
180 bendable_soft_levels,
181 } => {
182 assert_eq!(bendable_hard_levels, Some(2));
183 assert_eq!(bendable_soft_levels, Some(3));
184 }
185 _ => panic!("Expected PlanningScore"),
186 }
187 }
188
189 #[test]
190 fn test_value_range_provider() {
191 let ann = PlanningAnnotation::value_range_provider("timeslots");
192 match ann {
193 PlanningAnnotation::ValueRangeProvider { id } => {
194 assert_eq!(id, Some("timeslots".to_string()));
195 }
196 _ => panic!("Expected ValueRangeProvider"),
197 }
198 }
199
200 #[test]
201 fn test_inverse_relation_shadow() {
202 let ann = PlanningAnnotation::inverse_relation_shadow("visits");
203 match ann {
204 PlanningAnnotation::InverseRelationShadowVariable {
205 source_variable_name,
206 } => {
207 assert_eq!(source_variable_name, "visits");
208 }
209 _ => panic!("Expected InverseRelationShadowVariable"),
210 }
211 }
212
213 #[test]
214 fn test_is_planning_variable() {
215 let var = PlanningAnnotation::planning_variable(vec![]);
216 assert!(var.is_planning_variable());
217 assert!(var.is_any_variable());
218 assert!(!var.is_planning_list_variable());
219 assert!(!var.is_shadow_variable());
220 }
221
222 #[test]
223 fn test_is_planning_list_variable() {
224 let var = PlanningAnnotation::planning_list_variable(vec![]);
225 assert!(!var.is_planning_variable());
226 assert!(var.is_any_variable());
227 assert!(var.is_planning_list_variable());
228 assert!(!var.is_shadow_variable());
229 }
230
231 #[test]
232 fn test_is_shadow_variable() {
233 let shadow = PlanningAnnotation::inverse_relation_shadow("test");
234 assert!(shadow.is_shadow_variable());
235 assert!(!shadow.is_any_variable());
236 }
237
238 #[test]
239 fn test_json_serialization_planning_id() {
240 let ann = PlanningAnnotation::PlanningId;
241 let json = serde_json::to_string(&ann).unwrap();
242 assert!(json.contains("\"type\":\"PlanningId\""));
243
244 let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
245 assert_eq!(parsed, ann);
246 }
247
248 #[test]
249 fn test_json_serialization_planning_variable() {
250 let ann =
251 PlanningAnnotation::planning_variable(vec!["rooms".to_string(), "slots".to_string()]);
252 let json = serde_json::to_string(&ann).unwrap();
253 assert!(json.contains("\"type\":\"PlanningVariable\""));
254 assert!(json.contains("\"value_range_provider_refs\""));
255
256 let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
257 assert_eq!(parsed, ann);
258 }
259
260 #[test]
261 fn test_json_serialization_planning_score_bendable() {
262 let ann = PlanningAnnotation::planning_score_bendable(2, 3);
263 let json = serde_json::to_string(&ann).unwrap();
264 assert!(json.contains("\"bendable_hard_levels\":2"));
265 assert!(json.contains("\"bendable_soft_levels\":3"));
266
267 let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
268 assert_eq!(parsed, ann);
269 }
270
271 #[test]
272 fn test_json_deserialization_defaults() {
273 let json = r#"{"type":"PlanningVariable"}"#;
274 let parsed: PlanningAnnotation = serde_json::from_str(json).unwrap();
275 match parsed {
276 PlanningAnnotation::PlanningVariable {
277 value_range_provider_refs,
278 allows_unassigned,
279 } => {
280 assert!(value_range_provider_refs.is_empty());
281 assert!(!allows_unassigned);
282 }
283 _ => panic!("Expected PlanningVariable"),
284 }
285 }
286
287 #[test]
288 fn test_simple_annotations() {
289 let annotations = vec![
290 PlanningAnnotation::PlanningEntity,
291 PlanningAnnotation::PlanningSolution,
292 PlanningAnnotation::ProblemFactProperty,
293 PlanningAnnotation::ProblemFactCollectionProperty,
294 PlanningAnnotation::PlanningEntityProperty,
295 PlanningAnnotation::PlanningEntityCollectionProperty,
296 PlanningAnnotation::PlanningPin,
297 ];
298
299 for ann in annotations {
300 let json = serde_json::to_string(&ann).unwrap();
301 let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
302 assert_eq!(parsed, ann);
303 }
304 }
305}