use solverforge_core::score::SoftScore;
use crate::api::constraint_set::IncrementalConstraint;
use crate::stream::collection_extract::{source, ChangeSource};
use crate::stream::joiner::{equal_bi, equal_on};
use crate::stream::relational::{operator::Pair, Leaf};
use crate::stream::ConstraintFactory;
use super::fixtures::{assignment_key, shift_key};
use super::fixtures::{
rel_assignments, rel_employees, rel_shifts, sample, RelAssignment, RelEmployee, RelSchedule,
RelShift,
};
use super::oracle::{oracle_rows, oracle_score};
use super::row_keys::{employee_key, row_pair_employee_code};
fn fluent_night_staffed() -> impl IncrementalConstraint<RelSchedule, SoftScore> {
ConstraintFactory::<RelSchedule, SoftScore>::new()
.for_each(source(
rel_assignments as fn(&RelSchedule) -> &[RelAssignment],
ChangeSource::Descriptor(1),
))
.join((
source(
rel_shifts as fn(&RelSchedule) -> &[RelShift],
ChangeSource::Descriptor(0),
),
equal_bi(
assignment_key as fn(&RelAssignment) -> u32,
shift_key as fn(&RelShift) -> u32,
),
))
.join((
source(
rel_employees as fn(&RelSchedule) -> &[RelEmployee],
ChangeSource::Descriptor(2),
),
equal_on(
row_pair_employee_code
as fn(&Pair<Leaf<'_, RelAssignment>, Leaf<'_, RelShift>>) -> String,
employee_key as fn(&Leaf<'_, RelEmployee>) -> String,
),
))
.filter(
|_assignment: &RelAssignment, shift: &RelShift, _employee: &RelEmployee| shift.night,
)
.penalize(
|_assignment: &RelAssignment, _shift: &RelShift, _employee: &RelEmployee| {
SoftScore::of(1)
},
)
.named("night shift staffed")
}
#[test]
fn fluent_independent_key_chain_matches_oracle() {
let schedule = sample();
let constraint = fluent_night_staffed();
assert_eq!(constraint.evaluate(&schedule), oracle_score(&schedule));
assert_eq!(
constraint.match_count(&schedule),
oracle_rows(&schedule).len()
);
assert_eq!(oracle_rows(&schedule).len(), 2);
}
#[test]
fn fluent_chain_initialize_then_incremental_matches_evaluate() {
let schedule = sample();
let mut constraint = fluent_night_staffed();
assert_eq!(constraint.initialize(&schedule), SoftScore::of(-2));
let delta = constraint.on_retract(&schedule, 0, 1);
assert_eq!(delta, SoftScore::of(1));
let trimmed = RelSchedule {
shifts: schedule.shifts.clone(),
assignments: schedule.assignments[1..].to_vec(),
employees: schedule.employees.clone(),
score: None,
};
assert_eq!(constraint.evaluate(&trimmed), SoftScore::of(-1));
assert_eq!(constraint.match_count(&trimmed), 1);
let delta = constraint.on_insert(&schedule, 0, 1);
assert_eq!(delta, SoftScore::of(-1));
assert_eq!(constraint.evaluate(&schedule), SoftScore::of(-2));
let matches = constraint.get_matches(&schedule);
assert_eq!(matches.len(), 2);
let total: SoftScore = matches
.iter()
.fold(SoftScore::of(0), |acc, m| acc + m.score);
assert_eq!(total, SoftScore::of(-2));
}