|
|
@@ -2,10 +2,15 @@ package fr.jaquin.bdlg.planner;
|
|
|
|
|
|
import java.util.UUID;
|
|
|
import java.util.concurrent.ExecutionException;
|
|
|
-
|
|
|
+import java.util.stream.Collector;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import fr.jaquin.bdlg.planner.domain.Assignement;
|
|
|
+import fr.jaquin.bdlg.planner.domain.MealAssignement;
|
|
|
+import fr.jaquin.bdlg.planner.domain.MealSlot;
|
|
|
import fr.jaquin.bdlg.planner.domain.Planning;
|
|
|
import fr.jaquin.bdlg.planner.domain.PlanningInput;
|
|
|
import fr.jaquin.bdlg.planner.domain.PlanningSolution;
|
|
|
+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;
|
|
|
@@ -25,10 +30,10 @@ public class SolverController {
|
|
|
@Autowired
|
|
|
private SolverManager<Planning, UUID> solverManager;
|
|
|
@Autowired
|
|
|
- private ScoreManager<Planning, HardMediumSoftScore> scoreManager;
|
|
|
+ private ScoreManager<Planning, HardMediumSoftScore> scoreManager;
|
|
|
+ private Collector<CharSequence, ?, String> listCollector = Collectors.joining(",", "[", "]");
|
|
|
|
|
|
-
|
|
|
- @CrossOrigin(origins = "http://localhost:8081")
|
|
|
+ @CrossOrigin(origins = "http://localhost:8081")
|
|
|
@PostMapping("/solve")
|
|
|
public PlanningSolution solve(@RequestBody PlanningInput inputs) {
|
|
|
Planning problem = inputs.generatePlanningProblem();
|
|
|
@@ -44,8 +49,47 @@ public class SolverController {
|
|
|
} catch (InterruptedException | ExecutionException e) {
|
|
|
throw new IllegalStateException("Solving failed.", e);
|
|
|
}
|
|
|
- System.out.println(scoreManager.explainScore(solution));
|
|
|
- return PlanningSolution.from(solution);
|
|
|
+
|
|
|
+ ScoreExplanation<Planning, HardMediumSoftScore> explanation =
|
|
|
+ scoreManager.explainScore(solution);
|
|
|
+ PlanningSolution output = PlanningSolution.from(solution);
|
|
|
+ output.setExplanation(stringifyExplanation(explanation));
|
|
|
+ System.out.println(explanation.getSummary());
|
|
|
+ return output;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String stringifyExplanation(ScoreExplanation<Planning, HardMediumSoftScore> 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() + "\"}";
|
|
|
+ }
|
|
|
+ return "\"" + val.getClass().getName() + "\"";
|
|
|
}
|
|
|
|
|
|
}
|