Remove min by shift type boundaries, improve logging, add more tests, add logs in tests, move fixtures in separate file, move restrictions_violated logic into valid_residents of next slot
This commit is contained in:
@@ -2,9 +2,9 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
resident::ResidentId,
|
||||
resident::{Resident, ResidentId},
|
||||
schedule::ShiftType,
|
||||
slot::{Day, Slot},
|
||||
slot::Slot,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -12,175 +12,91 @@ pub struct WorkloadBounds {
|
||||
pub max_workloads: HashMap<ResidentId, u8>,
|
||||
pub max_holiday_shifts: HashMap<ResidentId, u8>,
|
||||
pub max_by_shift_type: HashMap<(ResidentId, ShiftType), u8>,
|
||||
pub min_by_shift_type: HashMap<(ResidentId, ShiftType), u8>,
|
||||
}
|
||||
|
||||
impl WorkloadBounds {
|
||||
pub fn new_with_config(config: &UserConfig) -> Self {
|
||||
let residents = &config.residents;
|
||||
let total_slots = config.total_slots;
|
||||
let total_holiday_slots = config.total_holiday_slots;
|
||||
|
||||
let mut bounds = Self::default();
|
||||
bounds.calculate_max_workloads(config);
|
||||
bounds.calculate_max_holiday_shifts(config);
|
||||
bounds.calculate_max_by_shift_type(config);
|
||||
bounds.calculate_max_workloads(residents, total_slots);
|
||||
debug_assert!(bounds.max_workloads.values().sum::<u8>() >= total_slots);
|
||||
bounds.calculate_max_holiday_shifts(residents, total_holiday_slots);
|
||||
debug_assert!(bounds.max_holiday_shifts.values().sum::<u8>() >= total_holiday_slots);
|
||||
bounds.calculate_max_by_shift_type(residents);
|
||||
debug_assert!(bounds.max_by_shift_type.values().sum::<u8>() >= total_slots);
|
||||
bounds
|
||||
}
|
||||
|
||||
/// get map with total amount of slots in a month for each type of shift
|
||||
pub fn get_initial_supply(&self, config: &UserConfig) -> HashMap<ShiftType, u8> {
|
||||
let mut supply = HashMap::new();
|
||||
let total_days = config.total_days;
|
||||
|
||||
for d in 1..=total_days {
|
||||
if Day(d).is_open_shift() {
|
||||
*supply.entry(ShiftType::OpenFirst).or_insert(0) += 1;
|
||||
*supply.entry(ShiftType::OpenSecond).or_insert(0) += 1;
|
||||
} else {
|
||||
*supply.entry(ShiftType::Closed).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
supply
|
||||
}
|
||||
|
||||
/// this is called after the user config params have been initialized, can be done with the builder (lite) pattern
|
||||
/// initialize a hashmap for O(1) search calls for the residents' max workload
|
||||
pub fn calculate_max_workloads(&mut self, config: &UserConfig) {
|
||||
let auto_computed_residents: Vec<_> = config
|
||||
.residents
|
||||
pub fn calculate_max_workloads(&mut self, residents: &Vec<Resident>, total_slots: u8) {
|
||||
let non_manual_residents: Vec<_> = residents
|
||||
.iter()
|
||||
.filter(|r| r.max_shifts.is_none())
|
||||
.collect();
|
||||
|
||||
// if all residents have a manually set max shifts size, just use those values for the max workload
|
||||
if auto_computed_residents.is_empty() {
|
||||
for r in &config.residents {
|
||||
// all residents' max workload were manually inserted
|
||||
if non_manual_residents.is_empty() {
|
||||
for r in residents {
|
||||
self.max_workloads.insert(r.id, r.max_shifts.unwrap_or(0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Untested scenario: Resident has manual max_shifts and also reduced workload flag
|
||||
// Probably should forbid using both options from GUI
|
||||
let total_manual_workload: u8 = residents.iter().map(|r| r.max_shifts.unwrap_or(0)).sum();
|
||||
let remaining_slots = total_slots - total_manual_workload;
|
||||
let workload_share = remaining_slots.div_ceil(non_manual_residents.len() as u8);
|
||||
|
||||
let manual_max_shifts_sum: u8 = config
|
||||
.residents
|
||||
.iter()
|
||||
.map(|r| r.max_shifts.unwrap_or(0))
|
||||
.sum();
|
||||
|
||||
let max_shifts_ceiling = ((config.total_slots - manual_max_shifts_sum) as f32
|
||||
/ auto_computed_residents.len() as f32)
|
||||
.ceil() as u8;
|
||||
|
||||
for r in &config.residents {
|
||||
let max_shifts = match r.max_shifts {
|
||||
Some(shifts) => shifts,
|
||||
None if r.reduced_load => max_shifts_ceiling - 1,
|
||||
None => max_shifts_ceiling,
|
||||
for r in residents {
|
||||
let max_workload = match r.max_shifts {
|
||||
Some(max_shifts) => max_shifts,
|
||||
None if r.reduced_load => workload_share - 1,
|
||||
None => workload_share,
|
||||
};
|
||||
self.max_workloads.insert(r.id, max_shifts);
|
||||
self.max_workloads.insert(r.id, max_workload);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_max_holiday_shifts(&mut self, config: &UserConfig) {
|
||||
let total_slots = config.total_slots;
|
||||
let total_holiday_slots = config.total_holiday_slots;
|
||||
|
||||
for r in &config.residents {
|
||||
let workload_limit = *self.max_workloads.get(&r.id).unwrap_or(&0);
|
||||
|
||||
let share = (workload_limit as f32 / total_slots as f32) * total_holiday_slots as f32;
|
||||
let holiday_limit = share.ceil() as u8;
|
||||
|
||||
self.max_holiday_shifts.insert(r.id, holiday_limit);
|
||||
pub fn calculate_max_holiday_shifts(
|
||||
&mut self,
|
||||
residents: &Vec<Resident>,
|
||||
total_holiday_slots: u8,
|
||||
) {
|
||||
let total_residents = residents.len();
|
||||
let holiday_share = total_holiday_slots.div_ceil(total_residents as u8);
|
||||
for r in residents {
|
||||
self.max_holiday_shifts.insert(r.id, holiday_share);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_max_by_shift_type(&mut self, config: &UserConfig) {
|
||||
let mut supply_by_shift_type = self.get_initial_supply(config);
|
||||
let mut local_limits = HashMap::new();
|
||||
let mut local_thresholds = HashMap::new();
|
||||
|
||||
let all_shift_types = [
|
||||
pub fn calculate_max_by_shift_type(&mut self, residents: &Vec<Resident>) {
|
||||
let mut upper_limits = HashMap::new();
|
||||
let shift_types = [
|
||||
ShiftType::OpenFirst,
|
||||
ShiftType::OpenSecond,
|
||||
ShiftType::Closed,
|
||||
];
|
||||
|
||||
// residents with 1 available shift types
|
||||
for res in config
|
||||
.residents
|
||||
.iter()
|
||||
.filter(|r| r.allowed_types.len() == 1)
|
||||
{
|
||||
let shift_type = res.allowed_types[0];
|
||||
let total_limit = *self.max_workloads.get(&res.id).unwrap_or(&0);
|
||||
for r in residents {
|
||||
let total_limit = *self.max_workloads.get(&r.id).unwrap_or(&0);
|
||||
let n_allowed = r.allowed_types.len();
|
||||
|
||||
local_limits.insert((res.id, shift_type), total_limit);
|
||||
local_thresholds.insert((res.id, shift_type), total_limit.saturating_sub(2));
|
||||
|
||||
for other_type in all_shift_types {
|
||||
if other_type != shift_type {
|
||||
local_limits.insert((res.id, other_type), 0);
|
||||
local_thresholds.insert((res.id, other_type), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s) = supply_by_shift_type.get_mut(&shift_type) {
|
||||
*s = s.saturating_sub(total_limit)
|
||||
}
|
||||
}
|
||||
|
||||
// residents with 2 available shift types
|
||||
for res in config
|
||||
.residents
|
||||
.iter()
|
||||
.filter(|r| r.allowed_types.len() == 2)
|
||||
{
|
||||
let total_limit = *self.max_workloads.get(&res.id).unwrap_or(&0);
|
||||
let per_type = ((total_limit as f32) / 2.0).ceil() as u8;
|
||||
|
||||
let deduct_amount = (total_limit as f32 / 2.0) as u8;
|
||||
|
||||
for shift_type in all_shift_types {
|
||||
if res.allowed_types.contains(&shift_type) {
|
||||
local_limits.insert((res.id, shift_type), per_type);
|
||||
local_thresholds.insert((res.id, shift_type), per_type.saturating_sub(2));
|
||||
if let Some(s) = supply_by_shift_type.get_mut(&shift_type) {
|
||||
*s = s.saturating_sub(deduct_amount);
|
||||
for shift_type in shift_types {
|
||||
let limit = if r.allowed_types.contains(&shift_type) {
|
||||
if n_allowed == 1 {
|
||||
total_limit
|
||||
} else {
|
||||
(total_limit as f32 / n_allowed as f32).floor() as u8 + 1
|
||||
}
|
||||
} else {
|
||||
local_limits.insert((res.id, shift_type), 0);
|
||||
local_thresholds.insert((res.id, shift_type), 0);
|
||||
}
|
||||
0
|
||||
};
|
||||
|
||||
upper_limits.insert((r.id, shift_type), limit);
|
||||
}
|
||||
}
|
||||
|
||||
// residents with 3 available shift types
|
||||
for res in config
|
||||
.residents
|
||||
.iter()
|
||||
.filter(|r| r.allowed_types.len() == 3)
|
||||
{
|
||||
let total_limit = *self.max_workloads.get(&res.id).unwrap_or(&0);
|
||||
let per_type = ((total_limit as f32) / 3.0).ceil() as u8;
|
||||
|
||||
let deduct_amount = (total_limit as f32 / 3.0) as u8;
|
||||
|
||||
for shift_type in all_shift_types {
|
||||
if res.allowed_types.contains(&shift_type) {
|
||||
local_limits.insert((res.id, shift_type), per_type);
|
||||
local_thresholds.insert((res.id, shift_type), per_type.saturating_sub(2));
|
||||
if let Some(s) = supply_by_shift_type.get_mut(&shift_type) {
|
||||
*s = s.saturating_sub(deduct_amount);
|
||||
}
|
||||
} else {
|
||||
local_limits.insert((res.id, shift_type), 0);
|
||||
local_thresholds.insert((res.id, shift_type), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.max_by_shift_type = local_limits;
|
||||
self.min_by_shift_type = local_thresholds;
|
||||
self.max_by_shift_type = upper_limits;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,71 +108,47 @@ pub struct WorkloadTracker {
|
||||
}
|
||||
|
||||
impl WorkloadTracker {
|
||||
pub fn insert(&mut self, res_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
*self.total_counts.entry(res_id).or_insert(0) += 1;
|
||||
pub fn insert(&mut self, r_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
*self.total_counts.entry(r_id).or_insert(0) += 1;
|
||||
*self
|
||||
.type_counts
|
||||
.entry((res_id, slot.shift_type()))
|
||||
.entry((r_id, slot.shift_type()))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
if config.is_holiday_or_weekend_slot(slot.day.0) {
|
||||
*self.holidays.entry(res_id).or_insert(0) += 1;
|
||||
*self.holidays.entry(r_id).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, resident_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
if let Some(count) = self.total_counts.get_mut(&resident_id) {
|
||||
pub fn remove(&mut self, r_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
if let Some(count) = self.total_counts.get_mut(&r_id) {
|
||||
*count = count.saturating_sub(1);
|
||||
}
|
||||
|
||||
if let Some(count) = self.type_counts.get_mut(&(resident_id, slot.shift_type())) {
|
||||
if let Some(count) = self.type_counts.get_mut(&(r_id, slot.shift_type())) {
|
||||
*count = count.saturating_sub(1);
|
||||
}
|
||||
|
||||
if config.is_holiday_or_weekend_slot(slot.day.0) {
|
||||
if let Some(count) = self.holidays.get_mut(&resident_id) {
|
||||
if let Some(count) = self.holidays.get_mut(&r_id) {
|
||||
*count = count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_workload(&self, res_id: &ResidentId) -> u8 {
|
||||
*self.total_counts.get(res_id).unwrap_or(&0)
|
||||
pub fn current_workload(&self, r_id: &ResidentId) -> u8 {
|
||||
*self.total_counts.get(r_id).unwrap_or(&0)
|
||||
}
|
||||
|
||||
pub fn current_holiday_workload(&self, resident_id: &ResidentId) -> u8 {
|
||||
*self.holidays.get(resident_id).unwrap_or(&0)
|
||||
pub fn current_holiday_workload(&self, r_id: &ResidentId) -> u8 {
|
||||
*self.holidays.get(r_id).unwrap_or(&0)
|
||||
}
|
||||
|
||||
pub fn are_all_thresholds_met(&self, config: &UserConfig, bounds: &WorkloadBounds) -> bool {
|
||||
const SHIFT_TYPES: [ShiftType; 3] = [
|
||||
ShiftType::OpenFirst,
|
||||
ShiftType::OpenSecond,
|
||||
ShiftType::Closed,
|
||||
];
|
||||
pub fn reached_workload_limit(&self, bounds: &WorkloadBounds, r_id: &ResidentId) -> bool {
|
||||
let current_load = self.current_workload(r_id);
|
||||
|
||||
for r in &config.residents {
|
||||
for shift_type in SHIFT_TYPES {
|
||||
let current_load = self.type_counts.get(&(r.id, shift_type)).unwrap_or(&0);
|
||||
if let Some(&min) = bounds.min_by_shift_type.get(&(r.id, shift_type)) {
|
||||
if *current_load < min {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_total_workload_exceeded(
|
||||
&self,
|
||||
bounds: &WorkloadBounds,
|
||||
resident_id: &ResidentId,
|
||||
) -> bool {
|
||||
let current_load = self.current_workload(resident_id);
|
||||
|
||||
if let Some(&max) = bounds.max_workloads.get(resident_id) {
|
||||
if current_load > max {
|
||||
if let Some(&max) = bounds.max_workloads.get(r_id) {
|
||||
if current_load >= max {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -264,15 +156,11 @@ impl WorkloadTracker {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_holiday_workload_exceeded(
|
||||
&self,
|
||||
bounds: &WorkloadBounds,
|
||||
resident_id: &ResidentId,
|
||||
) -> bool {
|
||||
let current_load = self.current_holiday_workload(resident_id);
|
||||
pub fn reached_holiday_limit(&self, bounds: &WorkloadBounds, r_id: &ResidentId) -> bool {
|
||||
let current_load = self.current_holiday_workload(r_id);
|
||||
|
||||
if let Some(&max) = bounds.max_holiday_shifts.get(resident_id) {
|
||||
if current_load > max {
|
||||
if let Some(&max) = bounds.max_holiday_shifts.get(r_id) {
|
||||
if current_load >= max {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -280,27 +168,24 @@ impl WorkloadTracker {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_max_shift_type_exceeded(
|
||||
pub fn reached_shift_type_limit(
|
||||
&self,
|
||||
bounds: &WorkloadBounds,
|
||||
resident_id: &ResidentId,
|
||||
r_id: &ResidentId,
|
||||
slot: &Slot,
|
||||
) -> bool {
|
||||
let shift_type = slot.shift_type();
|
||||
let current_load = self
|
||||
.type_counts
|
||||
.get(&(*resident_id, shift_type))
|
||||
.unwrap_or(&0);
|
||||
let current_load = self.type_counts.get(&(*r_id, shift_type)).unwrap_or(&0);
|
||||
|
||||
if let Some(&max) = bounds.max_by_shift_type.get(&(*resident_id, shift_type)) {
|
||||
return *current_load > max;
|
||||
if let Some(&max) = bounds.max_by_shift_type.get(&(*r_id, shift_type)) {
|
||||
return *current_load >= max;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_type_count(&self, res_id: &ResidentId, shift_type: ShiftType) -> u8 {
|
||||
*self.type_counts.get(&(*res_id, shift_type)).unwrap_or(&0)
|
||||
pub fn get_type_count(&self, r_id: &ResidentId, shift_type: ShiftType) -> u8 {
|
||||
*self.type_counts.get(&(*r_id, shift_type)).unwrap_or(&0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +193,9 @@ impl WorkloadTracker {
|
||||
mod tests {
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
fixtures::{complex_config, hard_config, minimal_config},
|
||||
resident::{Resident, ResidentId},
|
||||
schedule::ShiftType,
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
};
|
||||
@@ -317,11 +204,11 @@ mod tests {
|
||||
#[fixture]
|
||||
fn config() -> UserConfig {
|
||||
UserConfig::default().with_residents(vec![
|
||||
Resident::new(1, "Stefanos").with_max_shifts(2),
|
||||
Resident::new(2, "Iordanis").with_max_shifts(2),
|
||||
Resident::new(3, "Maria").with_reduced_load(),
|
||||
Resident::new(4, "Veatriki"),
|
||||
Resident::new(5, "Takis"),
|
||||
Resident::new(1, "R1").with_max_shifts(2),
|
||||
Resident::new(2, "R2").with_max_shifts(2),
|
||||
Resident::new(3, "R3").with_reduced_load(),
|
||||
Resident::new(4, "R4"),
|
||||
Resident::new(5, "R5"),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -333,53 +220,252 @@ mod tests {
|
||||
#[rstest]
|
||||
fn test_max_workloads(config: UserConfig) {
|
||||
let bounds = WorkloadBounds::new_with_config(&config);
|
||||
|
||||
assert_eq!(bounds.max_workloads[&ResidentId(1)], 2);
|
||||
assert_eq!(bounds.max_workloads[&ResidentId(2)], 2);
|
||||
assert!(bounds.max_workloads[&ResidentId(3)] > 0);
|
||||
assert_eq!(2, bounds.max_workloads[&ResidentId(1)]);
|
||||
assert_eq!(2, bounds.max_workloads[&ResidentId(2)]);
|
||||
assert_eq!(13, bounds.max_workloads[&ResidentId(3)]);
|
||||
assert_eq!(14, bounds.max_workloads[&ResidentId(4)]);
|
||||
assert_eq!(14, bounds.max_workloads[&ResidentId(5)]);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_is_total_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId(1);
|
||||
fn test_reached_workload_limit(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let r_id = ResidentId(1);
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.max_workloads.insert(res_id, 1);
|
||||
bounds.max_workloads.insert(r_id, 1);
|
||||
|
||||
let slot_1 = Slot::new(Day(1), ShiftPosition::First);
|
||||
let slot_2 = Slot::new(Day(2), ShiftPosition::First);
|
||||
|
||||
tracker.insert(res_id, &config, slot_1);
|
||||
assert!(!tracker.is_total_workload_exceeded(&bounds, &res_id,));
|
||||
assert!(!tracker.reached_workload_limit(&bounds, &r_id,));
|
||||
|
||||
tracker.insert(res_id, &config, slot_2);
|
||||
assert!(tracker.is_total_workload_exceeded(&bounds, &res_id,));
|
||||
tracker.insert(r_id, &config, slot_1);
|
||||
assert!(tracker.reached_workload_limit(&bounds, &r_id,));
|
||||
|
||||
tracker.insert(r_id, &config, slot_2);
|
||||
assert!(tracker.reached_workload_limit(&bounds, &r_id,));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_is_holiday_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId(1);
|
||||
fn test_reached_holiday_limit(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let r_id = ResidentId(1);
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.max_holiday_shifts.insert(res_id, 1);
|
||||
bounds.max_holiday_shifts.insert(r_id, 1);
|
||||
|
||||
let sat = Slot::new(Day(7), ShiftPosition::First);
|
||||
let sun = Slot::new(Day(8), ShiftPosition::First);
|
||||
let sat = Slot::new(Day(11), ShiftPosition::First);
|
||||
let sun = Slot::new(Day(12), ShiftPosition::First);
|
||||
|
||||
tracker.insert(res_id, &config, sat);
|
||||
assert!(!tracker.is_holiday_workload_exceeded(&bounds, &res_id));
|
||||
assert!(!tracker.reached_holiday_limit(&bounds, &r_id));
|
||||
|
||||
tracker.insert(res_id, &config, sun);
|
||||
assert!(tracker.is_holiday_workload_exceeded(&bounds, &res_id));
|
||||
tracker.insert(r_id, &config, sat);
|
||||
assert!(tracker.reached_holiday_limit(&bounds, &r_id));
|
||||
|
||||
tracker.insert(r_id, &config, sun);
|
||||
assert!(tracker.reached_holiday_limit(&bounds, &r_id));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_backtracking_accuracy(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId(1);
|
||||
let r_id = ResidentId(1);
|
||||
let slot = Slot::new(Day(1), ShiftPosition::First);
|
||||
|
||||
tracker.insert(res_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&res_id), 1);
|
||||
tracker.insert(r_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&r_id), 1);
|
||||
|
||||
tracker.remove(res_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&res_id), 0);
|
||||
tracker.remove(r_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&r_id), 0);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_workloads_minimal(minimal_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&minimal_config.residents, minimal_config.total_slots);
|
||||
|
||||
assert_eq!(9, *bounds.max_workloads.get(&ResidentId(1)).unwrap());
|
||||
assert_eq!(9, *bounds.max_workloads.get(&ResidentId(2)).unwrap());
|
||||
assert_eq!(9, *bounds.max_workloads.get(&ResidentId(3)).unwrap());
|
||||
assert_eq!(9, *bounds.max_workloads.get(&ResidentId(4)).unwrap());
|
||||
assert_eq!(9, *bounds.max_workloads.get(&ResidentId(5)).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_workloads_complex(complex_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&complex_config.residents, complex_config.total_slots);
|
||||
|
||||
assert_eq!(3, *bounds.max_workloads.get(&ResidentId(1)).unwrap());
|
||||
assert_eq!(3, *bounds.max_workloads.get(&ResidentId(2)).unwrap());
|
||||
assert_eq!(3, *bounds.max_workloads.get(&ResidentId(3)).unwrap());
|
||||
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(4)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(5)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(6)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(7)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(8)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(9)).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_workloads_hard(hard_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&hard_config.residents, hard_config.total_slots);
|
||||
|
||||
assert_eq!(5, *bounds.max_workloads.get(&ResidentId(6)).unwrap());
|
||||
assert_eq!(5, *bounds.max_workloads.get(&ResidentId(7)).unwrap());
|
||||
assert_eq!(5, *bounds.max_workloads.get(&ResidentId(8)).unwrap());
|
||||
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(1)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(2)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(3)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(4)).unwrap());
|
||||
assert_eq!(6, *bounds.max_workloads.get(&ResidentId(5)).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_holiday_shifts_complex(complex_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_holiday_shifts(
|
||||
&complex_config.residents,
|
||||
complex_config.total_holiday_slots,
|
||||
);
|
||||
|
||||
for i in 1..=9 {
|
||||
assert_eq!(2, *bounds.max_holiday_shifts.get(&ResidentId(i)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_holiday_shifts_minimal(minimal_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_holiday_shifts(
|
||||
&minimal_config.residents,
|
||||
minimal_config.total_holiday_slots,
|
||||
);
|
||||
|
||||
for i in 1..=5 {
|
||||
assert_eq!(3, *bounds.max_holiday_shifts.get(&ResidentId(i)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_holiday_shifts_hard(hard_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds
|
||||
.calculate_max_holiday_shifts(&hard_config.residents, hard_config.total_holiday_slots);
|
||||
|
||||
for i in 1..=8 {
|
||||
assert_eq!(2, *bounds.max_holiday_shifts.get(&ResidentId(i)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_by_shift_type_minimal(minimal_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&minimal_config.residents, minimal_config.total_slots);
|
||||
bounds.calculate_max_by_shift_type(&minimal_config.residents);
|
||||
let m = bounds.max_by_shift_type;
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(1), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(1), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(1), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(2), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(2), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(2), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(3), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(3), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(3), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(4), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(4), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(4), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(5), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(5), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(5), ShiftType::Closed)).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_by_shift_type_complex(complex_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&complex_config.residents, complex_config.total_slots);
|
||||
bounds.calculate_max_by_shift_type(&complex_config.residents);
|
||||
let m = bounds.max_by_shift_type;
|
||||
|
||||
assert_eq!(2, *m.get(&(ResidentId(1), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(1), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(1), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(2, *m.get(&(ResidentId(2), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(2), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(2), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(2, *m.get(&(ResidentId(3), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(3), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(3), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(0, *m.get(&(ResidentId(4), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(0, *m.get(&(ResidentId(4), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(6, *m.get(&(ResidentId(4), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(4, *m.get(&(ResidentId(5), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(4, *m.get(&(ResidentId(5), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(0, *m.get(&(ResidentId(5), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(6), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(6), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(6), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(7), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(7), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(7), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(8), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(8), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(8), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(9), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(9), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(9), ShiftType::Closed)).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_calculate_max_by_shift_type_hard(hard_config: UserConfig) {
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.calculate_max_workloads(&hard_config.residents, hard_config.total_slots);
|
||||
bounds.calculate_max_by_shift_type(&hard_config.residents);
|
||||
let m = bounds.max_by_shift_type;
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(1), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(1), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(1), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(2), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(2), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(2), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(3), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(3), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(3), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(4), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(4), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(4), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(3, *m.get(&(ResidentId(5), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(5), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(3, *m.get(&(ResidentId(5), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(0, *m.get(&(ResidentId(6), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(5, *m.get(&(ResidentId(6), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(0, *m.get(&(ResidentId(6), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(2, *m.get(&(ResidentId(7), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(7), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(2, *m.get(&(ResidentId(7), ShiftType::Closed)).unwrap());
|
||||
|
||||
assert_eq!(0, *m.get(&(ResidentId(8), ShiftType::OpenFirst)).unwrap());
|
||||
assert_eq!(5, *m.get(&(ResidentId(8), ShiftType::OpenSecond)).unwrap());
|
||||
assert_eq!(0, *m.get(&(ResidentId(8), ShiftType::Closed)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user