nu_analytics/core/models/
plan.rs

1//! Plan model
2
3use serde::{Deserialize, Serialize};
4
5/// Represents a curriculum plan (graduation plan)
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct Plan {
8    /// Plan name (e.g., "Standard CS Track", "Honors Track")
9    pub name: String,
10
11    /// Required courses - stored as course keys (e.g., "CS 2510")
12    pub courses: Vec<String>,
13
14    /// Associated degree identifier (e.g., "BS Computer Science")
15    pub degree_id: String,
16
17    /// Institution name (optional, defaults to parent school)
18    pub institution: Option<String>,
19}
20
21impl Plan {
22    /// Create a new plan
23    ///
24    /// # Arguments
25    /// * `name` - Plan name
26    /// * `degree_id` - Identifier for the associated degree
27    #[must_use]
28    pub const fn new(name: String, degree_id: String) -> Self {
29        Self {
30            name,
31            courses: Vec::new(),
32            degree_id,
33            institution: None,
34        }
35    }
36
37    /// Add a course to the plan
38    ///
39    /// # Arguments
40    /// * `course_key` - Course key in format "PREFIX NUMBER"
41    pub fn add_course(&mut self, course_key: String) {
42        if !self.courses.contains(&course_key) {
43            self.courses.push(course_key);
44        }
45    }
46
47    /// Remove a course from the plan
48    ///
49    /// # Arguments
50    /// * `course_key` - Course key to remove
51    ///
52    /// # Returns
53    /// `true` if the course was removed, `false` if it wasn't in the plan
54    pub fn remove_course(&mut self, course_key: &str) -> bool {
55        if let Some(pos) = self.courses.iter().position(|c| c == course_key) {
56            self.courses.remove(pos);
57            true
58        } else {
59            false
60        }
61    }
62
63    /// Set the institution name (for transfer plans)
64    pub fn set_institution(&mut self, institution: String) {
65        self.institution = Some(institution);
66    }
67
68    /// Get total number of courses in the plan
69    #[must_use]
70    pub const fn course_count(&self) -> usize {
71        self.courses.len()
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_plan_creation() {
81        let plan = Plan::new(
82            "Standard Track".to_string(),
83            "BS Computer Science".to_string(),
84        );
85
86        assert_eq!(plan.name, "Standard Track");
87        assert_eq!(plan.degree_id, "BS Computer Science");
88        assert!(plan.courses.is_empty());
89        assert!(plan.institution.is_none());
90    }
91
92    #[test]
93    fn test_add_course() {
94        let mut plan = Plan::new(
95            "Standard Track".to_string(),
96            "BS Computer Science".to_string(),
97        );
98
99        plan.add_course("CS1800".to_string());
100        plan.add_course("CS2510".to_string());
101
102        assert_eq!(plan.course_count(), 2);
103        assert!(plan.courses.contains(&"CS1800".to_string()));
104        assert!(plan.courses.contains(&"CS2510".to_string()));
105    }
106
107    #[test]
108    fn test_add_duplicate_course() {
109        let mut plan = Plan::new(
110            "Standard Track".to_string(),
111            "BS Computer Science".to_string(),
112        );
113
114        plan.add_course("CS1800".to_string());
115        plan.add_course("CS1800".to_string());
116
117        assert_eq!(plan.course_count(), 1);
118    }
119
120    #[test]
121    fn test_remove_course() {
122        let mut plan = Plan::new(
123            "Standard Track".to_string(),
124            "BS Computer Science".to_string(),
125        );
126
127        plan.add_course("CS1800".to_string());
128        plan.add_course("CS2510".to_string());
129
130        assert!(plan.remove_course("CS1800"));
131        assert_eq!(plan.course_count(), 1);
132        assert!(!plan.courses.contains(&"CS1800".to_string()));
133
134        // Try removing again - should return false
135        assert!(!plan.remove_course("CS1800"));
136    }
137
138    #[test]
139    fn test_set_institution() {
140        let mut plan = Plan::new(
141            "Transfer Plan".to_string(),
142            "BS Computer Science".to_string(),
143        );
144
145        plan.set_institution("Community College".to_string());
146        assert_eq!(plan.institution, Some("Community College".to_string()));
147    }
148
149    #[test]
150    fn test_plan_with_multiple_courses() {
151        let mut plan = Plan::new(
152            "Honors Track".to_string(),
153            "BS Computer Science".to_string(),
154        );
155
156        let courses = vec!["CS1800", "CS2510", "CS3500", "MATH1342"];
157        for course in courses {
158            plan.add_course(course.to_string());
159        }
160
161        assert_eq!(plan.course_count(), 4);
162    }
163}