///|
pub fn person_minutes(
problem : ScheduleProblem,
assignments : Array[Assignment],
person_id : PersonId,
) -> Int {
assigned_minutes(problem, assignments, person_id)
}
///|
pub fn total_scheduled_minutes(
problem : ScheduleProblem,
assignments : Array[Assignment],
) -> Int {
let mut total = 0
for assignment in assignments {
match find_shift(problem, assignment.shift_id) {
Some(shift) => total = total + shift.time.duration()
None => ()
}
}
total
}
///|
pub fn preference_score(
problem : ScheduleProblem,
assignments : Array[Assignment],
) -> Int {
let mut total = 0
for assignment in assignments {
match find_person(problem, assignment.person_id) {
Some(person) =>
total = total + preference_for(person, assignment.shift_id)
None => ()
}
}
total
}
///|
pub fn absolute(value : Int) -> Int {
if value < 0 {
-value
} else {
value
}
}
///|
pub fn fairness_penalty(
problem : ScheduleProblem,
assignments : Array[Assignment],
) -> Int {
let mut penalty = 0
for person in problem.people {
penalty = penalty +
absolute(
person_minutes(problem, assignments, person.id) - person.target_minutes,
)
}
penalty
}
///|
pub fn disruption_count(
existing : Array[Assignment],
proposed : Array[Assignment],
) -> Int {
let mut changes = 0
for old in existing {
let mut preserved = false
for fresh in proposed {
if same_assignment(old, fresh) {
preserved = true
}
}
if !preserved {
changes = changes + 1
}
}
for fresh in proposed {
let mut known = false
for old in existing {
if same_assignment(old, fresh) {
known = true
}
}
if !known {
changes = changes + 1
}
}
changes
}
///|
pub fn count_uncovered(
problem : ScheduleProblem,
assignments : Array[Assignment],
) -> Int {
let mut missing = 0
for shift in problem.shifts {
for slot = 0; slot < shift.required_people; slot = slot + 1 {
let mut found = false
for assignment in assignments {
if assignment.shift_id == shift.id && assignment.slot == slot {
found = true
}
}
if !found {
missing = missing + 1
}
}
}
missing
}
///|
pub fn evaluate_schedule(
problem : ScheduleProblem,
assignments : Array[Assignment],
) -> ScheduleMetrics {
let diagnostics = validate_assignments(
problem,
assignments,
require_complete=false,
)
let mut violations = 0
for item in diagnostics {
if item.severity >= 2 {
violations = violations + 1
}
}
let uncovered = count_uncovered(problem, assignments)
let pref = preference_score(problem, assignments)
let fair = fairness_penalty(problem, assignments)
let disruptions = disruption_count(problem.existing, assignments)
let objective = violations * 1000000 +
uncovered * 100000 -
pref * problem.preference_weight +
fair * problem.fairness_weight +
disruptions * problem.disruption_weight
{
hard_violations: violations,
uncovered_slots: uncovered,
preference_score: pref,
fairness_penalty: fair,
disruption_count: disruptions,
total_minutes: total_scheduled_minutes(problem, assignments),
objective,
}
}
///|
pub fn incremental_cost(
problem : ScheduleProblem,
assignments : Array[Assignment],
shift : Shift,
person : Person,
) -> Int {
let before = person_minutes(problem, assignments, person.id)
let after = before + shift.time.duration()
let fairness_delta = absolute(after - person.target_minutes) -
absolute(before - person.target_minutes)
let preference = preference_for(person, shift.id)
let mut disruption = 1
for old in problem.existing {
if old.shift_id == shift.id && old.person_id == person.id {
disruption = -1
}
}
fairness_delta * problem.fairness_weight -
preference * problem.preference_weight +
disruption * problem.disruption_weight
}
///|
pub fn better_metrics(a : ScheduleMetrics, b : ScheduleMetrics) -> Bool {
a.objective < b.objective
}