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 = ` ${horizontalGuides} ${points} ${labels} `; } 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("_", " ")}
${detailBits.length ? detailBits.join(" | ") : "No extra parameters"}
Reward: ${entry.reward.toFixed(4)}
Message: ${entry.message || "-"}
`; }).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();