solverforge_core/domain/
annotations.rs

1//! Planning annotations for domain model fields.
2//!
3//! These annotations match the Timefold solver annotations 1:1 and are serialized
4//! to JSON for the solver service using the exact format Java expects.
5
6use serde::{Deserialize, Serialize};
7
8use crate::wasm::Expression;
9
10/// Helper for skip_serializing_if on boolean fields
11fn is_false(b: &bool) -> bool {
12    !*b
13}
14
15/// Helper for skip_serializing_if on empty Vec fields
16fn is_empty_vec(v: &[String]) -> bool {
17    v.is_empty()
18}
19
20/// Helper for skip_serializing_if on None Option fields
21fn is_none<T>(opt: &Option<T>) -> bool {
22    opt.is_none()
23}
24
25/// Planning annotations for domain fields.
26///
27/// Matches Timefold annotations 1:1:
28/// - PlanningVariable: valueRangeProviderRefs[], allowsUnassigned
29/// - PlanningListVariable: valueRangeProviderRefs[], allowsUnassignedValues
30/// - ValueRangeProvider: id
31/// - PlanningScore: bendableHardLevelsSize, bendableSoftLevelsSize
32/// - InverseRelationShadowVariable: sourceVariableName
33///
34/// Serialization matches the Java solver service expectations exactly:
35/// - Tag: `"annotation"` (not `"type"`)
36/// - Field names use camelCase
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(tag = "annotation")]
39pub enum PlanningAnnotation {
40    /// Marks a field as the unique identifier for a planning entity.
41    PlanningId,
42
43    /// Marks a class as a planning entity that the solver can change.
44    /// Used at class level in DomainClass.
45    PlanningEntity,
46
47    /// Marks a class as the planning solution containing entities and problem facts.
48    /// Used at class level in DomainClass.
49    PlanningSolution,
50
51    /// Marks a field as a planning variable that the solver assigns.
52    /// Matches Timefold's @PlanningVariable annotation.
53    PlanningVariable {
54        /// References to ValueRangeProvider ids that provide values for this variable.
55        /// If empty, auto-detection by type is used.
56        #[serde(
57            default,
58            rename = "valueRangeProviderRefs",
59            skip_serializing_if = "is_empty_vec"
60        )]
61        value_range_provider_refs: Vec<String>,
62
63        /// If true, null is a valid value (variable can be unassigned).
64        #[serde(default, rename = "allowsUnassigned", skip_serializing_if = "is_false")]
65        allows_unassigned: bool,
66    },
67
68    /// Marks a field as a list planning variable for list-based assignment.
69    /// Matches Timefold's @PlanningListVariable annotation.
70    PlanningListVariable {
71        /// References to ValueRangeProvider ids that provide values for this variable.
72        /// If empty, auto-detection by type is used.
73        #[serde(
74            default,
75            rename = "valueRangeProviderRefs",
76            skip_serializing_if = "is_empty_vec"
77        )]
78        value_range_provider_refs: Vec<String>,
79
80        /// If true, elements can remain unassigned (not in any list).
81        #[serde(
82            default,
83            rename = "allowsUnassignedValues",
84            skip_serializing_if = "is_false"
85        )]
86        allows_unassigned_values: bool,
87    },
88
89    /// Marks a field as the score of the solution.
90    /// Matches Timefold's @PlanningScore annotation.
91    PlanningScore {
92        /// Number of hard levels for bendable scores.
93        #[serde(
94            default,
95            rename = "bendable_hard_levels",
96            skip_serializing_if = "is_none"
97        )]
98        bendable_hard_levels: Option<i32>,
99
100        /// Number of soft levels for bendable scores.
101        #[serde(
102            default,
103            rename = "bendable_soft_levels",
104            skip_serializing_if = "is_none"
105        )]
106        bendable_soft_levels: Option<i32>,
107    },
108
109    /// Marks a collection as providing values for planning variables.
110    /// Matches Timefold's @ValueRangeProvider annotation.
111    ValueRangeProvider {
112        /// The id used by PlanningVariable.valueRangeProviderRefs to reference this provider.
113        /// If empty, auto-detection by type is used.
114        #[serde(default, skip_serializing_if = "is_none")]
115        id: Option<String>,
116    },
117
118    /// Marks a field as a problem fact (immutable input data).
119    ProblemFactProperty,
120
121    /// Marks a collection field as containing problem facts.
122    ProblemFactCollectionProperty,
123
124    /// Marks a field as containing a single planning entity.
125    PlanningEntityProperty,
126
127    /// Marks a collection field as containing planning entities.
128    PlanningEntityCollectionProperty,
129
130    /// Marks a field as pinned (solver won't change it).
131    PlanningPin,
132
133    /// Shadow variable that tracks the inverse of a list variable relationship.
134    /// Matches Timefold's @InverseRelationShadowVariable annotation.
135    InverseRelationShadowVariable {
136        /// The name of the source variable on the other side of the relationship.
137        #[serde(rename = "source_variable_name")]
138        source_variable_name: String,
139    },
140
141    /// Shadow variable that tracks the index position within a list variable.
142    /// Matches Timefold's @IndexShadowVariable annotation.
143    IndexShadowVariable {
144        /// The name of the source list variable.
145        #[serde(rename = "source_variable_name")]
146        source_variable_name: String,
147    },
148
149    /// Shadow variable that references the next element in a list variable.
150    /// Matches Timefold's @NextElementShadowVariable annotation.
151    NextElementShadowVariable {
152        /// The name of the source list variable.
153        #[serde(rename = "source_variable_name")]
154        source_variable_name: String,
155    },
156
157    /// Shadow variable that references the previous element in a list variable.
158    /// Matches Timefold's @PreviousElementShadowVariable annotation.
159    PreviousElementShadowVariable {
160        /// The name of the source list variable.
161        #[serde(rename = "source_variable_name")]
162        source_variable_name: String,
163    },
164
165    /// Shadow variable that tracks the anchor (starting entity) in a chained variable.
166    /// Matches Timefold's @AnchorShadowVariable annotation.
167    AnchorShadowVariable {
168        /// The name of the source chained variable.
169        #[serde(rename = "source_variable_name")]
170        source_variable_name: String,
171    },
172
173    /// Shadow variable updated via cascading updates through a list.
174    /// Matches Timefold's @CascadingUpdateShadowVariable annotation.
175    CascadingUpdateShadowVariable {
176        /// The method name to call for computing the value.
177        #[serde(rename = "target_method_name")]
178        target_method_name: String,
179        /// Expression that computes the shadow variable value.
180        /// Param(0) refers to the entity itself (the entity pointer).
181        /// REQUIRED for WASM generation - build fails if None.
182        #[serde(skip_serializing_if = "Option::is_none")]
183        compute_expression: Option<Expression>,
184    },
185
186    /// Shadow variable that piggybacks on another shadow variable's updates.
187    /// Matches Timefold's @PiggybackShadowVariable annotation.
188    PiggybackShadowVariable {
189        /// The name of the shadow variable to piggyback on.
190        #[serde(rename = "shadow_variable_name")]
191        shadow_variable_name: String,
192    },
193
194    /// Comparator for ordering entities by difficulty during solving.
195    /// Matches Timefold's difficultyComparatorClass annotation.
196    DifficultyComparator {
197        /// The function name that compares two entities by difficulty.
198        #[serde(rename = "comparator_function")]
199        comparator_function: String,
200    },
201
202    /// Comparator for ordering values by strength during solving.
203    /// Matches Timefold's strengthComparatorClass annotation.
204    StrengthComparator {
205        /// The function name that compares two values by strength.
206        #[serde(rename = "comparator_function")]
207        comparator_function: String,
208    },
209}
210
211impl PlanningAnnotation {
212    // === PlanningId ===
213
214    /// Creates a PlanningId annotation.
215    pub fn planning_id() -> Self {
216        PlanningAnnotation::PlanningId
217    }
218
219    // === PlanningVariable ===
220
221    /// Creates a PlanningVariable annotation with default settings.
222    pub fn planning_variable(value_range_provider_refs: Vec<String>) -> Self {
223        PlanningAnnotation::PlanningVariable {
224            value_range_provider_refs,
225            allows_unassigned: false,
226        }
227    }
228
229    /// Creates a PlanningVariable annotation that allows unassigned values.
230    pub fn planning_variable_unassigned(value_range_provider_refs: Vec<String>) -> Self {
231        PlanningAnnotation::PlanningVariable {
232            value_range_provider_refs,
233            allows_unassigned: true,
234        }
235    }
236
237    /// Creates a PlanningVariable with full control over all fields.
238    pub fn planning_variable_full(
239        value_range_provider_refs: Vec<String>,
240        allows_unassigned: bool,
241    ) -> Self {
242        PlanningAnnotation::PlanningVariable {
243            value_range_provider_refs,
244            allows_unassigned,
245        }
246    }
247
248    // === PlanningListVariable ===
249
250    /// Creates a PlanningListVariable annotation with default settings.
251    pub fn planning_list_variable(value_range_provider_refs: Vec<String>) -> Self {
252        PlanningAnnotation::PlanningListVariable {
253            value_range_provider_refs,
254            allows_unassigned_values: false,
255        }
256    }
257
258    /// Creates a PlanningListVariable annotation that allows unassigned values.
259    pub fn planning_list_variable_unassigned(value_range_provider_refs: Vec<String>) -> Self {
260        PlanningAnnotation::PlanningListVariable {
261            value_range_provider_refs,
262            allows_unassigned_values: true,
263        }
264    }
265
266    /// Creates a PlanningListVariable with full control over all fields.
267    pub fn planning_list_variable_full(
268        value_range_provider_refs: Vec<String>,
269        allows_unassigned_values: bool,
270    ) -> Self {
271        PlanningAnnotation::PlanningListVariable {
272            value_range_provider_refs,
273            allows_unassigned_values,
274        }
275    }
276
277    // === PlanningScore ===
278
279    /// Creates a PlanningScore annotation for non-bendable scores.
280    pub fn planning_score() -> Self {
281        PlanningAnnotation::PlanningScore {
282            bendable_hard_levels: None,
283            bendable_soft_levels: None,
284        }
285    }
286
287    /// Creates a PlanningScore annotation for bendable scores.
288    pub fn planning_score_bendable(hard_levels: i32, soft_levels: i32) -> Self {
289        PlanningAnnotation::PlanningScore {
290            bendable_hard_levels: Some(hard_levels),
291            bendable_soft_levels: Some(soft_levels),
292        }
293    }
294
295    // === ValueRangeProvider ===
296
297    /// Creates a ValueRangeProvider annotation without an explicit id.
298    pub fn value_range_provider() -> Self {
299        PlanningAnnotation::ValueRangeProvider { id: None }
300    }
301
302    /// Creates a ValueRangeProvider annotation with an explicit id.
303    pub fn value_range_provider_with_id(id: impl Into<String>) -> Self {
304        PlanningAnnotation::ValueRangeProvider {
305            id: Some(id.into()),
306        }
307    }
308
309    // === Shadow Variables ===
310
311    /// Creates an InverseRelationShadowVariable annotation.
312    pub fn inverse_relation_shadow(source_variable_name: impl Into<String>) -> Self {
313        PlanningAnnotation::InverseRelationShadowVariable {
314            source_variable_name: source_variable_name.into(),
315        }
316    }
317
318    /// Creates an IndexShadowVariable annotation.
319    pub fn index_shadow(source_variable_name: impl Into<String>) -> Self {
320        PlanningAnnotation::IndexShadowVariable {
321            source_variable_name: source_variable_name.into(),
322        }
323    }
324
325    /// Creates a NextElementShadowVariable annotation.
326    pub fn next_element_shadow(source_variable_name: impl Into<String>) -> Self {
327        PlanningAnnotation::NextElementShadowVariable {
328            source_variable_name: source_variable_name.into(),
329        }
330    }
331
332    /// Creates a PreviousElementShadowVariable annotation.
333    pub fn previous_element_shadow(source_variable_name: impl Into<String>) -> Self {
334        PlanningAnnotation::PreviousElementShadowVariable {
335            source_variable_name: source_variable_name.into(),
336        }
337    }
338
339    /// Creates an AnchorShadowVariable annotation.
340    pub fn anchor_shadow(source_variable_name: impl Into<String>) -> Self {
341        PlanningAnnotation::AnchorShadowVariable {
342            source_variable_name: source_variable_name.into(),
343        }
344    }
345
346    /// Creates a CascadingUpdateShadowVariable annotation with expression.
347    /// Use this when you have the compute expression ready.
348    pub fn cascading_update_shadow(
349        target_method_name: impl Into<String>,
350        compute_expression: Expression,
351    ) -> Self {
352        PlanningAnnotation::CascadingUpdateShadowVariable {
353            target_method_name: target_method_name.into(),
354            compute_expression: Some(compute_expression),
355        }
356    }
357
358    /// Creates a CascadingUpdateShadowVariable annotation without expression.
359    /// The expression MUST be set before WASM generation or build will fail.
360    /// This is used by the derive macro; call set_cascading_expression on
361    /// the DomainModel to provide the expression before building.
362    pub fn cascading_update_shadow_pending(target_method_name: impl Into<String>) -> Self {
363        PlanningAnnotation::CascadingUpdateShadowVariable {
364            target_method_name: target_method_name.into(),
365            compute_expression: None,
366        }
367    }
368
369    /// Creates a PiggybackShadowVariable annotation.
370    pub fn piggyback_shadow(shadow_variable_name: impl Into<String>) -> Self {
371        PlanningAnnotation::PiggybackShadowVariable {
372            shadow_variable_name: shadow_variable_name.into(),
373        }
374    }
375
376    // === Comparators ===
377
378    /// Creates a DifficultyComparator annotation.
379    pub fn difficulty_comparator(comparator_function: impl Into<String>) -> Self {
380        PlanningAnnotation::DifficultyComparator {
381            comparator_function: comparator_function.into(),
382        }
383    }
384
385    /// Creates a StrengthComparator annotation.
386    pub fn strength_comparator(comparator_function: impl Into<String>) -> Self {
387        PlanningAnnotation::StrengthComparator {
388            comparator_function: comparator_function.into(),
389        }
390    }
391
392    // === Collection Properties ===
393
394    /// Creates a ProblemFactProperty annotation.
395    pub fn problem_fact_property() -> Self {
396        PlanningAnnotation::ProblemFactProperty
397    }
398
399    /// Creates a ProblemFactCollectionProperty annotation.
400    pub fn problem_fact_collection_property() -> Self {
401        PlanningAnnotation::ProblemFactCollectionProperty
402    }
403
404    /// Creates a PlanningEntityProperty annotation.
405    pub fn planning_entity_property() -> Self {
406        PlanningAnnotation::PlanningEntityProperty
407    }
408
409    /// Creates a PlanningEntityCollectionProperty annotation.
410    pub fn planning_entity_collection_property() -> Self {
411        PlanningAnnotation::PlanningEntityCollectionProperty
412    }
413
414    // === Query Methods ===
415
416    /// Returns true if this is a PlanningVariable annotation.
417    pub fn is_planning_variable(&self) -> bool {
418        matches!(self, PlanningAnnotation::PlanningVariable { .. })
419    }
420
421    /// Returns true if this is a PlanningListVariable annotation.
422    pub fn is_planning_list_variable(&self) -> bool {
423        matches!(self, PlanningAnnotation::PlanningListVariable { .. })
424    }
425
426    /// Returns true if this is any kind of planning variable (regular or list).
427    pub fn is_any_variable(&self) -> bool {
428        self.is_planning_variable() || self.is_planning_list_variable()
429    }
430
431    /// Returns true if this is a shadow variable annotation.
432    pub fn is_shadow_variable(&self) -> bool {
433        matches!(
434            self,
435            PlanningAnnotation::InverseRelationShadowVariable { .. }
436                | PlanningAnnotation::IndexShadowVariable { .. }
437                | PlanningAnnotation::NextElementShadowVariable { .. }
438                | PlanningAnnotation::PreviousElementShadowVariable { .. }
439                | PlanningAnnotation::AnchorShadowVariable { .. }
440                | PlanningAnnotation::CascadingUpdateShadowVariable { .. }
441                | PlanningAnnotation::PiggybackShadowVariable { .. }
442        )
443    }
444
445    /// Returns the value_range_provider_refs if this is a planning variable.
446    pub fn value_range_provider_refs(&self) -> Option<&Vec<String>> {
447        match self {
448            PlanningAnnotation::PlanningVariable {
449                value_range_provider_refs,
450                ..
451            } => Some(value_range_provider_refs),
452            PlanningAnnotation::PlanningListVariable {
453                value_range_provider_refs,
454                ..
455            } => Some(value_range_provider_refs),
456            _ => None,
457        }
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    #[test]
466    fn test_planning_id() {
467        let ann = PlanningAnnotation::PlanningId;
468        assert_eq!(ann, PlanningAnnotation::PlanningId);
469    }
470
471    #[test]
472    fn test_planning_variable() {
473        let ann = PlanningAnnotation::planning_variable(vec!["rooms".to_string()]);
474        match ann {
475            PlanningAnnotation::PlanningVariable {
476                value_range_provider_refs,
477                allows_unassigned,
478            } => {
479                assert_eq!(value_range_provider_refs, vec!["rooms"]);
480                assert!(!allows_unassigned);
481            }
482            _ => panic!("Expected PlanningVariable"),
483        }
484    }
485
486    #[test]
487    fn test_planning_variable_unassigned() {
488        let ann = PlanningAnnotation::planning_variable_unassigned(vec!["rooms".to_string()]);
489        match ann {
490            PlanningAnnotation::PlanningVariable {
491                value_range_provider_refs,
492                allows_unassigned,
493            } => {
494                assert_eq!(value_range_provider_refs, vec!["rooms"]);
495                assert!(allows_unassigned);
496            }
497            _ => panic!("Expected PlanningVariable"),
498        }
499    }
500
501    #[test]
502    fn test_planning_list_variable() {
503        let ann = PlanningAnnotation::planning_list_variable(vec!["visits".to_string()]);
504        match ann {
505            PlanningAnnotation::PlanningListVariable {
506                value_range_provider_refs,
507                allows_unassigned_values,
508            } => {
509                assert_eq!(value_range_provider_refs, vec!["visits"]);
510                assert!(!allows_unassigned_values);
511            }
512            _ => panic!("Expected PlanningListVariable"),
513        }
514    }
515
516    #[test]
517    fn test_planning_list_variable_unassigned() {
518        let ann = PlanningAnnotation::planning_list_variable_unassigned(vec!["visits".to_string()]);
519        match ann {
520            PlanningAnnotation::PlanningListVariable {
521                value_range_provider_refs,
522                allows_unassigned_values,
523            } => {
524                assert_eq!(value_range_provider_refs, vec!["visits"]);
525                assert!(allows_unassigned_values);
526            }
527            _ => panic!("Expected PlanningListVariable"),
528        }
529    }
530
531    #[test]
532    fn test_planning_score() {
533        let ann = PlanningAnnotation::planning_score();
534        match ann {
535            PlanningAnnotation::PlanningScore {
536                bendable_hard_levels,
537                bendable_soft_levels,
538            } => {
539                assert!(bendable_hard_levels.is_none());
540                assert!(bendable_soft_levels.is_none());
541            }
542            _ => panic!("Expected PlanningScore"),
543        }
544    }
545
546    #[test]
547    fn test_planning_score_bendable() {
548        let ann = PlanningAnnotation::planning_score_bendable(2, 3);
549        match ann {
550            PlanningAnnotation::PlanningScore {
551                bendable_hard_levels,
552                bendable_soft_levels,
553            } => {
554                assert_eq!(bendable_hard_levels, Some(2));
555                assert_eq!(bendable_soft_levels, Some(3));
556            }
557            _ => panic!("Expected PlanningScore"),
558        }
559    }
560
561    #[test]
562    fn test_value_range_provider() {
563        let ann = PlanningAnnotation::value_range_provider();
564        match ann {
565            PlanningAnnotation::ValueRangeProvider { id } => {
566                assert!(id.is_none());
567            }
568            _ => panic!("Expected ValueRangeProvider"),
569        }
570    }
571
572    #[test]
573    fn test_value_range_provider_with_id() {
574        let ann = PlanningAnnotation::value_range_provider_with_id("rooms");
575        match ann {
576            PlanningAnnotation::ValueRangeProvider { id } => {
577                assert_eq!(id, Some("rooms".to_string()));
578            }
579            _ => panic!("Expected ValueRangeProvider"),
580        }
581    }
582
583    #[test]
584    fn test_inverse_relation_shadow() {
585        let ann = PlanningAnnotation::inverse_relation_shadow("visits");
586        match ann {
587            PlanningAnnotation::InverseRelationShadowVariable {
588                source_variable_name,
589            } => {
590                assert_eq!(source_variable_name, "visits");
591            }
592            _ => panic!("Expected InverseRelationShadowVariable"),
593        }
594    }
595
596    #[test]
597    fn test_is_planning_variable() {
598        let var = PlanningAnnotation::planning_variable(vec![]);
599        assert!(var.is_planning_variable());
600        assert!(var.is_any_variable());
601        assert!(!var.is_planning_list_variable());
602        assert!(!var.is_shadow_variable());
603    }
604
605    #[test]
606    fn test_is_planning_list_variable() {
607        let var = PlanningAnnotation::planning_list_variable(vec![]);
608        assert!(!var.is_planning_variable());
609        assert!(var.is_any_variable());
610        assert!(var.is_planning_list_variable());
611        assert!(!var.is_shadow_variable());
612    }
613
614    #[test]
615    fn test_is_shadow_variable() {
616        let shadow = PlanningAnnotation::inverse_relation_shadow("test");
617        assert!(shadow.is_shadow_variable());
618        assert!(!shadow.is_any_variable());
619    }
620
621    #[test]
622    fn test_value_range_provider_refs_getter() {
623        let var = PlanningAnnotation::planning_variable(vec!["rooms".to_string()]);
624        assert_eq!(
625            var.value_range_provider_refs(),
626            Some(&vec!["rooms".to_string()])
627        );
628
629        let list_var = PlanningAnnotation::planning_list_variable(vec!["visits".to_string()]);
630        assert_eq!(
631            list_var.value_range_provider_refs(),
632            Some(&vec!["visits".to_string()])
633        );
634
635        let score = PlanningAnnotation::planning_score();
636        assert!(score.value_range_provider_refs().is_none());
637    }
638
639    // JSON serialization tests - must match Java solver service expectations
640
641    #[test]
642    fn test_json_planning_id() {
643        let ann = PlanningAnnotation::PlanningId;
644        let json = serde_json::to_string(&ann).unwrap();
645        assert_eq!(json, r#"{"annotation":"PlanningId"}"#);
646
647        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
648        assert_eq!(parsed, ann);
649    }
650
651    #[test]
652    fn test_json_planning_variable_default() {
653        let ann = PlanningAnnotation::planning_variable(vec![]);
654        let json = serde_json::to_string(&ann).unwrap();
655        // Empty refs and allows_unassigned=false should both be omitted
656        assert_eq!(json, r#"{"annotation":"PlanningVariable"}"#);
657
658        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
659        assert_eq!(parsed, ann);
660    }
661
662    #[test]
663    fn test_json_planning_variable_with_refs() {
664        let ann = PlanningAnnotation::planning_variable(vec!["rooms".to_string()]);
665        let json = serde_json::to_string(&ann).unwrap();
666        assert_eq!(
667            json,
668            r#"{"annotation":"PlanningVariable","valueRangeProviderRefs":["rooms"]}"#
669        );
670
671        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
672        assert_eq!(parsed, ann);
673    }
674
675    #[test]
676    fn test_json_planning_variable_allows_unassigned() {
677        let ann = PlanningAnnotation::planning_variable_unassigned(vec![]);
678        let json = serde_json::to_string(&ann).unwrap();
679        assert_eq!(
680            json,
681            r#"{"annotation":"PlanningVariable","allowsUnassigned":true}"#
682        );
683
684        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
685        assert_eq!(parsed, ann);
686    }
687
688    #[test]
689    fn test_json_planning_variable_full() {
690        let ann = PlanningAnnotation::planning_variable_full(
691            vec!["rooms".to_string(), "timeslots".to_string()],
692            true,
693        );
694        let json = serde_json::to_string(&ann).unwrap();
695        assert!(json.contains(r#""valueRangeProviderRefs":["rooms","timeslots"]"#));
696        assert!(json.contains(r#""allowsUnassigned":true"#));
697
698        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
699        assert_eq!(parsed, ann);
700    }
701
702    #[test]
703    fn test_json_planning_list_variable_default() {
704        let ann = PlanningAnnotation::planning_list_variable(vec![]);
705        let json = serde_json::to_string(&ann).unwrap();
706        assert_eq!(json, r#"{"annotation":"PlanningListVariable"}"#);
707
708        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
709        assert_eq!(parsed, ann);
710    }
711
712    #[test]
713    fn test_json_planning_list_variable_with_refs() {
714        let ann = PlanningAnnotation::planning_list_variable(vec!["visits".to_string()]);
715        let json = serde_json::to_string(&ann).unwrap();
716        assert_eq!(
717            json,
718            r#"{"annotation":"PlanningListVariable","valueRangeProviderRefs":["visits"]}"#
719        );
720
721        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
722        assert_eq!(parsed, ann);
723    }
724
725    #[test]
726    fn test_json_planning_list_variable_allows_unassigned() {
727        let ann = PlanningAnnotation::planning_list_variable_unassigned(vec!["visits".to_string()]);
728        let json = serde_json::to_string(&ann).unwrap();
729        assert!(json.contains(r#""valueRangeProviderRefs":["visits"]"#));
730        assert!(json.contains(r#""allowsUnassignedValues":true"#));
731
732        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
733        assert_eq!(parsed, ann);
734    }
735
736    #[test]
737    fn test_json_planning_score() {
738        let ann = PlanningAnnotation::PlanningScore {
739            bendable_hard_levels: None,
740            bendable_soft_levels: None,
741        };
742        let json = serde_json::to_string(&ann).unwrap();
743        assert_eq!(json, r#"{"annotation":"PlanningScore"}"#);
744
745        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
746        assert_eq!(parsed, ann);
747    }
748
749    #[test]
750    fn test_json_planning_score_bendable() {
751        let ann = PlanningAnnotation::planning_score_bendable(2, 3);
752        let json = serde_json::to_string(&ann).unwrap();
753        assert!(json.contains(r#""bendable_hard_levels":2"#));
754        assert!(json.contains(r#""bendable_soft_levels":3"#));
755
756        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
757        assert_eq!(parsed, ann);
758    }
759
760    #[test]
761    fn test_json_value_range_provider() {
762        let ann = PlanningAnnotation::ValueRangeProvider { id: None };
763        let json = serde_json::to_string(&ann).unwrap();
764        assert_eq!(json, r#"{"annotation":"ValueRangeProvider"}"#);
765
766        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
767        assert_eq!(parsed, ann);
768    }
769
770    #[test]
771    fn test_json_value_range_provider_with_id() {
772        let ann = PlanningAnnotation::value_range_provider_with_id("rooms");
773        let json = serde_json::to_string(&ann).unwrap();
774        assert_eq!(json, r#"{"annotation":"ValueRangeProvider","id":"rooms"}"#);
775
776        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
777        assert_eq!(parsed, ann);
778    }
779
780    #[test]
781    fn test_json_inverse_relation_shadow() {
782        let ann = PlanningAnnotation::inverse_relation_shadow("visits");
783        let json = serde_json::to_string(&ann).unwrap();
784        assert_eq!(
785            json,
786            r#"{"annotation":"InverseRelationShadowVariable","source_variable_name":"visits"}"#
787        );
788
789        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
790        assert_eq!(parsed, ann);
791    }
792
793    #[test]
794    fn test_json_simple_annotations() {
795        let annotations = vec![
796            (
797                PlanningAnnotation::PlanningEntity,
798                r#"{"annotation":"PlanningEntity"}"#,
799            ),
800            (
801                PlanningAnnotation::PlanningSolution,
802                r#"{"annotation":"PlanningSolution"}"#,
803            ),
804            (
805                PlanningAnnotation::ProblemFactProperty,
806                r#"{"annotation":"ProblemFactProperty"}"#,
807            ),
808            (
809                PlanningAnnotation::ProblemFactCollectionProperty,
810                r#"{"annotation":"ProblemFactCollectionProperty"}"#,
811            ),
812            (
813                PlanningAnnotation::PlanningEntityProperty,
814                r#"{"annotation":"PlanningEntityProperty"}"#,
815            ),
816            (
817                PlanningAnnotation::PlanningEntityCollectionProperty,
818                r#"{"annotation":"PlanningEntityCollectionProperty"}"#,
819            ),
820            (
821                PlanningAnnotation::PlanningPin,
822                r#"{"annotation":"PlanningPin"}"#,
823            ),
824        ];
825
826        for (ann, expected_json) in annotations {
827            let json = serde_json::to_string(&ann).unwrap();
828            assert_eq!(json, expected_json);
829
830            let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
831            assert_eq!(parsed, ann);
832        }
833    }
834
835    #[test]
836    fn test_json_deserialization_defaults() {
837        // Test that missing optional fields get defaults
838        let json = r#"{"annotation":"PlanningVariable"}"#;
839        let parsed: PlanningAnnotation = serde_json::from_str(json).unwrap();
840        match parsed {
841            PlanningAnnotation::PlanningVariable {
842                value_range_provider_refs,
843                allows_unassigned,
844            } => {
845                assert!(value_range_provider_refs.is_empty());
846                assert!(!allows_unassigned);
847            }
848            _ => panic!("Expected PlanningVariable"),
849        }
850    }
851
852    #[test]
853    fn test_json_deserialization_with_refs() {
854        let json = r#"{"annotation":"PlanningVariable","valueRangeProviderRefs":["a","b"]}"#;
855        let parsed: PlanningAnnotation = serde_json::from_str(json).unwrap();
856        match parsed {
857            PlanningAnnotation::PlanningVariable {
858                value_range_provider_refs,
859                allows_unassigned,
860            } => {
861                assert_eq!(
862                    value_range_provider_refs,
863                    vec!["a".to_string(), "b".to_string()]
864                );
865                assert!(!allows_unassigned);
866            }
867            _ => panic!("Expected PlanningVariable"),
868        }
869    }
870
871    // === Shadow Variable Tests ===
872
873    #[test]
874    fn test_index_shadow() {
875        let ann = PlanningAnnotation::index_shadow("visits");
876        match &ann {
877            PlanningAnnotation::IndexShadowVariable {
878                source_variable_name,
879            } => {
880                assert_eq!(source_variable_name, "visits");
881            }
882            _ => panic!("Expected IndexShadowVariable"),
883        }
884        assert!(ann.is_shadow_variable());
885    }
886
887    #[test]
888    fn test_next_element_shadow() {
889        let ann = PlanningAnnotation::next_element_shadow("visits");
890        match &ann {
891            PlanningAnnotation::NextElementShadowVariable {
892                source_variable_name,
893            } => {
894                assert_eq!(source_variable_name, "visits");
895            }
896            _ => panic!("Expected NextElementShadowVariable"),
897        }
898        assert!(ann.is_shadow_variable());
899    }
900
901    #[test]
902    fn test_previous_element_shadow() {
903        let ann = PlanningAnnotation::previous_element_shadow("visits");
904        match &ann {
905            PlanningAnnotation::PreviousElementShadowVariable {
906                source_variable_name,
907            } => {
908                assert_eq!(source_variable_name, "visits");
909            }
910            _ => panic!("Expected PreviousElementShadowVariable"),
911        }
912        assert!(ann.is_shadow_variable());
913    }
914
915    #[test]
916    fn test_anchor_shadow() {
917        let ann = PlanningAnnotation::anchor_shadow("chain");
918        match &ann {
919            PlanningAnnotation::AnchorShadowVariable {
920                source_variable_name,
921            } => {
922                assert_eq!(source_variable_name, "chain");
923            }
924            _ => panic!("Expected AnchorShadowVariable"),
925        }
926        assert!(ann.is_shadow_variable());
927    }
928
929    #[test]
930    fn test_cascading_update_shadow() {
931        use crate::wasm::Expression;
932        let expr = Expression::Int64Literal { value: 0 };
933        let ann = PlanningAnnotation::cascading_update_shadow("updateArrivalTime", expr);
934        match &ann {
935            PlanningAnnotation::CascadingUpdateShadowVariable {
936                target_method_name,
937                compute_expression: _,
938            } => {
939                assert_eq!(target_method_name, "updateArrivalTime");
940            }
941            _ => panic!("Expected CascadingUpdateShadowVariable"),
942        }
943        assert!(ann.is_shadow_variable());
944    }
945
946    #[test]
947    fn test_piggyback_shadow() {
948        let ann = PlanningAnnotation::piggyback_shadow("arrivalTime");
949        match &ann {
950            PlanningAnnotation::PiggybackShadowVariable {
951                shadow_variable_name,
952            } => {
953                assert_eq!(shadow_variable_name, "arrivalTime");
954            }
955            _ => panic!("Expected PiggybackShadowVariable"),
956        }
957        assert!(ann.is_shadow_variable());
958    }
959
960    #[test]
961    fn test_json_index_shadow() {
962        let ann = PlanningAnnotation::index_shadow("visits");
963        let json = serde_json::to_string(&ann).unwrap();
964        assert_eq!(
965            json,
966            r#"{"annotation":"IndexShadowVariable","source_variable_name":"visits"}"#
967        );
968        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
969        assert_eq!(parsed, ann);
970    }
971
972    #[test]
973    fn test_json_next_element_shadow() {
974        let ann = PlanningAnnotation::next_element_shadow("visits");
975        let json = serde_json::to_string(&ann).unwrap();
976        assert_eq!(
977            json,
978            r#"{"annotation":"NextElementShadowVariable","source_variable_name":"visits"}"#
979        );
980        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
981        assert_eq!(parsed, ann);
982    }
983
984    #[test]
985    fn test_json_previous_element_shadow() {
986        let ann = PlanningAnnotation::previous_element_shadow("visits");
987        let json = serde_json::to_string(&ann).unwrap();
988        assert_eq!(
989            json,
990            r#"{"annotation":"PreviousElementShadowVariable","source_variable_name":"visits"}"#
991        );
992        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
993        assert_eq!(parsed, ann);
994    }
995
996    #[test]
997    fn test_json_anchor_shadow() {
998        let ann = PlanningAnnotation::anchor_shadow("chain");
999        let json = serde_json::to_string(&ann).unwrap();
1000        assert_eq!(
1001            json,
1002            r#"{"annotation":"AnchorShadowVariable","source_variable_name":"chain"}"#
1003        );
1004        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
1005        assert_eq!(parsed, ann);
1006    }
1007
1008    #[test]
1009    fn test_json_cascading_update_shadow() {
1010        use crate::wasm::Expression;
1011        let expr = Expression::Int64Literal { value: 42 };
1012        let ann = PlanningAnnotation::cascading_update_shadow("updateArrivalTime", expr);
1013        let json = serde_json::to_string(&ann).unwrap();
1014        // Expression is serialized with the annotation
1015        assert!(json.contains(r#""annotation":"CascadingUpdateShadowVariable""#));
1016        assert!(json.contains(r#""target_method_name":"updateArrivalTime""#));
1017        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
1018        assert_eq!(parsed, ann);
1019    }
1020
1021    #[test]
1022    fn test_json_piggyback_shadow() {
1023        let ann = PlanningAnnotation::piggyback_shadow("arrivalTime");
1024        let json = serde_json::to_string(&ann).unwrap();
1025        assert_eq!(
1026            json,
1027            r#"{"annotation":"PiggybackShadowVariable","shadow_variable_name":"arrivalTime"}"#
1028        );
1029        let parsed: PlanningAnnotation = serde_json::from_str(&json).unwrap();
1030        assert_eq!(parsed, ann);
1031    }
1032
1033    #[test]
1034    fn test_all_shadow_variables_are_shadows() {
1035        use crate::wasm::Expression;
1036        let shadows = vec![
1037            PlanningAnnotation::inverse_relation_shadow("test"),
1038            PlanningAnnotation::index_shadow("test"),
1039            PlanningAnnotation::next_element_shadow("test"),
1040            PlanningAnnotation::previous_element_shadow("test"),
1041            PlanningAnnotation::anchor_shadow("test"),
1042            PlanningAnnotation::cascading_update_shadow(
1043                "test",
1044                Expression::Int64Literal { value: 0 },
1045            ),
1046            PlanningAnnotation::piggyback_shadow("test"),
1047        ];
1048        for shadow in shadows {
1049            assert!(shadow.is_shadow_variable(), "{:?} should be shadow", shadow);
1050            assert!(
1051                !shadow.is_any_variable(),
1052                "{:?} should not be any_variable",
1053                shadow
1054            );
1055        }
1056    }
1057}