Geoffrey De Smet

Build your own model

Score calculation

by Geoffrey De Smet

Architecture

Scoring basics

Incremental score calculation

Required skill

for (Shift shift : shifts) {
    if (!shift.employee.skills
            .contains(shift.requiredSkill)) {
        return true;
    }
}
return false;

Constraint streams required skill?

Constraint requiredSkill(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Shift.class)
            .groupBy(ConstraintCollectors.toList()) // BAD
            .filter(shifts -> {
                for (Shift shift : shifts) {
                    if (!shift.employee.skills
                            .contains(shift.requiredSkill)) {
                        return true;
                    }
                }
                return false;
            })
            .penalize(ONE_HARD)
            .asConstraint("Missing required skill");
}

Constraint streams required skill (good way)

Constraint requiredSkill(ConstraintFactory constraintFactory) {
    return constraintFactory.forEach(Shift.class)
            .filter(shift -> !shift.employee.skills
                    .contains(shift.requiredSkill))
            .penalize(ONE_HARD)
            .asConstraint("Missing required skill");
}

Avoids a score trap.

Constraint Streams

Unit testing

If it isn't tested, it doesn't work.
If it isn't documented, it isn't used.

Unit test

@Test
void requiredSkill() {
    var employee = new Employee("Amy", Set.of());
    constraintVerifier.verifyThat(
             MyConstraintProvider::requiredSkill)
        .given(employee,
            new Shift("Plumber", employee))
        .penalizes(1);
}

@Test
void requiredSkill() {
    var employee = new Employee("Beth", Set.of("Plumber"));
    constraintVerifier.verifyThat(
             MyConstraintProvider::requiredSkill)
        .given(employee,
            new Shift("Plumber", employee))
        .penalizes(0);
}

Score corruption

Score corruption

Constraint dependencyTask(...) {
    return constraintFactory.forEach(Task.class)
            .filter(task ->
                    task.start < task.dependencyTask.end)
            .penalize(ONE_HARD)
            .asConstraint("Starts before dependency task");
}

No score corruption

Constraint dependencyTask(...) {
    return constraintFactory.forEach(Task.class)
            .join(Task.class, equal(task -> task,
                    task -> task.dependencyTask)
            .filter(task1, task2 ->
                    task1.start < task2.end)
            .penalize(ONE_HARD)
            .asConstraint("Starts before dependency task");
}

Tips

Shadow variables

Q & A