ERNEST2002/Playbook-Grading-OS
0
1/* =========================================================2 PLAYBOOK UNICORN OS - MASTER AGENTIC CONTROLLER (app.js)3 v17: Fully Automated, Live Search, Dropdown Chat & HOD Insights.4========================================================= */5 6// --- 1. ENTERPRISE DEBUGGER KERNEL ---7const debuggerConsole = document.getElementById('debuggerConsole');8const debuggerLogs = document.getElementById('debuggerLogs');9const toggleDebugMode = document.getElementById('toggleDebugMode');10const toggleVoice = document.getElementById('toggleVoice');11 12function logDebug(message, type="info") {13 console.log(`[PLAYBOOK UI] ${message}`); 14 if (toggleDebugMode && toggleDebugMode.checked && debuggerLogs) {15 const p = document.createElement('p');16 p.textContent = `[UI ${new Date().toLocaleTimeString()}] ${message}`;17 if(type === "error") p.className = "debug-error";18 if(type === "warn") p.className = "debug-warn";19 debuggerLogs.appendChild(p);20 debuggerConsole.scrollTop = debuggerConsole.scrollHeight;21 }22}23 24function logBackend(message) {25 if (toggleDebugMode && toggleDebugMode.checked && debuggerLogs) {26 const p = document.createElement('p');27 p.textContent = `[SERVER] ${message}`;28 p.className = "debug-backend";29 debuggerLogs.appendChild(p);30 debuggerConsole.scrollTop = debuggerConsole.scrollHeight;31 }32}33 34if (toggleDebugMode && debuggerConsole) {35 toggleDebugMode.addEventListener('change', () => {36 debuggerConsole.style.display = toggleDebugMode.checked ? "block" : "none";37 logDebug(`Debug mode ${toggleDebugMode.checked ? 'enabled' : 'disabled'}`);38 });39}40 41// THE SETTINGS MODAL42const btnSettings = document.getElementById('btnSettings');43const btnCloseSettings = document.getElementById('btnCloseSettings');44const settingsModal = document.getElementById('settingsModal');45 46if(btnSettings) {47 btnSettings.addEventListener('click', () => {48 settingsModal.classList.remove('hidden');49 });50}51if(btnCloseSettings) {52 btnCloseSettings.addEventListener('click', () => {53 settingsModal.classList.add('hidden');54 });55}56 57logDebug("UI Initialized. Playbook Neural pathways connected.", "info");58 59// --- 2. SYSTEM STATE ---60let currentState = {61 view: 'phaseUpload',62 language: 'en',63 selectedFile: null,64 activeSessionId: null, 65 activeSessionName: "",66 currentStudentContext: null,67 schemeFile: null,68 liveResultsData: [] // Tunahifadhi array hapa kwa ajili ya Search/Filter69};70 71const views = {72 phaseUpload: document.getElementById('phaseUpload'),73 phaseGoldStandard: document.getElementById('phaseGoldStandard'),74 phaseMarking: document.getElementById('phaseMarking'),75 phaseAnalytics: document.getElementById('phaseAnalytics')76};77 78// --- 3. VOICE ENGINE & MIC CONTROL ---79async function speakOutLoud(text) {80 if (toggleVoice && !toggleVoice.checked) return;81 logDebug(`System Speaking: ${text}`);82 try {83 const utterance = new SpeechSynthesisUtterance(text);84 window.speechSynthesis.speak(utterance);85 } catch (error) {86 logDebug("Browser Voice Failed.", "warn");87 }88}89 90const btnVoiceUniversal = document.getElementById('btnVoiceUniversal');91const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;92if (SpeechRecognition && btnVoiceUniversal) {93 const recognition = new SpeechRecognition();94 recognition.lang = 'en-US';95 btnVoiceUniversal.addEventListener('click', () => {96 recognition.start();97 btnVoiceUniversal.classList.add('recording-pulse');98 logDebug("Microphone active. Listening...");99 });100 recognition.onresult = (e) => {101 btnVoiceUniversal.classList.remove('recording-pulse');102 const transcript = e.results[0][0].transcript;103 logDebug(`Voice command received: ${transcript}`);104 askUniversalAI(transcript);105 };106 recognition.onerror = (e) => {107 btnVoiceUniversal.classList.remove('recording-pulse');108 logDebug(`Microphone Error: Make sure PC has Internet connection.`, "error");109 }110}111 112const universalChatInput = document.getElementById('universalChatInput');113if (universalChatInput) {114 universalChatInput.addEventListener('keypress', (e) => {115 if (e.key === 'Enter') {116 e.preventDefault(); 117 askUniversalAI(universalChatInput.value);118 }119 });120}121 122const chatHistory = document.getElementById('universalChatHistory');123 124function addUniversalChatMessage(sender, text) {125 if (!chatHistory) return;126 const bubble = document.createElement('div');127 bubble.className = `bubble ${sender}`;128 bubble.innerHTML = text;129 chatHistory.appendChild(bubble);130 chatHistory.scrollTop = chatHistory.scrollHeight;131}132 133async function askUniversalAI(transcript) {134 if (!transcript.trim()) return;135 136 addUniversalChatMessage('user', transcript); 137 if(universalChatInput) universalChatInput.value = '';138 139 try {140 logDebug(`Sending Playbook Agent Request: ${transcript}`);141 const response = await fetch('/api/agent/command', {142 method: 'POST', headers: { 'Content-Type': 'application/json' },143 body: JSON.stringify({ 144 transcript: transcript, 145 current_view: currentState.view, 146 ui_language: currentState.language,147 active_session_id: currentState.activeSessionId // Chatbot inajua Session sasa148 })149 });150 const data = await response.json();151 if (data.status === 'success' && data.command) {152 logDebug(`Agent Action: ${data.command.ui_action}`);153 if(data.command.spoken_reply) {154 addUniversalChatMessage('playbook', data.command.spoken_reply);155 speakOutLoud(data.command.spoken_reply);156 }157 if(data.command.ui_action === 'NAVIGATE') switchPhase(data.command.payload);158 if(data.command.ui_action === 'PAUSE_GRADING') sendGradingControl('PAUSE');159 if(data.command.ui_action === 'RESUME_GRADING') sendGradingControl('RESUME');160 }161 } catch (error) { 162 logDebug("Playbook Agent Communication Failed", "error"); 163 addUniversalChatMessage('playbook', "I lost connection to the Playbook AI backend.");164 }165}166 167// --- 4. MULTI-SESSION SIDEBAR & CLEANUP LOGIC ---168async function loadSidebarSessions() {169 const listContainer = document.getElementById('sessionListContainer');170 if (!listContainer) return;171 try {172 const res = await fetch('/api/sessions');173 const data = await res.json();174 if (data.status === 'success') {175 listContainer.innerHTML = '<h4 class="sub-header">ACTIVE SESSIONS</h4>';176 data.data.forEach(session => {177 let statusColor = session.status === 'GRADING' ? 'pulsing' : (session.status === 'COMPLETED' ? 'green' : 'orange');178 listContainer.insertAdjacentHTML('beforeend', `179 <div class="session-item ${session.session_id === currentState.activeSessionId ? 'active' : ''}" onclick="loadSession('${session.session_id}', '${session.name}')">180 <div><b>${session.name}</b><br><small>${session.status}: ${session.processed}</small></div>181 <span class="status-dot ${statusColor}"></span>182 </div>183 `);184 });185 }186 } catch(e) { logDebug("Sidebar fetch failed", "warn"); }187}188 189// FIX: Usafi wa kioo ukibadilisha Session (Hoja 10)190function loadSession(sessionId, sessionName) {191 currentState.activeSessionId = sessionId;192 currentState.activeSessionName = sessionName;193 document.getElementById('navSessionTitle').textContent = sessionName;194 195 // Futa data za zamani kwenye UI196 document.getElementById('studentGrid').innerHTML = '';197 document.getElementById('liveCounter').textContent = '0 / 0 Scripts';198 document.getElementById('flaggedCountDisplay').textContent = '0';199 document.getElementById('avgScoreDisplay').textContent = '0%';200 document.getElementById('navTotal').textContent = '0';201 document.getElementById('navFlagged').textContent = '0';202 currentState.liveResultsData = [];203 204 switchPhase('phaseMarking');205 loadSidebarSessions(); 206}207 208window.addEventListener('DOMContentLoaded', () => loadSidebarSessions());209 210// --- 5. DATA UPLOAD & SCHEME COMPILER ---211const hiddenFileInput = document.createElement('input');212hiddenFileInput.type = 'file'; 213hiddenFileInput.accept = '.pdf';214hiddenFileInput.style.display = 'none'; 215document.body.appendChild(hiddenFileInput);216 217document.getElementById('btnBrowseFiles').addEventListener('click', () => {218 if (!document.getElementById('sessionNameInput').value.trim()) { alert("Name the session first."); return; }219 hiddenFileInput.click();220});221 222hiddenFileInput.addEventListener('change', async (e) => {223 if (e.target.files.length === 0) return;224 const file = e.target.files[0];225 const formData = new FormData();226 formData.append("file", file);227 formData.append("session_name", document.getElementById('sessionNameInput').value.trim());228 229 logDebug(`Uploading Exam Scripts to Cloud: ${file.name}`);230 document.getElementById('btnBrowseFiles').textContent = "⏳ Uploading to Cloud...";231 try {232 const res = await fetch('/api/upload/exam', { method: 'POST', body: formData });233 const data = await res.json();234 if (data.status === 'success' || data.status === 'info') {235 currentState.activeSessionId = data.session_id;236 logDebug(`Cloud Session Initialized: ${data.session_id}`);237 speakOutLoud("Scripts safely uploaded to Cloud. Please define the marking logic.");238 switchPhase('phaseGoldStandard');239 loadSidebarSessions();240 }241 } catch (err) { logDebug("Upload Error", "error"); }242 document.getElementById('btnBrowseFiles').textContent = "Browse Scripts";243});244 245const schemeFileInput = document.getElementById('schemeFileInput');246if (schemeFileInput) {247 schemeFileInput.addEventListener('change', (e) => {248 if (e.target.files.length > 0) {249 currentState.schemeFile = e.target.files[0];250 logDebug(`Scheme Document Attached: ${currentState.schemeFile.name}`);251 document.getElementById('schemeUploadBox').innerHTML = `<p style="font-size: 24px; margin-bottom: 8px;">✅</p><p><b>Document Attached:</b><br><small>${currentState.schemeFile.name}</small></p>`;252 }253 });254}255 256const btnSaveSchemeAndStart = document.getElementById('btnSaveSchemeAndStart');257if(btnSaveSchemeAndStart) {258 btnSaveSchemeAndStart.addEventListener('click', async () => {259 if (!currentState.activeSessionId) {260 alert("⚠️ SYSTEM HALT: You cannot save logic without uploading student scripts first.");261 logDebug("Attempted to save logic without an active session ID.", "error");262 return;263 }264 265 logDebug("Compiling System Logic Persona for Playbook AI...");266 const schemeText = document.getElementById('markingSchemeText').value;267 268 const formData = new FormData();269 formData.append("session_id", currentState.activeSessionId);270 271 let personaStr = `272 CALIBRATION MATRIX:273 1. Math/Logic Rules: ${document.getElementById('calibMethodology').value}274 2. Grammar Rules: ${document.getElementById('calibGrammar').value}275 3. Verbosity Rules: ${document.getElementById('calibVerbosity').value}276 4. Incomplete Answers: ${document.getElementById('calibBlank').value}277 5. Custom Teacher Expectations: ${document.getElementById('customPersonaText').value || 'Standard rules.'}278 `;279 280 formData.append("matrix_rules", personaStr);281 formData.append("scheme_text", schemeText);282 283 if (currentState.schemeFile) {284 formData.append("scheme_file", currentState.schemeFile);285 }286 287 btnSaveSchemeAndStart.textContent = "⏳ Processing Logic Profile...";288 try {289 const res = await fetch('/api/scheme/save_advanced', {290 method: 'POST',291 body: formData 292 });293 const data = await res.json();294 if(data.status === 'success') {295 logDebug("Logic Saved. Auto-Grading Initiated.");296 speakOutLoud("Playbook AI has started the mass grading process automatically.");297 switchPhase('phaseMarking');298 document.getElementById('btnPauseMarking').classList.remove('hidden');299 document.getElementById('btnResumeMarking').classList.add('hidden');300 }301 } catch(e) { logDebug("Logic Save Error: " + e.message, "error"); }302 btnSaveSchemeAndStart.textContent = "Save Logic & Auto-Start Marking ➔";303 });304}305 306// --- 6. FULL REVIEW PANEL & OVERRIDE ENGINE ---307async function openReviewPanel(regNo, confidence) {308 currentState.currentStudentContext = regNo;309 document.getElementById('reviewPanel').classList.remove('hidden');310 document.getElementById('reviewTitle').textContent = `Reg No: ${regNo}`;311 document.getElementById('reviewConfidence').textContent = `AI Confidence: ${confidence}%`;312 313 try {314 const res = await fetch(`/api/student/${regNo}`);315 const data = await res.json();316 const db = data.data;317 318 if(db.image_base64) {319 document.getElementById('docCanvas').innerHTML = `<img src="data:image/png;base64,${db.image_base64}" style="width:100%; border:1px solid #CCC;">`;320 } else {321 document.getElementById('docCanvas').innerHTML = `<p class="sub-text">Image not available</p>`;322 }323 324 const breakdownZone = document.getElementById('reasoningBreakdown');325 breakdownZone.innerHTML = '';326 if (db.breakdown && Object.keys(db.breakdown).length > 0) {327 for (const [qNum, qData] of Object.entries(db.breakdown)) {328 breakdownZone.insertAdjacentHTML('beforeend', `329 <div class="question-breakdown" style="padding: 12px; border: 1px solid #E5E5E5; border-radius: 8px; margin-bottom: 8px; background: #FAFAFA;">330 <div class="q-header" style="display: flex; justify-content: space-between; font-weight: bold; font-size: 15px;">331 <span>Question ${qNum.replace('Q','')}</span>332 <span class="score" style="color: #1E8E3E;">${qData.score} Marks</span>333 </div>334 <p class="reasoning" style="margin-top: 8px; font-size: 13px; color: #333; line-height: 1.5;"><b>AI Remarks:</b><br>${qData.reason}</p>335 </div>336 `);337 }338 } else {339 breakdownZone.innerHTML = `<p class="sub-text">Overall AI Logic: ${db.explanation}</p>`;340 }341 342 const recommendationsText = document.getElementById('aiRecommendationsText');343 if (recommendationsText) {344 recommendationsText.innerHTML = db.recommendations ? db.recommendations.replace(/\n/g, '<br>') : "No specific recommendations provided.";345 }346 347 document.getElementById('overrideScoreInput').value = db.total_score;348 } catch (error) { logDebug("Data Fetch Error for Review Panel", "error"); }349}350 351function closeReviewPanel() {352 document.getElementById('reviewPanel').classList.add('hidden');353 currentState.currentStudentContext = null;354}355 356const btnSubmitOverride = document.getElementById('btnSubmitOverride');357if(btnSubmitOverride) {358 btnSubmitOverride.addEventListener('click', async () => {359 const res = await fetch('/api/override', {360 method: 'POST', headers: { 'Content-Type': 'application/json' },361 body: JSON.stringify({ reg_no: currentState.currentStudentContext, new_score: parseFloat(document.getElementById('overrideScoreInput').value), reason: document.getElementById('overrideReasonInput').value })362 });363 if(res.ok) {364 logDebug(`Override successful for ${currentState.currentStudentContext}`);365 closeReviewPanel();366 }367 });368}369 370const btnUndoOverride = document.getElementById('btnUndoOverride');371if(btnUndoOverride) {372 btnUndoOverride.addEventListener('click', async () => {373 const res = await fetch('/api/override/undo', {374 method: 'POST', headers: { 'Content-Type': 'application/json' },375 body: JSON.stringify({ reg_no: currentState.currentStudentContext })376 });377 const data = await res.json();378 if(data.status === 'success') {379 logDebug(`Override Undone for ${currentState.currentStudentContext}`);380 closeReviewPanel();381 }382 });383}384 385// --- 7. POLLING, LIVE DASHBOARD & FILTER ENGINE ---386let lastLogLine = 0;387let bellCurveChartInstance = null;388let qdiChartInstance = null;389 390setInterval(async () => {391 if (currentState.view === 'phaseMarking' && currentState.activeSessionId) {392 try {393 const res = await fetch(`/api/grading/status/${currentState.activeSessionId}`);394 const data = await res.json();395 if (data.status !== 'IDLE') {396 397 // Update Progress Texts398 document.getElementById('liveCounter').textContent = `${data.processed} / ${data.total} Scripts`;399 document.getElementById('navTotal').textContent = data.total;400 401 // Update Flagged Counts402 document.getElementById('flaggedCountDisplay').textContent = data.flagged;403 document.getElementById('navFlagged').textContent = data.flagged;404 405 // Update Progress Bar406 let percent = data.total > 0 ? (data.processed / data.total) * 100 : 0;407 document.getElementById('liveProgressBar').style.width = `${percent}%`;408 409 // Update Average Score410 if(data.live_results && data.live_results.length > 0) {411 currentState.liveResultsData = data.live_results;412 let totalScore = data.live_results.reduce((sum, item) => sum + item.score, 0);413 let avg = totalScore / data.live_results.length;414 document.getElementById('avgScoreDisplay').textContent = `${avg.toFixed(1)}%`;415 416 // Chora kadi upya kwa kutumia Engine yetu mpya ya Ku-filter417 applyFiltersAndRender();418 }419 420 // Toggle play/pause buttons421 if(data.status === 'COMPLETED') {422 document.getElementById('liveEta').textContent = "✅ Grading Completed Successfully.";423 document.getElementById('btnPauseMarking').classList.add('hidden');424 document.getElementById('btnResumeMarking').classList.add('hidden');425 }426 }427 } catch (e) { }428 }429 430 if (toggleDebugMode && toggleDebugMode.checked) {431 try {432 const res = await fetch(`/api/logs?start=${lastLogLine}`);433 const data = await res.json();434 if (data.status === 'success' && data.logs.length > 0) {435 data.logs.forEach(log => {436 logBackend(log.trim());437 });438 lastLogLine += data.logs.length;439 }440 } catch (e) { }441 }442}, 2000);443 444// --- 8. SEARCH AND FILTER ENGINE ---445const searchInput = document.getElementById('searchStudentInput');446const filterSelect = document.getElementById('filterSelect');447const confidenceInput = document.getElementById('confidenceThreshold');448 449if(searchInput) searchInput.addEventListener('input', applyFiltersAndRender);450if(filterSelect) filterSelect.addEventListener('change', applyFiltersAndRender);451if(confidenceInput) confidenceInput.addEventListener('input', applyFiltersAndRender);452 453function applyFiltersAndRender() {454 if(!currentState.liveResultsData || currentState.liveResultsData.length === 0) return;455 456 let filteredData = [...currentState.liveResultsData].reverse(); 457 458 // 1. Apply Search459 const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';460 if(searchTerm) {461 filteredData = filteredData.filter(r => r.reg_no.toLowerCase().includes(searchTerm));462 }463 464 // 2. Apply Dropdown Filter465 const filterType = filterSelect ? filterSelect.value : 'ALL';466 if(filterType === 'FLAGGED') {467 filteredData = filteredData.filter(r => r.flag === 'RED');468 } else if (filterType === 'TOP') {469 filteredData = filteredData.filter(r => r.score >= 80); 470 }471 472 // 3. Apply Confidence Threshold (Dynamic Flagging)473 const confThresh = confidenceInput ? parseFloat(confidenceInput.value) : 80;474 475 const grid = document.getElementById('studentGrid');476 grid.innerHTML = '';477 478 filteredData.forEach(r => {479 let isFlagged = (r.flag === 'RED' || r.confidence < confThresh);480 481 grid.insertAdjacentHTML('beforeend', `482 <div class="student-card ${isFlagged ? 'border-red' : ''}" onclick="openReviewPanel('${r.reg_no}', '${r.confidence}')">483 <h4>${r.reg_no}</h4>484 <h3>${r.score}%</h3>485 <small>${isFlagged ? '⚠️ Human Review Needed' : '✔ Playbook Verified'}</small>486 </div>487 `);488 });489}490 491function switchPhase(phase) {492 currentState.view = phase;493 Object.values(views).forEach(v => v.classList.add('hidden'));494 views[phase].classList.remove('hidden');495 logDebug(`Mapped to view: ${phase}`);496 if(phase === 'phaseAnalytics') renderAnalytics();497}498 499// FULL ENTERPRISE ANALYTICS RENDERING (WITH HOD INSIGHTS)500async function renderAnalytics() {501 try {502 const res = await fetch(`/api/analytics/${currentState.activeSessionId}`);503 const data = await res.json();504 if(data.status === 'success') {505 const stats = data.stats;506 document.getElementById('exportSummaryText').textContent = `Class Mean: ${stats.mean}% | Std Dev: ${stats.std_dev} | Total Scripts: ${stats.total_students}`;507 508 // WEKA EXECUTIVE SUMMARY KWENYE KIOO509 const insightBox = document.getElementById('hodInsightBox');510 const insightText = document.getElementById('hodInsightText');511 if (stats.executive_summary) {512 insightBox.style.display = 'block';513 insightText.textContent = stats.executive_summary;514 }515 516 // Bell Curve (Score Distribution)517 const ctx = document.getElementById('bellCurveChart').getContext('2d');518 if(bellCurveChartInstance) bellCurveChartInstance.destroy();519 bellCurveChartInstance = new Chart(ctx, {520 type: 'bar',521 data: { 522 labels: Object.keys(stats.distribution), 523 datasets: [{ 524 label: 'Student Count', 525 data: Object.values(stats.distribution), 526 backgroundColor: '#111',527 borderRadius: 4528 }] 529 }530 });531 532 // QDI (Question Difficulty Index)533 const ctxQdi = document.getElementById('qdiChart').getContext('2d');534 if(qdiChartInstance) qdiChartInstance.destroy();535 qdiChartInstance = new Chart(ctxQdi, {536 type: 'bar',537 data: { 538 labels: Object.keys(data.qdi), 539 datasets: [{ 540 label: 'Average Score per Question', 541 data: Object.values(data.qdi).map(item => item.average_score || item), 542 backgroundColor: Object.values(data.qdi).map(item => {543 let score = item.average_score || item;544 return score < 5 ? '#D93025' : '#1E8E3E';545 }) 546 }] 547 },548 options: { indexAxis: 'y' }549 });550 }551 } catch (e) { logDebug("Analytics Failed to Load", "error"); }552}553 554const btnExportZipArchive = document.getElementById('btnExportZipArchive');555if (btnExportZipArchive) {556 btnExportZipArchive.addEventListener('click', () => {557 window.location.href = `/api/export/excel/${currentState.activeSessionId}`;558 logDebug("Official Excel Results downloaded.");559 });560}561 562// THE NEW EXPORT BUTTON FOR ALL PDFS (ZIP BATCH)563const btnExportAllPDFs = document.getElementById('btnExportAllPDFs');564if (btnExportAllPDFs) {565 btnExportAllPDFs.addEventListener('click', () => {566 alert("Preparing ZIP containing all student PDFs. This might take a few moments...");567 window.location.href = `/api/export/batch_pdfs/${currentState.activeSessionId}`; 568 logDebug("Batch PDF Download Requested.");569 });570}571 572const btnExportSinglePDF = document.getElementById('btnExportSinglePDF');573if (btnExportSinglePDF) {574 btnExportSinglePDF.addEventListener('click', () => {575 if(currentState.currentStudentContext) {576 window.location.href = `/api/export/pdf/${currentState.currentStudentContext}`;577 logDebug(`Downloaded PDF for ${currentState.currentStudentContext}`);578 }579 });580}581 582async function sendGradingControl(action) {583 await fetch('/api/grading/control', {584 method: 'POST', headers: { 'Content-Type': 'application/json' },585 body: JSON.stringify({ session_id: currentState.activeSessionId, action: action })586 });587 logDebug(`Grading status changed to: ${action}`);588}589 590const btnPause = document.getElementById('btnPauseMarking');591if(btnPause) btnPause.addEventListener('click', () => {592 sendGradingControl('PAUSE');593 btnPause.classList.add('hidden');594 document.getElementById('btnResumeMarking').classList.remove('hidden');595});596 597const btnResume = document.getElementById('btnResumeMarking');598if(btnResume) btnResume.addEventListener('click', () => {599 sendGradingControl('RESUME');600 btnResume.classList.add('hidden');601 document.getElementById('btnPauseMarking').classList.remove('hidden');602});603 604// Playbook "What-If" Simulation Engine605const btnRunSimulation = document.getElementById('btnRunSimulation');606if (btnRunSimulation) {607 btnRunSimulation.addEventListener('click', async () => {608 const cmd = document.getElementById('simulationCommandInput').value;609 if (!cmd) return;610 611 btnRunSimulation.textContent = "Simulating...";612 try {613 const grid = document.getElementById('studentGrid');614 const firstCard = grid.querySelector('.student-card h4');615 if (!firstCard) { alert("No graded scripts available for simulation."); return; }616 617 const refRegNo = firstCard.textContent;618 const res = await fetch('/api/simulate', {619 method: 'POST', headers: { 'Content-Type': 'application/json' },620 body: JSON.stringify({ reg_no: refRegNo, command: cmd })621 });622 const data = await res.json();623 const resultBox = document.getElementById('simulationResult');624 resultBox.style.display = "block";625 626 if(data.status === 'success') {627 resultBox.innerHTML = `<b>Playbook Simulation:</b> New Projected Score for reference script is ${data.simulation.new_total}%. (Original was altered by your command).`;628 } else {629 resultBox.textContent = "Simulation Failed.";630 }631 } catch(e) { }632 btnRunSimulation.textContent = "Run Simulation";633 });634}