diff --git a/src/lib.rs b/src/lib.rs index a6f71dc7..1fe8cd74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,9 @@ pub fn run_scheduler( // So MinDayBudget will get chosen last unless flex is equal and order happens to favor MinDayBudget // => TODO: order activities before placing? + base_activities = activity_placer::reset_postponed(base_activities); + base_activities = activity_placer::place(&mut calendar, base_activities); + calendar.log_impossible_base_activities(base_activities); calendar.print() diff --git a/src/models/activity.rs b/src/models/activity.rs index 2dcc7888..3c634795 100644 --- a/src/models/activity.rs +++ b/src/models/activity.rs @@ -131,8 +131,8 @@ impl Activity { flex } - pub fn get_best_scheduling_index_and_length(&self) -> Option<(usize, usize)> { - let mut best_scheduling_index_and_conflicts: Option<(usize, usize, usize)> = None; + pub fn get_best_scheduling_index_length_conflicts(&self) -> Option<(usize, usize, usize)> { + let mut best_scheduling_index_length_conflicts: Option<(usize, usize, usize)> = None; for hour_index in 0..self.calendar_overlay.len() { let mut conflicts = 0; if self.calendar_overlay[hour_index].is_some() { @@ -159,15 +159,15 @@ impl Activity { 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 { + match best_scheduling_index_length_conflicts { None => { - best_scheduling_index_and_conflicts = - Some((hour_index, conflicts, offset_size)); + best_scheduling_index_length_conflicts = + Some((hour_index, offset_size, conflicts)); } - Some((_, best_conflicts, _)) => { + Some((_, _, best_conflicts)) => { if conflicts < best_conflicts || conflicts == 0 { - best_scheduling_index_and_conflicts = - Some((hour_index, conflicts, offset_size)); + best_scheduling_index_length_conflicts = + Some((hour_index, offset_size, conflicts)); } } } @@ -177,7 +177,7 @@ impl Activity { } } } - best_scheduling_index_and_conflicts.map(|(best_index, _, size)| (best_index, size)) + best_scheduling_index_length_conflicts } pub(crate) fn get_simple_activities(goal: &Goal, calendar: &Calendar) -> Vec { @@ -533,6 +533,8 @@ pub enum Status { Processed, Scheduled, Impossible, + Postponed, + BestEffort, } #[derive(Clone, Debug, PartialEq)] diff --git a/src/services/activity_placer.rs b/src/services/activity_placer.rs index c9c9cfdc..d77777fd 100644 --- a/src/services/activity_placer.rs +++ b/src/services/activity_placer.rs @@ -11,18 +11,30 @@ pub fn place(calendar: &mut Calendar, mut activities: Vec) -> Vec 0 + && activities[act_index_to_schedule].deadline.is_none() + && !calendar.is_budget(activities[act_index_to_schedule].goal_id.clone()) + && activities[act_index_to_schedule].status != Status::BestEffort + { + println!( + "Postponing placement of {:?} - since no deadline and conflicts > 0...\n", + activities[act_index_to_schedule].title + ); + activities[act_index_to_schedule].status = Status::Postponed; + continue; + } println!("reserving {:?} hours...", best_size); for duration_offset in 0..best_size { Rc::make_mut(&mut calendar.hours[best_hour_index + duration_offset]); @@ -75,12 +87,18 @@ fn find_act_index_to_schedule(activities: &[Activity]) -> Option { if activities[index].status == Status::Scheduled || activities[index].status == Status::Impossible || activities[index].status == Status::Processed + || activities[index].status == Status::Postponed { continue; } + let current_act_flex = activities[index].flex(); + println!( + "Flex {:?} for {:?}", + current_act_flex, activities[index].title + ); match act_index_to_schedule { None => act_index_to_schedule = Some(index), - Some(_) => match activities[index].flex() { + Some(_) => match current_act_flex { 0 => { println!("Found activity index {:?} with flex 0...", &index); continue; @@ -94,7 +112,7 @@ fn find_act_index_to_schedule(activities: &[Activity]) -> Option { } } _ => { - if activities[act_index_to_schedule?].flex() < activities[index].flex() { + if activities[act_index_to_schedule?].flex() < current_act_flex { act_index_to_schedule = Some(index); } } @@ -103,3 +121,12 @@ fn find_act_index_to_schedule(activities: &[Activity]) -> Option { } act_index_to_schedule } + +pub(crate) fn reset_postponed(mut base_activities: Vec) -> Vec { + for activity in base_activities.iter_mut() { + if activity.status == Status::Postponed { + activity.status = Status::BestEffort; + } + } + base_activities +} diff --git a/tests/jsons/stable/conflict-without-deadline/expected.json b/tests/jsons/stable/conflict-without-deadline/expected.json new file mode 100644 index 00000000..c9bb47f8 --- /dev/null +++ b/tests/jsons/stable/conflict-without-deadline/expected.json @@ -0,0 +1,58 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "4", + "title": "not-anytime", + "duration": 1, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T01:00:00" + }, + { + "taskid": 1, + "goalid": "2", + "title": "anytime-without-deadline", + "duration": 1, + "start": "2022-01-01T01:00:00", + "deadline": "2022-01-01T02:00:00" + }, + { + "taskid": 2, + "goalid": "3", + "title": "anytime-without-deadline-2", + "duration": 1, + "start": "2022-01-01T02:00:00", + "deadline": "2022-01-01T03:00:00" + }, + { + "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 9, + "start": "2022-01-01T03:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 4, + "goalid": "1", + "title": "anytime-with-deadline", + "duration": 1, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-01T13:00:00" + }, + { + "taskid": 5, + "goalid": "free", + "title": "free", + "duration": 11, + "start": "2022-01-01T13:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file diff --git a/tests/jsons/stable/conflict-without-deadline/input.json b/tests/jsons/stable/conflict-without-deadline/input.json new file mode 100644 index 00000000..3b42bc50 --- /dev/null +++ b/tests/jsons/stable/conflict-without-deadline/input.json @@ -0,0 +1,32 @@ + { + "startDate": "2022-01-01T00:00:00", + "endDate": "2022-01-02T00:00:00", + "goals": [ + { + "id": "1", + "title": "anytime-with-deadline", + "minDuration": 1, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-02T00:00:00" + }, + { + "id": "2", + "title": "anytime-without-deadline", + "minDuration": 1, + "start": "2022-01-01T00:00:00" + }, + { + "id": "3", + "title": "anytime-without-deadline-2", + "minDuration": 1, + "start": "2022-01-01T00:00:00" + }, + { + "id": "4", + "title": "not-anytime", + "minDuration": 1, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T12:00:00" + } + ] + } \ No newline at end of file diff --git a/tests/jsons/stable/conflict-without-deadline/observed.json b/tests/jsons/stable/conflict-without-deadline/observed.json new file mode 100644 index 00000000..c9bb47f8 --- /dev/null +++ b/tests/jsons/stable/conflict-without-deadline/observed.json @@ -0,0 +1,58 @@ +{ + "scheduled": [ + { + "day": "2022-01-01", + "tasks": [ + { + "taskid": 0, + "goalid": "4", + "title": "not-anytime", + "duration": 1, + "start": "2022-01-01T00:00:00", + "deadline": "2022-01-01T01:00:00" + }, + { + "taskid": 1, + "goalid": "2", + "title": "anytime-without-deadline", + "duration": 1, + "start": "2022-01-01T01:00:00", + "deadline": "2022-01-01T02:00:00" + }, + { + "taskid": 2, + "goalid": "3", + "title": "anytime-without-deadline-2", + "duration": 1, + "start": "2022-01-01T02:00:00", + "deadline": "2022-01-01T03:00:00" + }, + { + "taskid": 3, + "goalid": "free", + "title": "free", + "duration": 9, + "start": "2022-01-01T03:00:00", + "deadline": "2022-01-01T12:00:00" + }, + { + "taskid": 4, + "goalid": "1", + "title": "anytime-with-deadline", + "duration": 1, + "start": "2022-01-01T12:00:00", + "deadline": "2022-01-01T13:00:00" + }, + { + "taskid": 5, + "goalid": "free", + "title": "free", + "duration": 11, + "start": "2022-01-01T13:00:00", + "deadline": "2022-01-02T00:00:00" + } + ] + } + ], + "impossible": [] +} \ No newline at end of file