// ==UserScript==
// @name ReadTheory: Proficiency Leaderboard
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Leaderboard for classes who practice with readtheory.org
// @author https://greasyfork.org/en/users/567951-stuart-saddler
// @match https://readtheory.org/app/teacher/reports
// @license MIT
// @grant none
// @downloadURL https://update.greasyfork.org/scripts/557056/ReadTheory%3A%20Proficiency%20Leaderboard.user.js
// @updateURL https://update.greasyfork.org/scripts/557056/ReadTheory%3A%20Proficiency%20Leaderboard.meta.js
// ==/UserScript==
// Source: https://github.com/thepick/userscripts
(function() {
'use strict';
var GRADE_WEIGHT = 0.05;
var DEFAULT_THRESHOLD = 7;
var showThresholdOnly = false;
var quizThreshold = DEFAULT_THRESHOLD;
function init() {
var btn = document.createElement('button');
btn.innerHTML = '⚖️ True Proficiency Score';
Object.assign(btn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '99999',
padding: '12px 24px',
backgroundColor: '#8e44ad',
color: 'white',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '16px',
fontWeight: 'bold',
boxShadow: '0 4px 15px rgba(0,0,0,0.3)',
fontFamily: 'Segoe UI, sans-serif',
transition: 'background 0.2s'
});
btn.onmouseover = function() {
btn.style.backgroundColor = '#9b59b6';
};
btn.onmouseout = function() {
btn.style.backgroundColor = '#8e44ad';
};
btn.onclick = runExtraction;
document.body.appendChild(btn);
}
function runExtraction() {
var containers = document.querySelectorAll('.highcharts-container');
if (containers.length === 0) {
alert("No charts found. Please scroll down to load ALL charts (Quizzes, Grade Level, Knowledge Points).");
return;
}
var studentMap = {};
var chartsFound = 0;
containers.forEach(function(container) {
var parent = container.parentNode;
var vue = parent.__vue__;
if (!vue || !vue.chart) return;
var chart = vue.chart;
var categories = chart.xAxis && chart.xAxis[0] ? chart.xAxis[0].categories : null;
var seriesList = chart.series;
if (!categories || !seriesList || seriesList.length === 0) return;
var firstSeries = seriesList[0].name;
var metric = null;
if (firstSeries === "Above pretest level" || firstSeries === "Quizzes Taken") metric = 'quizzes';
else if (firstSeries === "Current Grade Level") metric = 'grade';
else if (firstSeries === "Total Knowledge points") metric = 'knowledge';
if (!metric) return;
chartsFound++;
categories.forEach(function(name, i) {
if (!studentMap[name]) studentMap[name] = {
name: name,
grade: 0,
knowledge: 0,
quizzes: 0
};
var val = 0;
if (metric === 'quizzes') {
seriesList.forEach(function(s) {
var p = s.data[i];
var v = (typeof p === 'object') ?
((p.y != null ? p.y : (p.options && p.options.y != null ? p.options.y : 0))) :
(p || 0);
val += v;
});
} else {
var p = seriesList[0].data[i];
val = (typeof p === 'object') ?
((p.y != null ? p.y : (p.options && p.options.y != null ? p.options.y : 0))) :
(p || 0);
}
if (metric === 'grade') studentMap[name].grade = val;
if (metric === 'knowledge') studentMap[name].knowledge = val;
if (metric === 'quizzes') studentMap[name].quizzes = val;
});
});
if (chartsFound < 3) {
alert("Some charts missing. Please scroll to 'Quizzes Taken', 'Grade Level', and 'Knowledge Points'.");
return;
}
var results = Object.keys(studentMap)
.map(function(name) {
var s = studentMap[name];
if (!(s.knowledge > 0 && s.quizzes > 0)) return null;
var efficiency = s.knowledge / s.quizzes;
var gradeBonus = Math.max(0, s.grade - 1);
var multiplier = 1 + (gradeBonus * GRADE_WEIGHT);
var finalScore = efficiency * multiplier;
var out = Object.assign({}, s);
out.efficiency = efficiency.toFixed(1);
out.multStr = multiplier.toFixed(2) + 'x';
out.adjustedScore = Math.round(finalScore * 10);
return out;
})
.filter(function(s) {
return s;
})
.sort(function(a, b) {
return b.adjustedScore - a.adjustedScore;
});
showUI(results, quizThreshold, showThresholdOnly);
}
function showUI(data, quizThreshold, showThresholdOnly) {
var id = 'rt-toggle-modal';
if (document.getElementById(id)) document.getElementById(id).remove();
var modal = document.createElement('div');
modal.id = id;
Object.assign(modal.style, {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '97%',
maxWidth: '1000px',
maxHeight: '95vh',
backgroundColor: 'white',
borderRadius: '12px',
boxShadow: '0 25px 60px rgba(0,0,0,0.4)',
zIndex: '100000',
display: 'flex',
flexDirection: 'column',
fontFamily: 'Segoe UI, sans-serif'
});
var controls = '' +
'
' +
'
' +
'
⚖️ Proficiency Leaderboard
' +
'
' +
'Formula: (KP ÷ Quizzes) × (1 + (Grade-1) × ' + GRADE_WEIGHT + ')' +
'
' +
'
' +
'
' +
'' +
'' +
'quizzes or more' +
'
' +
'
' +
'
';
var qualified = data;
var notQualified = [];
if (showThresholdOnly) {
qualified = data.filter(function(s) {
return s.quizzes >= quizThreshold;
});
notQualified = data.filter(function(s) {
return s.quizzes < quizThreshold;
});
}
var tableRows = function(arr) {
return arr.map(function(s, i) {
return '' +
'' +
'| ' + (i + 1) + ' | ' +
'' + s.name + ' | ' +
'' + s.grade.toFixed(2) + ' | ' +
'' + s.quizzes + ' | ' +
'' + s.knowledge + ' | ' +
'' + s.efficiency + ' | ' +
'' + s.multStr + ' | ' +
'' + s.adjustedScore + ' | ' +
'
';
}).join('');
};
var mainTable = '' +
'' +
'' +
'' +
'| Rank | ' +
'Student | ' +
'Grade | ' +
'Quiz | ' +
'KP | ' +
'Pts/Quiz | ' +
'Diff. Mult | ' +
'Score | ' +
'
' +
'' +
'' +
tableRows(qualified) +
'' +
'
';
var notQualifiedTable = notQualified.length ? (
'' +
'
' +
'Practice More to Qualify (' + quizThreshold + '+ quizzes required)' +
'
' +
'
' +
'' +
'' +
'| Rank | ' +
'Student | ' +
'Grade | ' +
'Quiz | ' +
'KP | ' +
'Pts/Quiz | ' +
'Mult | ' +
'Score | ' +
'
' +
'' +
'' +
tableRows(notQualified) +
'' +
'
' +
'
'
) : '';
var content = '' +
'' +
mainTable +
notQualifiedTable +
'
';
var footer = '' +
'' +
'' +
'
';
modal.innerHTML = controls + content + footer;
document.body.appendChild(modal);
document.getElementById('rt-dl-csv').onclick = function() {
var rows = qualified.concat(notQualified);
var csv = ['Rank,Name,Grade,Quizzes,Total KP,Pts Per Quiz,Multiplier,Proficiency Score']
.concat(rows.map(function(s, i) {
return (i + 1) + ',"' + s.name + '",' + s.grade + ',' + s.quizzes + ',' + s.knowledge + ',' + s.efficiency + ',' + s.multStr + ',' + s.adjustedScore;
}))
.join('\n');
var a = document.createElement('a');
a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv);
a.download = 'ReadTheory_Leaderboard.csv';
a.click();
};
document.getElementById('rt-close').onclick = function() {
modal.remove();
};
document.getElementById('rt-threshold-toggle').onchange = function(e) {
showThresholdOnly = e.target.checked;
showUI(data, quizThreshold, showThresholdOnly);
};
document.getElementById('rt-threshold-input').onchange = function(e) {
var val = parseInt(e.target.value, 10);
if (isNaN(val) || val < 1) val = 1;
quizThreshold = val;
showUI(data, quizThreshold, showThresholdOnly);
};
}
setTimeout(init, 1500);
})();