File size: 1,455 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
use crate::domain::{Plan, PlanConstraintStreams, Shift};
use solverforge::prelude::*;
use solverforge::IncrementalConstraint;

const SCORE_SCALE: i64 = 100_000;
const STRUCTURAL_MINUTE_HARD_UNITS: i64 = 20;

/// Hard-penalizes same-employee shift pairs separated by less than 10 hours.
pub fn constraint() -> impl IncrementalConstraint<Plan, HardSoftDecimalScore> {
    ConstraintFactory::<Plan, HardSoftDecimalScore>::new()
        .shifts()
        .filter(|shift: &Shift| shift.employee_idx.is_some())
        .join(joiner::equal(|shift: &Shift| shift.employee_idx))
        .filter(|a: &Shift, b: &Shift| {
            if a.index >= b.index {
                return false;
            }

            let (earlier, later) = if a.end <= b.start {
                (a, b)
            } else if b.end <= a.start {
                (b, a)
            } else {
                return false;
            };

            let gap_minutes = (later.start - earlier.end).num_minutes();
            (0..600).contains(&gap_minutes)
        })
        .penalize(hard_weight(|a: &Shift, b: &Shift| {
            let (earlier, later) = if a.end <= b.start { (a, b) } else { (b, a) };
            let gap_minutes = (later.start - earlier.end).num_minutes();
            HardSoftDecimalScore::of_hard_scaled(
                (600 - gap_minutes) * STRUCTURAL_MINUTE_HARD_UNITS * SCORE_SCALE,
            )
        }))
        .named("At least 10 hours between 2 shifts")
}