Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ryan778
GitHub Repository: Ryan778/Ryan778.github.io
Path: blob/master/final-grade-calculator/core.js
574 views
1
/* core.js
2
* Functions related directly to grade calculation and conversion
3
* (C) 2019 Ryan Zhang.
4
This code is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
This code is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this code. If not, see <https://www.gnu.org/licenses/>.
16
17
Note that this license does NOT apply to the entire site. However, if you're interested in anything on here, contact me at https://ryan778.github.io/about-me/ and we can discuss.
18
*/
19
20
21
function convToGrade(letter){
22
//Returns number: approximate of letter grade, -1 if unknown
23
letter = letter.toLowerCase();
24
letter = letter.replace(/ /g, '');
25
if(letter.length > 3){return -1}
26
let val = letter.charCodeAt(0);
27
let grade = -1;
28
if(val >= 97 && val <= 100){ //A through D
29
grade = 105 - 10*(val-96); //Returns middle of grade (ex. A -> 95)
30
let mod = letter.slice(1, 3);
31
switch(mod){
32
case '+':
33
grade += 3;
34
break;
35
case '-':
36
grade -= 3;
37
break;
38
case '++':
39
grade += 5;
40
break;
41
case '--':
42
grade -= 5;
43
break;
44
case '':
45
break;
46
default:
47
grade = -1; //Unknown grade
48
}
49
}
50
else if(letter === 'f'){
51
grade = 50}
52
else if(letter === 'f-' || letter === 'f--'){
53
grade = 0}
54
else if(letter === 's'){grade = 100} //"Satisfactory"
55
else if(letter === 'u'){grade = 0} //"Unsatisfactory"
56
return grade;
57
}
58
59
function convToLetter(grade){
60
// Returns string: letter grade, input number
61
if(typeof grade !== 'number'){
62
return '??'
63
}
64
grade = Math.round(grade*100)/100 //Fix floating point rounding errors
65
if(grade > 100){return 'A++'}
66
else if(grade === 100){return 'A+'}
67
else if(grade < 60){
68
if(grade < 50){
69
return 'F-'}
70
return 'F'
71
}
72
let letter = String.fromCharCode(Math.floor((199.9999-grade)/10)+87).toUpperCase();
73
if(grade%10 >= 8){letter += '+'}
74
else if(grade%10 < 4){letter += '-'}
75
return letter;
76
}
77
78
function handleQueryAction(t, n) {
79
//Returns undefined, sends event to ga
80
gtag('event', 'fgc_query', {
81
type: t,
82
value: n
83
});
84
}
85
86
/* The "Math" section with lots and lots of somewhat complicated equations that even I barely / kinda understand */
87
function calcTestDrop(){
88
//Requires: testWorth, testAvg, totalTests, lowestTest (from globalData)
89
//Returns [before (weighted), weighted, overall] rounded to four decimal places
90
let w = globalData.testWorth, a = globalData.testAvg, t = globalData.totalTests, l = globalData.lowestTest, s = globalData.totalTests*globalData.testAvg;
91
return [Math.round(10000*a*(w/100))/10000, Math.round((1000000*(s-l)/((t-1)*100))*(w/100))/10000, Math.round(1000000*(s-l)/((t-1)*100))/10000]
92
}
93
94
function calcTargetGradeS(t){ // "simple" target grade - standard settings w/o test adjustments
95
let c = globalData.currentGrade, f = globalData.finalWorth, w = globalData.testWorth, a = globalData.testAvg, o = globalData.totalTests, l = globalData.lowestTest;
96
return (t-c*(1-(f/100)))/(f/100);
97
}
98
99
function calcTargetTestGrade(t){
100
/* t = targetGrade
101
@requires currentGrade, targetGrade, testsWorth, either (tests taken + test average) or (points in cat + points test is worth)
102
// w(i/j) + d = w((i+s)/(j+k))
103
*/
104
let d = (t - globalData.currentGrade)/100;
105
let w = globalData.testWorth/100, a, i, j, k;
106
// a = test average (out of 1.00), i = test category points, j = test category total points, k = points this test is worth
107
if(globalData.unequalTests){
108
// a = globalData.testCatPts / globalData.testCatTotalPts
109
i = globalData.testCatPts;
110
j = globalData.testCatTotalPts;
111
k = globalData.testWorthPts;
112
}
113
else{
114
// a = globalData.testAvg / 100
115
i = globalData.totalTests * (globalData.testAvg / 100);
116
j = globalData.totalTests;
117
k = 1;
118
}
119
if(j === 0){
120
// d = a*s
121
return 100*d/a}
122
let s = (k*d + j*d)/w + i*k/j; // output
123
return 100*(s/k);
124
}
125
126
function calcTargetGrade(t){
127
/* t = targetGrade
128
@requires currentGrade, finalWorth, testPolicy, testWorth, testAvg, totalTests, lowestTest (from globalData)
129
@returns Array [Weighted, Full] - Dynamic is only returned for testPolicy one and two, and is a multiplier of finalScore
130
*/
131
//targetGrade = (finalWorth/100)*result + currentGrade*(1-(finalWorth/100))
132
133
if(globalData.gradeType === 1){
134
// (cur + req) / (total + worth) = goal
135
let w = globalData.finalWorthPts, c = globalData.currentGradePts, tp = globalData.currentGradeTotalPts;
136
let r = ((t/100)*(tp+w) - c);
137
$('#res-pts').show();
138
$('#res-val-pts').text(`${r.toFixed(1)}/${w.toFixed(1)} pts`);
139
return 100*r/w;
140
}
141
else if(globalData.examType === 1){
142
return calcTargetTestGrade(t); // test grades are done a bit differently so that's done separately
143
}
144
145
let c = globalData.currentGrade, f = globalData.finalWorth, w = globalData.testWorth, a = globalData.testAvg, o = globalData.totalTests, l = globalData.lowestTest, s = globalData.totalTests*globalData.testAvg, res; //s = total test score
146
if(globalData.testPolicy !== 0){
147
$('#resi').find('b')[0].innerText = (a).toFixed(2);
148
$('#resi').find('b')[1].innerText = ((a*(w/100)).toFixed(2));
149
$('#resi').find('b')[2].innerText = (w).toFixed(2);
150
}
151
switch(globalData.testPolicy){
152
case 0:
153
//result = (targetGrade - currentGrade*(1-(finalWorth/100))) / (finalWorth/100)
154
return (t-c*(1-(f/100)))/(f/100);
155
break;
156
case 1: //Lowest test dropped
157
let td = calcTestDrop();
158
let diff = td[1] - td[0];
159
$('#resi_a').find('b')[0].innerText = (td[2]).toFixed(2);
160
$('#resi_a').find('b')[1].innerText = (td[1]).toFixed(2);
161
$('#resi_c').find('span')[0].innerText = detPlu(diff);
162
$('#resi_c').find('b')[0].innerText = (diff).toFixed(2);
163
return ((t - diff)-c*(1-(f/100)))/(f/100);
164
break;
165
case 2:
166
//targetGrade = (finalWorth/100)*result + currentGrade*(1-(finalWorth/100)) + testReplacementDifference
167
//targetGrade = (finalWorth/100)*result + currentGrade*(1-(finalWorth/100)) + (testWorth/100)*[ [(avgTestGrade * o_totalTests) + (result - lowestTest)]/(o_totalTests) - (avgTestGrade) ]
168
//targetGrade = (currentGrade * totalTests * (finalWorth - 100) + ((lowestTest * testWorth) + 100*totalTests*targetGrade) / (finalGrade*totalTests + testWorth)
169
//targetGrade = (F/100) * R + C(1 - (F/100)) + (W/100)*(R-L)/O
170
//https://www.wolframalpha.com/input/?i=T+%3D+(F%2F100)*R+%2B+C*(1-(F%2F100))+%2B+(W%2F100)*%5B(R-L)%5D%2FO,+solve+for+R
171
res = (c*(f - 100)*o + l*w + 100*o*t)/(f*o + w);
172
if(res > calcTargetGradeS(t)){
173
$('#res_moreInfo').hide();
174
$('#res-warn-grdAdj').show();
175
return calcTargetGradeS(t);
176
}
177
$('#res_moreInfo').show();
178
$('#res-warn-grdAdj').hide();
179
$('#resi_b').find('b')[0].innerText = (res - l).toFixed(2);
180
$('#resi_b').find('b')[1].innerText = ((res - l) / o).toFixed(2);
181
$('#resi_c').find('span')[0].innerText = detPlu((w / 100) * (res - l) / o);
182
$('#resi_c').find('b')[0].innerText = ((w / 100) * (res - l) / o).toFixed(2);
183
return res;
184
break;
185
case 3:
186
//Same as case 2 but with (0.5 * (result - lowestTest)) instead of (result - lowestTest)
187
//https://www.wolframalpha.com/input/?i=T+%3D+(F%2F100)*R+%2B+C*(1-(F%2F100))+%2B+(W%2F100)*%5B0.5(R-L)%5D%2FO,+solve+for+R
188
res = (2*c*(f - 100)*o + l*w + 200*o*t)/(2*f*o + w);
189
console.log(res); console.log(calcTargetGradeS(t));
190
if(res > calcTargetGradeS(t)){
191
$('#res_moreInfo').hide();
192
$('#res-warn-grdAdj').show();
193
return calcTargetGradeS(t);
194
}
195
$('#res_moreInfo').show();
196
$('#res-warn-grdAdj').hide();
197
$('#resi_b').find('b')[0].innerText = (0.5*(res - l)).toFixed(2);
198
$('#resi_b').find('b')[1].innerText = (0.5 * (res - l) / o).toFixed(2);
199
$('#resi_c').find('span')[0].innerText = detPlu((w / 100) * 0.5 * (res - l) / o);
200
$('#resi_c').find('b')[0].innerText = ((w / 100) * 0.5 * (res - l) / o).toFixed(2);
201
return res;
202
break;
203
}
204
}
205
206
function calcResultingGrade(score) {
207
// simple resulting grade calculation
208
// supports weighted and unweighted, but does NOT account for test adjustments
209
let f = score;
210
let w = globalData.finalWorth, wp = globalData.finalWorthPts, c = globalData.currentGrade, cp = globalData.currentGradePts, ct = globalData.currentGradeTotalPts;
211
return globalData.gradeType?(100 * (cp + (f/100*wp)) / (ct + wp)):(f*(w/100)) + (c*(1-(w/100)))
212
}
213
214