package fr.jaquin.bdlg.planner.controller; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collector; import java.util.stream.Collectors; import org.optaplanner.core.api.score.ScoreExplanation; import org.optaplanner.core.api.score.ScoreManager; import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore; import org.optaplanner.core.api.solver.SolverJob; import org.optaplanner.core.api.solver.SolverManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import fr.jaquin.bdlg.planner.solver.domain.Assignement; import fr.jaquin.bdlg.planner.solver.domain.MealAssignement; import fr.jaquin.bdlg.planner.solver.domain.MealSlot; import fr.jaquin.bdlg.planner.solver.domain.Planning; import fr.jaquin.bdlg.planner.solver.domain.PlanningInput; import fr.jaquin.bdlg.planner.solver.domain.PlanningSolution; @RestController public class SolverController { @Autowired private SolverManager solverManager; @Autowired private ScoreManager scoreManager; private Collector listCollector = Collectors.joining(",", "[", "]"); @PostMapping("/planning/solve") public PlanningSolution solve(@RequestBody PlanningInput inputs) { Planning problem = inputs.generatePlanningProblem(); UUID problemId = UUID.randomUUID(); // Submit the problem to start solving SolverJob solverJob = solverManager.solve(problemId, problem); Planning solution; try { // Wait until the solving ends solution = solverJob.getFinalBestSolution(); } catch (InterruptedException | ExecutionException e) { throw new IllegalStateException("Solving failed.", e); } ScoreExplanation explanation = scoreManager.explainScore(solution); PlanningSolution output = PlanningSolution.from(solution); output.setExplanation(stringifyExplanation(explanation)); // System.out.println(explanation.getSummary()); return output; } private String stringifyExplanation(ScoreExplanation explanation) { return explanation.getConstraintMatchTotalMap().values().stream() // Return only Hard Constraint .filter(predicate -> predicate.getScore().getHardScore() < 0) // Create a dictionnary with the name of the constraint as key .map(constraint -> "\"" + constraint.getConstraintName() + "\":" // Populate the value with an array of justification objects + constraint.getConstraintMatchSet().stream() // Filter pair that have a negative impact on the score .filter(matchElt -> matchElt.getScore().getHardScore() < 0) .map(match -> match.getJustificationList().stream() .map(elt -> stringifyConstraint(elt)).collect(listCollector)) .collect(listCollector)) .collect(Collectors.joining(",", "{", "}")); } private String stringifyConstraint(Object val) { if (val instanceof Assignement) { Assignement casted = (Assignement) val; return "{\"type\":\"Assignement\",\"slotId\":\"" + casted.getSlot().getId() + "\",\"volonteerId\":" + casted.getVolonteer().getId().toString() + "}"; } if (val instanceof MealAssignement) { MealAssignement casted = (MealAssignement) val; return "{\"type\":\"MealAssignement\",\"slotId\":\"" + casted.getMealSlot().getId() + "\",\"volonteerId\":" + casted.getVolonteer().getId().toString() + "}"; } if (val instanceof MealSlot) { MealSlot casted = (MealSlot) val; return "{\"type\":\"MealSlot\",\"slotId\":\"" + casted.getId() + "\"}"; } if (val instanceof Integer) { return val.toString(); } return "\"" + val.getClass().getName() + "\""; } }