ReadTheory Average Grade Level Calculator (Past 10 Quizzes)

Calculates the average grade level from the past 10 (or fewer) quizzes available on ReadTheory

// ==UserScript==
// @name         ReadTheory Average Grade Level Calculator (Past 10 Quizzes)
// @namespace    https://greasyfork.org/en/users/567951-stuart-saddler
// @version      1.6
// @description  Calculates the average grade level from the past 10 (or fewer) quizzes available on ReadTheory
// @author       Stuart Saddler
// @icon         https://images-na.ssl-images-amazon.com/images/I/41Y-bktG5oL.png
// @license      MIT
// @match        http://readtheory.org/app/teacher/reports/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Function to convert grade level text to numbers
    function gradeTextToNumber(gradeText) {
        const gradeMap = {
            'one': 1,
            'two': 2,
            'three': 3,
            'four': 4,
            'five': 5,
            'six': 6,
            'seven': 7,
            'eight': 8,
            'nine': 9,
            'ten': 10,
            'eleven': 11,
            'twelve': 12
        };
        return gradeMap[gradeText.toLowerCase().trim()] || 0; // Convert text to lowercase and trim whitespace
    }

    // Function to calculate the average of an array of numbers
    function calculateAverage(grades) {
        const total = grades.reduce((acc, grade) => acc + grade, 0);
        return (grades.length > 0) ? (total / grades.length).toFixed(1) : 0; // Rounded to 1 decimal place
    }

    // Function to extract grades and display average
    function calculateAndDisplayAverage() {
        console.log('Attempting to calculate average grade level...');

        // Extract the grade levels from the table by searching for "Grade Level" column header
        const gradeHeaders = document.querySelectorAll('th');
        let gradeColumnIndex = -1;

        gradeHeaders.forEach((header, index) => {
            if (header.innerText.toLowerCase().includes('grade level')) {
                gradeColumnIndex = index + 1; // Get the 1-based index of the column
            }
        });

        if (gradeColumnIndex === -1) {
            console.error('Could not find the "Grade Level" column.');
            return;
        }

        const gradeElements = [...document.querySelectorAll(`tbody td:nth-child(${gradeColumnIndex})`)];

        if (gradeElements.length === 0) {
            console.error('Grade elements not found. Is the table loaded?');
            return;
        }

        console.log('Found grade elements:', gradeElements);

        // Extract grades for the last 10 or fewer quizzes
        const lastGrades = gradeElements.slice(0, 10).map(td => gradeTextToNumber(td.innerText));

        console.log('Extracted grade numbers:', lastGrades);

        // Calculate the average grade level
        const averageGrade = calculateAverage(lastGrades);
        console.log('Calculated average grade level:', averageGrade);

        // Create or update a floating window to display the result
        let floatingWindow = document.querySelector('#average-grade-floating-window');
        if (!floatingWindow) {
            floatingWindow = document.createElement('div');
            floatingWindow.id = 'average-grade-floating-window';
            floatingWindow.style.position = 'fixed';
            floatingWindow.style.bottom = '100px';
            floatingWindow.style.right = '20px';
            floatingWindow.style.padding = '10px';
            floatingWindow.style.backgroundColor = '#f1f1f1';
            floatingWindow.style.border = '2px solid #333';
            floatingWindow.style.zIndex = '9999';
            floatingWindow.style.boxShadow = '0px 4px 8px rgba(0, 0, 0, 0.1)';
            floatingWindow.style.fontSize = '16px';
            floatingWindow.style.color = '#333';
            document.body.appendChild(floatingWindow);
        }

        floatingWindow.innerText = `Average Grade Level (Last ${lastGrades.length} Quizzes): ${averageGrade}`;
        console.log('Floating window displayed or updated with average grade.');
    }

    // Function to wait for the quiz history table to be available in the DOM
    function waitForTableAndCalculateAverage() {
        const observer = new MutationObserver((mutationsList, observer) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    const gradeHeader = document.querySelector('th');
                    if (gradeHeader) {
                        console.log('Quiz history table detected, calculating...');
                        observer.disconnect(); // Stop observing once the table is detected
                        calculateAndDisplayAverage();
                        return;
                    }
                }
            }
        });

        // Start observing the entire document for changes
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Function to monitor the studentid for changes in the quiz history panel
    function observeStudentIDChange() {
        const quizPanel = document.querySelector('.quiz-history-panel');
        if (quizPanel) {
            const observer = new MutationObserver((mutationsList, observer) => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'attributes' && mutation.attributeName === 'studentid') {
                        console.log('Detected change in studentid, recalculating...');
                        calculateAndDisplayAverage();
                    }
                }
            });

            observer.observe(quizPanel, { attributes: true, attributeFilter: ['studentid'] });
            console.log('Started observing changes in studentid attribute.');
        } else {
            console.error('Quiz history panel not found.');
        }
    }

    // Function to monitor the student dropdown for changes
    function observeStudentDropdown() {
        const dropdown = document.querySelector('select'); // Assuming dropdown is a select element
        if (dropdown) {
            dropdown.addEventListener('change', () => {
                console.log('Student dropdown changed, waiting for studentid update...');
                setTimeout(() => {
                    observeStudentIDChange(); // Check for studentid updates
                }, 1000); // Wait 1 second before recalculating to allow time for data load
            });
            console.log('Started observing student dropdown for changes.');
        } else {
            console.error('Student dropdown not found.');
        }
    }

    // Ensure the page is fully loaded, then start observing
    window.addEventListener('load', () => {
        console.log('Page fully loaded, waiting for table...');
        waitForTableAndCalculateAverage(); // Recalculate on page load
        observeStudentDropdown(); // Also watch for student selection changes
        observeStudentIDChange(); // Watch for studentid changes
        // NEW LINE ADDED HERE:
        document.addEventListener('change', event => { if(event.target.tagName === 'SELECT') setTimeout(calculateAndDisplayAverage, 1500); });
    });

})();