Change ResidentId(String) to ResidentId(u8), impl Copy for ShiftType, use u8 for all UserConfig params
This commit is contained in:
@@ -52,8 +52,7 @@ impl WorkloadBounds {
|
||||
// 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 {
|
||||
self.max_workloads
|
||||
.insert(r.id.clone(), r.max_shifts.unwrap_or(0) as u8);
|
||||
self.max_workloads.insert(r.id, r.max_shifts.unwrap_or(0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -61,23 +60,23 @@ impl WorkloadBounds {
|
||||
// Untested scenario: Resident has manual max_shifts and also reduced workload flag
|
||||
// Probably should forbid using both options from GUI
|
||||
|
||||
let manual_max_shifts_sum: usize = config
|
||||
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 as usize - manual_max_shifts_sum) as f32
|
||||
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 as u8,
|
||||
Some(shifts) => shifts,
|
||||
None if r.reduced_load => max_shifts_ceiling - 1,
|
||||
None => max_shifts_ceiling,
|
||||
};
|
||||
self.max_workloads.insert(r.id.clone(), max_shifts);
|
||||
self.max_workloads.insert(r.id, max_shifts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ impl WorkloadBounds {
|
||||
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.clone(), holiday_limit);
|
||||
self.max_holiday_shifts.insert(r.id, holiday_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,23 +111,20 @@ impl WorkloadBounds {
|
||||
.iter()
|
||||
.filter(|r| r.allowed_types.len() == 1)
|
||||
{
|
||||
let shift_type = &res.allowed_types[0];
|
||||
let shift_type = res.allowed_types[0];
|
||||
let total_limit = *self.max_workloads.get(&res.id).unwrap_or(&0);
|
||||
|
||||
local_limits.insert((res.id.clone(), shift_type.clone()), total_limit);
|
||||
local_thresholds.insert(
|
||||
(res.id.clone(), shift_type.clone()),
|
||||
total_limit.saturating_sub(2),
|
||||
);
|
||||
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 {
|
||||
for other_type in all_shift_types {
|
||||
if other_type != shift_type {
|
||||
local_limits.insert((res.id.clone(), other_type.clone()), 0);
|
||||
local_thresholds.insert((res.id.clone(), other_type.clone()), 0);
|
||||
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) {
|
||||
if let Some(s) = supply_by_shift_type.get_mut(&shift_type) {
|
||||
*s = s.saturating_sub(total_limit)
|
||||
}
|
||||
}
|
||||
@@ -144,19 +140,16 @@ impl WorkloadBounds {
|
||||
|
||||
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.clone(), shift_type.clone()), per_type);
|
||||
local_thresholds.insert(
|
||||
(res.id.clone(), shift_type.clone()),
|
||||
per_type.saturating_sub(2),
|
||||
);
|
||||
if let Some(s) = supply_by_shift_type.get_mut(shift_type) {
|
||||
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.clone(), shift_type.clone()), 0);
|
||||
local_thresholds.insert((res.id.clone(), shift_type.clone()), 0);
|
||||
local_limits.insert((res.id, shift_type), 0);
|
||||
local_thresholds.insert((res.id, shift_type), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,19 +165,16 @@ impl WorkloadBounds {
|
||||
|
||||
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.clone(), shift_type.clone()), per_type);
|
||||
local_thresholds.insert(
|
||||
(res.id.clone(), shift_type.clone()),
|
||||
per_type.saturating_sub(2),
|
||||
);
|
||||
if let Some(s) = supply_by_shift_type.get_mut(shift_type) {
|
||||
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.clone(), shift_type.clone()), 0);
|
||||
local_thresholds.insert((res.id.clone(), shift_type.clone()), 0);
|
||||
local_limits.insert((res.id, shift_type), 0);
|
||||
local_thresholds.insert((res.id, shift_type), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,32 +192,29 @@ pub struct WorkloadTracker {
|
||||
}
|
||||
|
||||
impl WorkloadTracker {
|
||||
pub fn insert(&mut self, res_id: &ResidentId, config: &UserConfig, slot: Slot) {
|
||||
*self.total_counts.entry(res_id.clone()).or_insert(0) += 1;
|
||||
pub fn insert(&mut self, res_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
*self.total_counts.entry(res_id).or_insert(0) += 1;
|
||||
*self
|
||||
.type_counts
|
||||
.entry((res_id.clone(), slot.shift_type()))
|
||||
.entry((res_id, slot.shift_type()))
|
||||
.or_insert(0) += 1;
|
||||
|
||||
if config.is_holiday_or_weekend_slot(slot.day.0) {
|
||||
*self.holidays.entry(res_id.clone()).or_insert(0) += 1;
|
||||
*self.holidays.entry(res_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, resident_id: ResidentId, config: &UserConfig, slot: Slot) {
|
||||
if let Some(count) = self.total_counts.get_mut(&resident_id) {
|
||||
*count = count.saturating_sub(1);
|
||||
}
|
||||
|
||||
if let Some(count) = self
|
||||
.type_counts
|
||||
.get_mut(&(resident_id.clone(), slot.shift_type()))
|
||||
{
|
||||
if let Some(count) = self.type_counts.get_mut(&(resident_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(&resident_id) {
|
||||
*count = count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
@@ -250,14 +237,8 @@ impl WorkloadTracker {
|
||||
|
||||
for r in &config.residents {
|
||||
for shift_type in SHIFT_TYPES {
|
||||
let current_load = self
|
||||
.type_counts
|
||||
.get(&(r.id.clone(), shift_type.clone()))
|
||||
.unwrap_or(&0);
|
||||
if let Some(&min) = bounds
|
||||
.min_by_shift_type
|
||||
.get(&(r.id.clone(), shift_type.clone()))
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -308,21 +289,18 @@ impl WorkloadTracker {
|
||||
let shift_type = slot.shift_type();
|
||||
let current_load = self
|
||||
.type_counts
|
||||
.get(&(resident_id.clone(), shift_type.clone()))
|
||||
.get(&(*resident_id, shift_type))
|
||||
.unwrap_or(&0);
|
||||
|
||||
if let Some(&max) = bounds
|
||||
.max_by_shift_type
|
||||
.get(&(resident_id.clone(), shift_type.clone()))
|
||||
{
|
||||
if let Some(&max) = bounds.max_by_shift_type.get(&(*resident_id, shift_type)) {
|
||||
return *current_load > max;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_type_count(&self, res_id: &ResidentId, stype: ShiftType) -> u8 {
|
||||
*self.type_counts.get(&(res_id.clone(), stype)).unwrap_or(&0)
|
||||
pub fn get_type_count(&self, res_id: &ResidentId, shift_type: ShiftType) -> u8 {
|
||||
*self.type_counts.get(&(*res_id, shift_type)).unwrap_or(&0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,11 +317,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, "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"),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -356,52 +334,52 @@ mod tests {
|
||||
fn test_max_workloads(config: UserConfig) {
|
||||
let bounds = WorkloadBounds::new_with_config(&config);
|
||||
|
||||
assert_eq!(bounds.max_workloads[&ResidentId("1".to_string())], 2);
|
||||
assert_eq!(bounds.max_workloads[&ResidentId("2".to_string())], 2);
|
||||
assert!(bounds.max_workloads[&ResidentId("3".to_string())] > 0);
|
||||
assert_eq!(bounds.max_workloads[&ResidentId(1)], 2);
|
||||
assert_eq!(bounds.max_workloads[&ResidentId(2)], 2);
|
||||
assert!(bounds.max_workloads[&ResidentId(3)] > 0);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_is_total_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId("1".to_string());
|
||||
let res_id = ResidentId(1);
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.max_workloads.insert(res_id.clone(), 1);
|
||||
bounds.max_workloads.insert(res_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);
|
||||
tracker.insert(res_id, &config, slot_1);
|
||||
assert!(!tracker.is_total_workload_exceeded(&bounds, &res_id,));
|
||||
|
||||
tracker.insert(&res_id, &config, slot_2);
|
||||
tracker.insert(res_id, &config, slot_2);
|
||||
assert!(tracker.is_total_workload_exceeded(&bounds, &res_id,));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_is_holiday_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId("1".to_string());
|
||||
let res_id = ResidentId(1);
|
||||
let mut bounds = WorkloadBounds::default();
|
||||
bounds.max_holiday_shifts.insert(res_id.clone(), 1);
|
||||
bounds.max_holiday_shifts.insert(res_id, 1);
|
||||
|
||||
let sat = Slot::new(Day(7), ShiftPosition::First);
|
||||
let sun = Slot::new(Day(8), ShiftPosition::First);
|
||||
|
||||
tracker.insert(&res_id, &config, sat);
|
||||
tracker.insert(res_id, &config, sat);
|
||||
assert!(!tracker.is_holiday_workload_exceeded(&bounds, &res_id));
|
||||
|
||||
tracker.insert(&res_id, &config, sun);
|
||||
tracker.insert(res_id, &config, sun);
|
||||
assert!(tracker.is_holiday_workload_exceeded(&bounds, &res_id));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_backtracking_accuracy(mut tracker: WorkloadTracker, config: UserConfig) {
|
||||
let res_id = ResidentId("1".to_string());
|
||||
let res_id = ResidentId(1);
|
||||
let slot = Slot::new(Day(1), ShiftPosition::First);
|
||||
|
||||
tracker.insert(&res_id, &config, slot);
|
||||
tracker.insert(res_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&res_id), 1);
|
||||
|
||||
tracker.remove(&res_id, &config, slot);
|
||||
tracker.remove(res_id, &config, slot);
|
||||
assert_eq!(tracker.current_workload(&res_id), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user