From f1088070fdb896246979b7cfbbee609423ff5155 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 23 Jan 2024 19:43:05 +0100 Subject: [PATCH 01/28] add test budget-with-subgoal --- .../stable/budget-with-subgoal/expected.json | 184 +++++++++++ .../stable/budget-with-subgoal/input.json | 37 +++ .../stable/budget-with-subgoal/observed.json | 288 ++++++++++++++++++ 3 files changed, 509 insertions(+) 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 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..567138dd --- /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": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "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..8883287d --- /dev/null +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -0,0 +1,288 @@ +{ + "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": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "start": "2024-01-08T06:00:00", + "deadline": "2024-01-08T14:00:00" + }, + { + "taskid": 2, + "goalid": "free", + "title": "free", + "duration": 4, + "start": "2024-01-08T14:00:00", + "deadline": "2024-01-08T18:00:00" + }, + { + "taskid": 3, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-08T18:00:00", + "deadline": "2024-01-08T19:00:00" + }, + { + "taskid": 4, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-08T19:00:00", + "deadline": "2024-01-09T00:00:00" + } + ] + }, + { + "day": "2024-01-09", + "tasks": [ + { + "taskid": 5, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-09T00:00:00", + "deadline": "2024-01-09T06:00:00" + }, + { + "taskid": 6, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "start": "2024-01-09T06:00:00", + "deadline": "2024-01-09T14:00:00" + }, + { + "taskid": 7, + "goalid": "free", + "title": "free", + "duration": 4, + "start": "2024-01-09T14:00:00", + "deadline": "2024-01-09T18:00:00" + }, + { + "taskid": 8, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-09T18:00:00", + "deadline": "2024-01-09T19:00:00" + }, + { + "taskid": 9, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-09T19:00:00", + "deadline": "2024-01-10T00:00:00" + } + ] + }, + { + "day": "2024-01-10", + "tasks": [ + { + "taskid": 10, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-10T00:00:00", + "deadline": "2024-01-10T06:00:00" + }, + { + "taskid": 11, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "start": "2024-01-10T06:00:00", + "deadline": "2024-01-10T14:00:00" + }, + { + "taskid": 12, + "goalid": "free", + "title": "free", + "duration": 4, + "start": "2024-01-10T14:00:00", + "deadline": "2024-01-10T18:00:00" + }, + { + "taskid": 13, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-10T18:00:00", + "deadline": "2024-01-10T19:00:00" + }, + { + "taskid": 14, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-10T19:00:00", + "deadline": "2024-01-11T00:00:00" + } + ] + }, + { + "day": "2024-01-11", + "tasks": [ + { + "taskid": 15, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-11T00:00:00", + "deadline": "2024-01-11T06:00:00" + }, + { + "taskid": 16, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "start": "2024-01-11T06:00:00", + "deadline": "2024-01-11T14:00:00" + }, + { + "taskid": 17, + "goalid": "free", + "title": "free", + "duration": 4, + "start": "2024-01-11T14:00:00", + "deadline": "2024-01-11T18:00:00" + }, + { + "taskid": 18, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-11T18:00:00", + "deadline": "2024-01-11T19:00:00" + }, + { + "taskid": 19, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-11T19:00:00", + "deadline": "2024-01-12T00:00:00" + } + ] + }, + { + "day": "2024-01-12", + "tasks": [ + { + "taskid": 20, + "goalid": "free", + "title": "free", + "duration": 6, + "start": "2024-01-12T00:00:00", + "deadline": "2024-01-12T06:00:00" + }, + { + "taskid": 21, + "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", + "title": "Work 💪🏽", + "duration": 8, + "start": "2024-01-12T06:00:00", + "deadline": "2024-01-12T14:00:00" + }, + { + "taskid": 22, + "goalid": "free", + "title": "free", + "duration": 4, + "start": "2024-01-12T14:00:00", + "deadline": "2024-01-12T18:00:00" + }, + { + "taskid": 23, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-12T18:00:00", + "deadline": "2024-01-12T19:00:00" + }, + { + "taskid": 24, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-12T19:00:00", + "deadline": "2024-01-13T00:00:00" + } + ] + }, + { + "day": "2024-01-13", + "tasks": [ + { + "taskid": 25, + "goalid": "free", + "title": "free", + "duration": 18, + "start": "2024-01-13T00:00:00", + "deadline": "2024-01-13T18:00:00" + }, + { + "taskid": 26, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-13T18:00:00", + "deadline": "2024-01-13T19:00:00" + }, + { + "taskid": 27, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-13T19:00:00", + "deadline": "2024-01-14T00:00:00" + } + ] + }, + { + "day": "2024-01-14", + "tasks": [ + { + "taskid": 28, + "goalid": "free", + "title": "free", + "duration": 18, + "start": "2024-01-14T00:00:00", + "deadline": "2024-01-14T18:00:00" + }, + { + "taskid": 29, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Dinner 🍽️", + "duration": 1, + "start": "2024-01-14T18:00:00", + "deadline": "2024-01-14T19:00:00" + }, + { + "taskid": 30, + "goalid": "free", + "title": "free", + "duration": 5, + "start": "2024-01-14T19:00:00", + "deadline": "2024-01-15T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file From cb9eaa6cb460cc3fa6d4fe89889f7a18152ac3c7 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sat, 3 Feb 2024 09:36:54 +0100 Subject: [PATCH 02/28] undo duplicating function --- src/models/activity.rs | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index db440edf..7e17b1bd 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -284,9 +284,18 @@ impl Activity { ) -> Vec { let mut activities: Vec = vec![]; - let compatible_hours_overlay = - Self::get_compatible_overlay_hours(goal_to_use, calendar, time_budget); - + 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 { @@ -312,8 +321,18 @@ impl Activity { ) -> Vec { let mut activities: Vec = vec![]; - let compatible_hours_overlay = - Self::get_compatible_overlay_hours(goal_to_use, calendar, time_budget); + 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; @@ -437,25 +456,6 @@ impl Activity { } self.calendar_overlay = empty_overlay; } - - fn get_compatible_overlay_hours( - goal_to_use: &Goal, - calendar: &Calendar, - time_budget: &TimeBudget, - ) -> Vec>> { - 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)), - ) - } } #[derive(Debug, PartialEq, Clone, Deserialize)] From d41e998383a2384a50bd556374d1659fe39d1917 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Tue, 23 Jan 2024 21:45:33 +0100 Subject: [PATCH 03/28] add passing budget-and-one-goal test --- .../budget-and-goal-one-day/expected.json | 42 ---------------- .../budget-and-goal-one-day/expected.json | 50 +++++++++++++++++++ .../budget-and-goal-one-day/input.json | 28 +++++++---- .../budget-and-goal-one-day/observed.json | 31 ++++++------ 4 files changed, 85 insertions(+), 66 deletions(-) delete mode 100644 tests/jsons/stable.bak/budget-and-goal-one-day/expected.json 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%) 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/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 From e950aee705b8c5d45c0459c58a9e79ec5ad64256 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Tue, 23 Jan 2024 08:51:02 +0100 Subject: [PATCH 04/28] remove irrelevant code --- Cargo.toml | 4 - src/bin/flamegraph-bin.rs | 17 --- src/models/activity.rs | 226 ++++++++++++++--------------- 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 --------------------- 11 files changed, 113 insertions(+), 929 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 diff --git a/Cargo.toml b/Cargo.toml index a58a8bab..4a36a926 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/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/models/activity.rs b/src/models/activity.rs index a6af1186..3e382b56 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -177,13 +177,47 @@ 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); + pub(crate) fn get_activities_from_simple_goal( + goal: &Goal, + calendar: &Calendar, + ) -> Vec { + if goal.children.is_some() || goal.filters.as_ref().is_some() { + return vec![]; } - self.calendar_overlay = empty_overlay; + let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar); + let mut activities: Vec = Vec::with_capacity(1); + + let activity_total_duration = goal.min_duration.unwrap(); + 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 compatible_hours_overlay = Activity::get_compatible_hours_overlay( + calendar, + goal.filters.clone(), + adjusted_goal_start, + adjusted_goal_deadline, + ); + + 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, + 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? + status: Status::Unprocessed, + }; + dbg!(&activity); + activities.push(activity); + + activities } pub(crate) fn get_activities_from_budget_goal( @@ -246,45 +280,78 @@ impl Activity { activities } - pub(crate) fn get_activities_from_simple_goal( - goal: &Goal, + pub fn get_activities_to_get_min_week_budget( + goal_to_use: &Goal, calendar: &Calendar, + time_budget: &TimeBudget, ) -> Vec { - 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); - let mut activities: Vec = Vec::with_capacity(1); + let mut activities: Vec = vec![]; - let activity_total_duration = goal.min_duration.unwrap(); - 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 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.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)), ); - 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, - }; - dbg!(&activity); - activities.push(activity); + }); activities } @@ -302,10 +369,10 @@ impl Activity { 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() + .as_ref() + .unwrap() + .upgrade() + .is_none() { //block was stolen/lost to some other activity self.calendar_overlay[hour_index] = None; @@ -339,7 +406,7 @@ impl Activity { if block_size_found < self.min_block_size { // found block in calendar that is too small to fit min_block size for index_to_set_to_none in - self.calendar_overlay.len() - block_size_found..self.calendar_overlay.len() + self.calendar_overlay.len() - block_size_found..self.calendar_overlay.len() { self.calendar_overlay[index_to_set_to_none] = None; } @@ -389,82 +456,15 @@ 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(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 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 - } } #[derive(Debug, PartialEq, Clone, Deserialize)] 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); -} From 9fdf886240af89e4453c209484acf7de43ae6229 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Tue, 23 Jan 2024 09:24:56 +0100 Subject: [PATCH 05/28] remove multiple clone from filter_option --- src/models/activity.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 3e382b56..b5d7b8b9 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -39,29 +39,29 @@ impl Activity { let mut compatible = true; if filter_option.is_some() { - if filter_option.clone().unwrap().after_time - < filter_option.clone().unwrap().before_time + let filter_option = filter_option.clone().unwrap(); { - //normal case 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; - } - } 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.after_time + < filter_option.before_time { - compatible = false; + //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; + } } } if filter_option - .as_ref() - .unwrap() .on_days .contains(&calendar.get_week_day_of(hour_index)) { From 1c617c9e2b3b3abba3f4dcb1370721d943bad2f9 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Tue, 23 Jan 2024 09:30:57 +0100 Subject: [PATCH 06/28] remove unnecessary match nesting --- src/models/activity.rs | 64 +++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index b5d7b8b9..41484b64 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -127,46 +127,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; } } } From 5495f60a00733149e1f242dbb42387d3a0b7d11f Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Tue, 23 Jan 2024 09:36:11 +0100 Subject: [PATCH 07/28] remove code duplication --- src/models/activity.rs | 42 ++++++++++++++++++------------------------ src/models/calendar.rs | 32 ++++++++------------------------ 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 41484b64..26001c91 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -281,18 +281,8 @@ impl Activity { ) -> 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 compatible_hours_overlay = + Self::get_compatible_overlay_hours(goal_to_use, calendar, time_budget); let max_hours = time_budget.max_scheduled - time_budget.scheduled; @@ -319,18 +309,8 @@ impl Activity { ) -> 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 compatible_hours_overlay = + Self::get_compatible_overlay_hours(goal_to_use, calendar, time_budget); let max_hours = time_budget.max_scheduled - time_budget.scheduled; @@ -459,6 +439,20 @@ impl Activity { self.calendar_overlay = empty_overlay; } + fn get_compatible_overlay_hours(goal_to_use: &Goal, calendar: &Calendar, time_budget: &TimeBudget) -> Vec>> { + 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)), + ) + } } #[derive(Debug, PartialEq, Clone, Deserialize)] diff --git a/src/models/calendar.rs b/src/models/calendar.rs index b2619531..ee3c679a 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -281,37 +281,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,7 +314,7 @@ impl Calendar { } } } - self.impossible_activities.extend(impossible_activities); + impossible_activities } } impl Debug for Calendar { From b620a4df42f40be807b0873bfc4e2a66d4e4bb4b Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Tue, 23 Jan 2024 10:30:11 +0100 Subject: [PATCH 08/28] remove unwrap --- build_templates/tests_mod.rs | 7 +- src/lib.rs | 12 +- src/models/activity.rs | 220 +++++++++++++++-------------- src/models/budget.rs | 66 +++++---- src/models/calendar.rs | 93 ++++++------ src/services/activity_generator.rs | 33 ++--- src/services/activity_placer.rs | 52 +++---- 7 files changed, 244 insertions(+), 239 deletions(-) diff --git a/build_templates/tests_mod.rs b/build_templates/tests_mod.rs index 2461e46c..73f7379f 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -55,9 +55,10 @@ mod TEST_MODULE_NAME { 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); + if let Some(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(); diff --git a/src/lib.rs b/src/lib.rs index ded8e85c..c68b7bee 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,7 +79,7 @@ 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 input: Input = from_value(input.clone())?; let final_tasks = run_scheduler(input.start_date, input.end_date, input.goals); Ok(to_value(&final_tasks)?) } @@ -108,9 +108,11 @@ pub fn run_scheduler( 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(); diff --git a/src/models/activity.rs b/src/models/activity.rs index 26001c91..db440edf 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -39,12 +39,9 @@ impl Activity { let mut compatible = true; if filter_option.is_some() { - let filter_option = filter_option.clone().unwrap(); - { + if let Some(filter_option) = filter_option.clone() { let hour_of_day = hour_index % 24; - if filter_option.after_time - < filter_option.before_time - { + if filter_option.after_time < filter_option.before_time { //normal case if hour_of_day < filter_option.after_time { compatible = false; @@ -60,14 +57,14 @@ impl Activity { compatible = false; } } - } - if filter_option - .on_days - .contains(&calendar.get_week_day_of(hour_index)) - { - // OK - } else { - compatible = false; + if filter_option + .on_days + .contains(&calendar.get_week_day_of(hour_index)) + { + // OK + } else { + compatible = false; + } } } @@ -112,10 +109,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; + } } } } @@ -181,35 +178,36 @@ impl Activity { let (adjusted_goal_start, adjusted_goal_deadline) = goal.get_adj_start_deadline(calendar); let mut activities: Vec = Vec::with_capacity(1); - let activity_total_duration = goal.min_duration.unwrap(); - 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()? - }; + 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 compatible_hours_overlay = Activity::get_compatible_hours_overlay( - calendar, - goal.filters.clone(), - adjusted_goal_start, - adjusted_goal_deadline, - ); - - 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, - 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? - status: Status::Unprocessed, - }; - dbg!(&activity); - activities.push(activity); + let compatible_hours_overlay = Activity::get_compatible_hours_overlay( + calendar, + goal.filters.clone(), + adjusted_goal_start, + adjusted_goal_deadline, + ); + + 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, + 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? + status: Status::Unprocessed, + }; + dbg!(&activity); + activities.push(activity); + } activities } @@ -221,55 +219,60 @@ impl Activity { if goal.children.is_some() || goal.filters.as_ref().is_none() { return vec![]; } - if goal.budget_config.as_ref().unwrap().min_per_day == 0 { - 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); 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)); - - let compatible_hours_overlay = Activity::get_compatible_hours_overlay( - calendar, - Some(filter_option.clone()), - activity_start, - activity_deadline, - ); + 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, + ); + + 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: goal.budget_config.as_ref().unwrap().max_per_day, - 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, - status: Status::Unprocessed, - }; - dbg!(&activity); - activities.push(activity); + 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 } @@ -341,15 +344,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; + } } } @@ -380,7 +379,7 @@ impl Activity { if block_size_found < self.min_block_size { // found block in calendar that is too small to fit min_block size for index_to_set_to_none in - self.calendar_overlay.len() - block_size_found..self.calendar_overlay.len() + self.calendar_overlay.len() - block_size_found..self.calendar_overlay.len() { self.calendar_overlay[index_to_set_to_none] = None; } @@ -439,7 +438,11 @@ impl Activity { self.calendar_overlay = empty_overlay; } - fn get_compatible_overlay_hours(goal_to_use: &Goal, calendar: &Calendar, time_budget: &TimeBudget) -> Vec>> { + fn get_compatible_overlay_hours( + goal_to_use: &Goal, + calendar: &Calendar, + time_budget: &TimeBudget, + ) -> Vec>> { Activity::get_compatible_hours_overlay( calendar, goal_to_use.filters.clone(), @@ -473,18 +476,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!( @@ -494,9 +497,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 d2c403b0..78d88e89 100644 --- a/src/models/budget.rs +++ b/src/models/budget.rs @@ -105,8 +105,6 @@ impl Debug for TimeBudget { &self.min_scheduled, &self.max_scheduled ) - .unwrap(); - Ok(()) } } @@ -116,28 +114,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 ee3c679a..46a795c2 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 { @@ -202,8 +206,10 @@ impl Calendar { //check 1 let mut min_per_day_sum = 0; - for _ in goal.filters.clone().unwrap().on_days { - min_per_day_sum += budget_config.min_per_day; + 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); @@ -228,47 +234,42 @@ 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.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), + }); + 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), + }); + 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); } } } @@ -319,37 +320,35 @@ impl Calendar { } 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/services/activity_generator.rs b/src/services/activity_generator.rs index 5bf96a46..94150894 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -25,7 +25,7 @@ pub fn generate_budget_goal_activities(calendar: &Calendar, goals: &Vec) - 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; @@ -45,8 +45,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 @@ -64,7 +63,7 @@ pub fn generate_get_to_week_min_budget_activities( } } dbg!(&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( @@ -73,20 +72,18 @@ 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, + )); + } } } } diff --git a/src/services/activity_placer.rs b/src/services/activity_placer.rs index 2023b6c5..2a78fb39 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,25 +60,26 @@ 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); } dbg!(&calendar); + Some(()) } fn find_act_index_to_schedule(activities: &[Activity]) -> Option { @@ -98,7 +99,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); @@ -106,8 +107,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); } } From be48fc351029e423d3ac5b684e76406d30009da6 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Sun, 28 Jan 2024 07:29:35 +0100 Subject: [PATCH 09/28] refactor test setup --- build_templates/run_test.rs | 2 -- build_templates/tests_mod.rs | 38 ++---------------------------- scripts/cargo-check.sh | 2 +- src/bin/main.rs | 2 +- src/lib.rs | 14 +++++------ src/models/calendar.rs | 2 +- src/services/activity_generator.rs | 28 ++++++++-------------- 7 files changed, 22 insertions(+), 66 deletions(-) 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 73f7379f..f4b88977 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -7,12 +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, activity_placer}; - use scheduler::technical::input_output; + use scheduler::technical::input_output::Input; use std::path::Path; fn test(folder: &str) { @@ -36,37 +32,7 @@ mod TEST_MODULE_NAME { // 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); - 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(); - - if let Some(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 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/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/main.rs b/src/bin/main.rs index 1ff873a1..0dabc3e2 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -12,7 +12,7 @@ fn main() { dbg!(&json); let input: Input = serde_json::from_value(json).unwrap(); dbg!(&input); - run_scheduler(input.start_date, input.end_date, input.goals); + run_scheduler(input.start_date, input.end_date, &input.goals); } #[derive(Deserialize, Debug)] diff --git a/src/lib.rs b/src/lib.rs index c68b7bee..01b4fc04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,36 +80,36 @@ 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())?; - let final_tasks = run_scheduler(input.start_date, input.end_date, input.goals); + 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); dbg!(&calendar); - 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_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); + activity_generator::generate_budget_goal_activities(&calendar, goals); dbg!(&calendar); activity_placer::place(&mut calendar, budget_goal_activities); calendar.log_impossible_min_day_budgets(); if let Some(get_to_week_min_budget_activities) = - activity_generator::generate_get_to_week_min_budget_activities(&calendar, &goals) + activity_generator::generate_get_to_week_min_budget_activities(&calendar, goals) { activity_placer::place(&mut calendar, get_to_week_min_budget_activities); } @@ -118,7 +118,7 @@ pub fn run_scheduler( 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/calendar.rs b/src/models/calendar.rs index 46a795c2..f94431bd 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -194,7 +194,7 @@ 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![]; diff --git a/src/services/activity_generator.rs b/src/services/activity_generator.rs index 94150894..7d8923a5 100644 --- a/src/services/activity_generator.rs +++ b/src/services/activity_generator.rs @@ -1,25 +1,17 @@ use crate::models::{activity::Activity, budget::TimeBudgetType, calendar::Calendar, goal::Goal}; -pub fn generate_simple_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_simple_goal(goal, calendar); - dbg!(&goal_activities); - 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)) + .collect::>() } -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 +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( From 87aee54870e57eb86fdd03b3d95ea068aba6f4d4 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Sun, 28 Jan 2024 07:52:41 +0100 Subject: [PATCH 10/28] refactor pattern matching --- src/models/calendar.rs | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/models/calendar.rs b/src/models/calendar.rs index f94431bd..da13fd52 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -200,31 +200,31 @@ impl Calendar { 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; - 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); + //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 + ); + } - //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()); } } @@ -235,7 +235,7 @@ impl Calendar { //get the first children if any let mut descendants: Vec = vec![]; if let Some(goal) = goal_map.get(&budget_id) { - match goal.children.as_ref() { + match &goal.children { Some(children) => { descendants.append(children.clone().as_mut()); } From c388d8adfa9771c45f90cb0c90938bc41db89d55 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Mon, 29 Jan 2024 17:30:12 +0100 Subject: [PATCH 11/28] change the ordering of Concepts.md --- documentation/functional/Concepts.md | 38 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/documentation/functional/Concepts.md b/documentation/functional/Concepts.md index bb6d2961..79d42f8e 100644 --- a/documentation/functional/Concepts.md +++ b/documentation/functional/Concepts.md @@ -2,8 +2,16 @@ 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 forged into a WASM. + +**The scheduler translates the users goals into scheduled tasks.** + +### 1) Goal + +A Goal is the most important concept in ZinZen®. + 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: @@ -23,7 +31,10 @@ Goals are organized together with Budgets in a Directed Acyclical Graph (DAG) an - 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. -### 2) Budgets + + +### 2) Budget + 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. @@ -46,8 +57,17 @@ 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'. +### 3) Task + +Tasks are only relevant once _all_ scheduling is done. +At that point all scheduled Steps are either impossible or scheduled. + +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. + +### 4) Step -### 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. @@ -75,17 +95,11 @@ A Step with Duration 4 and a Timeline with one Slot of [8-14] can placed in 3 wa - OR 10-14 and thus has a flexibility of 3. -### 3) Slots +### 5) Slot + 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. - -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 From a7c00317e5cbe035f70753b2c8cebab6e3897776 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Mon, 29 Jan 2024 18:12:06 +0100 Subject: [PATCH 12/28] change Algorithm-Overview.md --- .../functional/Algorithm-Overview.md | 52 ++++--------------- documentation/functional/Concepts.md | 20 +++++-- 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/documentation/functional/Algorithm-Overview.md b/documentation/functional/Algorithm-Overview.md index 2525b2ad..ae1513a2 100644 --- a/documentation/functional/Algorithm-Overview.md +++ b/documentation/functional/Algorithm-Overview.md @@ -1,60 +1,30 @@ ## 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 spans from start to end date. -### 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) The Celendar places occupied Slots by the given Goals -### 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. +### 3) Simple Goal Activities are calculated -## Running the 'placing' algorithm +... and applied to the Calendar. -### 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. +... and applied to the Calendar. -### 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. +### 4) Based on the Budget's time -### 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. +... these activities are placed into the open Slots of the Calendar. +### 5) print??? // ToDo -### 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 79d42f8e..7d45dc06 100644 --- a/documentation/functional/Concepts.md +++ b/documentation/functional/Concepts.md @@ -8,7 +8,13 @@ The scheduler algorithm is just a transformation function forged into a WASM. **The scheduler translates the users goals into scheduled tasks.** -### 1) Goal +### 0) Calendar + +The calendar is the overaching datastructure which contains all scheduled tasks from a start date to an end date. + +In general, it helps calculating where Slots are occupied by tasks and results in possibly unplaceable tasks. + +### 2) Goal A Goal is the most important concept in ZinZen®. @@ -33,7 +39,7 @@ Goals are organized together with Budgets in a Directed Acyclical Graph (DAG) an -### 2) Budget +### 3) Budget 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. @@ -57,7 +63,11 @@ 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'. -### 3) Task +### 4) Activity + +Goals and Budgets are broken down and represented as activities in the Calendar. + +### 5) Task Tasks are only relevant once _all_ scheduling is done. At that point all scheduled Steps are either impossible or scheduled. @@ -66,7 +76,7 @@ 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. -### 4) Step +### 6) Step 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. @@ -95,7 +105,7 @@ A Step with Duration 4 and a Timeline with one Slot of [8-14] can placed in 3 wa - OR 10-14 and thus has a flexibility of 3. -### 5) Slot +### 7) Slot Slots are periods of time: [StartDateTime; EndDateTime[. Currently the granularity of Slots is in hours. From 9e1a76d3e2c62942e44656f7971139131f960f49 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Mon, 29 Jan 2024 18:16:05 +0100 Subject: [PATCH 13/28] remove all references of flamegraph --- documentation/technical/Profiling.md | 10 +--------- documentation/technical/Project-Structure.md | 4 ++-- documentation/technical/Troubleshooting.md | 10 ---------- 3 files changed, 3 insertions(+), 21 deletions(-) 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) - - From ce42ec427a59ecd9cabe9fa65640645f6a6a6bd3 Mon Sep 17 00:00:00 2001 From: Romeo Disca Date: Mon, 29 Jan 2024 18:22:13 +0100 Subject: [PATCH 14/28] get rid of Steps and Slots --- documentation/functional/Concepts.md | 51 ++++------------------------ 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/documentation/functional/Concepts.md b/documentation/functional/Concepts.md index 7d45dc06..b4607266 100644 --- a/documentation/functional/Concepts.md +++ b/documentation/functional/Concepts.md @@ -12,7 +12,7 @@ The scheduler algorithm is just a transformation function forged into a WASM. The calendar is the overaching datastructure which contains all scheduled tasks from a start date to an end date. -In general, it helps calculating where Slots are occupied by tasks and results in possibly unplaceable tasks. +In general, it helps calculating where Activities are occupied by tasks and results in possibly unplaceable tasks. ### 2) Goal @@ -25,7 +25,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: @@ -35,7 +35,7 @@ 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. @@ -55,7 +55,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 @@ -70,46 +70,9 @@ Goals and Budgets are broken down and represented as activities in the Calendar. ### 5) Task 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 Steps are then transformed into Tasks: -- Every Step becomes a Task +The Activities are then transformed into Tasks: +- Every Activity becomes a Task - Any Tasks for that 'touch' AND have the same Goal should be merged. -### 6) Step - -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. - -### 7) Slot - -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. - From d2883b4429f43cdec01553c680cdae2b23372c43 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Mon, 29 Jan 2024 19:15:18 +0100 Subject: [PATCH 15/28] fine-tune algorithm wording --- .../functional/Algorithm-Overview.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/documentation/functional/Algorithm-Overview.md b/documentation/functional/Algorithm-Overview.md index ae1513a2..67ee6f1b 100644 --- a/documentation/functional/Algorithm-Overview.md +++ b/documentation/functional/Algorithm-Overview.md @@ -4,24 +4,30 @@ This document contains a high-level overview of the scheduling algorithm. ### 1) The Calendar is created -... and spans from start to end date. +... and allocates Hours from start to end date. +Each Hour on the Calendar can be Free or Occupied. -### 2) The Celendar places occupied Slots by the given Goals +### 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. -### 3) Simple Goal Activities are calculated +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. -... and applied to the Calendar. +### 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 +} ### 3) Budget Goal Activities are calculated -... and applied to the Calendar. - -### 4) Based on the Budget's time - -... these activities are placed into the open Slots of the Calendar. - -### 5) print??? // ToDo +Same loop as in 3 +### 5) Calendar print +Sequentially step through all the Hours on the Calendar and group sequential hours occupied by the same Activity as a Task. ## Why this algorithm? From d8dba778f2471bffe8e18d7578714d8e820472fa Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Mon, 29 Jan 2024 19:21:36 +0100 Subject: [PATCH 16/28] fine-tune Concepts --- documentation/functional/Concepts.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/documentation/functional/Concepts.md b/documentation/functional/Concepts.md index b4607266..ae43281f 100644 --- a/documentation/functional/Concepts.md +++ b/documentation/functional/Concepts.md @@ -4,20 +4,17 @@ To move this doc page to Cargo docs there is [issue #414](https://github.com/tij ## Overall Concept -The scheduler algorithm is just a transformation function forged into a WASM. +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 scheduled tasks from a start date to an end date. - -In general, it helps calculating where Activities are occupied by tasks and results in possibly unplaceable tasks. +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 the most important concept in ZinZen®. - 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: @@ -65,14 +62,13 @@ The min-max per week has to be compatible with the min-max per day in combinatio ### 4) Activity -Goals and Budgets are broken down and represented as activities in the Calendar. +Goals and Budgets are both broken down and represented as Activities to be placed on the Calendar by the activity_placer. ### 5) Task Tasks are only relevant once _all_ scheduling is done. At that point all scheduled Activities are either impossible or scheduled. -The Activities are then transformed into Tasks: -- Every Activity becomes a Task -- Any Tasks for that 'touch' AND have the same Goal should be merged. +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. From 24c23e6080a037c640d6e24a340a662989bcbec4 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Mon, 29 Jan 2024 19:25:50 +0100 Subject: [PATCH 17/28] update broken license reference --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a36a926..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" From 5695a4ec0a0ce9c48e6d3cc49f4f83d7ab5df70a Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 28 Jan 2024 09:04:18 +0100 Subject: [PATCH 18/28] remove confusing deserialize from output structs --- build_templates/tests_mod.rs | 5 +---- src/models/task.rs | 8 ++++---- src/technical/input_output.rs | 9 +++------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/build_templates/tests_mod.rs b/build_templates/tests_mod.rs index f4b88977..aa3a9507 100644 --- a/build_templates/tests_mod.rs +++ b/build_templates/tests_mod.rs @@ -27,11 +27,8 @@ 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(); + let desired_output: String = input_output::get_output_string_from_json(output_path); - // ONLY do this if expected is malformatted ... check that contents don't change! - // input_output::write_to_file(output_path, &desired_output).unwrap(); 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/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/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> { From 682e2e2c0d0569b5ff6242cd0c43e6be7d38e6bf Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 07:32:20 +0100 Subject: [PATCH 19/28] fix unwrap bug --- src/models/calendar.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/models/calendar.rs b/src/models/calendar.rs index da13fd52..c9169a1c 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -270,6 +270,24 @@ impl Calendar { } } } + let descendant_of_which_to_add_children = descendants.pop().unwrap(); + if goal_map + .get(&descendant_of_which_to_add_children) + .unwrap() + .children + .is_some() + { + 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); } } } From 5c7b135e7d81688283d579c56702c571b68ed7ac Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 07:35:35 +0100 Subject: [PATCH 20/28] correct expectation --- tests/jsons/stable/budget-with-subgoal/expected.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/jsons/stable/budget-with-subgoal/expected.json b/tests/jsons/stable/budget-with-subgoal/expected.json index 567138dd..9f16214f 100644 --- a/tests/jsons/stable/budget-with-subgoal/expected.json +++ b/tests/jsons/stable/budget-with-subgoal/expected.json @@ -13,9 +13,9 @@ }, { "taskid": 1, - "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", - "title": "Work 💪🏽", - "duration": 8, + "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" }, From 2f7e668979c3b767cfd3b9593a21c4a07d579400 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 07:36:04 +0100 Subject: [PATCH 21/28] update observed --- .../stable/budget-with-subgoal/observed.json | 215 ++++++------------ 1 file changed, 74 insertions(+), 141 deletions(-) diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json index 8883287d..4fd1ac12 100644 --- a/tests/jsons/stable/budget-with-subgoal/observed.json +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -5,42 +5,34 @@ "tasks": [ { "taskid": 0, + "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", + "title": "Plan my work week", + "duration": 1, + "start": "2024-01-08T00:00:00", + "deadline": "2024-01-08T01:00:00" + }, + { + "taskid": 1, "goalid": "free", "title": "free", - "duration": 6, - "start": "2024-01-08T00:00:00", + "duration": 5, + "start": "2024-01-08T01:00:00", "deadline": "2024-01-08T06:00:00" }, { - "taskid": 1, + "taskid": 2, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", "duration": 8, "start": "2024-01-08T06:00:00", "deadline": "2024-01-08T14:00:00" }, - { - "taskid": 2, - "goalid": "free", - "title": "free", - "duration": 4, - "start": "2024-01-08T14:00:00", - "deadline": "2024-01-08T18:00:00" - }, { "taskid": 3, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-08T18:00:00", - "deadline": "2024-01-08T19:00:00" - }, - { - "taskid": 4, "goalid": "free", "title": "free", - "duration": 5, - "start": "2024-01-08T19:00:00", + "duration": 10, + "start": "2024-01-08T14:00:00", "deadline": "2024-01-09T00:00:00" } ] @@ -49,7 +41,7 @@ "day": "2024-01-09", "tasks": [ { - "taskid": 5, + "taskid": 4, "goalid": "free", "title": "free", "duration": 6, @@ -57,7 +49,7 @@ "deadline": "2024-01-09T06:00:00" }, { - "taskid": 6, + "taskid": 5, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", "duration": 8, @@ -65,27 +57,11 @@ "deadline": "2024-01-09T14:00:00" }, { - "taskid": 7, + "taskid": 6, "goalid": "free", "title": "free", - "duration": 4, + "duration": 10, "start": "2024-01-09T14:00:00", - "deadline": "2024-01-09T18:00:00" - }, - { - "taskid": 8, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-09T18:00:00", - "deadline": "2024-01-09T19:00:00" - }, - { - "taskid": 9, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-09T19:00:00", "deadline": "2024-01-10T00:00:00" } ] @@ -94,7 +70,7 @@ "day": "2024-01-10", "tasks": [ { - "taskid": 10, + "taskid": 7, "goalid": "free", "title": "free", "duration": 6, @@ -102,7 +78,7 @@ "deadline": "2024-01-10T06:00:00" }, { - "taskid": 11, + "taskid": 8, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", "duration": 8, @@ -110,27 +86,11 @@ "deadline": "2024-01-10T14:00:00" }, { - "taskid": 12, + "taskid": 9, "goalid": "free", "title": "free", - "duration": 4, + "duration": 10, "start": "2024-01-10T14:00:00", - "deadline": "2024-01-10T18:00:00" - }, - { - "taskid": 13, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-10T18:00:00", - "deadline": "2024-01-10T19:00:00" - }, - { - "taskid": 14, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-10T19:00:00", "deadline": "2024-01-11T00:00:00" } ] @@ -139,7 +99,7 @@ "day": "2024-01-11", "tasks": [ { - "taskid": 15, + "taskid": 10, "goalid": "free", "title": "free", "duration": 6, @@ -147,7 +107,7 @@ "deadline": "2024-01-11T06:00:00" }, { - "taskid": 16, + "taskid": 11, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", "duration": 8, @@ -155,27 +115,11 @@ "deadline": "2024-01-11T14:00:00" }, { - "taskid": 17, + "taskid": 12, "goalid": "free", "title": "free", - "duration": 4, + "duration": 10, "start": "2024-01-11T14:00:00", - "deadline": "2024-01-11T18:00:00" - }, - { - "taskid": 18, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-11T18:00:00", - "deadline": "2024-01-11T19:00:00" - }, - { - "taskid": 19, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-11T19:00:00", "deadline": "2024-01-12T00:00:00" } ] @@ -184,7 +128,7 @@ "day": "2024-01-12", "tasks": [ { - "taskid": 20, + "taskid": 13, "goalid": "free", "title": "free", "duration": 6, @@ -192,35 +136,19 @@ "deadline": "2024-01-12T06:00:00" }, { - "taskid": 21, + "taskid": 14, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", - "duration": 8, + "duration": 7, "start": "2024-01-12T06:00:00", - "deadline": "2024-01-12T14:00:00" - }, - { - "taskid": 22, - "goalid": "free", - "title": "free", - "duration": 4, - "start": "2024-01-12T14:00:00", - "deadline": "2024-01-12T18:00:00" + "deadline": "2024-01-12T13:00:00" }, { - "taskid": 23, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-12T18:00:00", - "deadline": "2024-01-12T19:00:00" - }, - { - "taskid": 24, + "taskid": 15, "goalid": "free", "title": "free", - "duration": 5, - "start": "2024-01-12T19:00:00", + "duration": 11, + "start": "2024-01-12T13:00:00", "deadline": "2024-01-13T00:00:00" } ] @@ -229,27 +157,11 @@ "day": "2024-01-13", "tasks": [ { - "taskid": 25, + "taskid": 16, "goalid": "free", "title": "free", - "duration": 18, + "duration": 24, "start": "2024-01-13T00:00:00", - "deadline": "2024-01-13T18:00:00" - }, - { - "taskid": 26, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-13T18:00:00", - "deadline": "2024-01-13T19:00:00" - }, - { - "taskid": 27, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-13T19:00:00", "deadline": "2024-01-14T00:00:00" } ] @@ -258,31 +170,52 @@ "day": "2024-01-14", "tasks": [ { - "taskid": 28, + "taskid": 17, "goalid": "free", "title": "free", - "duration": 18, + "duration": 24, "start": "2024-01-14T00:00:00", - "deadline": "2024-01-14T18:00:00" - }, - { - "taskid": 29, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Dinner 🍽️", - "duration": 1, - "start": "2024-01-14T18:00:00", - "deadline": "2024-01-14T19:00:00" - }, - { - "taskid": 30, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-14T19:00:00", "deadline": "2024-01-15T00:00:00" } ] } ], - "impossible": [] + "impossible": [ + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 5, + "periodStartDateTime": "2024-01-09T00:00:00", + "periodEndDateTime": "2024-01-10T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-10T00:00:00", + "periodEndDateTime": "2024-01-11T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-11T00:00:00", + "periodEndDateTime": "2024-01-12T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-12T00:00:00", + "periodEndDateTime": "2024-01-13T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-13T00:00:00", + "periodEndDateTime": "2024-01-14T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 39, + "periodStartDateTime": "2024-01-09T00:00:00", + "periodEndDateTime": "2024-01-16T00:00:00" + } + ] } \ No newline at end of file From bff07eef980af3a80c71fa56d60a993fc10dd3bd Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 07:49:28 +0100 Subject: [PATCH 22/28] bundle generate activities so conflicts are clear --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 01b4fc04..1e00849d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,12 +98,15 @@ pub fn run_scheduler( 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!(&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(); From 30b0d25067b2425552b33d2f48cd30a170f3b7e4 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 09:11:28 +0100 Subject: [PATCH 23/28] this was already fixed nicer :) --- src/models/calendar.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/models/calendar.rs b/src/models/calendar.rs index 17ac03bb..da13fd52 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -270,26 +270,6 @@ impl Calendar { } } } - - let descendant_of_which_to_add_children = descendants.pop().unwrap(); - if goal_map - .get(&descendant_of_which_to_add_children) - .unwrap() - .children - .is_some() - { - 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); - } } } From 17fb43baafdf2bddd4969c60ff0826c5c7f62a94 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 09:12:28 +0100 Subject: [PATCH 24/28] update observed --- .../stable/budget-with-subgoal/observed.json | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json index 4fd1ac12..20208bd1 100644 --- a/tests/jsons/stable/budget-with-subgoal/observed.json +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -139,16 +139,16 @@ "taskid": 14, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", - "duration": 7, + "duration": 8, "start": "2024-01-12T06:00:00", - "deadline": "2024-01-12T13:00:00" + "deadline": "2024-01-12T14:00:00" }, { "taskid": 15, "goalid": "free", "title": "free", - "duration": 11, - "start": "2024-01-12T13:00:00", + "duration": 10, + "start": "2024-01-12T14:00:00", "deadline": "2024-01-13T00:00:00" } ] @@ -183,7 +183,7 @@ "impossible": [ { "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 5, + "hoursMissing": 6, "periodStartDateTime": "2024-01-09T00:00:00", "periodEndDateTime": "2024-01-10T00:00:00" }, @@ -213,9 +213,33 @@ }, { "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 39, + "hoursMissing": 6, "periodStartDateTime": "2024-01-09T00:00:00", - "periodEndDateTime": "2024-01-16T00:00:00" + "periodEndDateTime": "2024-01-10T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-10T00:00:00", + "periodEndDateTime": "2024-01-11T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-11T00:00:00", + "periodEndDateTime": "2024-01-12T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-12T00:00:00", + "periodEndDateTime": "2024-01-13T00:00:00" + }, + { + "id": "678eab49-960e-4519-ad0b-031a2f22aaba", + "hoursMissing": 6, + "periodStartDateTime": "2024-01-13T00:00:00", + "periodEndDateTime": "2024-01-14T00:00:00" } ] } \ No newline at end of file From cb710f32578d6e9933fa64d036ca81712c9f19be Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 12:48:43 +0100 Subject: [PATCH 25/28] budget children should participate in budget --- src/models/calendar.rs | 2 +- tests/jsons/stable/budget-with-subgoal/observed.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/models/calendar.rs b/src/models/calendar.rs index da13fd52..9ab364ca 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -266,8 +266,8 @@ impl Calendar { 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); } + descendants_added.push(descendant_of_which_to_add_children); } } } diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json index 20208bd1..a76ad583 100644 --- a/tests/jsons/stable/budget-with-subgoal/observed.json +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -139,16 +139,16 @@ "taskid": 14, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", - "duration": 8, + "duration": 7, "start": "2024-01-12T06:00:00", - "deadline": "2024-01-12T14:00:00" + "deadline": "2024-01-12T13:00:00" }, { "taskid": 15, "goalid": "free", "title": "free", - "duration": 10, - "start": "2024-01-12T14:00:00", + "duration": 11, + "start": "2024-01-12T13:00:00", "deadline": "2024-01-13T00:00:00" } ] @@ -183,7 +183,7 @@ "impossible": [ { "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, + "hoursMissing": 5, "periodStartDateTime": "2024-01-09T00:00:00", "periodEndDateTime": "2024-01-10T00:00:00" }, @@ -213,7 +213,7 @@ }, { "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, + "hoursMissing": 5, "periodStartDateTime": "2024-01-09T00:00:00", "periodEndDateTime": "2024-01-10T00:00:00" }, From 6c0115290a448c334917bde24e9b423e0cf6bd72 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 15:48:18 +0100 Subject: [PATCH 26/28] add filters to calendar.budget so it can be used in overlay generation --- src/models/activity.rs | 4 ++- src/models/budget.rs | 7 ++++- src/models/calendar.rs | 11 +++++++ .../stable/budget-with-subgoal/observed.json | 30 +++++++++---------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 7e17b1bd..54f23b30 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -186,9 +186,11 @@ impl Activity { // 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, - goal.filters.clone(), + filters_option, adjusted_goal_start, adjusted_goal_deadline, ); diff --git a/src/models/budget.rs b/src/models/budget.rs index 78d88e89..80f4852d 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) { diff --git a/src/models/calendar.rs b/src/models/calendar.rs index 9ab364ca..429e2239 100644 --- a/src/models/calendar.rs +++ b/src/models/calendar.rs @@ -244,6 +244,7 @@ impl Calendar { 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; } @@ -258,6 +259,7 @@ impl Calendar { 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; } @@ -317,6 +319,15 @@ impl Calendar { } 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 { diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json index a76ad583..b77aee7f 100644 --- a/tests/jsons/stable/budget-with-subgoal/observed.json +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -5,26 +5,26 @@ "tasks": [ { "taskid": 0, - "goalid": "103b2eff-6ba5-47b5-ad4f-81d8e8ef5998", - "title": "Plan my work week", - "duration": 1, + "goalid": "free", + "title": "free", + "duration": 6, "start": "2024-01-08T00:00:00", - "deadline": "2024-01-08T01:00:00" + "deadline": "2024-01-08T06:00:00" }, { "taskid": 1, - "goalid": "free", - "title": "free", - "duration": 5, - "start": "2024-01-08T01:00:00", - "deadline": "2024-01-08T06:00:00" + "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": 8, - "start": "2024-01-08T06:00:00", + "duration": 7, + "start": "2024-01-08T07:00:00", "deadline": "2024-01-08T14:00:00" }, { @@ -139,16 +139,16 @@ "taskid": 14, "goalid": "678eab49-960e-4519-ad0b-031a2f22aaba", "title": "Work 💪🏽", - "duration": 7, + "duration": 8, "start": "2024-01-12T06:00:00", - "deadline": "2024-01-12T13:00:00" + "deadline": "2024-01-12T14:00:00" }, { "taskid": 15, "goalid": "free", "title": "free", - "duration": 11, - "start": "2024-01-12T13:00:00", + "duration": 10, + "start": "2024-01-12T14:00:00", "deadline": "2024-01-13T00:00:00" } ] From faffdf10337cecc398a0a37c23eb193c9f8aae00 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sun, 4 Feb 2024 15:59:25 +0100 Subject: [PATCH 27/28] don't skip a goal only because it has children --- src/models/activity.rs | 2 +- .../stable/budget-with-subgoal/observed.json | 63 +------------------ 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 54f23b30..70de973a 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -218,7 +218,7 @@ impl Activity { goal: &Goal, calendar: &Calendar, ) -> Vec { - if goal.children.is_some() || goal.filters.as_ref().is_none() { + if goal.filters.as_ref().is_none() { return vec![]; } if let Some(config) = &goal.budget_config { diff --git a/tests/jsons/stable/budget-with-subgoal/observed.json b/tests/jsons/stable/budget-with-subgoal/observed.json index b77aee7f..9f16214f 100644 --- a/tests/jsons/stable/budget-with-subgoal/observed.json +++ b/tests/jsons/stable/budget-with-subgoal/observed.json @@ -180,66 +180,5 @@ ] } ], - "impossible": [ - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 5, - "periodStartDateTime": "2024-01-09T00:00:00", - "periodEndDateTime": "2024-01-10T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-10T00:00:00", - "periodEndDateTime": "2024-01-11T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-11T00:00:00", - "periodEndDateTime": "2024-01-12T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-12T00:00:00", - "periodEndDateTime": "2024-01-13T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-13T00:00:00", - "periodEndDateTime": "2024-01-14T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 5, - "periodStartDateTime": "2024-01-09T00:00:00", - "periodEndDateTime": "2024-01-10T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-10T00:00:00", - "periodEndDateTime": "2024-01-11T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-11T00:00:00", - "periodEndDateTime": "2024-01-12T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-12T00:00:00", - "periodEndDateTime": "2024-01-13T00:00:00" - }, - { - "id": "678eab49-960e-4519-ad0b-031a2f22aaba", - "hoursMissing": 6, - "periodStartDateTime": "2024-01-13T00:00:00", - "periodEndDateTime": "2024-01-14T00:00:00" - } - ] + "impossible": [] } \ No newline at end of file From 360e2d1f739852fa4c896291fc7ac2eeeda9b975 Mon Sep 17 00:00:00 2001 From: Tijl Leenders Date: Sat, 17 Feb 2024 09:48:00 +0100 Subject: [PATCH 28/28] cargo fmt --- src/models/activity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/activity.rs b/src/models/activity.rs index 827c1675..2b57ce81 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -309,7 +309,7 @@ impl Activity { .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(), + goal_to_use.not_on.clone(), ); let max_hours = time_budget.max_scheduled - time_budget.scheduled; @@ -347,7 +347,7 @@ impl Activity { .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(), + goal_to_use.not_on.clone(), ); let max_hours = time_budget.max_scheduled - time_budget.scheduled;