From 9617d0025dd5330ca76065e766a4534987f11e09 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 23 Jan 2024 20:48:03 +0100 Subject: [PATCH 01/31] add test filler-goal --- tests/jsons/stable/filler-goal/expected.json | 50 +++++++++++++++++ tests/jsons/stable/filler-goal/input.json | 27 +++++++++ tests/jsons/stable/filler-goal/observed.json | 58 ++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 tests/jsons/stable/filler-goal/expected.json create mode 100644 tests/jsons/stable/filler-goal/input.json create mode 100644 tests/jsons/stable/filler-goal/observed.json diff --git a/tests/jsons/stable/filler-goal/expected.json b/tests/jsons/stable/filler-goal/expected.json new file mode 100644 index 00000000..9b3c69c0 --- /dev/null +++ b/tests/jsons/stable/filler-goal/expected.json @@ -0,0 +1,50 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T10:00:00" + }, + { + "taskid": 1, + "goalid": "2", + "title": "Buy stuff", + "duration": 1, + "start": "2022-01-01T10:00:00", + "deadline": "2022-01-01T11:00:00" + }, + { + "taskid": 2, + "goalid": "3", + "title": "Invite friends", + "duration": 1, + "start": "2022-01-01T11:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 3, + "goalid": "1", + "title": "Plan a party", + "duration": 1, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-01T14:00:00" + }, + { + "taskid": 4, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2022-01-01T14:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable/filler-goal/input.json b/tests/jsons/stable/filler-goal/input.json new file mode 100644 index 00000000..5ef06346 --- /dev/null +++ b/tests/jsons/stable/filler-goal/input.json @@ -0,0 +1,27 @@ + { + "startDate": "2022-01-01T00:00:00", + "endDate": "2022-01-02T00:00:00", + "goals": [ + { + "id": "1", + "title": "Plan a party", + "minDuration": 4, + "start": "2022-01-01T10:00:00", + "deadline": "2022-01-01T18:00:00", + "children": [ + "2", + "3" + ] + }, + { + "id": "2", + "title": "Buy stuff", + "minDuration": 1 + }, + { + "id": "3", + "title": "Invite friends", + "minDuration": 1 + } + ] + } \ No newline at end of file diff --git a/tests/jsons/stable/filler-goal/observed.json b/tests/jsons/stable/filler-goal/observed.json new file mode 100644 index 00000000..5f66bc55 --- /dev/null +++ b/tests/jsons/stable/filler-goal/observed.json @@ -0,0 +1,58 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T10:00:00" + }, + { + "taskid": 1, + "goalid": "2", + "title": "dentist", + "duration": 1, + "start": "2022-01-01T10:00:00", + "deadline": "2022-01-01T11:00:00" + }, + { + "taskid": 2, + "goalid": "1", + "title": "shopping", + "duration": 1, + "start": "2022-01-01T11:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 1, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-01T13:00:00" + }, + { + "taskid": 4, + "goalid": "3", + "title": "exercise", + "duration": 1, + "start": "2022-01-01T13:00:00", + "deadline": "2022-01-01T14:00:00" + }, + { + "taskid": 5, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2022-01-01T14:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file From c7078d235909fe7fdbd1dff8b267ac10ae776723 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Thu, 29 Feb 2024 17:22:31 +0200 Subject: [PATCH 02/31] feat: - developed goal.get_parent_goal - passing parent_goal to functiontionality responsible for adjusing start and deadlines for activities --- src/models/activity.rs | 10 ++++++--- src/models/calendar.rs | 1 + src/models/goal.rs | 33 +++++++++++++++++++++++++++++- src/services/activity_generator.rs | 9 ++++++-- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index a6af1186..c76846d7 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -189,6 +189,7 @@ impl Activity { pub(crate) fn get_activities_from_budget_goal( goal: &Goal, calendar: &Calendar, + parent_goal: Option, ) -> Vec { if goal.children.is_some() || goal.filters.as_ref().is_none() { return vec![]; @@ -196,7 +197,8 @@ impl Activity { if goal.budget_config.as_ref().unwrap().min_per_day == 0 { return vec![]; } - let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar); + let (adjusted_goal_start, adjusted_goal_deadline) = + goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); let filter_option = goal.filters.clone().unwrap(); @@ -249,11 +251,13 @@ impl Activity { pub(crate) fn get_activities_from_simple_goal( goal: &Goal, calendar: &Calendar, + parent_goal: Option, ) -> Vec { - if goal.children.is_some() || goal.filters.as_ref().is_some() { + if goal.filters.as_ref().is_some() { return vec![]; } - let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar); + let (adjusted_goal_start, adjusted_goal_deadline) = + goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); let activity_total_duration = goal.min_duration.unwrap(); diff --git a/src/models/calendar.rs b/src/models/calendar.rs index b2619531..96741ff4 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -46,6 +46,7 @@ impl Calendar { for _ in 0..hours.capacity() { hours.push(Rc::new(Hour::Free)); } + println!("DEBUG::hours: {:?}", hours); Self { start_date_time, end_date_time, diff --git a/src/models/goal.rs b/src/models/goal.rs index 2fdd498d..42c82977 100644 --- a/src/models/goal.rs +++ b/src/models/goal.rs @@ -39,7 +39,13 @@ pub struct BudgetConfig { } impl Goal { - pub fn get_adj_start_deadline(&self, calendar: &Calendar) -> (NaiveDateTime, NaiveDateTime) { + pub fn get_adj_start_deadline( + &self, + calendar: &Calendar, + parent_goal: Option, + ) -> (NaiveDateTime, NaiveDateTime) { + + let mut adjusted_goal_start = self.start; if self.start.year() == 1970 { adjusted_goal_start = calendar.start_date_time; @@ -48,6 +54,17 @@ impl Goal { if self.deadline.year() == 1970 { adjusted_goal_deadline = calendar.end_date_time; } + + // Make sure child goal not fall outside of parent goal start and deadline + if let Some(parent_goal) = parent_goal { // means this is a child goal + if adjusted_goal_start < parent_goal.start { + adjusted_goal_start = parent_goal.start; + } + if adjusted_goal_deadline > parent_goal.deadline { + adjusted_goal_deadline = parent_goal.deadline; + } + } + if self.filters.is_none() { return (adjusted_goal_start, adjusted_goal_deadline); } @@ -71,4 +88,18 @@ impl Goal { } (adjusted_goal_start, adjusted_goal_deadline) } + + /// Get parent goal of this goal based in provided list of goals + pub fn get_parent_goal(&self, goals: &Vec) -> Option { + let parent_goal = goals.iter().find(|goal| { + if let Some(childs) = &goal.children { + childs.contains(&self.id) + } + else { + false + } + }); + + parent_goal.cloned() + } } diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 5bf96a46..bbc51d7b 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -4,7 +4,10 @@ pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &Vec) - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { - let mut goal_activities = Activity::get_activities_from_simple_goal(goal, calendar); + let parent_goal = goal.get_parent_goal(&goals); + + let mut goal_activities = + Activity::get_activities_from_simple_goal(goal, calendar, parent_goal); dbg!(&goal_activities); activities.append(&mut goal_activities); } @@ -15,7 +18,9 @@ pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { - let mut goal_activities = Activity::get_activities_from_budget_goal(goal, calendar); + let parent_goal = goal.get_parent_goal(&goals); + let mut goal_activities = + Activity::get_activities_from_budget_goal(goal, calendar, parent_goal); dbg!(&goal_activities); activities.append(&mut goal_activities); } From 7bd42ff358ac7836c045afedd5e778157438d43e Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Thu, 29 Feb 2024 17:23:41 +0200 Subject: [PATCH 03/31] updated observed file --- tests/jsons/stable/filler-goal/observed.json | 26 +++++++------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/jsons/stable/filler-goal/observed.json b/tests/jsons/stable/filler-goal/observed.json index 5f66bc55..21e34fa0 100644 --- a/tests/jsons/stable/filler-goal/observed.json +++ b/tests/jsons/stable/filler-goal/observed.json @@ -14,41 +14,33 @@ { "taskid": 1, "goalid": "2", - "title": "dentist", + "title": "Buy stuff", "duration": 1, "start": "2022-01-01T10:00:00", "deadline": "2022-01-01T11:00:00" }, { "taskid": 2, - "goalid": "1", - "title": "shopping", + "goalid": "3", + "title": "Invite friends", "duration": 1, "start": "2022-01-01T11:00:00", "deadline": "2022-01-01T12:00:00" }, { "taskid": 3, - "goalid": "free", - "title": "free", - "duration": 1, + "goalid": "1", + "title": "Plan a party", + "duration": 4, "start": "2022-01-01T12:00:00", - "deadline": "2022-01-01T13:00:00" + "deadline": "2022-01-01T16:00:00" }, { "taskid": 4, - "goalid": "3", - "title": "exercise", - "duration": 1, - "start": "2022-01-01T13:00:00", - "deadline": "2022-01-01T14:00:00" - }, - { - "taskid": 5, "goalid": "free", "title": "free", - "duration": 10, - "start": "2022-01-01T14:00:00", + "duration": 8, + "start": "2022-01-01T16:00:00", "deadline": "2022-01-02T00:00:00" } ] From b69e2c05248cadce24cae4c138e1339a167f4a35 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Thu, 29 Feb 2024 17:50:07 +0200 Subject: [PATCH 04/31] fix: provide min_duration to 1 if not available in a goal. --- src/models/activity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index c76846d7..5158eaa6 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -260,7 +260,7 @@ impl Activity { goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); - let activity_total_duration = goal.min_duration.unwrap(); + let activity_total_duration = goal.min_duration.unwrap_or(1); let mut min_block_size = activity_total_duration; if activity_total_duration > 8 { min_block_size = 1; From 5970c027d36f62170921cbf924bc7278434b5d5f Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 2 Mar 2024 21:40:56 +0200 Subject: [PATCH 05/31] fix: avoid passing parent_goal to functions which generating activities for budgeted goals --- src/models/activity.rs | 3 +-- src/services/activity_generator.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 5158eaa6..88098a80 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -189,7 +189,6 @@ impl Activity { pub(crate) fn get_activities_from_budget_goal( goal: &Goal, calendar: &Calendar, - parent_goal: Option, ) -> Vec { if goal.children.is_some() || goal.filters.as_ref().is_none() { return vec![]; @@ -198,7 +197,7 @@ impl Activity { return vec![]; } let (adjusted_goal_start, adjusted_goal_deadline) = - goal.get_adj_start_deadline(calendar, parent_goal); + goal.get_adj_start_deadline(calendar, None); let mut activities: Vec = Vec::with_capacity(1); let filter_option = goal.filters.clone().unwrap(); diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index bbc51d7b..991df7e8 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -18,9 +18,8 @@ pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { - let parent_goal = goal.get_parent_goal(&goals); let mut goal_activities = - Activity::get_activities_from_budget_goal(goal, calendar, parent_goal); + Activity::get_activities_from_budget_goal(goal, calendar); dbg!(&goal_activities); activities.append(&mut goal_activities); } From 401ee93aa0c74721351ac206fba892dbd87e2cab Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 2 Mar 2024 22:02:55 +0200 Subject: [PATCH 06/31] fix: corrected duration for activity plan a party --- tests/jsons/stable/filler-goal/expected.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jsons/stable/filler-goal/expected.json b/tests/jsons/stable/filler-goal/expected.json index 9b3c69c0..05042abe 100644 --- a/tests/jsons/stable/filler-goal/expected.json +++ b/tests/jsons/stable/filler-goal/expected.json @@ -31,7 +31,7 @@ "taskid": 3, "goalid": "1", "title": "Plan a party", - "duration": 1, + "duration": 2, "start": "2022-01-01T12:00:00", "deadline": "2022-01-01T14:00:00" }, From 97b5cb1f08d75c7ee42b4c0f53ffb8ac8415a3f2 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Wed, 6 Mar 2024 09:51:07 +0200 Subject: [PATCH 07/31] feature: develop function to adjust activities for related to parent goals --- src/services/activity_generator.rs | 183 ++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 991df7e8..232dcf27 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -5,7 +5,7 @@ pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &Vec) - let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { let parent_goal = goal.get_parent_goal(&goals); - + let mut goal_activities = Activity::get_activities_from_simple_goal(goal, calendar, parent_goal); dbg!(&goal_activities); @@ -18,8 +18,7 @@ pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { - let mut goal_activities = - Activity::get_activities_from_budget_goal(goal, calendar); + let mut goal_activities = Activity::get_activities_from_budget_goal(goal, calendar); dbg!(&goal_activities); activities.append(&mut goal_activities); } @@ -97,3 +96,181 @@ pub fn generate_top_up_week_budget_activities( dbg!(&top_up_activities); top_up_activities } + +pub fn adjust_parent_activities(activities: &Vec, goals: &Vec) -> Vec { + let mut activities_to_return = vec![]; + // >>> + + let mut child_activities: Vec = vec![]; + + // Get activities for parent and child goals + let mut parent_activities: Vec = goals + .iter() + .filter_map(|goal| { + if let Some(children) = &goal.children { + let activities_for_parent: Vec = activities + .iter() + .filter(|activity| activity.goal_id == goal.id) + .cloned() + .collect(); + let child_activities_for_parent: Vec = children + .iter() + .map(|child_id| { + if let Some(child_activity) = activities + .iter() + .find(|activity| activity.goal_id == *child_id) + { + child_activity.clone() + } else { + panic!("Could not find child activity with goal id {}", child_id); + } + }) + .collect(); + child_activities.extend(child_activities_for_parent); + return Some(activities_for_parent); + } + None + }) + .flat_map(|activities| activities) + .collect(); + + if parent_activities.is_empty() { + return activities.clone(); + } + + // For each parent_activity + parent_activities.iter_mut().for_each(|parent_activity| { + let mut parent_duraton = parent_activity.total_duration; + + // For each child_activity + // get total_duration of child goals + // deduct it from parent_activity.total_duration + child_activities.iter().for_each(|child_activity| { + let child_duration = child_activity.total_duration; + parent_duraton -= child_duration; + }); + parent_activity.total_duration = parent_duraton; + parent_activity.min_block_size = parent_duraton; + parent_activity.max_block_size = parent_duraton; + parent_activity.duration_left = parent_duraton; + }); + + // Unify parent_activities and child_activities based on Activity.id into single list which is activities_to_return + activities_to_return.extend(parent_activities.into_iter()); + activities_to_return.extend(child_activities.into_iter()); + + // Sort activities_to_return like the incoming parameter activities + // activities_to_return.sort_by_key(|activity| activities.iter().position(|&x| x == activity)); + + // <<< + activities_to_return +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::activity::{ActivityType, Status}; + use chrono::NaiveDateTime; + + #[test] + fn test_adjust_parent_activities() { + let start_date = + NaiveDateTime::parse_from_str("2022-01-01T10:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(); + let deadline_date = + NaiveDateTime::parse_from_str("2022-01-01T18:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(); + // Sample input data + let goal1 = Goal { + id: "1".to_string(), + title: "Plan a party".to_string(), + min_duration: Some(4), + start: start_date, + deadline: deadline_date, + children: Some(vec!["2".to_string(), "3".to_string()]), + budget_config: None, + filters: None, + }; + let goal2 = Goal { + id: "2".to_string(), + title: "Buy stuff".to_string(), + min_duration: Some(1), + start: start_date, + deadline: deadline_date, + children: None, + budget_config: None, + filters: None, + }; + let goal3 = Goal { + id: "3".to_string(), + title: "Invite friends".to_string(), + min_duration: Some(1), + start: start_date, + deadline: deadline_date, + children: None, + budget_config: None, + filters: None, + }; + let goals = vec![goal1.clone(), goal2.clone(), goal3.clone()]; + + let activity1 = Activity { + goal_id: "1".to_string(), + activity_type: ActivityType::SimpleGoal, + title: "Plan a party".to_string(), + min_block_size: 4, + max_block_size: 4, + calendar_overlay: vec![], + time_budgets: vec![], + total_duration: 4, + duration_left: 4, + status: Status::Unprocessed, + }; + let activity2 = Activity { + goal_id: "2".to_string(), + activity_type: ActivityType::SimpleGoal, + title: "Buy stuff".to_string(), + min_block_size: 1, + max_block_size: 1, + calendar_overlay: vec![], + time_budgets: vec![], + total_duration: 1, + duration_left: 1, + status: Status::Unprocessed, + }; + let activity3 = Activity { + goal_id: "3".to_string(), + activity_type: ActivityType::SimpleGoal, + title: "Invite friends".to_string(), + min_block_size: 1, + max_block_size: 1, + calendar_overlay: vec![], + time_budgets: vec![], + total_duration: 1, + duration_left: 1, + status: Status::Unprocessed, + }; + let expected_activities = vec![activity1.clone(), activity2.clone(), activity3.clone()]; + + // Call the function + let adjusted_activities = adjust_parent_activities(&expected_activities, &goals); + + // Make sure sort of activities are the same as expected + assert!(adjusted_activities.len() == 3); + assert_eq!( + adjusted_activities[0].goal_id, + expected_activities[0].goal_id + ); + assert_eq!( + adjusted_activities[1].goal_id, + expected_activities[1].goal_id + ); + assert_eq!( + adjusted_activities[2].goal_id, + expected_activities[2].goal_id + ); + + // Make sure adjusted parent activities are as expected + assert_eq!(adjusted_activities[0].total_duration, 2); + assert_eq!(adjusted_activities[0].max_block_size, 2); + assert_eq!(adjusted_activities[0].min_block_size, 2); + assert_eq!(adjusted_activities[0].duration_left, 2); + } +} From 3f616eeff29d5b6b36fbe63d4896c1c84fd9f169 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Wed, 6 Mar 2024 09:51:50 +0200 Subject: [PATCH 08/31] ref: call function adjust_parent_activities in test template --- build_templates/tests_mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_templates/tests_mod.rs b/build_templates/tests_mod.rs index 2461e46c..bb25ad44 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -10,6 +10,7 @@ mod TEST_MODULE_NAME { use crate::calendar::Calendar; use crate::Input; use scheduler::models::activity::Activity; + use scheduler::services::activity_generator::adjust_parent_activities; use scheduler::services::{activity_generator, activity_placer}; use scheduler::technical::input_output; @@ -45,6 +46,8 @@ mod TEST_MODULE_NAME { let simple_goal_activities = activity_generator::generate_simple_goal_activities(&calendar, &input.goals); dbg!(&simple_goal_activities); + let simple_goal_activities = adjust_parent_activities(&simple_goal_activities, &input.goals); + dbg!(&simple_goal_activities); activity_placer::place(&mut calendar, simple_goal_activities); //generate and place budget goal activities From 217bbb9d68da2001967cea669dd1f0398f9dbea4 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Wed, 6 Mar 2024 09:52:29 +0200 Subject: [PATCH 09/31] ref: observed feedback after developing function adjust_parent_activities --- tests/jsons/stable/filler-goal/observed.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/jsons/stable/filler-goal/observed.json b/tests/jsons/stable/filler-goal/observed.json index 21e34fa0..05042abe 100644 --- a/tests/jsons/stable/filler-goal/observed.json +++ b/tests/jsons/stable/filler-goal/observed.json @@ -31,16 +31,16 @@ "taskid": 3, "goalid": "1", "title": "Plan a party", - "duration": 4, + "duration": 2, "start": "2022-01-01T12:00:00", - "deadline": "2022-01-01T16:00:00" + "deadline": "2022-01-01T14:00:00" }, { "taskid": 4, "goalid": "free", "title": "free", - "duration": 8, - "start": "2022-01-01T16:00:00", + "duration": 10, + "start": "2022-01-01T14:00:00", "deadline": "2022-01-02T00:00:00" } ] From 26699beca8bc1d1db78e961c2fd54afccf34516c Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Wed, 6 Mar 2024 11:03:17 +0200 Subject: [PATCH 10/31] fix: Now Daily habits activity can be shows in the activities --- .../stable/default-budgets/expected.json | 103 ++++++++++-------- .../stable/default-budgets/observed.json | 103 ++++++++++-------- 2 files changed, 110 insertions(+), 96 deletions(-) diff --git a/tests/jsons/stable/default-budgets/expected.json b/tests/jsons/stable/default-budgets/expected.json index 970aa17e..1692f147 100644 --- a/tests/jsons/stable/default-budgets/expected.json +++ b/tests/jsons/stable/default-budgets/expected.json @@ -5,38 +5,54 @@ "tasks": [ { "taskid": 0, - "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", - "title": "Sleep πŸ˜΄πŸŒ™", - "duration": 5, + "goalid": "44faf46c-73ad-4140-a5cb-d9a69f51859b", + "title": "Daily habits πŸ”", + "duration": 1, "start": "2024-01-08T00:00:00", - "deadline": "2024-01-08T05:00:00" + "deadline": "2024-01-08T01:00:00" }, { "taskid": 1, - "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", - "title": "Me time 🧘🏽😌", - "duration": 1, - "start": "2024-01-08T05:00:00", - "deadline": "2024-01-08T06:00:00" + "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", + "title": "Sleep πŸ˜΄πŸŒ™", + "duration": 6, + "start": "2024-01-08T01:00:00", + "deadline": "2024-01-08T07:00:00" }, { "taskid": 2, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, - "start": "2024-01-08T06:00:00", - "deadline": "2024-01-08T07:00:00" + "start": "2024-01-08T07:00:00", + "deadline": "2024-01-08T08:00:00" }, { "taskid": 3, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 5, - "start": "2024-01-08T07:00:00", - "deadline": "2024-01-08T12:00:00" + "duration": 1, + "start": "2024-01-08T08:00:00", + "deadline": "2024-01-08T09:00:00" }, { "taskid": 4, + "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", + "title": "Family time πŸ₯°", + "duration": 1, + "start": "2024-01-08T09:00:00", + "deadline": "2024-01-08T10:00:00" + }, + { + "taskid": 5, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 2, + "start": "2024-01-08T10:00:00", + "deadline": "2024-01-08T12:00:00" + }, + { + "taskid": 6, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, @@ -44,7 +60,7 @@ "deadline": "2024-01-08T13:00:00" }, { - "taskid": 5, + "taskid": 7, "goalid": "free", "title": "free", "duration": 1, @@ -52,23 +68,15 @@ "deadline": "2024-01-08T14:00:00" }, { - "taskid": 6, + "taskid": 8, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 3, + "duration": 4, "start": "2024-01-08T14:00:00", - "deadline": "2024-01-08T17:00:00" - }, - { - "taskid": 7, - "goalid": "free", - "title": "free", - "duration": 1, - "start": "2024-01-08T17:00:00", "deadline": "2024-01-08T18:00:00" }, { - "taskid": 8, + "taskid": 9, "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", "title": "Dinner 🍽️", "duration": 1, @@ -76,7 +84,7 @@ "deadline": "2024-01-08T19:00:00" }, { - "taskid": 9, + "taskid": 10, "goalid": "free", "title": "free", "duration": 1, @@ -84,7 +92,7 @@ "deadline": "2024-01-08T20:00:00" }, { - "taskid": 10, + "taskid": 11, "goalid": "77e1f762-3a4f-44a3-8f24-1560641a3548", "title": "Walk 🚢🏽", "duration": 1, @@ -92,15 +100,15 @@ "deadline": "2024-01-08T21:00:00" }, { - "taskid": 11, - "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", - "title": "Family time πŸ₯°", + "taskid": 12, + "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", + "title": "Me time 🧘🏽😌", "duration": 1, "start": "2024-01-08T21:00:00", "deadline": "2024-01-08T22:00:00" }, { - "taskid": 12, + "taskid": 13, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 2, @@ -113,7 +121,7 @@ "day": "2024-01-09", "tasks": [ { - "taskid": 13, + "taskid": 14, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 5, @@ -121,7 +129,7 @@ "deadline": "2024-01-09T05:00:00" }, { - "taskid": 14, + "taskid": 15, "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", "title": "Me time 🧘🏽😌", "duration": 1, @@ -129,7 +137,7 @@ "deadline": "2024-01-09T06:00:00" }, { - "taskid": 15, + "taskid": 16, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, @@ -137,7 +145,7 @@ "deadline": "2024-01-09T07:00:00" }, { - "taskid": 16, + "taskid": 17, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", "duration": 5, @@ -145,27 +153,19 @@ "deadline": "2024-01-09T12:00:00" }, { - "taskid": 17, + "taskid": 18, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, "start": "2024-01-09T12:00:00", "deadline": "2024-01-09T13:00:00" }, - { - "taskid": 18, - "goalid": "free", - "title": "free", - "duration": 1, - "start": "2024-01-09T13:00:00", - "deadline": "2024-01-09T14:00:00" - }, { "taskid": 19, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 3, - "start": "2024-01-09T14:00:00", + "duration": 4, + "start": "2024-01-09T13:00:00", "deadline": "2024-01-09T17:00:00" }, { @@ -796,5 +796,12 @@ ] } ], - "impossible": [] + "impossible": [ + { + "id": "49b05463-56a0-4af5-9034-83822abf24f6", + "hoursMissing": 4, + "periodStartDateTime": "2024-01-10T00:00:00", + "periodEndDateTime": "2024-01-11T00:00:00" + } + ] } \ No newline at end of file diff --git a/tests/jsons/stable/default-budgets/observed.json b/tests/jsons/stable/default-budgets/observed.json index 970aa17e..1692f147 100644 --- a/tests/jsons/stable/default-budgets/observed.json +++ b/tests/jsons/stable/default-budgets/observed.json @@ -5,38 +5,54 @@ "tasks": [ { "taskid": 0, - "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", - "title": "Sleep πŸ˜΄πŸŒ™", - "duration": 5, + "goalid": "44faf46c-73ad-4140-a5cb-d9a69f51859b", + "title": "Daily habits πŸ”", + "duration": 1, "start": "2024-01-08T00:00:00", - "deadline": "2024-01-08T05:00:00" + "deadline": "2024-01-08T01:00:00" }, { "taskid": 1, - "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", - "title": "Me time 🧘🏽😌", - "duration": 1, - "start": "2024-01-08T05:00:00", - "deadline": "2024-01-08T06:00:00" + "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", + "title": "Sleep πŸ˜΄πŸŒ™", + "duration": 6, + "start": "2024-01-08T01:00:00", + "deadline": "2024-01-08T07:00:00" }, { "taskid": 2, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, - "start": "2024-01-08T06:00:00", - "deadline": "2024-01-08T07:00:00" + "start": "2024-01-08T07:00:00", + "deadline": "2024-01-08T08:00:00" }, { "taskid": 3, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 5, - "start": "2024-01-08T07:00:00", - "deadline": "2024-01-08T12:00:00" + "duration": 1, + "start": "2024-01-08T08:00:00", + "deadline": "2024-01-08T09:00:00" }, { "taskid": 4, + "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", + "title": "Family time πŸ₯°", + "duration": 1, + "start": "2024-01-08T09:00:00", + "deadline": "2024-01-08T10:00:00" + }, + { + "taskid": 5, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 2, + "start": "2024-01-08T10:00:00", + "deadline": "2024-01-08T12:00:00" + }, + { + "taskid": 6, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, @@ -44,7 +60,7 @@ "deadline": "2024-01-08T13:00:00" }, { - "taskid": 5, + "taskid": 7, "goalid": "free", "title": "free", "duration": 1, @@ -52,23 +68,15 @@ "deadline": "2024-01-08T14:00:00" }, { - "taskid": 6, + "taskid": 8, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 3, + "duration": 4, "start": "2024-01-08T14:00:00", - "deadline": "2024-01-08T17:00:00" - }, - { - "taskid": 7, - "goalid": "free", - "title": "free", - "duration": 1, - "start": "2024-01-08T17:00:00", "deadline": "2024-01-08T18:00:00" }, { - "taskid": 8, + "taskid": 9, "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", "title": "Dinner 🍽️", "duration": 1, @@ -76,7 +84,7 @@ "deadline": "2024-01-08T19:00:00" }, { - "taskid": 9, + "taskid": 10, "goalid": "free", "title": "free", "duration": 1, @@ -84,7 +92,7 @@ "deadline": "2024-01-08T20:00:00" }, { - "taskid": 10, + "taskid": 11, "goalid": "77e1f762-3a4f-44a3-8f24-1560641a3548", "title": "Walk 🚢🏽", "duration": 1, @@ -92,15 +100,15 @@ "deadline": "2024-01-08T21:00:00" }, { - "taskid": 11, - "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", - "title": "Family time πŸ₯°", + "taskid": 12, + "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", + "title": "Me time 🧘🏽😌", "duration": 1, "start": "2024-01-08T21:00:00", "deadline": "2024-01-08T22:00:00" }, { - "taskid": 12, + "taskid": 13, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 2, @@ -113,7 +121,7 @@ "day": "2024-01-09", "tasks": [ { - "taskid": 13, + "taskid": 14, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 5, @@ -121,7 +129,7 @@ "deadline": "2024-01-09T05:00:00" }, { - "taskid": 14, + "taskid": 15, "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", "title": "Me time 🧘🏽😌", "duration": 1, @@ -129,7 +137,7 @@ "deadline": "2024-01-09T06:00:00" }, { - "taskid": 15, + "taskid": 16, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, @@ -137,7 +145,7 @@ "deadline": "2024-01-09T07:00:00" }, { - "taskid": 16, + "taskid": 17, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", "duration": 5, @@ -145,27 +153,19 @@ "deadline": "2024-01-09T12:00:00" }, { - "taskid": 17, + "taskid": 18, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, "start": "2024-01-09T12:00:00", "deadline": "2024-01-09T13:00:00" }, - { - "taskid": 18, - "goalid": "free", - "title": "free", - "duration": 1, - "start": "2024-01-09T13:00:00", - "deadline": "2024-01-09T14:00:00" - }, { "taskid": 19, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 3, - "start": "2024-01-09T14:00:00", + "duration": 4, + "start": "2024-01-09T13:00:00", "deadline": "2024-01-09T17:00:00" }, { @@ -796,5 +796,12 @@ ] } ], - "impossible": [] + "impossible": [ + { + "id": "49b05463-56a0-4af5-9034-83822abf24f6", + "hoursMissing": 4, + "periodStartDateTime": "2024-01-10T00:00:00", + "periodEndDateTime": "2024-01-11T00:00:00" + } + ] } \ No newline at end of file From d09ee20fac15244a22532b8dd07a3068dd0cdd2e Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 03:11:58 +0200 Subject: [PATCH 11/31] fix: children of parent goal to match child goal id instead of child goal name --- tests/jsons/stable/default-budgets/input.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/jsons/stable/default-budgets/input.json b/tests/jsons/stable/default-budgets/input.json index b8c4aa06..44c3f688 100644 --- a/tests/jsons/stable/default-budgets/input.json +++ b/tests/jsons/stable/default-budgets/input.json @@ -7,11 +7,12 @@ "title": "Daily habits πŸ”", "createdAt": "2024-01-08T10:59:47.134Z", "children": [ - "breakfast", - "lunch", - "dinner", - "meTime", - "walk" + "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", + "18be6978-ffac-46db-8b70-d321c311428a", + "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "445f787b-d742-4441-9744-e81c286aa3c8", + "77e1f762-3a4f-44a3-8f24-1560641a3548", + "49b05463-56a0-4af5-9034-83822abf24f6" ] }, { From bc635f703be4f5d389e64cf33bdc5ce2367b1995 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 03:15:54 +0200 Subject: [PATCH 12/31] fix: avoid panics when no child activity match --- src/services/activity_generator.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 232dcf27..ed88d547 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -115,15 +115,11 @@ pub fn adjust_parent_activities(activities: &Vec, goals: &Vec) - .collect(); let child_activities_for_parent: Vec = children .iter() - .map(|child_id| { - if let Some(child_activity) = activities + .filter_map(|child_id| { + activities .iter() .find(|activity| activity.goal_id == *child_id) - { - child_activity.clone() - } else { - panic!("Could not find child activity with goal id {}", child_id); - } + .cloned() }) .collect(); child_activities.extend(child_activities_for_parent); From 711e5e04877bece763e437fd1edd7736ab4d8bf2 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 03:47:57 +0200 Subject: [PATCH 13/31] fix: avoid generate activities for goals which have children and have no start, deadline, and have no min_duration --- src/models/activity.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/models/activity.rs b/src/models/activity.rs index 88098a80..7a731710 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -255,6 +255,15 @@ impl Activity { if goal.filters.as_ref().is_some() { return vec![]; } + // Avoid goals which have children and have no start or deadline and have no min duration + if goal.children.is_some() + && goal.start.year() == 1970 + && goal.deadline.year() == 1970 + && goal.min_duration.is_none() + { + return vec![]; + } + let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); From 7567aaf9afa6e740632b90cfb711e51cccf9662f Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 03:48:39 +0200 Subject: [PATCH 14/31] test: update expected and observed output for default-budgets test case --- .../stable/default-budgets/expected.json | 103 ++++++++---------- .../stable/default-budgets/observed.json | 103 ++++++++---------- 2 files changed, 96 insertions(+), 110 deletions(-) diff --git a/tests/jsons/stable/default-budgets/expected.json b/tests/jsons/stable/default-budgets/expected.json index 1692f147..970aa17e 100644 --- a/tests/jsons/stable/default-budgets/expected.json +++ b/tests/jsons/stable/default-budgets/expected.json @@ -5,54 +5,38 @@ "tasks": [ { "taskid": 0, - "goalid": "44faf46c-73ad-4140-a5cb-d9a69f51859b", - "title": "Daily habits πŸ”", - "duration": 1, + "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", + "title": "Sleep πŸ˜΄πŸŒ™", + "duration": 5, "start": "2024-01-08T00:00:00", - "deadline": "2024-01-08T01:00:00" + "deadline": "2024-01-08T05:00:00" }, { "taskid": 1, - "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", - "title": "Sleep πŸ˜΄πŸŒ™", - "duration": 6, - "start": "2024-01-08T01:00:00", - "deadline": "2024-01-08T07:00:00" + "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", + "title": "Me time 🧘🏽😌", + "duration": 1, + "start": "2024-01-08T05:00:00", + "deadline": "2024-01-08T06:00:00" }, { "taskid": 2, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, - "start": "2024-01-08T07:00:00", - "deadline": "2024-01-08T08:00:00" + "start": "2024-01-08T06:00:00", + "deadline": "2024-01-08T07:00:00" }, { "taskid": 3, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 1, - "start": "2024-01-08T08:00:00", - "deadline": "2024-01-08T09:00:00" - }, - { - "taskid": 4, - "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", - "title": "Family time πŸ₯°", - "duration": 1, - "start": "2024-01-08T09:00:00", - "deadline": "2024-01-08T10:00:00" - }, - { - "taskid": 5, - "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", - "title": "Work πŸ’ͺ🏽", - "duration": 2, - "start": "2024-01-08T10:00:00", + "duration": 5, + "start": "2024-01-08T07:00:00", "deadline": "2024-01-08T12:00:00" }, { - "taskid": 6, + "taskid": 4, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, @@ -60,7 +44,7 @@ "deadline": "2024-01-08T13:00:00" }, { - "taskid": 7, + "taskid": 5, "goalid": "free", "title": "free", "duration": 1, @@ -68,15 +52,23 @@ "deadline": "2024-01-08T14:00:00" }, { - "taskid": 8, + "taskid": 6, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 4, + "duration": 3, "start": "2024-01-08T14:00:00", + "deadline": "2024-01-08T17:00:00" + }, + { + "taskid": 7, + "goalid": "free", + "title": "free", + "duration": 1, + "start": "2024-01-08T17:00:00", "deadline": "2024-01-08T18:00:00" }, { - "taskid": 9, + "taskid": 8, "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", "title": "Dinner 🍽️", "duration": 1, @@ -84,7 +76,7 @@ "deadline": "2024-01-08T19:00:00" }, { - "taskid": 10, + "taskid": 9, "goalid": "free", "title": "free", "duration": 1, @@ -92,7 +84,7 @@ "deadline": "2024-01-08T20:00:00" }, { - "taskid": 11, + "taskid": 10, "goalid": "77e1f762-3a4f-44a3-8f24-1560641a3548", "title": "Walk 🚢🏽", "duration": 1, @@ -100,15 +92,15 @@ "deadline": "2024-01-08T21:00:00" }, { - "taskid": 12, - "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", - "title": "Me time 🧘🏽😌", + "taskid": 11, + "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", + "title": "Family time πŸ₯°", "duration": 1, "start": "2024-01-08T21:00:00", "deadline": "2024-01-08T22:00:00" }, { - "taskid": 13, + "taskid": 12, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 2, @@ -121,7 +113,7 @@ "day": "2024-01-09", "tasks": [ { - "taskid": 14, + "taskid": 13, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 5, @@ -129,7 +121,7 @@ "deadline": "2024-01-09T05:00:00" }, { - "taskid": 15, + "taskid": 14, "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", "title": "Me time 🧘🏽😌", "duration": 1, @@ -137,7 +129,7 @@ "deadline": "2024-01-09T06:00:00" }, { - "taskid": 16, + "taskid": 15, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, @@ -145,7 +137,7 @@ "deadline": "2024-01-09T07:00:00" }, { - "taskid": 17, + "taskid": 16, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", "duration": 5, @@ -153,19 +145,27 @@ "deadline": "2024-01-09T12:00:00" }, { - "taskid": 18, + "taskid": 17, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, "start": "2024-01-09T12:00:00", "deadline": "2024-01-09T13:00:00" }, + { + "taskid": 18, + "goalid": "free", + "title": "free", + "duration": 1, + "start": "2024-01-09T13:00:00", + "deadline": "2024-01-09T14:00:00" + }, { "taskid": 19, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 4, - "start": "2024-01-09T13:00:00", + "duration": 3, + "start": "2024-01-09T14:00:00", "deadline": "2024-01-09T17:00:00" }, { @@ -796,12 +796,5 @@ ] } ], - "impossible": [ - { - "id": "49b05463-56a0-4af5-9034-83822abf24f6", - "hoursMissing": 4, - "periodStartDateTime": "2024-01-10T00:00:00", - "periodEndDateTime": "2024-01-11T00:00:00" - } - ] + "impossible": [] } \ No newline at end of file diff --git a/tests/jsons/stable/default-budgets/observed.json b/tests/jsons/stable/default-budgets/observed.json index 1692f147..970aa17e 100644 --- a/tests/jsons/stable/default-budgets/observed.json +++ b/tests/jsons/stable/default-budgets/observed.json @@ -5,54 +5,38 @@ "tasks": [ { "taskid": 0, - "goalid": "44faf46c-73ad-4140-a5cb-d9a69f51859b", - "title": "Daily habits πŸ”", - "duration": 1, + "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", + "title": "Sleep πŸ˜΄πŸŒ™", + "duration": 5, "start": "2024-01-08T00:00:00", - "deadline": "2024-01-08T01:00:00" + "deadline": "2024-01-08T05:00:00" }, { "taskid": 1, - "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", - "title": "Sleep πŸ˜΄πŸŒ™", - "duration": 6, - "start": "2024-01-08T01:00:00", - "deadline": "2024-01-08T07:00:00" + "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", + "title": "Me time 🧘🏽😌", + "duration": 1, + "start": "2024-01-08T05:00:00", + "deadline": "2024-01-08T06:00:00" }, { "taskid": 2, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, - "start": "2024-01-08T07:00:00", - "deadline": "2024-01-08T08:00:00" + "start": "2024-01-08T06:00:00", + "deadline": "2024-01-08T07:00:00" }, { "taskid": 3, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 1, - "start": "2024-01-08T08:00:00", - "deadline": "2024-01-08T09:00:00" - }, - { - "taskid": 4, - "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", - "title": "Family time πŸ₯°", - "duration": 1, - "start": "2024-01-08T09:00:00", - "deadline": "2024-01-08T10:00:00" - }, - { - "taskid": 5, - "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", - "title": "Work πŸ’ͺ🏽", - "duration": 2, - "start": "2024-01-08T10:00:00", + "duration": 5, + "start": "2024-01-08T07:00:00", "deadline": "2024-01-08T12:00:00" }, { - "taskid": 6, + "taskid": 4, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, @@ -60,7 +44,7 @@ "deadline": "2024-01-08T13:00:00" }, { - "taskid": 7, + "taskid": 5, "goalid": "free", "title": "free", "duration": 1, @@ -68,15 +52,23 @@ "deadline": "2024-01-08T14:00:00" }, { - "taskid": 8, + "taskid": 6, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 4, + "duration": 3, "start": "2024-01-08T14:00:00", + "deadline": "2024-01-08T17:00:00" + }, + { + "taskid": 7, + "goalid": "free", + "title": "free", + "duration": 1, + "start": "2024-01-08T17:00:00", "deadline": "2024-01-08T18:00:00" }, { - "taskid": 9, + "taskid": 8, "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", "title": "Dinner 🍽️", "duration": 1, @@ -84,7 +76,7 @@ "deadline": "2024-01-08T19:00:00" }, { - "taskid": 10, + "taskid": 9, "goalid": "free", "title": "free", "duration": 1, @@ -92,7 +84,7 @@ "deadline": "2024-01-08T20:00:00" }, { - "taskid": 11, + "taskid": 10, "goalid": "77e1f762-3a4f-44a3-8f24-1560641a3548", "title": "Walk 🚢🏽", "duration": 1, @@ -100,15 +92,15 @@ "deadline": "2024-01-08T21:00:00" }, { - "taskid": 12, - "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", - "title": "Me time 🧘🏽😌", + "taskid": 11, + "goalid": "f9a02a6a-8ba9-43d0-b4aa-c6503f77306f", + "title": "Family time πŸ₯°", "duration": 1, "start": "2024-01-08T21:00:00", "deadline": "2024-01-08T22:00:00" }, { - "taskid": 13, + "taskid": 12, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 2, @@ -121,7 +113,7 @@ "day": "2024-01-09", "tasks": [ { - "taskid": 14, + "taskid": 13, "goalid": "49b05463-56a0-4af5-9034-83822abf24f6", "title": "Sleep πŸ˜΄πŸŒ™", "duration": 5, @@ -129,7 +121,7 @@ "deadline": "2024-01-09T05:00:00" }, { - "taskid": 15, + "taskid": 14, "goalid": "445f787b-d742-4441-9744-e81c286aa3c8", "title": "Me time 🧘🏽😌", "duration": 1, @@ -137,7 +129,7 @@ "deadline": "2024-01-09T06:00:00" }, { - "taskid": 16, + "taskid": 15, "goalid": "40842a7d-c282-406f-9cdf-3d1fbd8e4f61", "title": "Breakfast πŸ₯πŸ₯£", "duration": 1, @@ -145,7 +137,7 @@ "deadline": "2024-01-09T07:00:00" }, { - "taskid": 17, + "taskid": 16, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", "duration": 5, @@ -153,19 +145,27 @@ "deadline": "2024-01-09T12:00:00" }, { - "taskid": 18, + "taskid": 17, "goalid": "18be6978-ffac-46db-8b70-d321c311428a", "title": "Lunch πŸ₯ͺ", "duration": 1, "start": "2024-01-09T12:00:00", "deadline": "2024-01-09T13:00:00" }, + { + "taskid": 18, + "goalid": "free", + "title": "free", + "duration": 1, + "start": "2024-01-09T13:00:00", + "deadline": "2024-01-09T14:00:00" + }, { "taskid": 19, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work πŸ’ͺ🏽", - "duration": 4, - "start": "2024-01-09T13:00:00", + "duration": 3, + "start": "2024-01-09T14:00:00", "deadline": "2024-01-09T17:00:00" }, { @@ -796,12 +796,5 @@ ] } ], - "impossible": [ - { - "id": "49b05463-56a0-4af5-9034-83822abf24f6", - "hoursMissing": 4, - "periodStartDateTime": "2024-01-10T00:00:00", - "periodEndDateTime": "2024-01-11T00:00:00" - } - ] + "impossible": [] } \ No newline at end of file From 45484d871c76a5e1c03b5fe72648d7775180a4e1 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 22:51:15 +0200 Subject: [PATCH 15/31] fix: remove dbg statements --- build_templates/tests_mod.rs | 3 --- src/bin/main.rs | 2 -- src/lib.rs | 3 --- src/models/activity.rs | 2 -- src/models/budget.rs | 1 - src/models/calendar.rs | 1 - src/services/activity_generator.rs | 6 ------ src/services/activity_placer.rs | 3 --- 8 files changed, 21 deletions(-) diff --git a/build_templates/tests_mod.rs b/build_templates/tests_mod.rs index bb25ad44..fdff46ed 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -45,15 +45,12 @@ mod TEST_MODULE_NAME { //generate and place simple goal activities let simple_goal_activities = activity_generator::generate_simple_goal_activities(&calendar, &input.goals); - dbg!(&simple_goal_activities); let simple_goal_activities = adjust_parent_activities(&simple_goal_activities, &input.goals); - dbg!(&simple_goal_activities); activity_placer::place(&mut calendar, simple_goal_activities); //generate and place budget goal activities let budget_goal_activities: Vec = activity_generator::generate_budget_goal_activities(&calendar, &input.goals); - dbg!(&calendar); activity_placer::place(&mut calendar, budget_goal_activities); calendar.log_impossible_min_day_budgets(); diff --git a/src/bin/main.rs b/src/bin/main.rs index 1ff873a1..3f65d869 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -9,9 +9,7 @@ fn main() { let path = Path::new("./tests/jsons/stable/algorithm-challenge/input.json"); let file = fs::File::open(path).expect("file should open read only"); let json: Value = serde_json::from_reader(file).expect("file should be proper JSON"); - dbg!(&json); let input: Input = serde_json::from_value(json).unwrap(); - dbg!(&input); run_scheduler(input.start_date, input.end_date, input.goals); } diff --git a/src/lib.rs b/src/lib.rs index ded8e85c..d6eb8d4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,20 +90,17 @@ pub fn run_scheduler( goals: Vec, ) -> FinalTasks { let mut calendar = Calendar::new(start_date, end_date); - dbg!(&calendar); calendar.add_budgets_from(&goals); //generate and place simple goal activities let simple_goal_activities = activity_generator::generate_simple_goal_activities(&calendar, &goals); - dbg!(&simple_goal_activities); activity_placer::place(&mut calendar, simple_goal_activities); //generate and place budget goal activities let budget_goal_activities: Vec = activity_generator::generate_budget_goal_activities(&calendar, &goals); - dbg!(&calendar); activity_placer::place(&mut calendar, budget_goal_activities); calendar.log_impossible_min_day_budgets(); diff --git a/src/models/activity.rs b/src/models/activity.rs index 7a731710..b6f5282e 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -241,7 +241,6 @@ impl Activity { duration_left: goal.budget_config.as_ref().unwrap().min_per_day, status: Status::Unprocessed, }; - dbg!(&activity); activities.push(activity); } activities @@ -295,7 +294,6 @@ impl Activity { duration_left: min_block_size, //TODO: Correct this - is it even necessary to have duration_left? status: Status::Unprocessed, }; - dbg!(&activity); activities.push(activity); activities diff --git a/src/models/budget.rs b/src/models/budget.rs index d2c403b0..ac3d6b7b 100644 --- a/src/models/budget.rs +++ b/src/models/budget.rs @@ -157,6 +157,5 @@ pub fn get_time_budgets_from(calendar: &Calendar, goal: &Goal) -> Vec) -> Vec { - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { let parent_goal = goal.get_parent_goal(&goals); let mut goal_activities = Activity::get_activities_from_simple_goal(goal, calendar, parent_goal); - dbg!(&goal_activities); activities.append(&mut goal_activities); } activities } pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) -> Vec { - dbg!(&goals); let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { let mut goal_activities = Activity::get_activities_from_budget_goal(goal, calendar); - dbg!(&goal_activities); activities.append(&mut goal_activities); } activities @@ -66,7 +62,6 @@ pub fn generate_get_to_week_min_budget_activities( } } } - dbg!(&get_to_week_min_budget_activities); get_to_week_min_budget_activities } @@ -93,7 +88,6 @@ pub fn generate_top_up_week_budget_activities( } } } - dbg!(&top_up_activities); top_up_activities } diff --git a/src/services/activity_placer.rs b/src/services/activity_placer.rs index 2023b6c5..d01b3f8d 100644 --- a/src/services/activity_placer.rs +++ b/src/services/activity_placer.rs @@ -75,10 +75,7 @@ pub fn place(calendar: &mut Calendar, mut activities: Vec) { activities[act_index_to_schedule.unwrap()].status = Status::Scheduled; (activities[act_index_to_schedule.unwrap()]).release_claims(); } - - dbg!(&calendar); } - dbg!(&calendar); } fn find_act_index_to_schedule(activities: &[Activity]) -> Option { From ef22885792a77670a99def376c7ca4c3a9fe1f04 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Fri, 8 Mar 2024 23:34:24 +0200 Subject: [PATCH 16/31] ref: clippy applied --- src/models/goal.rs | 10 ++++------ src/services/activity_generator.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/models/goal.rs b/src/models/goal.rs index 42c82977..a030a0cb 100644 --- a/src/models/goal.rs +++ b/src/models/goal.rs @@ -44,8 +44,6 @@ impl Goal { calendar: &Calendar, parent_goal: Option, ) -> (NaiveDateTime, NaiveDateTime) { - - let mut adjusted_goal_start = self.start; if self.start.year() == 1970 { adjusted_goal_start = calendar.start_date_time; @@ -56,7 +54,8 @@ impl Goal { } // Make sure child goal not fall outside of parent goal start and deadline - if let Some(parent_goal) = parent_goal { // means this is a child goal + if let Some(parent_goal) = parent_goal { + // means this is a child goal if adjusted_goal_start < parent_goal.start { adjusted_goal_start = parent_goal.start; } @@ -90,12 +89,11 @@ impl Goal { } /// Get parent goal of this goal based in provided list of goals - pub fn get_parent_goal(&self, goals: &Vec) -> Option { + pub fn get_parent_goal(&self, goals: &[Goal]) -> Option { let parent_goal = goals.iter().find(|goal| { if let Some(childs) = &goal.children { childs.contains(&self.id) - } - else { + } else { false } }); diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 0e5b9046..888ee1e7 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -3,7 +3,7 @@ use crate::models::{activity::Activity, budget::TimeBudgetType, calendar::Calend pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &Vec) -> Vec { let mut activities: Vec = Vec::with_capacity(goals.capacity()); for goal in goals { - let parent_goal = goal.get_parent_goal(&goals); + let parent_goal = goal.get_parent_goal(goals); let mut goal_activities = Activity::get_activities_from_simple_goal(goal, calendar, parent_goal); @@ -91,7 +91,7 @@ pub fn generate_top_up_week_budget_activities( top_up_activities } -pub fn adjust_parent_activities(activities: &Vec, goals: &Vec) -> Vec { +pub fn adjust_parent_activities(activities: &[Activity], goals: &[Goal]) -> Vec { let mut activities_to_return = vec![]; // >>> @@ -121,11 +121,11 @@ pub fn adjust_parent_activities(activities: &Vec, goals: &Vec) - } None }) - .flat_map(|activities| activities) + .flatten() .collect(); if parent_activities.is_empty() { - return activities.clone(); + return activities.to_owned(); } // For each parent_activity @@ -146,8 +146,8 @@ pub fn adjust_parent_activities(activities: &Vec, goals: &Vec) - }); // Unify parent_activities and child_activities based on Activity.id into single list which is activities_to_return - activities_to_return.extend(parent_activities.into_iter()); - activities_to_return.extend(child_activities.into_iter()); + activities_to_return.extend(parent_activities); + activities_to_return.extend(child_activities); // Sort activities_to_return like the incoming parameter activities // activities_to_return.sort_by_key(|activity| activities.iter().position(|&x| x == activity)); From 80ba7b0e452a2943d19996a73f0077319535b406 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sat, 9 Mar 2024 07:25:57 +0100 Subject: [PATCH 17/31] remode redundant comment --- src/models/activity.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index b6f5282e..df86839a 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -254,7 +254,6 @@ impl Activity { if goal.filters.as_ref().is_some() { return vec![]; } - // Avoid goals which have children and have no start or deadline and have no min duration if goal.children.is_some() && goal.start.year() == 1970 && goal.deadline.year() == 1970 From 09f953723ce45a42f52b14190a834394e548b1b5 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sat, 9 Mar 2024 07:26:32 +0100 Subject: [PATCH 18/31] remove unnecessary code --- src/models/activity.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index df86839a..ec8cf1ec 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -254,11 +254,7 @@ impl Activity { if goal.filters.as_ref().is_some() { return vec![]; } - if goal.children.is_some() - && goal.start.year() == 1970 - && goal.deadline.year() == 1970 - && goal.min_duration.is_none() - { + if goal.children.is_some() && goal.min_duration.is_none() { return vec![]; } From 98e950441cae380cf1003c648ebea0ca1dcc21fa Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sat, 9 Mar 2024 08:30:42 +0100 Subject: [PATCH 19/31] Merge branch 'main' into tijl/-/add-filler-goal-test --- Cargo.toml | 6 +- build_templates/run_test.rs | 2 - build_templates/tests_mod.rs | 42 +- .../functional/Algorithm-Overview.md | 64 +-- documentation/functional/Concepts.md | 75 ++- documentation/technical/Profiling.md | 10 +- documentation/technical/Project-Structure.md | 4 +- documentation/technical/Troubleshooting.md | 10 - scripts/cargo-check.sh | 2 +- src/bin/flamegraph-bin.rs | 17 - src/bin/main.rs | 3 +- src/lib.rs | 30 +- src/models/activity.rs | 456 +++++++++--------- src/models/budget.rs | 73 +-- src/models/calendar.rs | 170 ++++--- src/models/goal.rs | 7 + src/models/task.rs | 8 +- src/services/activity_generator.rs | 172 ++----- src/services/activity_placer.rs | 53 +- src/technical/input_output.rs | 9 +- src/tests/general.rs | 134 ----- src/tests/mod.rs | 5 - src/tests/slot.rs | 173 ------- src/tests/slots_iterator.rs | 116 ----- src/tests/step.rs | 48 -- src/tests/timeline/merge_slots.rs | 103 ---- src/tests/timeline/mod.rs | 47 -- src/tests/timeline/remove_slots.rs | 169 ------- tests/jsons/stable.bak/after-12/input.json | 15 - .../budget-and-goal-one-day/expected.json | 42 -- .../after-12/expected.json | 31 +- tests/jsons/stable/after-12/input.json | 29 ++ .../after-12/observed.json | 31 +- .../budget-and-goal-one-day/expected.json | 50 ++ .../budget-and-goal-one-day/input.json | 28 +- .../budget-and-goal-one-day/observed.json | 31 +- .../stable/budget-with-subgoal/expected.json | 184 +++++++ .../stable/budget-with-subgoal/input.json | 37 ++ .../stable/budget-with-subgoal/observed.json | 184 +++++++ tests/jsons/stable/filler-goal/observed.json | 12 +- tests/jsons/stable/not-on/expected.json | 34 ++ tests/jsons/stable/not-on/input.json | 19 + tests/jsons/stable/not-on/observed.json | 34 ++ 43 files changed, 1109 insertions(+), 1660 deletions(-) delete mode 100644 src/bin/flamegraph-bin.rs delete mode 100644 src/tests/general.rs delete mode 100644 src/tests/mod.rs delete mode 100644 src/tests/slot.rs delete mode 100644 src/tests/slots_iterator.rs delete mode 100644 src/tests/step.rs delete mode 100644 src/tests/timeline/merge_slots.rs delete mode 100644 src/tests/timeline/mod.rs delete mode 100644 src/tests/timeline/remove_slots.rs delete mode 100644 tests/jsons/stable.bak/after-12/input.json delete mode 100644 tests/jsons/stable.bak/budget-and-goal-one-day/expected.json rename tests/jsons/{stable.bak => stable}/after-12/expected.json (92%) create mode 100644 tests/jsons/stable/after-12/input.json rename tests/jsons/{stable.bak => stable}/after-12/observed.json (92%) create mode 100644 tests/jsons/stable/budget-and-goal-one-day/expected.json rename tests/jsons/{stable.bak => stable}/budget-and-goal-one-day/input.json (65%) rename tests/jsons/{stable.bak => stable}/budget-and-goal-one-day/observed.json (68%) create mode 100644 tests/jsons/stable/budget-with-subgoal/expected.json create mode 100644 tests/jsons/stable/budget-with-subgoal/input.json create mode 100644 tests/jsons/stable/budget-with-subgoal/observed.json create mode 100644 tests/jsons/stable/not-on/expected.json create mode 100644 tests/jsons/stable/not-on/input.json create mode 100644 tests/jsons/stable/not-on/observed.json diff --git a/Cargo.toml b/Cargo.toml index a58a8bab..ac8d46dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "zinzen" version = "0.2.0" edition = "2021" -license = "AGPL-3.0" +license = "AGPL-3.0-or-later" keywords = ["zinzen", "scheduler", "todo"] description = "Algorithm for auto-scheduling time-constrained tasks on a timeline" homepage = "https://github.com/tijlleenders/ZinZen-scheduler" @@ -25,10 +25,6 @@ crate-type = ['cdylib', 'lib'] name = 'scheduler' path = "src/lib.rs" -[[bin]] -name = "flamegraph" -path = "src/bin/flamegraph-bin.rs" - [profile.release] lto = true diff --git a/build_templates/run_test.rs b/build_templates/run_test.rs index 60bd4837..05a92c85 100644 --- a/build_templates/run_test.rs +++ b/build_templates/run_test.rs @@ -1,8 +1,6 @@ #![cfg_attr(rustfmt, rustfmt_skip)] //skip cargo fmt on autogenerated file extern crate scheduler; -use scheduler::technical::input_output::Input; -use scheduler::models::calendar; /// AUTO-GENERATED FILE. Do not change. /// Will be overwritten on build. Edit the file in build_templates or change test generation in build.rs diff --git a/build_templates/tests_mod.rs b/build_templates/tests_mod.rs index fdff46ed..aa3a9507 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -7,13 +7,8 @@ mod TEST_MODULE_NAME { // experimental tests //TEST_FUNCTIONS_EXPERIMENTAL - use crate::calendar::Calendar; - use crate::Input; - use scheduler::models::activity::Activity; - use scheduler::services::activity_generator::adjust_parent_activities; - use scheduler::services::{activity_generator, activity_placer}; - use scheduler::technical::input_output; + use scheduler::technical::input_output::Input; use std::path::Path; fn test(folder: &str) { @@ -32,40 +27,9 @@ mod TEST_MODULE_NAME { let actual_output_path = Path::new(&actual_output_path_str[..]); let input: Input = input_output::get_input_from_json(input_path).unwrap(); - let desired_output: String = - input_output::get_output_string_from_json(output_path).unwrap(); - - // ONLY do this if expected is malformatted ... check that contents don't change! - // input_output::write_to_file(output_path, &desired_output).unwrap(); - - let mut calendar = Calendar::new(input.start_date, input.end_date); - - calendar.add_budgets_from(&input.goals); - - //generate and place simple goal activities - let simple_goal_activities = - activity_generator::generate_simple_goal_activities(&calendar, &input.goals); - let simple_goal_activities = adjust_parent_activities(&simple_goal_activities, &input.goals); - activity_placer::place(&mut calendar, simple_goal_activities); - - //generate and place budget goal activities - let budget_goal_activities: Vec = - activity_generator::generate_budget_goal_activities(&calendar, &input.goals); - activity_placer::place(&mut calendar, budget_goal_activities); - - calendar.log_impossible_min_day_budgets(); - - let get_to_week_min_budget_activities = - activity_generator::generate_get_to_week_min_budget_activities(&calendar, &input.goals); - activity_placer::place(&mut calendar, get_to_week_min_budget_activities); - - calendar.log_impossible_min_week_budgets(); - - let top_up_week_budget_activities = - activity_generator::generate_top_up_week_budget_activities(&calendar, &input.goals); - activity_placer::place(&mut calendar, top_up_week_budget_activities); + let desired_output: String = input_output::get_output_string_from_json(output_path); - let output = calendar.print(); + let output = scheduler::run_scheduler(input.start_date, input.end_date, &input.goals); let actual_output = serde_json::to_string_pretty(&output).unwrap(); diff --git a/documentation/functional/Algorithm-Overview.md b/documentation/functional/Algorithm-Overview.md index 2525b2ad..67ee6f1b 100644 --- a/documentation/functional/Algorithm-Overview.md +++ b/documentation/functional/Algorithm-Overview.md @@ -1,60 +1,36 @@ ## ZinZen algorithm This document contains a high-level overview of the scheduling algorithm. -It has two parts: -- Preparation of Steps and Budgets -- Running the 'placing' algorithm +### 1) The Calendar is created -## Preparation of Steps and Budgets +... and allocates Hours from start to end date. +Each Hour on the Calendar can be Free or Occupied. -### Given: Goals and Budgets -We start with a Directed Acyclical Graph (DAG) of Goals and Budgets, like a tree. -See [Concepts](./Concepts.md) for more info on Goals and Budgets. +### 2) Activities are created from Goals/Budgets +Each Goal (simple or budget) gets translated to one or more Activities by activity_generator.rs. +Each Activity has an internal calendar_overlay of Vec>. +This get filtered on Activity creation for any Hour that doesn't fit the Goal/Budget constraints. -### 1) Add filler Goals if required -Only the Goals at the 'bottom of the DAG', the 'leaves of the tree', will be considered for generating Filler Goals. -Sometimes a leaf Goal does not 'consume' all the hours of its parent Goal. -In those cases a 'filler Goal' is required as a 'brother/sister' of the leaf Goal to add the remaining hours. -Example: -Goal 'Project X 20 hours' has a _only_ a subGoal 'Make project planning 3 hours' and nothing else. -A 'filler Goal' of 'Project X 17 hours' is required next to the planning subGoal. - -### 2) Extract Budgets -Extract any Budgets. -All the Steps generated from the subGoals/children of a Budget need to comply to the same Budget - and that's why they need a single Budget coordination point. -A single Goal can have multiple Budgets. A Budget can impact multiple Goals. - -### 3) Generate Steps from the DAG -See [Concepts](./Concepts.md) for more info on Steps. +After this point, the activity_placer doesn't know anything about dates/times - it just looks at how many blocks are available, and schedules each Activity in turn, counting conflicts via Rc::weak_count, updating the Calendar, which auto-updates the Activities overlay via the Rc:Weak that get invalidated when an Hour is converted from Free to Occupied. -## Running the 'placing' algorithm +### 3) Simple Goal Activities are scheduled +loop { + Calculate flex of each Activity. + If there is an Activity with flex = 1 ; schedule that + else, schedule the Activity with the highest flexibility + in the place with the least conflict with other Activities +} -### 1) Calculate flexibility -Calculate the flexibility of each Step, based upon the currently available Timeline with possible Slots. +### 3) Budget Goal Activities are calculated -### 2) Schedule Steps with flexibility 1 -They can only be scheduled on 1 place: we need to schedule them immediately. +Same loop as in 3 -### 3) If no Steps with flexibility 1, schedule the Step with the largest flexibility -Schedule it at a timeslot that has no conflict with other unscheduled Steps. -If not possible choose the timeslot with the least conflicts. -Only schedule if the chosen Slot respects the Budgets registered on the Step. +### 5) Calendar print +Sequentially step through all the Hours on the Calendar and group sequential hours occupied by the same Activity as a Task. -### 4) Update Timeline of other Steps where needed -Remove the scheduled Slot(s) from the Timeline of any Steps that still has (part of) the Slot. - -### 5) Update Budgets and Budget Steps - -Decrease Budget(s) - if the Step has any. -These Budgets also generated a set of minimum and optional Steps. These Steps need to be removed accordingly to avoid scheduling 'double'. - - -### 6) Repeat 1-5 until fully scheduled or impossible - - -### Why this algorithm? +## Why this algorithm? If you'r not convinced this algorithm is good - please suggest a better one. I'm all ears! Here's a short explanation / test case to show that giving priority to least flexible Steps is wrong: The-wrong-way diff --git a/documentation/functional/Concepts.md b/documentation/functional/Concepts.md index bb6d2961..ae43281f 100644 --- a/documentation/functional/Concepts.md +++ b/documentation/functional/Concepts.md @@ -2,8 +2,19 @@ To create a ubiquitous language for talking about the algorithm, all concepts used in the algorithm are defined below. To move this doc page to Cargo docs there is [issue #414](https://github.com/tijlleenders/ZinZen-scheduler/issues/414). -### 1) Goals -A Goal is the most important concept in ZinZen®, next to Budgets. +## Overall Concept + +The scheduler algorithm is just a transformation function of Goals/Budgets into Tasks on a Calendar. Technically this function is placed into a WASM to be used by the frontend whenever Goals/Budgets change. + +**The scheduler translates the users goals into scheduled tasks.** + +### 0) Calendar + +The calendar is the overaching datastructure which contains all Hours. +Hours have status Free or Occupied. If Occupied, the Hour knows the Activity and Goal that occupied it. + +### 2) Goal + A Goal is a description of something you want to get done. This can be small, like 'walk 4 hours' - or big like 'Protect the oceans from overfishing'. Goals come from the frontend/UI and are specified by the user. Goals are organized together with Budgets in a Directed Acyclical Graph (DAG) and have (optional) attributes: @@ -11,7 +22,7 @@ Goals are organized together with Budgets in a Directed Acyclical Graph (DAG) an - Title - The title. This is necessary only for easier debugging. - (Children) - The sub-goals 'in' this Goal. - Duration - A duration. Without this, the goal can be transparent in the DAG. -- (Repeat) - The number of repeats. This translates into number of Steps to generate. +- (Repeat) - The number of repeats. This translates into number of Activities to generate. - (Repeat interval) - Time between the repeats (x hours/days/weeks/months/years). - (Dependencies): - Starts: @@ -21,9 +32,12 @@ Goals are organized together with Budgets in a Directed Acyclical Graph (DAG) an - Ends with: - DateTime. Defaults to midnight if no time chosen. - Number hours spent - For example, consider the goal 'Write first draft of report' completed after investing 3 hours. -- (Not on) - A collection of Slots that are not allowed to be used. +- (Not on) - A collection of Activities that are not allowed to be used. + + + +### 3) Budget -### 2) Budgets Budgets reserve time on your calendar for a certain purpose. This time can be used by any Goals that are children of the Budget in the DAG. @@ -38,7 +52,7 @@ They also have (optional) attributes specific to Budgets: - Time of day - A pair of [0-23] numbers: - After time - Before time - If after time is greater than the before time, for example 'Sleep 22-6', the resulting Step Timeline Slot will span midnight. + If after time is greater than the before time, for example 'Sleep 22-6', the resulting Step Timeline Activitie will span midnight. - On days - The days of the week the Budget is allowed to use. - Min hours per day - Max hours per day @@ -46,46 +60,15 @@ They also have (optional) attributes specific to Budgets: - Max hours per week The min-max per week has to be compatible with the min-max per day in combination with the 'On days'. +### 4) Activity + +Goals and Budgets are both broken down and represented as Activities to be placed on the Calendar by the activity_placer. + +### 5) Task -### 2) Steps -Steps are the building blocks for the 'placing' algorithem of the scheduler. -Important!: Some older terminology and documentation describes this concept as 'Tasks' - but 'Task' is now reserved only for the final output sent to the frontend. - -Steps can be generated in two ways: -- From a Goal: - - Make a new Step with corresponding Timeline for every Repeat. -- From a Budget: - - Make a new Step for every day interval using the minimum hours per day attribute. - - Make a new optional Step for every day interval using the difference between min-max per day. - -Steps are organized in a list and have the following (optional) attributes: -- Duration -- Timeline - This is a collection of Slots that comply to the constraints set for this Step. -- Flexibility - This is how many different ways the Step can theoretically be 'placed' in the Timeline. It can be calculated using Duration and Timeline. This needs to be recalculated after every change to the Timeline. -- Type - used by 'placer' together with Flexibility to determine priority: - - Goal - - Budget - - Optional budget -- (Budgets) - A list of Budgets the Step needs to comply with - -Example on calculating Step Flexibility: -A Step with Duration 4 and a Timeline with one Slot of [8-14] can placed in 3 ways: -- 8-12 -- OR 9-13 -- OR 10-14 -and thus has a flexibility of 3. - -### 3) Slots -Slots are periods of time: [StartDateTime; EndDateTime[. -Currently the granularity of Slots is in hours. -A Slot can be 1h long, or max 7*24 hours (one week) long. -Important!: Slots are not unique: -- Multiple Steps can have similar or overlapping Slots in their Timeline. - -### 4) Tasks Tasks are only relevant once _all_ scheduling is done. -At that point all scheduled Steps are either impossible or scheduled. +At that point all scheduled Activities are either impossible or scheduled. + +The Hours on the Calendar are then transformed into Tasks: +- Every consecutive ('touching') set of Hours occupied by the same Goal becomes a Task with a start and end datetime. -The Steps are then transformed into Tasks: -- Every Step becomes a Task -- Any Tasks for that 'touch' AND have the same Goal should be merged. \ No newline at end of file diff --git a/documentation/technical/Profiling.md b/documentation/technical/Profiling.md index 1adcd3f8..e6905dd7 100644 --- a/documentation/technical/Profiling.md +++ b/documentation/technical/Profiling.md @@ -1,13 +1,5 @@ ## Profiling setup -### A) Flamegraph -[Flamegraph](https://github.com/flamegraph-rs/flamegraph) is a profiling tool for rust. The binary defined in [flamegraph-bin](../../src/bin/flamegraph-bin.rs) -defines the tests to run. Flamegraph can be invoked by running -```shell -cargo flamegraph --bin flamegraph -``` -(!) note: this does not currently work on MacOS (see the section `Running flamegraph` in [Troubleshooting](Troubleshooting.md)) - -### B) Samply +### Samply Samply is a general-purpose sampler that uses the firefox profiler. https://github.com/mstange/samply/ diff --git a/documentation/technical/Project-Structure.md b/documentation/technical/Project-Structure.md index d05985a4..95e720f0 100644 --- a/documentation/technical/Project-Structure.md +++ b/documentation/technical/Project-Structure.md @@ -3,9 +3,9 @@ ### Entrypoints src/lib.rs contains the 2 main entrypoints of the code: -1) run-scheduler +1) run_scheduler() 1) this is the main entry point for calling the scheduling algorithm as a Rust program -2) schedule +2) schedule() 1) this is the entry point for the exposed WASM module. should do the same as run-scheduler, without the logging. ### Tests diff --git a/documentation/technical/Troubleshooting.md b/documentation/technical/Troubleshooting.md index ce1cd744..da9e2450 100644 --- a/documentation/technical/Troubleshooting.md +++ b/documentation/technical/Troubleshooting.md @@ -43,13 +43,3 @@ Caused by: * solution: add the flag '--features skip-test-generation' to the publish command. For more information see [ADR-001: Generation of end-to-end tests happens with a feature flag ](../ADR/001-skip-generation-of-tests-feature-flag.md). - -### Running flamegraph -* issue: flamegraph fails when running on MacOS with message `dtrace: failed to initialize dtrace: DTrace requires additional privileges -failed to sample program` -* analysis: according to this link: https://github.com/flamegraph-rs/flamegraph/issues/31 flamegraph only works on MacOS by disabling -SIP as described in https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html#//apple_ref/doc/uid/TP40016462-CH5-SW1 -This seems like not a recommended thing to do -* solution: let someone not on MacOS run flamegraph ;) (workaround to be decided) - - diff --git a/scripts/cargo-check.sh b/scripts/cargo-check.sh index 3a9d5d44..94c2af7f 100755 --- a/scripts/cargo-check.sh +++ b/scripts/cargo-check.sh @@ -2,5 +2,5 @@ cargo check \ && cargo +nightly fmt --all --check \ - && cargo test -- --nocapture \ + && cargo test --tests e2e::after_12 -- --nocapture \ && cargo build diff --git a/src/bin/flamegraph-bin.rs b/src/bin/flamegraph-bin.rs deleted file mode 100644 index be472363..00000000 --- a/src/bin/flamegraph-bin.rs +++ /dev/null @@ -1,17 +0,0 @@ -extern crate scheduler; - -/// To generate a flamegraph of the scheduler on your machine, follow the platform-specific instructions [here](https://github.com/flamegraph-rs/flamegraph). -/// If you're running inside WSL2 you'll probably need to follow https://gist.github.com/abel0b/b1881e41b9e1c4b16d84e5e083c38a13 -/// Then `cargo flamegraph --bin flamegraph` -fn main() { - // let path = Path::new("./tests/jsons/stable/algorithm-challenge/input.json"); - // let input = get_input_from_json(path).unwrap(); - // let _output = scheduler::run_scheduler(input); -} - -// pub fn get_input_from_json>(path: P) -> Result<&JsValue, Box> { -// let file = File::open(path)?; -// let reader = BufReader::new(file); -// let input = serde_json::from_reader(reader)?; -// Ok(input) -// } diff --git a/src/bin/main.rs b/src/bin/main.rs index 3f65d869..aa057ae5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -10,7 +10,8 @@ fn main() { let file = fs::File::open(path).expect("file should open read only"); let json: Value = serde_json::from_reader(file).expect("file should be proper JSON"); let input: Input = serde_json::from_value(json).unwrap(); - run_scheduler(input.start_date, input.end_date, input.goals); + dbg!(&input); + run_scheduler(input.start_date, input.end_date, &input.goals); } #[derive(Deserialize, Debug)] diff --git a/src/lib.rs b/src/lib.rs index d6eb8d4f..a958a5a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ // let json_input: serde_json::Value = serde_json::json!({ // "TODO_working_example" // }); -// let input: Input = serde_json::from_value(json_input).unwrap(); +// let input: Input = serde_json::from_value(json_input)?; // let output = scheduler::run_scheduler(input); // ``` //! @@ -79,41 +79,47 @@ interface Input { pub fn schedule(input: &JsValue) -> Result { console_error_panic_hook::set_once(); // JsError implements From, so we can just use `?` on any Error - let input: Input = from_value(input.clone()).unwrap(); - let final_tasks = run_scheduler(input.start_date, input.end_date, input.goals); + let input: Input = from_value(input.clone())?; + let final_tasks = run_scheduler(input.start_date, input.end_date, &input.goals); Ok(to_value(&final_tasks)?) } pub fn run_scheduler( start_date: NaiveDateTime, end_date: NaiveDateTime, - goals: Vec, + goals: &[Goal], ) -> FinalTasks { let mut calendar = Calendar::new(start_date, end_date); - calendar.add_budgets_from(&goals); + calendar.add_budgets_from(goals); //generate and place simple goal activities let simple_goal_activities = - activity_generator::generate_simple_goal_activities(&calendar, &goals); - activity_placer::place(&mut calendar, simple_goal_activities); + activity_generator::generate_simple_goal_activities(&calendar, goals); + dbg!(&simple_goal_activities); //generate and place budget goal activities let budget_goal_activities: Vec = - activity_generator::generate_budget_goal_activities(&calendar, &goals); + activity_generator::generate_budget_goal_activities(&calendar, goals); + dbg!(&budget_goal_activities); + dbg!(&calendar); + + activity_placer::place(&mut calendar, simple_goal_activities); activity_placer::place(&mut calendar, budget_goal_activities); calendar.log_impossible_min_day_budgets(); - let get_to_week_min_budget_activities = - activity_generator::generate_get_to_week_min_budget_activities(&calendar, &goals); - activity_placer::place(&mut calendar, get_to_week_min_budget_activities); + if let Some(get_to_week_min_budget_activities) = + activity_generator::generate_get_to_week_min_budget_activities(&calendar, goals) + { + activity_placer::place(&mut calendar, get_to_week_min_budget_activities); + } //TODO: Test that day stays below min when week min being reached so other goals can get to the week min too calendar.log_impossible_min_week_budgets(); let top_up_week_budget_activities = - activity_generator::generate_top_up_week_budget_activities(&calendar, &goals); + activity_generator::generate_top_up_week_budget_activities(&calendar, goals); activity_placer::place(&mut calendar, top_up_week_budget_activities); //TODO: Test that day stays below min or max when week max being reachd diff --git a/src/models/activity.rs b/src/models/activity.rs index ec8cf1ec..dab01134 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -2,7 +2,7 @@ use chrono::{Datelike, Days, Duration, NaiveDateTime}; use serde::Deserialize; use super::budget::Budget; -use super::goal::Goal; +use super::goal::{Goal, Slot}; use super::{calendar::Calendar, goal::Filters}; use crate::models::budget::TimeBudget; use crate::models::calendar::Hour; @@ -32,6 +32,7 @@ impl Activity { filter_option: Option, adjusted_goal_start: NaiveDateTime, adjusted_goal_deadline: NaiveDateTime, + not_on: Option>, ) -> Vec>> { let mut compatible_hours_overlay: Vec>> = Vec::with_capacity(calendar.hours.capacity()); @@ -39,34 +40,40 @@ impl Activity { let mut compatible = true; if filter_option.is_some() { - if filter_option.clone().unwrap().after_time - < filter_option.clone().unwrap().before_time - { - //normal case + if let Some(filter_option) = filter_option.clone() { let hour_of_day = hour_index % 24; - if hour_of_day < filter_option.clone().unwrap().after_time { - compatible = false; - } - if hour_of_day >= filter_option.clone().unwrap().before_time { - compatible = false; + if filter_option.after_time < filter_option.before_time { + //normal case + if hour_of_day < filter_option.after_time { + compatible = false; + } + if hour_of_day >= filter_option.before_time { + compatible = false; + } + } else { + // special case where we know that compatible times cross the midnight boundary + if hour_of_day >= filter_option.before_time + && hour_of_day < filter_option.after_time + { + compatible = false; + } } - } else { - // special case where we know that compatible times cross the midnight boundary - let hour_of_day = hour_index % 24; - if hour_of_day >= filter_option.clone().unwrap().before_time - && hour_of_day < filter_option.clone().unwrap().after_time + if filter_option + .on_days + .contains(&calendar.get_week_day_of(hour_index)) { + // OK + } else { compatible = false; } } - if filter_option - .as_ref() - .unwrap() - .on_days - .contains(&calendar.get_week_day_of(hour_index)) + } + + let not_on = not_on.clone().unwrap_or_default(); + for slot in not_on.iter() { + if hour_index >= calendar.get_index_of(slot.start) + && hour_index < calendar.get_index_of(slot.end) { - // OK - } else { compatible = false; } } @@ -112,10 +119,10 @@ impl Activity { buffer += 1; if hour_pointer.upgrade().is_none() { buffer = 0; - } else if hour_pointer.upgrade().unwrap() == Hour::Free.into() - && self.min_block_size <= buffer - { - flex += 1; + } else if let Some(ptr) = hour_pointer.upgrade() { + if ptr == Hour::Free.into() && self.min_block_size <= buffer { + flex += 1; + } } } } @@ -127,46 +134,40 @@ impl Activity { let mut best_scheduling_index_and_conflicts: Option<(usize, usize, usize)> = None; for hour_index in 0..self.calendar_overlay.len() { let mut conflicts = 0; - match &self.calendar_overlay[hour_index] { - None => { - continue; - } - Some(_) => { - //TODO: shouldn't this logic be in creating the activity and then set to min_block_size so we can just use that here? - let offset_size: usize = match self.activity_type { - ActivityType::SimpleGoal => self.total_duration, - ActivityType::Budget => self.min_block_size, - ActivityType::GetToMinWeekBudget => 1, - ActivityType::TopUpWeekBudget => 1, - }; - for offset in 0..offset_size { - match &self.calendar_overlay[hour_index + offset] { - None => { - // panic!("Does this ever happen?"); - // Yes in algorithm_challenge test case - // TODO: do we need to mark all from hour_index till offset as None?" - continue; + if self.calendar_overlay[hour_index].is_some() { + //TODO: shouldn't this logic be in creating the activity and then set to min_block_size so we can just use that here? + let offset_size: usize = match self.activity_type { + ActivityType::SimpleGoal => self.total_duration, + ActivityType::Budget => self.min_block_size, + ActivityType::GetToMinWeekBudget => 1, + ActivityType::TopUpWeekBudget => 1, + }; + for offset in 0..offset_size { + match &self.calendar_overlay[hour_index + offset] { + None => { + // panic!("Does this ever happen?"); + // Yes in algorithm_challenge test case + // TODO: do we need to mark all from hour_index till offset as None?" + continue; + } + Some(weak) => { + if weak.upgrade().is_none() { + break; // this will reset conflicts too } - Some(weak) => { - if weak.upgrade().is_none() { - break; // this will reset conflicts too - } - conflicts += weak.weak_count(); - //if last position check if best so far - or so little we can break - if offset == offset_size - 1 { - match best_scheduling_index_and_conflicts { - None => { + conflicts += weak.weak_count(); + //if last position check if best so far - or so little we can break + if offset == offset_size - 1 { + match best_scheduling_index_and_conflicts { + None => { + best_scheduling_index_and_conflicts = + Some((hour_index, conflicts, offset_size)); + } + Some((_, best_conflicts, _)) => { + if conflicts < best_conflicts || conflicts == 0 { best_scheduling_index_and_conflicts = Some((hour_index, conflicts, offset_size)); } - Some((_, best_conflicts, _)) => { - if conflicts < best_conflicts || conflicts == 0 { - best_scheduling_index_and_conflicts = - Some((hour_index, conflicts, offset_size)); - } - } } - continue; } } } @@ -177,119 +178,195 @@ impl Activity { best_scheduling_index_and_conflicts.map(|(best_index, _, size)| (best_index, size)) } - pub(crate) fn release_claims(&mut self) { - let mut empty_overlay: Vec>> = - Vec::with_capacity(self.calendar_overlay.capacity()); - for _ in 0..self.calendar_overlay.capacity() { - empty_overlay.push(None); - } - self.calendar_overlay = empty_overlay; - } - - pub(crate) fn get_activities_from_budget_goal( + pub(crate) fn get_activities_from_simple_goal( goal: &Goal, calendar: &Calendar, + parent_goal: Option, ) -> Vec { - if goal.children.is_some() || goal.filters.as_ref().is_none() { - return vec![]; - } - if goal.budget_config.as_ref().unwrap().min_per_day == 0 { + if goal.children.is_some() || goal.filters.as_ref().is_some() { return vec![]; } let (adjusted_goal_start, adjusted_goal_deadline) = - goal.get_adj_start_deadline(calendar, None); + goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); - let filter_option = goal.filters.clone().unwrap(); - - //TODO: This is cutting something like Sleep into pieces - //Replace by an if on title == 'sleep' / "Sleep" / "Sleep πŸ˜΄πŸŒ™"? - //Yes ... but what about translations? => better to match on goalid - let mut adjusted_min_block_size = 1; - if goal.title.contains("leep") { - adjusted_min_block_size = goal.budget_config.as_ref().unwrap().min_per_day; - } - for day in 0..(adjusted_goal_deadline - adjusted_goal_start).num_days() as u64 { - if filter_option - .on_days - .contains(&adjusted_goal_start.add(Days::new(day)).weekday()) - { - // OK - } else { - // This day is not allowed - continue; - } - let activity_start = adjusted_goal_start.add(Days::new(day)); - let activity_deadline = adjusted_goal_start.add(Days::new(day + 1)); + if let Some(activity_total_duration) = goal.min_duration { + let mut min_block_size = activity_total_duration; + if activity_total_duration > 8 { + min_block_size = 1; + //todo!() //split into multiple activities so flexibilities are correct?? + // or yield flex 1 or maximum of the set from activity.flex()? + }; + + let filters_option: Option = calendar.get_filters_for(goal.id.clone()); let compatible_hours_overlay = Activity::get_compatible_hours_overlay( calendar, - Some(filter_option.clone()), - activity_start, - activity_deadline, + filters_option, + adjusted_goal_start, + adjusted_goal_deadline, + goal.not_on.clone(), ); let activity = Activity { goal_id: goal.id.clone(), - activity_type: ActivityType::Budget, + activity_type: ActivityType::SimpleGoal, title: goal.title.clone(), - min_block_size: adjusted_min_block_size, - max_block_size: goal.budget_config.as_ref().unwrap().max_per_day, + min_block_size, + max_block_size: min_block_size, calendar_overlay: compatible_hours_overlay, time_budgets: vec![], - total_duration: adjusted_min_block_size, - duration_left: goal.budget_config.as_ref().unwrap().min_per_day, + total_duration: activity_total_duration, + duration_left: min_block_size, //TODO: Correct this - is it even necessary to have duration_left? status: Status::Unprocessed, }; + dbg!(&activity); activities.push(activity); } + activities } - pub(crate) fn get_activities_from_simple_goal( + pub(crate) fn get_activities_from_budget_goal( goal: &Goal, calendar: &Calendar, - parent_goal: Option, ) -> Vec { - if goal.filters.as_ref().is_some() { + if goal.filters.as_ref().is_none() { return vec![]; } - if goal.children.is_some() && goal.min_duration.is_none() { - return vec![]; + if let Some(config) = &goal.budget_config { + if config.min_per_day == 0 { + return vec![]; + } } - let (adjusted_goal_start, adjusted_goal_deadline) = - goal.get_adj_start_deadline(calendar, parent_goal); + goal.get_adj_start_deadline(calendar, None); let mut activities: Vec = Vec::with_capacity(1); - let activity_total_duration = goal.min_duration.unwrap_or(1); - let mut min_block_size = activity_total_duration; - if activity_total_duration > 8 { - min_block_size = 1; - //todo!() //split into multiple activities so flexibilities are correct?? - // or yield flex 1 or maximum of the set from activity.flex()? - }; + for day in 0..(adjusted_goal_deadline - adjusted_goal_start).num_days() as u64 { + if let Some(filter_option) = &goal.filters { + if filter_option + .on_days + .contains(&adjusted_goal_start.add(Days::new(day)).weekday()) + { + // OK + } else { + // This day is not allowed + continue; + } + let activity_start = adjusted_goal_start.add(Days::new(day)); + let activity_deadline = adjusted_goal_start.add(Days::new(day + 1)); + + let compatible_hours_overlay = Activity::get_compatible_hours_overlay( + calendar, + Some(filter_option.clone()), + activity_start, + activity_deadline, + goal.not_on.clone(), + ); + + if let Some(config) = &goal.budget_config { + //TODO: This is cutting something like Sleep into pieces + //Replace by an if on title == 'sleep' / "Sleep" / "Sleep πŸ˜΄πŸŒ™"? + //Yes ... but what about translations? => better to match on goalid + let mut adjusted_min_block_size = 1; + if goal.title.contains("leep") { + adjusted_min_block_size = config.min_per_day; + } + + let activity = Activity { + goal_id: goal.id.clone(), + activity_type: ActivityType::Budget, + title: goal.title.clone(), + min_block_size: adjusted_min_block_size, + max_block_size: config.max_per_day, + calendar_overlay: compatible_hours_overlay, + time_budgets: vec![], + total_duration: adjusted_min_block_size, + duration_left: config.min_per_day, + status: Status::Unprocessed, + }; + dbg!(&activity); + activities.push(activity); + } + } + } + activities + } + + pub fn get_activities_to_get_min_week_budget( + goal_to_use: &Goal, + calendar: &Calendar, + time_budget: &TimeBudget, + ) -> Vec { + let mut activities: Vec = vec![]; + + let compatible_hours_overlay = Activity::get_compatible_hours_overlay( + calendar, + goal_to_use.filters.clone(), + calendar + .start_date_time + .sub(Duration::hours(24)) //TODO: fix magic number + .add(Duration::hours(time_budget.calendar_start_index as i64)), + calendar + .start_date_time + .sub(Duration::hours(24)) //TODO: fix magic number + .add(Duration::hours(time_budget.calendar_end_index as i64)), + goal_to_use.not_on.clone(), + ); + let max_hours = time_budget.max_scheduled - time_budget.scheduled; + + activities.push(Activity { + goal_id: goal_to_use.id.clone(), + activity_type: ActivityType::GetToMinWeekBudget, + title: goal_to_use.title.clone(), + min_block_size: 1, + max_block_size: max_hours, + calendar_overlay: compatible_hours_overlay, + time_budgets: vec![], + total_duration: max_hours, + duration_left: max_hours, + status: Status::Unprocessed, + }); + + activities + } + + pub fn get_activities_to_top_up_week_budget( + goal_to_use: &Goal, + calendar: &Calendar, + time_budget: &TimeBudget, + ) -> Vec { + let mut activities: Vec = vec![]; let compatible_hours_overlay = Activity::get_compatible_hours_overlay( calendar, - goal.filters.clone(), - adjusted_goal_start, - adjusted_goal_deadline, + goal_to_use.filters.clone(), + calendar + .start_date_time + .sub(Duration::hours(24)) //TODO: fix magic number + .add(Duration::hours(time_budget.calendar_start_index as i64)), + calendar + .start_date_time + .sub(Duration::hours(24)) //TODO: fix magic number + .add(Duration::hours(time_budget.calendar_end_index as i64)), + goal_to_use.not_on.clone(), ); - let activity = Activity { - goal_id: goal.id.clone(), - activity_type: ActivityType::SimpleGoal, - title: goal.title.clone(), - min_block_size, - max_block_size: min_block_size, + let max_hours = time_budget.max_scheduled - time_budget.scheduled; + + activities.push(Activity { + goal_id: goal_to_use.id.clone(), + activity_type: ActivityType::TopUpWeekBudget, + title: goal_to_use.title.clone(), + min_block_size: 1, + max_block_size: max_hours, calendar_overlay: compatible_hours_overlay, time_budgets: vec![], - total_duration: activity_total_duration, - duration_left: min_block_size, //TODO: Correct this - is it even necessary to have duration_left? + total_duration: max_hours, + duration_left: max_hours, status: Status::Unprocessed, - }; - activities.push(activity); + }); activities } @@ -305,15 +382,11 @@ impl Activity { //check if block is lost/stolen or not - as current weak pointer state could be disposed/stale/dead for hour_index in 0..self.calendar_overlay.len() { - if self.calendar_overlay[hour_index].is_some() - && self.calendar_overlay[hour_index] - .as_ref() - .unwrap() - .upgrade() - .is_none() - { - //block was stolen/lost to some other activity - self.calendar_overlay[hour_index] = None; + if let Some(overlay) = &self.calendar_overlay[hour_index] { + if self.calendar_overlay[hour_index].is_some() && overlay.upgrade().is_none() { + //block was stolen/lost to some other activity + self.calendar_overlay[hour_index] = None; + } } } @@ -394,81 +467,13 @@ impl Activity { self.status = Status::Impossible; } } - - pub fn get_activities_to_get_min_week_budget( - goal_to_use: &Goal, - calendar: &Calendar, - time_budget: &TimeBudget, - ) -> Vec { - let mut activities: Vec = vec![]; - - let compatible_hours_overlay = Activity::get_compatible_hours_overlay( - calendar, - goal_to_use.filters.clone(), - calendar - .start_date_time - .sub(Duration::hours(24)) //TODO: fix magic number - .add(Duration::hours(time_budget.calendar_start_index as i64)), - calendar - .start_date_time - .sub(Duration::hours(24)) //TODO: fix magic number - .add(Duration::hours(time_budget.calendar_end_index as i64)), - ); - - let max_hours = time_budget.max_scheduled - time_budget.scheduled; - - activities.push(Activity { - goal_id: goal_to_use.id.clone(), - activity_type: ActivityType::GetToMinWeekBudget, - title: goal_to_use.title.clone(), - min_block_size: 1, - max_block_size: max_hours, - calendar_overlay: compatible_hours_overlay, - time_budgets: vec![], - total_duration: max_hours, - duration_left: max_hours, - status: Status::Unprocessed, - }); - - activities - } - - pub fn get_activities_to_top_up_week_budget( - goal_to_use: &Goal, - calendar: &Calendar, - time_budget: &TimeBudget, - ) -> Vec { - let mut activities: Vec = vec![]; - - let compatible_hours_overlay = Activity::get_compatible_hours_overlay( - calendar, - goal_to_use.filters.clone(), - calendar - .start_date_time - .sub(Duration::hours(24)) //TODO: fix magic number - .add(Duration::hours(time_budget.calendar_start_index as i64)), - calendar - .start_date_time - .sub(Duration::hours(24)) //TODO: fix magic number - .add(Duration::hours(time_budget.calendar_end_index as i64)), - ); - - let max_hours = time_budget.max_scheduled - time_budget.scheduled; - - activities.push(Activity { - goal_id: goal_to_use.id.clone(), - activity_type: ActivityType::TopUpWeekBudget, - title: goal_to_use.title.clone(), - min_block_size: 1, - max_block_size: max_hours, - calendar_overlay: compatible_hours_overlay, - time_budgets: vec![], - total_duration: max_hours, - duration_left: max_hours, - status: Status::Unprocessed, - }); - - activities + pub(crate) fn release_claims(&mut self) { + let mut empty_overlay: Vec>> = + Vec::with_capacity(self.calendar_overlay.capacity()); + for _ in 0..self.calendar_overlay.capacity() { + empty_overlay.push(None); + } + self.calendar_overlay = empty_overlay; } } @@ -490,18 +495,18 @@ pub enum ActivityType { impl fmt::Debug for Activity { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f).unwrap(); - writeln!(f, "title: {:?}", self.title).unwrap(); - writeln!(f, "status:{:?}", self.status).unwrap(); - writeln!(f, "total duration: {:?}", self.total_duration).unwrap(); - writeln!(f, "duration left: {:?}", self.duration_left).unwrap(); - writeln!(f, "flex:{:?}", self.flex()).unwrap(); + writeln!(f)?; + writeln!(f, "title: {:?}", self.title)?; + writeln!(f, "status:{:?}", self.status)?; + writeln!(f, "total duration: {:?}", self.total_duration)?; + writeln!(f, "duration left: {:?}", self.duration_left)?; + writeln!(f, "flex:{:?}", self.flex())?; for hour_index in 0..self.calendar_overlay.capacity() { let day_index = hour_index / 24; let hour_of_day = hour_index % 24; match &self.calendar_overlay[hour_index] { None => { - write!(f, "-").unwrap(); + write!(f, "-")?; } Some(weak) => { writeln!( @@ -511,9 +516,8 @@ impl fmt::Debug for Activity { hour_of_day, hour_index, weak.weak_count(), - weak.upgrade().unwrap() - ) - .unwrap(); + weak.upgrade() + )?; } } } diff --git a/src/models/budget.rs b/src/models/budget.rs index ac3d6b7b..bbb42a73 100644 --- a/src/models/budget.rs +++ b/src/models/budget.rs @@ -6,13 +6,18 @@ use std::{ use chrono::{Datelike, Duration}; use serde::Deserialize; -use super::{activity::ActivityType, calendar::Calendar, goal::Goal}; +use super::{ + activity::ActivityType, + calendar::Calendar, + goal::{Filters, Goal}, +}; #[derive(Debug, Clone, Deserialize)] pub struct Budget { pub originating_goal_id: String, pub participating_goals: Vec, pub time_budgets: Vec, + pub time_filters: Filters, } impl Budget { pub fn reduce_for_(&mut self, goal: &str, duration_offset: usize) { @@ -105,8 +110,6 @@ impl Debug for TimeBudget { &self.min_scheduled, &self.max_scheduled ) - .unwrap(); - Ok(()) } } @@ -116,28 +119,32 @@ pub fn get_time_budgets_from(calendar: &Calendar, goal: &Goal) -> Vec Vec 24 { println!("Week boundary detected at hour_index {:?}", &hour_index); - time_budgets.push(TimeBudget { - time_budget_type: TimeBudgetType::Week, - calendar_start_index: start_pointer, - calendar_end_index: hour_index, - scheduled: 0, - min_scheduled: goal.budget_config.as_ref().unwrap().min_per_week, - max_scheduled: goal.budget_config.as_ref().unwrap().max_per_week, - }); + if let Some(config) = &goal.budget_config { + time_budgets.push(TimeBudget { + time_budget_type: TimeBudgetType::Week, + calendar_start_index: start_pointer, + calendar_end_index: hour_index, + scheduled: 0, + min_scheduled: config.min_per_week, + max_scheduled: config.max_per_week, + }); + } start_pointer = hour_index } } diff --git a/src/models/calendar.rs b/src/models/calendar.rs index b2619531..429e2239 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -79,8 +79,12 @@ impl Calendar { "can't request an index more than 1 day outside of calendar bounds for date {:?}\nCalendar starts at {:?} and ends at {:?}", date_time, self.start_date_time, self.end_date_time ) } - (date_time - self.start_date_time.checked_sub_days(Days::new(1)).unwrap()).num_hours() - as usize + (date_time + - self + .start_date_time + .checked_sub_days(Days::new(1)) + .unwrap_or_default()) + .num_hours() as usize } pub fn print(&self) -> FinalTasks { @@ -190,35 +194,37 @@ impl Calendar { } } - pub fn add_budgets_from(&mut self, goals: &Vec) { + pub fn add_budgets_from(&mut self, goals: &[Goal]) { //fill goal_map and budget_ids let mut goal_map: HashMap = HashMap::new(); let mut budget_ids: Vec = vec![]; for goal in goals { goal_map.insert(goal.id.clone(), goal.clone()); - match goal.budget_config.as_ref() { - Some(budget_config) => { - //Check if budget_config is realistic + if let Some(budget_config) = &goal.budget_config { + //Check if budget_config is realistic - //check 1 - let mut min_per_day_sum = 0; - for _ in goal.filters.clone().unwrap().on_days { + //check 1 + let mut min_per_day_sum = 0; + if let Some(filters) = &goal.filters { + for _ in &filters.on_days { min_per_day_sum += budget_config.min_per_day; } - if min_per_day_sum > budget_config.min_per_week { - panic!("Sum of min_per_day {:?} is higher than min_per_week {:?} for goal {:?}", min_per_day_sum,budget_config.min_per_week, goal.title); - } + } + if min_per_day_sum > budget_config.min_per_week { + panic!( + "Sum of min_per_day {:?} is higher than min_per_week {:?} for goal {:?}", + min_per_day_sum, budget_config.min_per_week, goal.title + ); + } - //check 2 - if budget_config.max_per_day > budget_config.max_per_week { - panic!( - "max_per_day {:?} is higher than max_per_week {:?} for goal {:?}", - budget_config.max_per_day, budget_config.max_per_week, goal.title - ); - } - budget_ids.push(goal.id.clone()); + //check 2 + if budget_config.max_per_day > budget_config.max_per_week { + panic!( + "max_per_day {:?} is higher than max_per_week {:?} for goal {:?}", + budget_config.max_per_day, budget_config.max_per_week, goal.title + ); } - None => continue, + budget_ids.push(goal.id.clone()); } } @@ -228,47 +234,44 @@ impl Calendar { let mut descendants_added: Vec = vec![budget_id.clone()]; //get the first children if any let mut descendants: Vec = vec![]; - match goal_map.get(&budget_id).as_ref().unwrap().children.as_ref() { - Some(children) => { - descendants.append(children.clone().as_mut()); - } - None => { - self.budgets.push(Budget { - originating_goal_id: budget_id.clone(), - participating_goals: descendants_added, - time_budgets: get_time_budgets_from( - self, - goal_map.get(&budget_id).as_ref().unwrap(), - ), - }); - continue; + if let Some(goal) = goal_map.get(&budget_id) { + match &goal.children { + Some(children) => { + descendants.append(children.clone().as_mut()); + } + None => { + self.budgets.push(Budget { + originating_goal_id: budget_id.clone(), + participating_goals: descendants_added, + time_budgets: get_time_budgets_from(self, goal), + time_filters: goal.filters.clone().unwrap(), + }); + continue; + } } } loop { //add children of each descendant until no more found if descendants.is_empty() { - self.budgets.push(Budget { - originating_goal_id: budget_id.clone(), - participating_goals: descendants_added, - time_budgets: get_time_budgets_from( - self, - goal_map.get(&budget_id).as_ref().unwrap(), - ), - }); - break; + if let Some(goal) = goal_map.get(&budget_id) { + self.budgets.push(Budget { + originating_goal_id: budget_id.clone(), + participating_goals: descendants_added, + time_budgets: get_time_budgets_from(self, goal), + time_filters: goal.filters.clone().unwrap(), + }); + break; + } + } + if let Some(descendant_of_which_to_add_children) = descendants.pop() { + if let Some(goal) = goal_map.get(&descendant_of_which_to_add_children) { + if let Some(children) = &goal.children { + descendants.extend(children.clone()); + } + descendants_added.push(descendant_of_which_to_add_children); + } } - let descendant_of_which_to_add_children = descendants.pop().unwrap(); - descendants.extend( - goal_map - .get(&descendant_of_which_to_add_children) - .unwrap() - .children - .as_ref() - .unwrap() - .clone(), - ); - descendants_added.push(descendant_of_which_to_add_children); } } } @@ -281,37 +284,21 @@ impl Calendar { } pub fn log_impossible_min_day_budgets(&mut self) { - let mut impossible_activities = vec![]; - for budget in &self.budgets { - for time_budget in &budget.time_budgets { - if time_budget.time_budget_type == TimeBudgetType::Day { - // Good - } else { - continue; - } - if time_budget.scheduled < time_budget.min_scheduled { - impossible_activities.push(ImpossibleActivity { - id: budget.originating_goal_id.clone(), - hours_missing: time_budget.min_scheduled - time_budget.scheduled, - period_start_date_time: self - .start_date_time - .add(Duration::hours(time_budget.calendar_start_index as i64)), - period_end_date_time: self - .start_date_time - .add(Duration::hours(time_budget.calendar_end_index as i64)), - }); - } - } - } + let impossible_activities = self.impossible_activities(); self.impossible_activities.extend(impossible_activities); } pub fn log_impossible_min_week_budgets(&mut self) { //TODO: merge with log_imossible_min_day_budgets, passing budget type as param + let impossible_activities = self.impossible_activities(); + self.impossible_activities.extend(impossible_activities); + } + + fn impossible_activities(&mut self) -> Vec { let mut impossible_activities = vec![]; for budget in &self.budgets { for time_budget in &budget.time_budgets { - if time_budget.time_budget_type == TimeBudgetType::Week { + if time_budget.time_budget_type == TimeBudgetType::Day { // Good } else { continue; @@ -330,42 +317,49 @@ impl Calendar { } } } - self.impossible_activities.extend(impossible_activities); + impossible_activities + } + + pub(crate) fn get_filters_for(&self, id: String) -> Option { + for budget in self.budgets.iter() { + if budget.participating_goals.contains(&id) { + return Some(budget.time_filters.clone()); + } + } + None } } impl Debug for Calendar { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - writeln!(f).unwrap(); + writeln!(f)?; for index in 0..self.hours.capacity() { - write!(f, "{:?} ", self.get_week_day_of(index)).unwrap(); + write!(f, "{:?} ", self.get_week_day_of(index))?; let mut index_string = index.to_string(); if index > 23 { index_string = index.to_string() + " " + &(index % 24).to_string(); } if self.hours[index] == Rc::new(Hour::Free) { if Rc::weak_count(&self.hours[index]) == 0 { - writeln!(f, "{} -", index_string).unwrap(); + writeln!(f, "{} -", index_string)?; } else { writeln!( f, "{} {:?} claims", index_string, Rc::weak_count(&self.hours[index]) - ) - .unwrap(); + )?; } } else { - writeln!(f, "{} {:?}", index_string, self.hours[index]).unwrap(); + writeln!(f, "{} {:?}", index_string, self.hours[index])?; } } writeln!( f, "{:?} impossible activities", self.impossible_activities.len() - ) - .unwrap(); + )?; for budget in &self.budgets { - writeln!(f, "{:?}", &budget).unwrap(); + writeln!(f, "{:?}", &budget)?; } Ok(()) } diff --git a/src/models/goal.rs b/src/models/goal.rs index a030a0cb..b4676cc6 100644 --- a/src/models/goal.rs +++ b/src/models/goal.rs @@ -19,6 +19,13 @@ pub struct Goal { pub min_duration: Option, pub title: String, pub children: Option>, + pub not_on: Option>, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Slot { + pub start: NaiveDateTime, + pub end: NaiveDateTime, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/models/task.rs b/src/models/task.rs index 4e4b9f67..fb5b57e0 100644 --- a/src/models/task.rs +++ b/src/models/task.rs @@ -1,16 +1,16 @@ ///Tasks are only used for outputting use chrono::{NaiveDate, NaiveDateTime}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use super::calendar::ImpossibleActivity; -#[derive(Deserialize, Serialize, Debug)] +#[derive(Serialize, Debug)] pub struct FinalTasks { pub scheduled: Vec, pub impossible: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct Task { pub taskid: usize, @@ -21,7 +21,7 @@ pub struct Task { pub deadline: NaiveDateTime, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Debug, Clone)] pub struct DayTasks { pub day: NaiveDate, pub tasks: Vec, diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 888ee1e7..fd9a5ec2 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -1,30 +1,25 @@ use crate::models::{activity::Activity, budget::TimeBudgetType, calendar::Calendar, goal::Goal}; -pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &Vec) -> Vec { - let mut activities: Vec = Vec::with_capacity(goals.capacity()); - for goal in goals { - let parent_goal = goal.get_parent_goal(goals); - - let mut goal_activities = - Activity::get_activities_from_simple_goal(goal, calendar, parent_goal); - activities.append(&mut goal_activities); - } - activities +pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &[Goal]) -> Vec { + goals + .iter() + .flat_map(|goal| { + Activity::get_activities_from_simple_goal(goal, calendar, goal.get_parent_goal(goals)) + }) + .collect::>() } -pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) -> Vec { - let mut activities: Vec = Vec::with_capacity(goals.capacity()); - for goal in goals { - let mut goal_activities = Activity::get_activities_from_budget_goal(goal, calendar); - activities.append(&mut goal_activities); - } - activities +pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &[Goal]) -> Vec { + goals + .iter() + .flat_map(|goal| Activity::get_activities_from_budget_goal(goal, calendar)) + .collect::>() } pub fn generate_get_to_week_min_budget_activities( calendar: &Calendar, goals: &[Goal], -) -> Vec { +) -> Option> { let mut get_to_week_min_budget_activities = vec![]; for budget in &calendar.budgets { let mut is_min_week_reached = true; @@ -44,8 +39,7 @@ pub fn generate_get_to_week_min_budget_activities( } else { let goal_to_use: &Goal = goals .iter() - .find(|g| g.id.eq(&budget.originating_goal_id)) - .unwrap(); + .find(|g| g.id.eq(&budget.originating_goal_id))?; for time_budget in &budget.time_budgets { if time_budget.time_budget_type == TimeBudgetType::Day && time_budget.scheduled == time_budget.min_scheduled @@ -62,7 +56,7 @@ pub fn generate_get_to_week_min_budget_activities( } } } - get_to_week_min_budget_activities + Some(get_to_week_min_budget_activities) } pub fn generate_top_up_week_budget_activities( @@ -71,23 +65,22 @@ pub fn generate_top_up_week_budget_activities( ) -> Vec { let mut top_up_activities = vec![]; for budget in &calendar.budgets { - let goal_to_use: &Goal = goals - .iter() - .find(|g| g.id.eq(&budget.originating_goal_id)) - .unwrap(); - for time_budget in &budget.time_budgets { - if time_budget.time_budget_type == TimeBudgetType::Day - && time_budget.min_scheduled < time_budget.max_scheduled - && time_budget.scheduled < time_budget.max_scheduled - { - top_up_activities.extend(Activity::get_activities_to_top_up_week_budget( - goal_to_use, - calendar, - time_budget, - )); + if let Some(goal_to_use) = goals.iter().find(|g| g.id.eq(&budget.originating_goal_id)) { + for time_budget in &budget.time_budgets { + if time_budget.time_budget_type == TimeBudgetType::Day + && time_budget.min_scheduled < time_budget.max_scheduled + && time_budget.scheduled < time_budget.max_scheduled + { + top_up_activities.extend(Activity::get_activities_to_top_up_week_budget( + goal_to_use, + calendar, + time_budget, + )); + } } } } + dbg!(&top_up_activities); top_up_activities } @@ -155,112 +148,3 @@ pub fn adjust_parent_activities(activities: &[Activity], goals: &[Goal]) -> Vec< // <<< activities_to_return } - -#[cfg(test)] -mod tests { - use super::*; - use crate::models::activity::{ActivityType, Status}; - use chrono::NaiveDateTime; - - #[test] - fn test_adjust_parent_activities() { - let start_date = - NaiveDateTime::parse_from_str("2022-01-01T10:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(); - let deadline_date = - NaiveDateTime::parse_from_str("2022-01-01T18:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(); - // Sample input data - let goal1 = Goal { - id: "1".to_string(), - title: "Plan a party".to_string(), - min_duration: Some(4), - start: start_date, - deadline: deadline_date, - children: Some(vec!["2".to_string(), "3".to_string()]), - budget_config: None, - filters: None, - }; - let goal2 = Goal { - id: "2".to_string(), - title: "Buy stuff".to_string(), - min_duration: Some(1), - start: start_date, - deadline: deadline_date, - children: None, - budget_config: None, - filters: None, - }; - let goal3 = Goal { - id: "3".to_string(), - title: "Invite friends".to_string(), - min_duration: Some(1), - start: start_date, - deadline: deadline_date, - children: None, - budget_config: None, - filters: None, - }; - let goals = vec![goal1.clone(), goal2.clone(), goal3.clone()]; - - let activity1 = Activity { - goal_id: "1".to_string(), - activity_type: ActivityType::SimpleGoal, - title: "Plan a party".to_string(), - min_block_size: 4, - max_block_size: 4, - calendar_overlay: vec![], - time_budgets: vec![], - total_duration: 4, - duration_left: 4, - status: Status::Unprocessed, - }; - let activity2 = Activity { - goal_id: "2".to_string(), - activity_type: ActivityType::SimpleGoal, - title: "Buy stuff".to_string(), - min_block_size: 1, - max_block_size: 1, - calendar_overlay: vec![], - time_budgets: vec![], - total_duration: 1, - duration_left: 1, - status: Status::Unprocessed, - }; - let activity3 = Activity { - goal_id: "3".to_string(), - activity_type: ActivityType::SimpleGoal, - title: "Invite friends".to_string(), - min_block_size: 1, - max_block_size: 1, - calendar_overlay: vec![], - time_budgets: vec![], - total_duration: 1, - duration_left: 1, - status: Status::Unprocessed, - }; - let expected_activities = vec![activity1.clone(), activity2.clone(), activity3.clone()]; - - // Call the function - let adjusted_activities = adjust_parent_activities(&expected_activities, &goals); - - // Make sure sort of activities are the same as expected - assert!(adjusted_activities.len() == 3); - assert_eq!( - adjusted_activities[0].goal_id, - expected_activities[0].goal_id - ); - assert_eq!( - adjusted_activities[1].goal_id, - expected_activities[1].goal_id - ); - assert_eq!( - adjusted_activities[2].goal_id, - expected_activities[2].goal_id - ); - - // Make sure adjusted parent activities are as expected - assert_eq!(adjusted_activities[0].total_duration, 2); - assert_eq!(adjusted_activities[0].max_block_size, 2); - assert_eq!(adjusted_activities[0].min_block_size, 2); - assert_eq!(adjusted_activities[0].duration_left, 2); - } -} diff --git a/src/services/activity_placer.rs b/src/services/activity_placer.rs index d01b3f8d..ae35c46d 100644 --- a/src/services/activity_placer.rs +++ b/src/services/activity_placer.rs @@ -5,7 +5,7 @@ use crate::models::{ calendar::{Calendar, Hour, ImpossibleActivity}, }; -pub fn place(calendar: &mut Calendar, mut activities: Vec) { +pub fn place(calendar: &mut Calendar, mut activities: Vec) -> Option<()> { loop { for activity in activities.iter_mut() { activity.update_overlay_with(&calendar.budgets); @@ -15,41 +15,41 @@ pub fn place(calendar: &mut Calendar, mut activities: Vec) { println!("Tried to schedule activity index None"); break; } - if activities[act_index_to_schedule.unwrap()].goal_id.len() > 5 { + if activities[act_index_to_schedule?].goal_id.len() > 5 { println!( "Next to schedule: {:?} {:?}", - &activities[act_index_to_schedule.unwrap()].title, - &activities[act_index_to_schedule.unwrap()].goal_id[0..5] + &activities[act_index_to_schedule?].title, + &activities[act_index_to_schedule?].goal_id[0..5] ); } else { println!( "Next to schedule: {:?} {:?}", - &activities[act_index_to_schedule.unwrap()].title, - &activities[act_index_to_schedule.unwrap()].goal_id + &activities[act_index_to_schedule?].title, + &activities[act_index_to_schedule?].goal_id ); } let best_hour_index_and_size: Option<(usize, usize)> = - activities[act_index_to_schedule.unwrap()].get_best_scheduling_index_and_length(); + activities[act_index_to_schedule?].get_best_scheduling_index_and_length(); let best_hour_index: usize; let best_size: usize; if best_hour_index_and_size.is_some() { - best_hour_index = best_hour_index_and_size.unwrap().0; - best_size = best_hour_index_and_size.unwrap().1; + best_hour_index = best_hour_index_and_size?.0; + best_size = best_hour_index_and_size?.1; println!( "Best index:{:?} and size {:?}", &best_hour_index, &best_size ); } else { - activities[act_index_to_schedule.unwrap()].release_claims(); - if activities[act_index_to_schedule.unwrap()].activity_type == ActivityType::Budget { - activities[act_index_to_schedule.unwrap()].status = Status::Processed; + activities[act_index_to_schedule?].release_claims(); + if activities[act_index_to_schedule?].activity_type == ActivityType::Budget { + activities[act_index_to_schedule?].status = Status::Processed; continue; } else { - activities[act_index_to_schedule.unwrap()].status = Status::Impossible; + activities[act_index_to_schedule?].status = Status::Impossible; } let impossible_activity = ImpossibleActivity { - id: activities[act_index_to_schedule.unwrap()].goal_id.clone(), - hours_missing: activities[act_index_to_schedule.unwrap()].duration_left, + id: activities[act_index_to_schedule?].goal_id.clone(), + hours_missing: activities[act_index_to_schedule?].duration_left, period_start_date_time: calendar.start_date_time, period_end_date_time: calendar.end_date_time, }; @@ -60,22 +60,24 @@ pub fn place(calendar: &mut Calendar, mut activities: Vec) { for duration_offset in 0..best_size { Rc::make_mut(&mut calendar.hours[best_hour_index + duration_offset]); calendar.hours[best_hour_index + duration_offset] = Rc::new(Hour::Occupied { - activity_index: act_index_to_schedule.unwrap(), - activity_title: activities[act_index_to_schedule.unwrap()].title.clone(), - activity_goalid: activities[act_index_to_schedule.unwrap()].goal_id.clone(), + activity_index: act_index_to_schedule?, + activity_title: activities[act_index_to_schedule?].title.clone(), + activity_goalid: activities[act_index_to_schedule?].goal_id.clone(), }); //TODO: activity doesn't need to know about time_budets => remove completely calendar.update_budgets_for( - &activities[act_index_to_schedule.unwrap()].goal_id.clone(), + &activities[act_index_to_schedule?].goal_id.clone(), best_hour_index + duration_offset, ); - activities[act_index_to_schedule.unwrap()].duration_left -= 1; + activities[act_index_to_schedule?].duration_left -= 1; } - if activities[act_index_to_schedule.unwrap()].duration_left == 0 { - activities[act_index_to_schedule.unwrap()].status = Status::Scheduled; - (activities[act_index_to_schedule.unwrap()]).release_claims(); + if activities[act_index_to_schedule?].duration_left == 0 { + activities[act_index_to_schedule?].status = Status::Scheduled; + (activities[act_index_to_schedule?]).release_claims(); } } + dbg!(&calendar); + Some(()) } fn find_act_index_to_schedule(activities: &[Activity]) -> Option { @@ -95,7 +97,7 @@ fn find_act_index_to_schedule(activities: &[Activity]) -> Option { continue; } 1 => { - if activities[act_index_to_schedule.unwrap()].flex() == 1 { + if activities[act_index_to_schedule?].flex() == 1 { break; } else { act_index_to_schedule = Some(index); @@ -103,8 +105,7 @@ fn find_act_index_to_schedule(activities: &[Activity]) -> Option { } } _ => { - if activities[act_index_to_schedule.unwrap()].flex() < activities[index].flex() - { + if activities[act_index_to_schedule?].flex() < activities[index].flex() { act_index_to_schedule = Some(index); } } diff --git a/src/technical/input_output.rs b/src/technical/input_output.rs index f691a6f4..bb1e4420 100644 --- a/src/technical/input_output.rs +++ b/src/technical/input_output.rs @@ -1,8 +1,8 @@ use crate::models::goal::Goal; -use crate::models::task::FinalTasks; use chrono::NaiveDateTime; use serde::Deserialize; use std::error::Error; +use std::fs; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; @@ -23,12 +23,9 @@ pub fn get_input_from_json>(path: P) -> Result>(path: P) -> Result { +pub fn get_output_string_from_json>(path: P) -> String { println!("get_output_string_from_json\n"); - let file = File::open(path).expect("Error reading file"); - let reader = BufReader::new(file); - let output: FinalTasks = serde_json::from_reader(reader)?; - serde_json::to_string_pretty(&output) + fs::read_to_string(path).unwrap() } pub fn write_to_file>(path: P, output: &str) -> Result<(), Box> { diff --git a/src/tests/general.rs b/src/tests/general.rs deleted file mode 100644 index 92868611..00000000 --- a/src/tests/general.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{models::goal::Day, models::slot::Slot}; -use chrono::*; -use std::vec; - -#[test] -fn divide_a_day_in_days() { - let slot_of_exactly_a_day = Slot { - start: NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 9, 27) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - }; - let exact_day_split_in_days: Vec = vec![Slot { - start: NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 9, 27) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - }]; - let result = slot_of_exactly_a_day.split_into_days(); - assert_eq!(exact_day_split_in_days, result); -} -#[test] -fn divide_two_days_in_days() { - let slot_of_exactly_two_day = Slot { - start: NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 9, 28) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - }; - let exactly_two_days_split_in_days: Vec = vec![ - Slot { - start: NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 9, 27) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - }, - Slot { - start: NaiveDate::from_ymd_opt(2022, 9, 27) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 9, 28) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - }, - ]; - let result = slot_of_exactly_two_day.split_into_days(); - assert_eq!(exactly_two_days_split_in_days, result); -} - -#[test] -fn divide_half_a_day_in_days() { - let slot_of_half_a_day = Slot { - start: NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(6, 0, 0) - .unwrap(), - }; - let half_a_day_split_in_days: Vec = vec![Slot { - start: NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(), - end: NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(6, 0, 0) - .unwrap(), - }]; - let result = slot_of_half_a_day.split_into_days(); - assert_eq!(half_a_day_split_in_days, result); -} - -#[test] -fn test_convert_day_object_from_string() { - let day: Day = Day::from("Tue".to_string()); - assert_eq!(day, Day::Tuesday); - - let day: Day = Day::from("tue".to_string()); - assert_eq!(day, Day::Tuesday); - - let day: Day = Day::from("thu".to_string()); - assert_eq!(day, Day::Thursday); -} - -#[test] -fn test_convert_day_object_into_string() { - let fri_converted: String = Day::Friday.into(); - - let fri_str: String = "Fri".to_string(); - assert_eq!(fri_str, fri_converted); - - let fri_str: String = "FRI".to_string(); - assert_ne!(fri_str, fri_converted); -} - -#[test] -fn test_subtract_2_slots() { - // Test Trait Sub for Slot to make sure it is working properly - - let (slot1, slot2) = ( - Slot::mock(Duration::hours(5), 2022, 10, 1, 5, 0), - Slot::mock(Duration::hours(5), 2022, 10, 1, 9, 0), - ); - - // expected result: [2022-10-01 09:00:00 --- 2022-10-01 10:00:00] - let expected = vec![Slot::mock(Duration::hours(4), 2022, 10, 1, 5, 0)]; - - let result = slot1 - slot2; - - assert_eq!(expected, result); -} - -// TODO 2023-07-02: test_compare_2_slots (removed empty test because of clippy warnings) diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index 7e82f6b0..00000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod general; -pub mod slot; -pub mod slots_iterator; -pub mod step; -pub mod timeline; diff --git a/src/tests/slot.rs b/src/tests/slot.rs deleted file mode 100644 index 972bcc3b..00000000 --- a/src/tests/slot.rs +++ /dev/null @@ -1,173 +0,0 @@ -// test case to fix if (other.start <= self.start) && (other.end >= self.end) -// Code snippet: else if (other.start <= self.start) && (other.end >= self.end) { - -use chrono::Duration; - -use crate::models::slot::Slot; - -/// Test if subtracting few hours from full day (or more duration) -/// - Expexted to return empty list -#[test] -fn test_subtract_few_hours_from_fullday() { - let year = 2022; - let month = 1; - let day = 1; - - let slot_few_hours = Slot::mock(Duration::hours(10), year, month, day, 5, 0); - let slot_full_day = Slot::mock(Duration::days(1), year, month, day, 0, 0); - - let expected_result: Vec = vec![]; - - let result = slot_few_hours - slot_full_day; - - assert_eq!(expected_result, result); -} - -#[test] -fn test_subtract_fullday_from_few_hours() { - /* - slot_full_day = Slot { - start: 2022-01-02T00:00:00, - end: 2022-01-03T00:00:00, - } - slot_few_hours = Slot { - start: 2022-01-02T05:00:00, - end: 2022-01-02T15:00:00, - } - */ - - let year = 2022; - let month = 1; - let day = 1; - - let slot_full_day = Slot::mock(Duration::days(1), year, month, day, 0, 0); - let slot_few_hours = Slot::mock(Duration::hours(10), year, month, day, 5, 0); - - let expected_result: Vec = vec![ - Slot::mock(Duration::hours(5), year, month, day, 0, 0), - Slot::mock(Duration::hours(9), year, month, day, 15, 0), - ]; - - let result = slot_full_day - slot_few_hours; - - assert_eq!(expected_result, result); -} - -#[test] -fn test_subtract_same_datetime() { - let year = 2022; - let month = 1; - let day = 1; - let hour: u32 = 0; - let min: u32 = 0; - let duration = Duration::hours(10); - - let slot1 = Slot::mock(duration, year, month, day, hour, min); - let slot2 = Slot::mock(duration, year, month, day, hour, min); - - let expected_result: Vec = vec![]; - let result = slot1 - slot2; - - assert_eq!(expected_result, result); -} - -#[test] -fn test_subtract_when_no_overlap() { - let year = 2022; - let month = 1; - let day = 1; - let hour: u32 = 0; - let min: u32 = 0; - let duration = Duration::hours(10); - - let slot1 = Slot::mock(duration, year, month, day, hour, min); - let slot2 = Slot::mock(duration, year, month, day + 1, hour, min); - - let expected_result: Vec = vec![slot1]; - - let result = slot1 - slot2; - - assert_eq!(expected_result, result); -} - -#[test] -fn test_is_conflicts_with() { - let year = 2023; - let month = 5; - let day = 5; - let hour: u32 = 0; - let min: u32 = 0; - let duration = Duration::hours(10); - - let base_slot = Slot::mock(duration, year, month, day, hour, min); - let conflicted_last_of_base = Slot::mock(duration, year, month, day, 9, min); - let conflicted_start_of_base = Slot::mock(duration, year, month, day - 1, 20, min); - let not_conflicted_with_base = Slot::mock(duration, year, month, day + 1, hour, min); - - let is_conflicted_start_of_base = base_slot.conflicts_with(&conflicted_start_of_base); - let is_conflicted_last_of_base = base_slot.conflicts_with(&conflicted_last_of_base); - let is_not_conflicted_with_base = base_slot.conflicts_with(¬_conflicted_with_base); - - // assert_eq!(expected_result, result); - assert!(is_conflicted_last_of_base); - assert!(is_conflicted_start_of_base); - assert!(!is_not_conflicted_with_base); -} - -#[test] -fn test_is_contains_slot() { - let year = 2023; - let month = 5; - let day = 5; - let hour: u32 = 0; - let min: u32 = 0; - let duration = Duration::hours(10); - - let base_slot = Slot::mock(duration, year, month, day, hour, min); - let contained_in_base = Slot::mock(Duration::hours(3), year, month, day, 2, min); - let equal_to_base = Slot::mock(duration, year, month, day, hour, min); - let overflow_base_from_start = Slot::mock(Duration::hours(3), year, month, day - 1, 23, min); - let overflow_base_from_end = Slot::mock(Duration::hours(3), year, month, day, 9, min); - let not_contained_in_base = Slot::mock(duration, year, month, day + 1, hour, min); - - let is_contained_in_base = base_slot.contains_slot(&contained_in_base); - - let is_equal_to_base_contained = base_slot.contains_slot(&equal_to_base); - - let is_overflow_base_from_start = base_slot.contains_slot(&overflow_base_from_start); - - let is_overflow_base_from_end = base_slot.contains_slot(&overflow_base_from_end); - - let is_not_contained_in_base = base_slot.contains_slot(¬_contained_in_base); - - assert!(is_contained_in_base); - assert!(is_equal_to_base_contained); - assert!(!is_overflow_base_from_start); - assert!(!is_overflow_base_from_end); - assert!(!is_not_contained_in_base); -} - -#[test] -fn test_hours_intersecting_with_slot() { - let year = 2023; - let month = 5; - let day = 5; - let hour: u32 = 0; - let min: u32 = 0; - let duration = Duration::hours(10); - - let base_slot = Slot::mock(duration, year, month, day, hour, min); - let intersected_last_of_base = Slot::mock(duration, year, month, day, 9, min); - let intersected_start_of_base = Slot::mock(duration, year, month, day - 1, 20, min); - let not_intersected_with_base = Slot::mock(duration, year, month, day + 1, hour, min); - - let is_intersected_start_of_base = base_slot.intersection(&intersected_start_of_base); - - let is_intersected_last_of_base = base_slot.intersection(&intersected_last_of_base); - - let is_not_intersected_with_base = base_slot.intersection(¬_intersected_with_base); - - assert_eq!(is_intersected_last_of_base, 1); - assert_eq!(is_intersected_start_of_base, 6); - assert_eq!(is_not_intersected_with_base, 0); -} diff --git a/src/tests/slots_iterator.rs b/src/tests/slots_iterator.rs deleted file mode 100644 index fe77abcc..00000000 --- a/src/tests/slots_iterator.rs +++ /dev/null @@ -1,116 +0,0 @@ -use chrono::NaiveDate; - -use crate::models::{ - repetition::Repetition, - slots_iterator::utils::{get_start_of_repeat_step, next_week}, -}; - -mod mocking { - use chrono::{NaiveDate, NaiveDateTime}; - - pub struct DateTime { - pub datetime: NaiveDateTime, - } - impl DateTime { - /// Get a NaiveDateTime based on a year, month and day with 0 for hms - pub fn get_by_date(year: i32, month: u32, day: u32) -> NaiveDateTime { - NaiveDate::from_ymd_opt(year, month, day) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - } - } -} - -#[test] -fn test_next_week_on_monday() { - let input = mocking::DateTime::get_by_date(2023, 5, 1); - let expected = mocking::DateTime::get_by_date(2023, 5, 8); - - let output = next_week(&input); - - assert_eq!(output, expected); -} - -#[test] -fn test_next_week_on_tuesday() { - let input = mocking::DateTime::get_by_date(2023, 5, 2); - let expected = mocking::DateTime::get_by_date(2023, 5, 9); - - let output = next_week(&input); - - assert_eq!(output, expected); -} - -#[test] -fn test_next_week_on_wednesday() { - let input = mocking::DateTime::get_by_date(2023, 5, 3); - let expected = mocking::DateTime::get_by_date(2023, 5, 10); - - let output = next_week(&input); - - assert_eq!(output, expected); -} - -#[test] -fn test_next_week_on_thursday() { - let input = mocking::DateTime::get_by_date(2023, 5, 4); - let expected = mocking::DateTime::get_by_date(2023, 5, 11); - - let output = next_week(&input); - - assert_eq!(output, expected); -} - -#[test] -fn test_next_week_on_friday() { - let input = mocking::DateTime::get_by_date(2023, 5, 5); - let expected = mocking::DateTime::get_by_date(2023, 5, 12); - - let output = next_week(&input); - - assert_eq!(output, expected); -} - -#[test] -fn get_next_weekend() { - let repetition = Repetition::WEEKENDS; - - let monday = NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(); - let saturday = NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(); - let monday_with_time = NaiveDate::from_ymd_opt(2022, 9, 26) - .unwrap() - .and_hms_opt(1, 33, 7) - .unwrap(); - let saturday_with_time = NaiveDate::from_ymd_opt(2022, 10, 1) - .unwrap() - .and_hms_opt(1, 33, 7) - .unwrap(); - let _next_weekend_from_monday = NaiveDate::from_ymd_opt(2022, 10, 8) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(); - let _next_weekend_from_weekend = NaiveDate::from_ymd_opt(2022, 10, 15) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(); - let next_weekend_from_monday = get_start_of_repeat_step(&monday, repetition); - let next_weekend_from_saturday = get_start_of_repeat_step(&saturday, repetition); - let next_weekend_from_monday_with_time = - get_start_of_repeat_step(&monday_with_time, repetition); - let next_weekend_from_saturday_with_time = - get_start_of_repeat_step(&saturday_with_time, repetition); - assert_eq!(next_weekend_from_monday, next_weekend_from_monday); - assert_eq!(next_weekend_from_saturday, next_weekend_from_saturday); - assert_eq!(next_weekend_from_monday_with_time, next_weekend_from_monday); - assert_eq!( - next_weekend_from_saturday_with_time, - next_weekend_from_saturday - ); -} diff --git a/src/tests/step.rs b/src/tests/step.rs deleted file mode 100644 index ec1d37e0..00000000 --- a/src/tests/step.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::models::{ - goal::{Goal, Tag}, - slot::Slot, - step::{NewStep, Step, StepStatus}, - timeline::Timeline, -}; -use chrono::Duration; - -#[test] -fn new_step() { - let step_id = 1; - let title = "Do laundry".to_string(); - let duration = 2; - let goal = Goal { - id: "1".to_string(), - title: title.to_string(), - tags: vec![Tag::Budget], - after_goals: None, - ..Default::default() - }; - let timeline = Timeline::new(); - let status = StepStatus::ReadyToSchedule; - let timeframe = Some(Slot::mock(Duration::days(2), 2023, 5, 1, 0, 0)); - - let new_step = NewStep { - step_id, - title: title.clone(), - duration, - goal: goal.clone(), - timeline: timeline.clone(), - status: status.clone(), - timeframe, - }; - - let step = Step::new(new_step); - - assert_eq!(step.id, step_id); - assert_eq!(step.title, title.to_string()); - assert_eq!(step.duration, duration); - assert_eq!(step.goal_id, goal.id); - assert_eq!(step.status, status); - assert_eq!(step.flexibility, 0); - assert_eq!(step.start, timeframe.map(|t| t.start)); - assert_eq!(step.deadline, timeframe.map(|t| t.end)); - assert_eq!(step.slots, timeline.slots.into_iter().collect::>()); - assert_eq!(step.tags, goal.tags); - assert_eq!(step.after_goals, goal.after_goals); -} diff --git a/src/tests/timeline/merge_slots.rs b/src/tests/timeline/merge_slots.rs deleted file mode 100644 index ddfccb9a..00000000 --- a/src/tests/timeline/merge_slots.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::models::{slot::Slot, timeline::Timeline}; -use chrono::Duration; - -#[test] -fn test_merge_all_consequent_slots() { - /* - - Timeline: 2023-05-01 from 00:00 to 05:00 - - Define timeline splitted into hours - - confirm that merge will return one slot which merge all slots into - one slot because they all are consequent - */ - let year: i32 = 2023; - let month: u32 = 5; - let day: u32 = 1; - - let start_hour: u32 = 0; - let end_hour: u32 = 5; - let duration = Duration::hours((end_hour - start_hour) as i64); - - let expected_timeline: Timeline = Timeline::mock(duration, year, month, day); - - let mut input_slots: Vec = vec![]; - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour, 0).split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 1, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 2, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 3, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 4, 0) - .split_into_1h_slots(), - ); - - let input_timeline: Timeline = Timeline { - slots: input_slots.into_iter().collect(), - }; - - let result_timeline = input_timeline.get_merged_slots(); - - assert_eq!(expected_timeline, result_timeline); -} - -#[test] -fn test_merge_some_consequent_slots() { - /* - - Timeline: 2023-05-01 from 00:00 to 05:00 and from 09:00 to 10:00 - - Define timeline splitted into hours - - confirm that merge will return 2 slots one for consequent and - one for non-consequent - */ - let year: i32 = 2023; - let month: u32 = 5; - let day: u32 = 1; - - let start_hour: u32 = 0; - let end_hour: u32 = 5; - let duration = Duration::hours((end_hour - start_hour) as i64); - - let mut expected_timeline: Timeline = Timeline::mock(duration, year, month, day); - expected_timeline - .slots - .insert(Slot::mock(Duration::hours(1), year, month, day, 9, 0)); - - let mut input_slots: Vec = vec![]; - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour, 0).split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 1, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 2, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 3, 0) - .split_into_1h_slots(), - ); - input_slots.append( - &mut Slot::mock(Duration::hours(1), year, month, day, start_hour + 4, 0) - .split_into_1h_slots(), - ); - input_slots - .append(&mut Slot::mock(Duration::hours(1), year, month, day, 9, 0).split_into_1h_slots()); - - let input_timeline: Timeline = Timeline { - slots: input_slots.into_iter().collect(), - }; - - let result_timeline = input_timeline.get_merged_slots(); - - assert_eq!(expected_timeline, result_timeline); -} diff --git a/src/tests/timeline/mod.rs b/src/tests/timeline/mod.rs deleted file mode 100644 index ea6a3adc..00000000 --- a/src/tests/timeline/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -pub mod merge_slots; -pub mod remove_slots; - -use crate::models::{slot::Slot, timeline::Timeline}; -use chrono::Duration; -use std::collections::BTreeSet; - -#[test] -fn test_initialize() { - let sample_slot = Slot::mock(Duration::hours(15), 2022, 10, 1, 5, 0); - - let expected_slot_in_timeline = Slot { - start: sample_slot.start, - end: sample_slot.end, - }; - let mut expected_collection_in_timeline = BTreeSet::new(); - expected_collection_in_timeline.insert(expected_slot_in_timeline); - let expected_timeline = Timeline { - slots: vec![expected_slot_in_timeline].into_iter().collect(), - }; - - if let Some(timeline) = Timeline::initialize(sample_slot.start, sample_slot.end) { - assert_eq!(expected_timeline, timeline); - } else { - panic!(); - } -} - -#[test] -fn test_get_next() { - let (slot1, slot2, slot3, slot4) = ( - Slot::mock(Duration::hours(2), 2022, 10, 1, 1, 0), - Slot::mock(Duration::hours(3), 2022, 10, 1, 3, 0), - Slot::mock(Duration::hours(4), 2022, 10, 1, 7, 0), - Slot::mock(Duration::hours(10), 2022, 10, 1, 12, 0), - ); - - let timeline = Timeline { - slots: vec![slot1, slot2, slot3, slot4].into_iter().collect(), - }; - - if let Some(next_slot) = timeline.get_slot(1) { - assert_eq!(slot2, next_slot); - } else { - panic!(); - } -} diff --git a/src/tests/timeline/remove_slots.rs b/src/tests/timeline/remove_slots.rs deleted file mode 100644 index 090903ca..00000000 --- a/src/tests/timeline/remove_slots.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::models::{slot::Slot, timeline::Timeline}; -use chrono::Duration; - -#[test] -fn test_remove_from() { - let sample_slot = Slot::mock(Duration::hours(15), 2022, 10, 1, 5, 0); - - if let Some(mut timeline) = Timeline::initialize(sample_slot.start, sample_slot.end) { - let slot_to_remove = Slot::mock(Duration::hours(5), 2022, 10, 1, 5, 0); - - timeline.remove_slots(vec![slot_to_remove]); - let result: Vec = timeline.slots.clone().into_iter().collect(); - let expected_result = vec![Slot::mock(Duration::hours(10), 2022, 10, 1, 10, 0)]; - - assert_eq!(expected_result, result); - } else { - panic!(); - } -} - -#[test] -fn test_remove_halfday_from_fullday() { - let slot_fullday = Slot::mock(Duration::hours(24), 2022, 10, 1, 00, 0); - let mut timeline = Timeline { - slots: vec![slot_fullday].into_iter().collect(), - }; - - let slot_halfday_night = Slot::mock(Duration::hours(12), 2022, 10, 1, 12, 0); - let slot_halfday_morning = Slot::mock(Duration::hours(12), 2022, 10, 1, 0, 0); - let expected_result = vec![slot_halfday_morning]; - - timeline.remove_slots(vec![slot_halfday_night]); - let result: Vec = timeline.slots.clone().into_iter().collect(); - - assert_eq!(expected_result, result); -} - -#[test] -fn test_remove_afternoon_hours_from_fullday() { - let slot_fullday = Slot::mock(Duration::hours(24), 2022, 10, 1, 00, 0); - let mut timeline_fullday = Timeline { - slots: vec![slot_fullday].into_iter().collect(), - }; - - let slot_afternoon = Slot::mock(Duration::hours(3), 2022, 10, 1, 12, 0); - - let expected_result = vec![ - Slot::mock(Duration::hours(12), 2022, 10, 1, 0, 0), - Slot::mock(Duration::hours(9), 2022, 10, 1, 15, 0), - ]; - - timeline_fullday.remove_slots(vec![slot_afternoon]); - let result: Vec = timeline_fullday.slots.clone().into_iter().collect(); - - assert_eq!(expected_result, result); -} - -#[test] -fn test_based_on_i284_7days() { - // Test based on failed test: issue-284-filter-days-of-week-7days - - let year: i32 = 2023; - let month: u32 = 3; - let day: u32 = 9; - let start_hour = 8; - let end_hour = 12; - let duration = Duration::hours(end_hour - start_hour as i64); - - let slots: Vec = vec![ - Slot::mock(duration, year, month, day, start_hour, 0), - Slot::mock(duration, year, month, day + 1, start_hour, 0), - Slot::mock(duration, year, month, day + 2, start_hour, 0), - Slot::mock(duration, year, month, day + 3, start_hour, 0), - Slot::mock(duration, year, month, day + 4, start_hour, 0), - Slot::mock(duration, year, month, day + 5, start_hour, 0), - Slot::mock(duration, year, month, day + 6, start_hour, 0), - Slot::mock(duration, year, month, day + 7, start_hour, 0), - Slot::mock(duration, year, month, day + 8, start_hour, 0), - Slot::mock(duration, year, month, day + 9, start_hour, 0), - Slot::mock(duration, year, month, day + 10, start_hour, 0), - ]; - - let mut timeline = Timeline { - slots: slots.clone().into_iter().collect(), - }; - - let expected_result: Vec = slots; - - timeline.remove_slots(vec![]); - let result: Vec = timeline.slots.clone().into_iter().collect(); - - assert_eq!(expected_result, result); -} - -/// Test based on edge case in funciton filter_not_on when timeline have many slots and -/// have many slots to filter -/// - timeline: 5 days (Starting Mon 2023-05-01 to Fri 2023-05-05) -/// - slots_to_filter: 2023-05-02 00 to 05 and 2023-05-04 13 to 17 -/// - Expected list of all 5 days except first 5 hours of 2023-05-02 and -/// except hours from 13 to 17 in 2023-05-04 -#[test] -fn test_based_on_edge_case_in_filter_not_on() { - let slots_to_filter: Vec = vec![ - Slot::mock(Duration::hours(5), 2023, 5, 2, 0, 0), - Slot::mock(Duration::hours(4), 2023, 5, 4, 13, 0), - ]; - - let mut timeline = Timeline::mock_as_days(5, 2023, 5, 1); - - let expected_result: Timeline = Timeline { - slots: vec![ - Slot::mock(Duration::days(1), 2023, 5, 1, 0, 0), - Slot::mock(Duration::hours(19), 2023, 5, 2, 5, 0), - Slot::mock(Duration::days(1), 2023, 5, 3, 0, 0), - Slot::mock(Duration::hours(13), 2023, 5, 4, 0, 0), - Slot::mock(Duration::hours(7), 2023, 5, 4, 17, 0), - Slot::mock(Duration::days(1), 2023, 5, 5, 0, 0), - ] - .into_iter() - .collect(), - }; - - timeline.remove_slots(slots_to_filter); - - assert_eq!(expected_result, timeline); -} - -/// Test based on edge case when asking to remove many slots same day -/// - timeline: 5 days (Starting Mon 2023-05-01 to Fri 2023-05-05) -/// - slots_to_filter: -/// - 2023-05-02 00 to 05 -/// - 2023-05-02 20 to 22 -/// - 2023-05-04 13 to 17 -/// - Expected list -/// - 2023-05-01 full day -/// - 2023-05-02 05 to 20 -/// - 2023-05-02 22 to 00 -/// - 2023-05-03 full day -/// - 2023-05-04 00 to 13 -/// - 2023-05-04 17 to 00 -/// - 2023-05-05 full day -#[test] -fn test_many_filters_same_day() { - let slots_to_filter: Vec = vec![ - Slot::mock(Duration::hours(5), 2023, 5, 2, 0, 0), - Slot::mock(Duration::hours(2), 2023, 5, 2, 20, 0), - Slot::mock(Duration::hours(4), 2023, 5, 4, 13, 0), - ]; - - let mut timeline = Timeline::mock_as_days(5, 2023, 5, 1); - - let expected_result: Timeline = Timeline { - slots: vec![ - Slot::mock(Duration::days(1), 2023, 5, 1, 0, 0), - Slot::mock(Duration::hours(15), 2023, 5, 2, 5, 0), - Slot::mock(Duration::hours(2), 2023, 5, 2, 22, 0), - Slot::mock(Duration::days(1), 2023, 5, 3, 0, 0), - Slot::mock(Duration::hours(13), 2023, 5, 4, 0, 0), - Slot::mock(Duration::hours(7), 2023, 5, 4, 17, 0), - Slot::mock(Duration::days(1), 2023, 5, 5, 0, 0), - ] - .into_iter() - .collect(), - }; - - timeline.remove_slots(slots_to_filter); - - assert_eq!(expected_result, timeline); -} diff --git a/tests/jsons/stable.bak/after-12/input.json b/tests/jsons/stable.bak/after-12/input.json deleted file mode 100644 index 97c69ec9..00000000 --- a/tests/jsons/stable.bak/after-12/input.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "startDate": "2022-10-10T00:00:00", - "endDate": "2022-10-17T00:00:00", - "goals": { - "1": { - "id": "1", - "title": "eat breakfast", - "min_duration": 1, - "repeat": "daily", - "filters": { - "after_time": 12 - } - } - } -} \ No newline at end of file diff --git a/tests/jsons/stable.bak/budget-and-goal-one-day/expected.json b/tests/jsons/stable.bak/budget-and-goal-one-day/expected.json deleted file mode 100644 index cd19a862..00000000 --- a/tests/jsons/stable.bak/budget-and-goal-one-day/expected.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "scheduled": [{ - "day": "2022-09-01", - "tasks": [{ - "taskid": 0, - "goalid": "free", - "title": "free", - "duration": 12, - "start": "2022-09-01T00:00:00", - "deadline": "2022-09-01T12:00:00" - }, - { - "taskid": 1, - "goalid": "1", - "title": "dentist", - "duration": 1, - "start": "2022-09-01T12:00:00", - "deadline": "2022-09-01T13:00:00" - }, - { - "taskid": 2, - "goalid": "2", - "title": "work", - "duration": 8, - "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T21:00:00" - }, - { - "taskid": 3, - "goalid": "free", - "title": "free", - "duration": 3, - "start": "2022-09-01T21:00:00", - "deadline": "2022-09-02T00:00:00" - } - ] - }], - "impossible": [{ - "day": "2022-09-01", - "tasks": [] - }] -} \ No newline at end of file diff --git a/tests/jsons/stable.bak/after-12/expected.json b/tests/jsons/stable/after-12/expected.json similarity index 92% rename from tests/jsons/stable.bak/after-12/expected.json rename to tests/jsons/stable/after-12/expected.json index d0a164da..cf794b72 100644 --- a/tests/jsons/stable.bak/after-12/expected.json +++ b/tests/jsons/stable/after-12/expected.json @@ -204,34 +204,5 @@ ] } ], - "impossible": [ - { - "day": "2022-10-10", - "tasks": [] - }, - { - "day": "2022-10-11", - "tasks": [] - }, - { - "day": "2022-10-12", - "tasks": [] - }, - { - "day": "2022-10-13", - "tasks": [] - }, - { - "day": "2022-10-14", - "tasks": [] - }, - { - "day": "2022-10-15", - "tasks": [] - }, - { - "day": "2022-10-16", - "tasks": [] - } - ] + "impossible": [] } \ No newline at end of file diff --git a/tests/jsons/stable/after-12/input.json b/tests/jsons/stable/after-12/input.json new file mode 100644 index 00000000..2d183a6e --- /dev/null +++ b/tests/jsons/stable/after-12/input.json @@ -0,0 +1,29 @@ +{ + "startDate": "2022-10-10T00:00:00", + "endDate": "2022-10-17T00:00:00", + "goals": [ + { + "id": "1", + "title": "eat breakfast", + "filters": { + "afterTime": 12, + "beforeTime": 0, + "onDays": [ + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + "sun" + ] + }, + "budget": { + "minPerDay": 1, + "maxPerDay": 1, + "minPerWeek": 7, + "maxPerWeek": 7 + } + } + ] +} \ No newline at end of file diff --git a/tests/jsons/stable.bak/after-12/observed.json b/tests/jsons/stable/after-12/observed.json similarity index 92% rename from tests/jsons/stable.bak/after-12/observed.json rename to tests/jsons/stable/after-12/observed.json index d0a164da..cf794b72 100644 --- a/tests/jsons/stable.bak/after-12/observed.json +++ b/tests/jsons/stable/after-12/observed.json @@ -204,34 +204,5 @@ ] } ], - "impossible": [ - { - "day": "2022-10-10", - "tasks": [] - }, - { - "day": "2022-10-11", - "tasks": [] - }, - { - "day": "2022-10-12", - "tasks": [] - }, - { - "day": "2022-10-13", - "tasks": [] - }, - { - "day": "2022-10-14", - "tasks": [] - }, - { - "day": "2022-10-15", - "tasks": [] - }, - { - "day": "2022-10-16", - "tasks": [] - } - ] + "impossible": [] } \ No newline at end of file diff --git a/tests/jsons/stable/budget-and-goal-one-day/expected.json b/tests/jsons/stable/budget-and-goal-one-day/expected.json new file mode 100644 index 00000000..0ec6d0a2 --- /dev/null +++ b/tests/jsons/stable/budget-and-goal-one-day/expected.json @@ -0,0 +1,50 @@ +{ + "scheduled": [ + { + "day": "2022-09-01", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 8, + "start": "2022-09-01T00:00:00", + "deadline": "2022-09-01T08:00:00" + }, + { + "taskid": 1, + "goalid": "2", + "title": "work", + "duration": 4, + "start": "2022-09-01T08:00:00", + "deadline": "2022-09-01T12:00:00" + }, + { + "taskid": 2, + "goalid": "1", + "title": "dentist", + "duration": 1, + "start": "2022-09-01T12:00:00", + "deadline": "2022-09-01T13:00:00" + }, + { + "taskid": 3, + "goalid": "2", + "title": "work", + "duration": 4, + "start": "2022-09-01T13:00:00", + "deadline": "2022-09-01T17:00:00" + }, + { + "taskid": 4, + "goalid": "free", + "title": "free", + "duration": 7, + "start": "2022-09-01T17:00:00", + "deadline": "2022-09-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable.bak/budget-and-goal-one-day/input.json b/tests/jsons/stable/budget-and-goal-one-day/input.json similarity index 65% rename from tests/jsons/stable.bak/budget-and-goal-one-day/input.json rename to tests/jsons/stable/budget-and-goal-one-day/input.json index 6dcf38da..0f80e4e5 100644 --- a/tests/jsons/stable.bak/budget-and-goal-one-day/input.json +++ b/tests/jsons/stable/budget-and-goal-one-day/input.json @@ -1,30 +1,38 @@ { "startDate": "2022-09-01T00:00:00", "endDate": "2022-09-02T00:00:00", - "goals": { - "1": { + "goals": [ + { "id": "1", "title": "dentist", - "min_duration": 1, + "minDuration": 1, "start": "2022-09-01T12:00:00", "deadline": "2022-09-01T15:00:00" }, - "2": { + { "id": "2", "title": "work", - "min_duration": 8, "start": "2022-09-01T00:00:00", "deadline": "2022-09-02T00:00:00", + "filters": { + "afterTime": 8, + "beforeTime": 21, + "onDays": [ + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + "sun" + ] + }, "budget": { "minPerDay": 8, "maxPerDay": 8, "minPerWeek": 56, "maxPerWeek": 56 - }, - "filters": { - "after_time": 8, - "before_time": 21 } } - } + ] } \ No newline at end of file diff --git a/tests/jsons/stable.bak/budget-and-goal-one-day/observed.json b/tests/jsons/stable/budget-and-goal-one-day/observed.json similarity index 68% rename from tests/jsons/stable.bak/budget-and-goal-one-day/observed.json rename to tests/jsons/stable/budget-and-goal-one-day/observed.json index 1df631ab..0ec6d0a2 100644 --- a/tests/jsons/stable.bak/budget-and-goal-one-day/observed.json +++ b/tests/jsons/stable/budget-and-goal-one-day/observed.json @@ -7,12 +7,20 @@ "taskid": 0, "goalid": "free", "title": "free", - "duration": 12, + "duration": 8, "start": "2022-09-01T00:00:00", - "deadline": "2022-09-01T12:00:00" + "deadline": "2022-09-01T08:00:00" }, { "taskid": 1, + "goalid": "2", + "title": "work", + "duration": 4, + "start": "2022-09-01T08:00:00", + "deadline": "2022-09-01T12:00:00" + }, + { + "taskid": 2, "goalid": "1", "title": "dentist", "duration": 1, @@ -20,28 +28,23 @@ "deadline": "2022-09-01T13:00:00" }, { - "taskid": 2, + "taskid": 3, "goalid": "2", "title": "work", - "duration": 8, + "duration": 4, "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T21:00:00" + "deadline": "2022-09-01T17:00:00" }, { - "taskid": 3, + "taskid": 4, "goalid": "free", "title": "free", - "duration": 3, - "start": "2022-09-01T21:00:00", + "duration": 7, + "start": "2022-09-01T17:00:00", "deadline": "2022-09-02T00:00:00" } ] } ], - "impossible": [ - { - "day": "2022-09-01", - "tasks": [] - } - ] + "impossible": [] } \ No newline at end of file diff --git a/tests/jsons/stable/budget-with-subgoal/expected.json b/tests/jsons/stable/budget-with-subgoal/expected.json new file mode 100644 index 00000000..9f16214f --- /dev/null +++ b/tests/jsons/stable/budget-with-subgoal/expected.json @@ -0,0 +1,184 @@ +{ + "scheduled": [ + { + "day": "2024-01-08", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-08T00:00:00", + "deadline": "2024-01-08T06:00:00" + }, + { + "taskid": 1, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Plan my work week", + "duration": 1, + "start": "2024-01-08T06:00:00", + "deadline": "2024-01-08T07:00:00" + }, + { + "taskid": 2, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 7, + "start": "2024-01-08T07:00:00", + "deadline": "2024-01-08T14:00:00" + }, + { + "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-08T14:00:00", + "deadline": "2024-01-09T00:00:00" + } + ] + }, + { + "day": "2024-01-09", + "tasks": [ + { + "taskid": 4, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-09T00:00:00", + "deadline": "2024-01-09T06:00:00" + }, + { + "taskid": 5, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-09T06:00:00", + "deadline": "2024-01-09T14:00:00" + }, + { + "taskid": 6, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-09T14:00:00", + "deadline": "2024-01-10T00:00:00" + } + ] + }, + { + "day": "2024-01-10", + "tasks": [ + { + "taskid": 7, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-10T00:00:00", + "deadline": "2024-01-10T06:00:00" + }, + { + "taskid": 8, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-10T06:00:00", + "deadline": "2024-01-10T14:00:00" + }, + { + "taskid": 9, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-10T14:00:00", + "deadline": "2024-01-11T00:00:00" + } + ] + }, + { + "day": "2024-01-11", + "tasks": [ + { + "taskid": 10, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-11T00:00:00", + "deadline": "2024-01-11T06:00:00" + }, + { + "taskid": 11, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-11T06:00:00", + "deadline": "2024-01-11T14:00:00" + }, + { + "taskid": 12, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-11T14:00:00", + "deadline": "2024-01-12T00:00:00" + } + ] + }, + { + "day": "2024-01-12", + "tasks": [ + { + "taskid": 13, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-12T00:00:00", + "deadline": "2024-01-12T06:00:00" + }, + { + "taskid": 14, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-12T06:00:00", + "deadline": "2024-01-12T14:00:00" + }, + { + "taskid": 15, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-12T14:00:00", + "deadline": "2024-01-13T00:00:00" + } + ] + }, + { + "day": "2024-01-13", + "tasks": [ + { + "taskid": 16, + "goalid": "free", + "title": "free", + "duration": 24, + "start": "2024-01-13T00:00:00", + "deadline": "2024-01-14T00:00:00" + } + ] + }, + { + "day": "2024-01-14", + "tasks": [ + { + "taskid": 17, + "goalid": "free", + "title": "free", + "duration": 24, + "start": "2024-01-14T00:00:00", + "deadline": "2024-01-15T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable/budget-with-subgoal/input.json b/tests/jsons/stable/budget-with-subgoal/input.json new file mode 100644 index 00000000..903126d6 --- /dev/null +++ b/tests/jsons/stable/budget-with-subgoal/input.json @@ -0,0 +1,37 @@ +{ + "startDate": "2024-01-08T00:00:00", + "endDate": "2024-01-15T00:00:00", + "goals": [ + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "filters": { + "afterTime": 6, + "beforeTime": 18, + "onDays": [ + "mon", + "tue", + "wed", + "thu", + "fri" + ] + }, + "createdAt": "2024-01-08T10:59:47.133Z", + "children": [ + "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998" + ], + "budget": { + "minPerDay": 6, + "maxPerDay": 10, + "minPerWeek": 40, + "maxPerWeek": 40 + } + }, + { + "id": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Plan my work week", + "createdAt": "2024-01-08T10:59:47.137Z", + "minDuration": 1 + } + ] +} \ No newline at end of file diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json new file mode 100644 index 00000000..9f16214f --- /dev/null +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -0,0 +1,184 @@ +{ + "scheduled": [ + { + "day": "2024-01-08", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-08T00:00:00", + "deadline": "2024-01-08T06:00:00" + }, + { + "taskid": 1, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Plan my work week", + "duration": 1, + "start": "2024-01-08T06:00:00", + "deadline": "2024-01-08T07:00:00" + }, + { + "taskid": 2, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 7, + "start": "2024-01-08T07:00:00", + "deadline": "2024-01-08T14:00:00" + }, + { + "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-08T14:00:00", + "deadline": "2024-01-09T00:00:00" + } + ] + }, + { + "day": "2024-01-09", + "tasks": [ + { + "taskid": 4, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-09T00:00:00", + "deadline": "2024-01-09T06:00:00" + }, + { + "taskid": 5, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-09T06:00:00", + "deadline": "2024-01-09T14:00:00" + }, + { + "taskid": 6, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-09T14:00:00", + "deadline": "2024-01-10T00:00:00" + } + ] + }, + { + "day": "2024-01-10", + "tasks": [ + { + "taskid": 7, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-10T00:00:00", + "deadline": "2024-01-10T06:00:00" + }, + { + "taskid": 8, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-10T06:00:00", + "deadline": "2024-01-10T14:00:00" + }, + { + "taskid": 9, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-10T14:00:00", + "deadline": "2024-01-11T00:00:00" + } + ] + }, + { + "day": "2024-01-11", + "tasks": [ + { + "taskid": 10, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-11T00:00:00", + "deadline": "2024-01-11T06:00:00" + }, + { + "taskid": 11, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-11T06:00:00", + "deadline": "2024-01-11T14:00:00" + }, + { + "taskid": 12, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-11T14:00:00", + "deadline": "2024-01-12T00:00:00" + } + ] + }, + { + "day": "2024-01-12", + "tasks": [ + { + "taskid": 13, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-12T00:00:00", + "deadline": "2024-01-12T06:00:00" + }, + { + "taskid": 14, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work πŸ’ͺ🏽", + "duration": 8, + "start": "2024-01-12T06:00:00", + "deadline": "2024-01-12T14:00:00" + }, + { + "taskid": 15, + "goalid": "free", + "title": "free", + "duration": 10, + "start": "2024-01-12T14:00:00", + "deadline": "2024-01-13T00:00:00" + } + ] + }, + { + "day": "2024-01-13", + "tasks": [ + { + "taskid": 16, + "goalid": "free", + "title": "free", + "duration": 24, + "start": "2024-01-13T00:00:00", + "deadline": "2024-01-14T00:00:00" + } + ] + }, + { + "day": "2024-01-14", + "tasks": [ + { + "taskid": 17, + "goalid": "free", + "title": "free", + "duration": 24, + "start": "2024-01-14T00:00:00", + "deadline": "2024-01-15T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable/filler-goal/observed.json b/tests/jsons/stable/filler-goal/observed.json index 05042abe..1b7ae7a4 100644 --- a/tests/jsons/stable/filler-goal/observed.json +++ b/tests/jsons/stable/filler-goal/observed.json @@ -29,18 +29,10 @@ }, { "taskid": 3, - "goalid": "1", - "title": "Plan a party", - "duration": 2, - "start": "2022-01-01T12:00:00", - "deadline": "2022-01-01T14:00:00" - }, - { - "taskid": 4, "goalid": "free", "title": "free", - "duration": 10, - "start": "2022-01-01T14:00:00", + "duration": 12, + "start": "2022-01-01T12:00:00", "deadline": "2022-01-02T00:00:00" } ] diff --git a/tests/jsons/stable/not-on/expected.json b/tests/jsons/stable/not-on/expected.json new file mode 100644 index 00000000..97459425 --- /dev/null +++ b/tests/jsons/stable/not-on/expected.json @@ -0,0 +1,34 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 11, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T11:00:00" + }, + { + "taskid": 1, + "goalid": "1", + "title": "shopping", + "duration": 1, + "start": "2022-01-01T11:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 2, + "goalid": "free", + "title": "free", + "duration": 12, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable/not-on/input.json b/tests/jsons/stable/not-on/input.json new file mode 100644 index 00000000..de0edf1b --- /dev/null +++ b/tests/jsons/stable/not-on/input.json @@ -0,0 +1,19 @@ + { + "startDate": "2022-01-01T00:00:00", + "endDate": "2022-01-02T00:00:00", + "goals": [ + { + "id": "1", + "title": "shopping", + "minDuration": 1, + "start": "2022-01-01T10:00:00", + "deadline": "2022-01-01T13:00:00", + "notOn": [ + { + "start": "2022-01-01T00:00:00", + "end": "2022-01-01T11:00:00" + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/jsons/stable/not-on/observed.json b/tests/jsons/stable/not-on/observed.json new file mode 100644 index 00000000..97459425 --- /dev/null +++ b/tests/jsons/stable/not-on/observed.json @@ -0,0 +1,34 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "free", + "title": "free", + "duration": 11, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T11:00:00" + }, + { + "taskid": 1, + "goalid": "1", + "title": "shopping", + "duration": 1, + "start": "2022-01-01T11:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 2, + "goalid": "free", + "title": "free", + "duration": 12, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file From a346673fc2ecfa7cd0f037930879fb45b689b2b9 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 18:05:17 +0200 Subject: [PATCH 20/31] fix: call function to adjust activities for parent goals adjust_parent_activities --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a958a5a6..6e2fd9ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,7 @@ use chrono::NaiveDateTime; use models::{activity::Activity, calendar::Calendar, goal::Goal, task::FinalTasks}; use serde_wasm_bindgen::{from_value, to_value}; use services::activity_generator; +use services::activity_generator::adjust_parent_activities; use services::activity_placer; use technical::input_output::Input; use wasm_bindgen::prelude::*; @@ -96,13 +97,11 @@ pub fn run_scheduler( //generate and place simple goal activities let simple_goal_activities = activity_generator::generate_simple_goal_activities(&calendar, goals); - dbg!(&simple_goal_activities); + let simple_goal_activities = adjust_parent_activities(&simple_goal_activities, goals); //generate and place budget goal activities let budget_goal_activities: Vec = activity_generator::generate_budget_goal_activities(&calendar, goals); - dbg!(&budget_goal_activities); - dbg!(&calendar); activity_placer::place(&mut calendar, simple_goal_activities); activity_placer::place(&mut calendar, budget_goal_activities); From 6d829cdda6ec670e937a6d70c19fa58b9313c33b Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 18:06:32 +0200 Subject: [PATCH 21/31] fix: avoid filter out goals which have children to be able to generate activities for them --- src/models/activity.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index dab01134..96fc9172 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -183,9 +183,13 @@ impl Activity { calendar: &Calendar, parent_goal: Option, ) -> Vec { - if goal.children.is_some() || goal.filters.as_ref().is_some() { + if goal.filters.as_ref().is_some() { return vec![]; } + if goal.children.is_some() && goal.min_duration.is_none() { + return vec![]; + } + let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar, parent_goal); let mut activities: Vec = Vec::with_capacity(1); From faf1c5d1f0c206bdb5fc62844dc2f5b9aba7b9fd Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 18:07:12 +0200 Subject: [PATCH 22/31] fix: corrected observed.json after passing test filler_goal --- tests/jsons/stable/filler-goal/observed.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/jsons/stable/filler-goal/observed.json b/tests/jsons/stable/filler-goal/observed.json index 1b7ae7a4..05042abe 100644 --- a/tests/jsons/stable/filler-goal/observed.json +++ b/tests/jsons/stable/filler-goal/observed.json @@ -29,10 +29,18 @@ }, { "taskid": 3, + "goalid": "1", + "title": "Plan a party", + "duration": 2, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-01T14:00:00" + }, + { + "taskid": 4, "goalid": "free", "title": "free", - "duration": 12, - "start": "2022-01-01T12:00:00", + "duration": 10, + "start": "2022-01-01T14:00:00", "deadline": "2022-01-02T00:00:00" } ] From 75360dbe4ccbcb5f6859296fd7b078f4f99f31b5 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 19:07:56 +0200 Subject: [PATCH 23/31] fix: avoid adjust goal deadline when parent deadline is empty "meansyear=1970" --- src/models/goal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/goal.rs b/src/models/goal.rs index b4676cc6..d5619211 100644 --- a/src/models/goal.rs +++ b/src/models/goal.rs @@ -66,7 +66,8 @@ impl Goal { if adjusted_goal_start < parent_goal.start { adjusted_goal_start = parent_goal.start; } - if adjusted_goal_deadline > parent_goal.deadline { + if adjusted_goal_deadline > parent_goal.deadline && parent_goal.deadline.year() != 1970 + { adjusted_goal_deadline = parent_goal.deadline; } } From 8fc2831e78d00e215e3f29f75fbc4515ba6681aa Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 19:17:09 +0200 Subject: [PATCH 24/31] test: changed expected output for test budget-and-goal-one-day to acceptable output --- .../budget-and-goal-one-day/expected.json | 18 +++++++++++++----- .../budget-and-goal-one-day/observed.json | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/tests/jsons/stable/budget-and-goal-one-day/expected.json b/tests/jsons/stable/budget-and-goal-one-day/expected.json index 0ec6d0a2..9d5fc189 100644 --- a/tests/jsons/stable/budget-and-goal-one-day/expected.json +++ b/tests/jsons/stable/budget-and-goal-one-day/expected.json @@ -29,18 +29,26 @@ }, { "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 2, + "start": "2022-09-01T13:00:00", + "deadline": "2022-09-01T15:00:00" + }, + { + "taskid": 4, "goalid": "2", "title": "work", "duration": 4, - "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T17:00:00" + "start": "2022-09-01T15:00:00", + "deadline": "2022-09-01T19:00:00" }, { - "taskid": 4, + "taskid": 5, "goalid": "free", "title": "free", - "duration": 7, - "start": "2022-09-01T17:00:00", + "duration": 5, + "start": "2022-09-01T19:00:00", "deadline": "2022-09-02T00:00:00" } ] diff --git a/tests/jsons/stable/budget-and-goal-one-day/observed.json b/tests/jsons/stable/budget-and-goal-one-day/observed.json index 0ec6d0a2..9d5fc189 100644 --- a/tests/jsons/stable/budget-and-goal-one-day/observed.json +++ b/tests/jsons/stable/budget-and-goal-one-day/observed.json @@ -29,18 +29,26 @@ }, { "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 2, + "start": "2022-09-01T13:00:00", + "deadline": "2022-09-01T15:00:00" + }, + { + "taskid": 4, "goalid": "2", "title": "work", "duration": 4, - "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T17:00:00" + "start": "2022-09-01T15:00:00", + "deadline": "2022-09-01T19:00:00" }, { - "taskid": 4, + "taskid": 5, "goalid": "free", "title": "free", - "duration": 7, - "start": "2022-09-01T17:00:00", + "duration": 5, + "start": "2022-09-01T19:00:00", "deadline": "2022-09-02T00:00:00" } ] From b220f89387b98e91bbfb8410fe3005e37f20c921 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 19:23:45 +0200 Subject: [PATCH 25/31] fix: clippy nots --- src/models/calendar.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/calendar.rs b/src/models/calendar.rs index 429e2239..b982b18d 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -168,8 +168,8 @@ impl Calendar { task_counter += 1; } current_task.duration = 1; - current_task.goalid = activity_goalid.clone(); - current_task.title = activity_title.clone(); + current_task.goalid.clone_from(activity_goalid); + current_task.title.clone_from(activity_title); current_task.start = self .start_date_time .add(Duration::hours(hour_offset as i64 - 24)); // TODO: Fix magic number offset everywhere in code From 1df487c898ae92344a1f5ae5d5c1835f0266abbc Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 19:29:09 +0200 Subject: [PATCH 26/31] fix: remove dbg statements --- src/bin/main.rs | 1 - src/models/activity.rs | 2 -- src/services/activity_generator.rs | 2 -- src/services/activity_placer.rs | 1 - 4 files changed, 6 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index aa057ae5..940ef70a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -10,7 +10,6 @@ fn main() { let file = fs::File::open(path).expect("file should open read only"); let json: Value = serde_json::from_reader(file).expect("file should be proper JSON"); let input: Input = serde_json::from_value(json).unwrap(); - dbg!(&input); run_scheduler(input.start_date, input.end_date, &input.goals); } diff --git a/src/models/activity.rs b/src/models/activity.rs index 96fc9172..a6c7a0b8 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -224,7 +224,6 @@ impl Activity { duration_left: min_block_size, //TODO: Correct this - is it even necessary to have duration_left? status: Status::Unprocessed, }; - dbg!(&activity); activities.push(activity); } @@ -290,7 +289,6 @@ impl Activity { duration_left: config.min_per_day, status: Status::Unprocessed, }; - dbg!(&activity); activities.push(activity); } } diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 5458f786..c4d1a5db 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -56,7 +56,6 @@ pub fn generate_get_to_week_min_budget_activities( } } } - dbg!(&get_to_week_min_budget_activities); Some(get_to_week_min_budget_activities) } @@ -81,7 +80,6 @@ pub fn generate_top_up_week_budget_activities( } } } - dbg!(&top_up_activities); top_up_activities } diff --git a/src/services/activity_placer.rs b/src/services/activity_placer.rs index ae35c46d..da8974d7 100644 --- a/src/services/activity_placer.rs +++ b/src/services/activity_placer.rs @@ -76,7 +76,6 @@ pub fn place(calendar: &mut Calendar, mut activities: Vec) -> Option<( (activities[act_index_to_schedule?]).release_claims(); } } - dbg!(&calendar); Some(()) } From 72bbd5f167471078eaf1d061840ad798dd4cb886 Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sat, 9 Mar 2024 19:29:48 +0200 Subject: [PATCH 27/31] fix: hidden clippy issue --- src/bin/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 940ef70a..fa422fc4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,6 +1,6 @@ use chrono::NaiveDateTime; use serde::Deserialize; -use serde_json::{self, Value}; +use serde_json::Value; use std::{fs, path::Path}; extern crate scheduler; use scheduler::{models::goal::Goal, run_scheduler}; From 1bf1222b0b130c415f4f54e52f375f04efefab4b Mon Sep 17 00:00:00 2001 From: Moaz bin Mokhtar Date: Sun, 10 Mar 2024 00:20:24 +0200 Subject: [PATCH 28/31] ref: update wasm-bindgen version to avoid CI error for wasm-bindgen --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd931de8..60e83fa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,9 +426,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -461,9 +461,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index ac8d46dc..fa8abc01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ chrono = { version = "0.4.31", features = ["wasmbind", "serde"] } lazy_static = "1.4.0" # simple WASM codegen -wasm-bindgen = { version = "0.2.84" } +wasm-bindgen = { version = "0.2.92" } # instead of wasm-bindgen::serde-serialize feature that may lead to a cyclic package dependency serde-wasm-bindgen = "0.5.0" From 17082c1e04172d3fd3a325c4b41265d3a8939b1b Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 19 Mar 2024 20:35:26 +0100 Subject: [PATCH 29/31] adjusting activities inside generate_activities --- src/lib.rs | 1 - src/services/activity_generator.rs | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e2fd9ee..d10569cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,6 @@ pub fn run_scheduler( //generate and place simple goal activities let simple_goal_activities = activity_generator::generate_simple_goal_activities(&calendar, goals); - let simple_goal_activities = adjust_parent_activities(&simple_goal_activities, goals); //generate and place budget goal activities let budget_goal_activities: Vec = diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index c4d1a5db..89902bc8 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -1,12 +1,13 @@ use crate::models::{activity::Activity, budget::TimeBudgetType, calendar::Calendar, goal::Goal}; pub fn generate_simple_goal_activities(calendar: &Calendar, goals: &[Goal]) -> Vec { - goals + let simple_goal_activities = goals .iter() .flat_map(|goal| { Activity::get_activities_from_simple_goal(goal, calendar, goal.get_parent_goal(goals)) }) - .collect::>() + .collect::>(); + adjust_parent_activities(&simple_goal_activities, goals) } pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &[Goal]) -> Vec { From 8f7d6de54e29e84f0d00ee9ea8415195329fb289 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 19 Mar 2024 20:36:22 +0100 Subject: [PATCH 30/31] adjust expected.json --- .../budget-and-goal-one-day/expected.json | 18 +++++------------- .../budget-and-goal-one-day/observed.json | 18 +++++------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/tests/jsons/stable/budget-and-goal-one-day/expected.json b/tests/jsons/stable/budget-and-goal-one-day/expected.json index 9d5fc189..0ec6d0a2 100644 --- a/tests/jsons/stable/budget-and-goal-one-day/expected.json +++ b/tests/jsons/stable/budget-and-goal-one-day/expected.json @@ -29,26 +29,18 @@ }, { "taskid": 3, - "goalid": "free", - "title": "free", - "duration": 2, - "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T15:00:00" - }, - { - "taskid": 4, "goalid": "2", "title": "work", "duration": 4, - "start": "2022-09-01T15:00:00", - "deadline": "2022-09-01T19:00:00" + "start": "2022-09-01T13:00:00", + "deadline": "2022-09-01T17:00:00" }, { - "taskid": 5, + "taskid": 4, "goalid": "free", "title": "free", - "duration": 5, - "start": "2022-09-01T19:00:00", + "duration": 7, + "start": "2022-09-01T17:00:00", "deadline": "2022-09-02T00:00:00" } ] diff --git a/tests/jsons/stable/budget-and-goal-one-day/observed.json b/tests/jsons/stable/budget-and-goal-one-day/observed.json index 9d5fc189..0ec6d0a2 100644 --- a/tests/jsons/stable/budget-and-goal-one-day/observed.json +++ b/tests/jsons/stable/budget-and-goal-one-day/observed.json @@ -29,26 +29,18 @@ }, { "taskid": 3, - "goalid": "free", - "title": "free", - "duration": 2, - "start": "2022-09-01T13:00:00", - "deadline": "2022-09-01T15:00:00" - }, - { - "taskid": 4, "goalid": "2", "title": "work", "duration": 4, - "start": "2022-09-01T15:00:00", - "deadline": "2022-09-01T19:00:00" + "start": "2022-09-01T13:00:00", + "deadline": "2022-09-01T17:00:00" }, { - "taskid": 5, + "taskid": 4, "goalid": "free", "title": "free", - "duration": 5, - "start": "2022-09-01T19:00:00", + "duration": 7, + "start": "2022-09-01T17:00:00", "deadline": "2022-09-02T00:00:00" } ] From e7db50426babc6d106b9607ff8ab660795c2fa10 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 19 Mar 2024 20:43:01 +0100 Subject: [PATCH 31/31] remove unused import --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d10569cc..abd49e7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,6 @@ use chrono::NaiveDateTime; use models::{activity::Activity, calendar::Calendar, goal::Goal, task::FinalTasks}; use serde_wasm_bindgen::{from_value, to_value}; use services::activity_generator; -use services::activity_generator::adjust_parent_activities; use services::activity_placer; use technical::input_output::Input; use wasm_bindgen::prelude::*;