const taskSelect = document.getElementById("task-select");
const taskSummary = document.getElementById("task-summary");
const currentScore = document.getElementById("current-score");
const currentSteps = document.getElementById("current-steps");
const currentStatus = document.getElementById("current-status");
const allScore = document.getElementById("all-score");
const allResults = document.getElementById("all-results");
const episodeLog = document.getElementById("episode-log");
const rewardChart = document.getElementById("reward-chart");
const finalSummary = document.getElementById("final-summary");
let taskCatalog = [];
function renderTaskSummary(task) {
taskSummary.innerHTML = `
${task.name}
Difficulty: ${task.difficulty}
${task.objective}
Max steps: ${task.max_steps}
`;
}
function buildLineChart(logs) {
if (!logs.length) {
rewardChart.innerHTML = "No rewards available.";
return;
}
const width = 380;
const height = 220;
const padding = 28;
const values = logs.map((entry) => entry.reward);
const maxReward = Math.max(...values, 1);
const minReward = Math.min(...values, 0);
const range = Math.max(maxReward - minReward, 0.25);
const toX = (index) => {
if (logs.length === 1) {
return width / 2;
}
return padding + (index * (width - padding * 2)) / (logs.length - 1);
};
const toY = (value) => {
return height - padding - ((value - minReward) / range) * (height - padding * 2);
};
const linePoints = logs
.map((entry, index) => `${toX(index)},${toY(entry.reward)}`)
.join(" ");
const horizontalGuides = [0, 0.25, 0.5, 0.75, 1]
.map((ratio) => {
const y = padding + ratio * (height - padding * 2);
return ``;
})
.join("");
const labels = logs
.map((entry, index) => {
const x = toX(index);
return `S${entry.step}`;
})
.join("");
const points = logs
.map((entry, index) => {
const x = toX(index);
const y = toY(entry.reward);
return `
${entry.reward.toFixed(2)}
`;
})
.join("");
rewardChart.innerHTML = `
`;
}
function renderEpisode(data) {
currentScore.textContent = data.score.toFixed(4);
currentSteps.textContent = String(data.steps_taken);
currentStatus.textContent = data.success ? "Contained" : "Needs work";
buildLineChart(data.logs);
finalSummary.innerHTML = `
Final score
${data.score.toFixed(4)}
Status
${data.success ? "Success" : "Needs improvement"}
Steps used
${data.steps_taken}
Quarantine quality
${(data.final_info.quarantine_score ?? 0).toFixed(4)}
Containment outcome
All affected nodes notified: ${data.final_info.all_affected_nodes_notified ? "Yes" : "No"}
All affected stock quarantined: ${data.final_info.all_affected_stock_quarantined ? "Yes" : "No"}
Grader focus
Notification score: ${(data.final_info.notification_score ?? 0).toFixed(4)}
Investigation score: ${(data.final_info.investigation_score ?? 0).toFixed(4)}
Efficiency score: ${(data.final_info.efficiency_score ?? 0).toFixed(4)}
`;
const logMarkup = data.logs.map((entry) => {
const actionType = entry.action.type || "action";
const detailBits = [];
if (entry.action.node_id) detailBits.push(`Node: ${entry.action.node_id}`);
if (entry.action.lot_id) detailBits.push(`Lot: ${entry.action.lot_id}`);
if (entry.action.quantity) detailBits.push(`Qty: ${entry.action.quantity}`);
return `
Step ${entry.step}
${actionType.replace("_", " ")}
`;
}).join("");
episodeLog.innerHTML = `
Task: ${data.task.name}
${logMarkup}
`;
}
function renderRunAll(data) {
allScore.textContent = data.average_score.toFixed(4);
allResults.innerHTML = data.episodes.map((episode) => `
${episode.task.name}
Difficulty: ${episode.task.difficulty}
Score: ${episode.score.toFixed(4)}
Steps: ${episode.steps_taken}
Status: ${episode.success ? "Success" : "Needs work"}
`).join("");
}
async function fetchTasks() {
const response = await fetch("/api/tasks");
const data = await response.json();
taskCatalog = data.tasks;
taskSelect.innerHTML = taskCatalog.map((task) => `
`).join("");
renderTaskSummary(taskCatalog[0]);
}
async function resetTask() {
const taskId = taskSelect.value;
const response = await fetch(`/reset?task_id=${encodeURIComponent(taskId)}`);
const data = await response.json();
currentScore.textContent = "-";
currentSteps.textContent = String(data.steps_taken || 0);
currentStatus.textContent = "Reset";
rewardChart.innerHTML = "Task reset. Run a task to render the reward trajectory.";
finalSummary.innerHTML = "Readable scoring highlights will appear here.";
episodeLog.textContent = JSON.stringify(data, null, 2);
}
async function runEpisode() {
const response = await fetch("/api/run_episode", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ task_id: taskSelect.value }),
});
const data = await response.json();
renderEpisode(data);
}
async function runAllTasks() {
const response = await fetch("/api/run_all");
const data = await response.json();
renderRunAll(data);
}
taskSelect.addEventListener("change", () => {
const task = taskCatalog.find((item) => item.task_id === taskSelect.value);
if (task) {
renderTaskSummary(task);
}
});
document.getElementById("reset-button").addEventListener("click", resetTask);
document.getElementById("run-button").addEventListener("click", runEpisode);
document.getElementById("run-all-button").addEventListener("click", runAllTasks);
fetchTasks();