Majen's picture
Initial submission
4d13031 verified
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 = `
<h3>${task.name}</h3>
<p><strong>Difficulty:</strong> ${task.difficulty}</p>
<p>${task.objective}</p>
<p><strong>Max steps:</strong> ${task.max_steps}</p>
`;
}
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 `<line class="chart-grid" x1="${padding}" y1="${y}" x2="${width - padding}" y2="${y}"></line>`;
})
.join("");
const labels = logs
.map((entry, index) => {
const x = toX(index);
return `<text class="chart-label" x="${x}" y="${height - 8}" text-anchor="middle">S${entry.step}</text>`;
})
.join("");
const points = logs
.map((entry, index) => {
const x = toX(index);
const y = toY(entry.reward);
return `
<circle class="chart-point" cx="${x}" cy="${y}" r="5"></circle>
<text class="chart-label" x="${x}" y="${y - 10}" text-anchor="middle">${entry.reward.toFixed(2)}</text>
`;
})
.join("");
rewardChart.innerHTML = `
<svg viewBox="0 0 ${width} ${height}" aria-label="Reward line chart">
${horizontalGuides}
<line class="chart-axis" x1="${padding}" y1="${height - padding}" x2="${width - padding}" y2="${height - padding}"></line>
<line class="chart-axis" x1="${padding}" y1="${padding}" x2="${padding}" y2="${height - padding}"></line>
<polyline class="chart-line" points="${linePoints}"></polyline>
${points}
${labels}
</svg>
`;
}
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 = `
<div class="summary-grid">
<div class="summary-pill">
<span>Final score</span>
<strong>${data.score.toFixed(4)}</strong>
</div>
<div class="summary-pill">
<span>Status</span>
<strong>${data.success ? "Success" : "Needs improvement"}</strong>
</div>
<div class="summary-pill">
<span>Steps used</span>
<strong>${data.steps_taken}</strong>
</div>
<div class="summary-pill">
<span>Quarantine quality</span>
<strong>${(data.final_info.quarantine_score ?? 0).toFixed(4)}</strong>
</div>
</div>
<div class="summary-card">
<strong>Containment outcome</strong>
<div>All affected nodes notified: ${data.final_info.all_affected_nodes_notified ? "Yes" : "No"}</div>
<div>All affected stock quarantined: ${data.final_info.all_affected_stock_quarantined ? "Yes" : "No"}</div>
</div>
<div class="summary-card">
<strong>Grader focus</strong>
<div>Notification score: ${(data.final_info.notification_score ?? 0).toFixed(4)}</div>
<div>Investigation score: ${(data.final_info.investigation_score ?? 0).toFixed(4)}</div>
<div>Efficiency score: ${(data.final_info.efficiency_score ?? 0).toFixed(4)}</div>
</div>
`;
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 `
<div class="log-step">
<div class="log-title">
<strong>Step ${entry.step}</strong>
<span class="action-chip">${actionType.replace("_", " ")}</span>
</div>
<div class="action-meta">
<div>${detailBits.length ? detailBits.join(" | ") : "No extra parameters"}</div>
<div>Reward: ${entry.reward.toFixed(4)}</div>
<div>Message: ${entry.message || "-"}</div>
</div>
</div>
`;
}).join("");
episodeLog.innerHTML = `
<div class="log-step">
<strong>Task:</strong> ${data.task.name}
</div>
${logMarkup}
`;
}
function renderRunAll(data) {
allScore.textContent = data.average_score.toFixed(4);
allResults.innerHTML = data.episodes.map((episode) => `
<div class="log-step">
<strong>${episode.task.name}</strong>
<div>Difficulty: ${episode.task.difficulty}</div>
<div>Score: ${episode.score.toFixed(4)}</div>
<div>Steps: ${episode.steps_taken}</div>
<div>Status: ${episode.success ? "Success" : "Needs work"}</div>
</div>
`).join("");
}
async function fetchTasks() {
const response = await fetch("/api/tasks");
const data = await response.json();
taskCatalog = data.tasks;
taskSelect.innerHTML = taskCatalog.map((task) => `
<option value="${task.task_id}">${task.difficulty.toUpperCase()} - ${task.name}</option>
`).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();