Skip to content

Commit a91119c

Browse files
feat: add model post processors results to FinalBestSolutionEvent
1 parent 517b90b commit a91119c

4 files changed

Lines changed: 53 additions & 5 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ai.timefold.solver.model.definition.api;
2+
3+
/**
4+
* Marker interface for an optional output produced by a {@link ModelPostProcessor} for a completed model run.
5+
*/
6+
public interface ModelPostProcessingResult {
7+
8+
}

model/definition/src/main/java/ai/timefold/solver/model/definition/api/ModelPostProcessor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ public interface ModelPostProcessor {
1212
*/
1313
void process(ModelOutput modelOutput, SolverModel<?> solverModel, String id);
1414

15+
/**
16+
* Process a successfully solved model run and optionally return a result to attach to the final best solution event.
17+
*
18+
* @param solverModel the model with all data taken from the solving
19+
* @param id unique identifier of the model
20+
* @return the result to attach to the final best solution event, or {@code null} if this post-processor contributes none
21+
*/
22+
default ModelPostProcessingResult processWithResult(ModelOutput modelOutput, SolverModel<?> solverModel, String id) {
23+
process(modelOutput, solverModel, id);
24+
return null;
25+
}
26+
1527
/**
1628
* Process successfully computed model (not yet solved).
1729
* <p>

model/definition/src/main/java/ai/timefold/solver/model/definition/internal/events/FinalBestSolutionEvent.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package ai.timefold.solver.model.definition.internal.events;
22

3+
import java.util.List;
4+
35
import ai.timefold.solver.core.api.solver.SolverJob;
6+
import ai.timefold.solver.model.definition.api.ModelPostProcessingResult;
47
import ai.timefold.solver.model.definition.api.SolverModel;
58
import ai.timefold.solver.model.definition.api.domain.Metadata;
69

@@ -9,8 +12,23 @@
912
*/
1013
public final class FinalBestSolutionEvent extends SolverWorkerEvent {
1114

15+
private final List<ModelPostProcessingResult> postProcessingResults;
16+
1217
public FinalBestSolutionEvent(Metadata metadata, SolverModel model, SolverJob job, String planName, String tenantName) {
18+
this(metadata, model, job, planName, tenantName, null);
19+
}
20+
21+
public FinalBestSolutionEvent(Metadata metadata, SolverModel model, SolverJob job, String planName, String tenantName,
22+
List<ModelPostProcessingResult> postProcessingResults) {
1323
super(metadata, model, job, planName, tenantName, null);
24+
this.postProcessingResults = postProcessingResults == null ? List.of() : List.copyOf(postProcessingResults);
25+
}
26+
27+
/**
28+
* The results contributed by the post-processors for this run, in the order the post-processors ran.
29+
*/
30+
public List<ModelPostProcessingResult> getPostProcessingResults() {
31+
return postProcessingResults;
1432
}
1533

1634
}

model/worker/src/main/java/ai/timefold/solver/model/worker/impl/SolverWorker.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static ai.timefold.solver.model.definition.internal.platform.EnvironmentVars.ENV_TIMEFOLD_TENANT_NAME;
66

77
import java.time.Duration;
8+
import java.util.ArrayList;
89
import java.util.List;
910
import java.util.Optional;
1011
import java.util.concurrent.ConcurrentHashMap;
@@ -33,6 +34,7 @@
3334
import ai.timefold.solver.model.definition.api.ModelConvertorBase;
3435
import ai.timefold.solver.model.definition.api.ModelInput;
3536
import ai.timefold.solver.model.definition.api.ModelOutput;
37+
import ai.timefold.solver.model.definition.api.ModelPostProcessingResult;
3638
import ai.timefold.solver.model.definition.api.ModelPostProcessor;
3739
import ai.timefold.solver.model.definition.api.SolverModel;
3840
import ai.timefold.solver.model.definition.api.SolvingStatus;
@@ -700,10 +702,12 @@ protected void notifyOnComplete(String id, SolverModel solverModel) {
700702
extractOutputMetrics(solverModel));
701703

702704
processor.onNext(metadata);
703-
sendEvent(finalSolutionEmitter, new FinalBestSolutionEvent(metadata, solverModel,
704-
new SolverWorkerJobState(SolverStatus.NOT_SOLVING, solverJob), planName, tenantName));
705705

706-
postProcessCompleteOutput(id, modelOutput, solverModel);
706+
List<ModelPostProcessingResult> postProcessingResults = postProcessCompleteOutput(id, modelOutput, solverModel);
707+
708+
sendEvent(finalSolutionEmitter, new FinalBestSolutionEvent(metadata, solverModel,
709+
new SolverWorkerJobState(SolverStatus.NOT_SOLVING, solverJob), planName, tenantName,
710+
postProcessingResults));
707711

708712
sendEvent(scheduleCompletedEmitter, new ItemCompleted(id));
709713

@@ -729,15 +733,21 @@ private void postProcessOutput(String id, ModelOutput modelOutput, SolverModel s
729733
}
730734
}
731735

732-
private void postProcessCompleteOutput(String id, ModelOutput modelOutput, SolverModel solverModel) {
736+
private List<ModelPostProcessingResult> postProcessCompleteOutput(String id, ModelOutput modelOutput,
737+
SolverModel solverModel) {
738+
List<ModelPostProcessingResult> results = new ArrayList<>();
733739
for (ModelPostProcessor processor : modelPostProcessors) {
734740
try {
735-
processor.process(modelOutput, solverModel, id);
741+
ModelPostProcessingResult result = processor.processWithResult(modelOutput, solverModel, id);
742+
if (result != null) {
743+
results.add(result);
744+
}
736745
} catch (Throwable e) {
737746
LOGGER.warn("Unexpected error while invoking post processor {} that returned {}",
738747
processor.getClass().getSimpleName(), e.getMessage());
739748
}
740749
}
750+
return results;
741751
}
742752

743753
private void storeSolvedInput(String datasetId, ModelOutput modelOutput) {

0 commit comments

Comments
 (0)