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,
    )
}