Spaces:
Sleeping
Sleeping
File size: 3,794 Bytes
7596726 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | use chrono::Timelike;
use std::cmp::Reverse;
use crate::domain::{Employee, Shift};
use super::support::{
can_mark_preference_date, date_pressure_for_shift, mark_preference_date, preferred_shift_date,
shift_prefers_doctor_family, shift_same_shape, PreferenceKind,
};
use super::PreferenceAnalysis;
use crate::data::data_seed::employees::EmployeeBlueprint;
use crate::data::data_seed::skills::is_specialty_skill;
use crate::data::data_seed::vocabulary::DOCTOR;
use crate::data::data_seed::witness::{shift_priority_rank, WitnessRoster};
/// Adds a small number of witness-relative preference swaps.
///
/// This is the sharpest source of local-search signal in the dataset: one
/// employee is marked as disliking a date while another feasible employee is
/// marked as preferring it.
pub(super) fn assign_exchange_preferences(
employees: &mut [Employee],
blueprints: &[EmployeeBlueprint],
shifts: &[Shift],
witness: &WitnessRoster,
analysis: &PreferenceAnalysis,
max_exchange_marks_per_employee: usize,
) {
let mut exchange_marks_by_employee = vec![0usize; employees.len()];
let mut shift_order: Vec<usize> = (0..shifts.len()).collect();
shift_order.sort_by_key(|&shift_index| {
let shift = &shifts[shift_index];
(
Reverse(analysis.candidate_counts[shift_index]),
Reverse(date_pressure_for_shift(shift, &analysis.date_pressure)),
shift_priority_rank(shift),
shift.start,
shift_index,
)
});
for shift_index in shift_order {
let shift = &shifts[shift_index];
if analysis.candidate_counts[shift_index] < 6
|| is_specialty_skill(&shift.required_skill)
|| shift.start.time().hour() == 22
{
continue;
}
let holder = witness.assignments[shift_index];
let date = preferred_shift_date(shift, &analysis.date_pressure);
let Some(alternative) = analysis.candidate_lists[shift_index]
.iter()
.copied()
.filter(|&candidate| candidate != holder)
.filter(|&candidate| {
exchange_marks_by_employee[candidate] < max_exchange_marks_per_employee
})
.filter(|&candidate| {
can_mark_preference_date(&employees[candidate], date, PreferenceKind::Desired)
})
.min_by_key(|&candidate| {
exchange_alternative_key(candidate, shift, shifts, blueprints, witness, analysis)
})
else {
continue;
};
if mark_preference_date(&mut employees[alternative], date, PreferenceKind::Desired) {
exchange_marks_by_employee[alternative] += 1;
}
}
}
/// Lower keys mean "better alternative employee for this exchange mark".
fn exchange_alternative_key(
candidate: usize,
shift: &Shift,
shifts: &[Shift],
blueprints: &[EmployeeBlueprint],
witness: &WitnessRoster,
analysis: &PreferenceAnalysis,
) -> (usize, usize, usize, usize, usize, usize) {
let date = preferred_shift_date(shift, &analysis.date_pressure);
let same_date_in_witness =
usize::from(witness.employee_touched_dates[candidate].contains(&date));
let same_shape_load = analysis.witness_shifts_by_employee[candidate]
.iter()
.filter(|&&other_shift_index| shift_same_shape(shift, &shifts[other_shift_index]))
.count();
(
same_date_in_witness,
usize::from(
blueprints[candidate].skills.contains(DOCTOR) != shift_prefers_doctor_family(shift),
),
same_shape_load,
witness.employee_touched_dates[candidate].len(),
usize::MAX - *analysis.date_pressure.get(&date).unwrap_or(&0),
candidate,
)
}
|