CoolFace
Apppublic

ERNEST2002/Playbook-Grading-OS

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
app.js634 linesDownload Raw Back to root
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}