solverforge-hospital / static /app /shell /solver-controller.mjs
blackopsrepl's picture
feat(app): add hospital scheduling application
b7e7f16
// Thin adapter around the shared `SF.createSolver()` controller.
export function createSolverController({
sf,
backend,
statusBar,
onPlan,
onAnalysis,
onMeta,
onLifecycle,
onError,
}) {
const solver = sf.createSolver({
backend,
statusBar,
onProgress(meta) {
publishMeta(meta);
},
onSolution(snapshot, meta) {
publishSnapshot(snapshot, meta);
},
onPaused(snapshot, meta) {
publishSnapshot(snapshot, meta);
},
onResumed(meta) {
publishMeta(meta);
},
onCancelled(snapshot, meta) {
publishSnapshot(snapshot, meta);
},
onComplete(snapshot, meta) {
publishSnapshot(snapshot, meta);
},
onFailure(_message, meta, snapshot, analysis) {
publishSnapshot(snapshot, meta);
if (analysis) onAnalysis(analysis);
},
onAnalysis(analysis) {
onAnalysis(analysis);
publishLifecycle();
},
onError(message) {
if (typeof onError === 'function') onError(message);
publishLifecycle();
},
});
return {
// Starts a new solve after cleaning up any previous terminal retained job.
async start(planProvider) {
if (solver.isRunning() || solver.getLifecycleState() === 'PAUSED') return;
await cleanupTerminalJob();
const plan = await planProvider();
await solver.start(plan);
publishMeta({
id: solver.getJobId(),
jobId: solver.getJobId(),
lifecycleState: solver.getLifecycleState(),
currentScore: null,
bestScore: null,
telemetry: null,
});
},
pause() {
return solver.pause().then(() => {
publishLifecycle();
});
},
resume() {
return solver.resume().then(() => {
publishLifecycle();
});
},
cancel() {
return solver.cancel().then(() => {
publishLifecycle();
});
},
analyzeSnapshot() {
return solver.analyzeSnapshot().then((analysis) => {
onAnalysis(analysis);
publishLifecycle();
return analysis;
});
},
getLifecycleState() {
return solver.getLifecycleState();
},
getJobId() {
return solver.getJobId();
},
getSnapshotRevision() {
return solver.getSnapshotRevision();
},
};
// Publishes the latest solution and lifecycle metadata back to the app shell.
function publishSnapshot(snapshot, meta) {
if (snapshot && snapshot.solution) onPlan(snapshot.solution);
publishMeta(meta);
}
// Publishes status metadata and refreshes the shell lifecycle markers.
function publishMeta(meta) {
onMeta(meta || null);
publishLifecycle();
}
// Keeps HTML data attributes in sync with the underlying solver lifecycle.
function publishLifecycle() {
onLifecycle({
jobId: solver.getJobId(),
snapshotRevision: solver.getSnapshotRevision(),
lifecycleState: solver.getLifecycleState(),
});
}
// Deletes old terminal jobs so "Solve" always starts from a clean retained slot.
function cleanupTerminalJob() {
const state = solver.getLifecycleState();
if (!solver.getJobId() || state === 'IDLE' || state === 'PAUSED' || solver.isRunning()) {
return Promise.resolve();
}
return solver.delete()
.then(() => {
onAnalysis(null);
onMeta(null);
publishLifecycle();
})
.catch((error) => {
if (typeof onError === 'function') onError(error);
throw error;
});
}
}