grading
Version:
Grading of student submissions, in particular programming tests.
176 lines • 7.79 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ItemStatistics = exports.Statistics = exports.computeStatistics = void 0;
const gradingschema_1 = require("./gradingschema");
function subItemType(itemType) {
const ITEM_TYPE_NAMES = ['exam', 'task', 'grading', 'test'];
const current = ITEM_TYPE_NAMES.indexOf(itemType);
if (current < 0) {
return "unknown";
}
if (current == ITEM_TYPE_NAMES.length - 1) {
return "subtest(?!)";
}
return ITEM_TYPE_NAMES[current + 1];
}
function computeStatistics(gradingSchema, results) {
const examStatistics = new ItemStatistics(gradingSchema.exam, gradingSchema.exam, gradingSchema.points, 'exam');
const statistics = new Statistics(examStatistics);
statistics.appliedCorrections = results.appliedCorrections;
// Generate overall scheme
gradingSchema.tasks.forEach((task, taskIndex) => {
const taskLabel = (taskIndex + 1) + ". " + task.name;
const taskStatistic = examStatistics.addItemStatistics(new ItemStatistics(taskLabel, taskLabel, task.points, 'task'));
task.grading.forEach((grading, gradingIndex) => {
const gradingLabel = (taskIndex + 1) + "." + (gradingIndex + 1) + ".";
const gradingTitle = (taskIndex + 1) + "." + (gradingIndex + 1) + ". " + (0, gradingschema_1.gradingText)(grading);
const gradingStatistics = taskStatistic.addItemStatistics(new ItemStatistics(gradingLabel, gradingTitle, grading.points, 'grading'));
grading.tests.forEach((test, testIndex) => {
const testLabel = gradingLabel + (testIndex + 1) + ".";
const testTitle = gradingLabel + (testIndex + 1) + ". " + test;
gradingStatistics.addItemStatistics(new ItemStatistics(testLabel, testTitle, 1, 'test'));
});
});
});
// Fill data
results.studentResults.forEach((studentResult, studentIndex) => {
statistics.registerSubmission(studentResult.coverageResult.stmtCoverage, studentResult.userTestsCount);
examStatistics.registerSubmission(studentResult.pointsRaw, studentResult.skipped());
studentResult.taskResults.forEach((taskResult, taskIndex) => {
const taskLabel = (taskIndex + 1) + ". " + taskResult.name;
const taskStatistic = examStatistics.getItemStatistics(taskIndex, taskLabel, studentResult);
taskStatistic.registerSubmission(taskResult.points, taskResult.skipped());
taskResult.gradingResults.forEach((gradingResult, gradingIndex) => {
const gradingLabel = (taskIndex + 1) + "." + (gradingIndex + 1) + ".";
const gradingStatistics = taskStatistic.getItemStatistics(gradingIndex, gradingLabel);
gradingStatistics.registerSubmission(gradingResult.points, gradingResult.skipped());
gradingResult.testResults.forEach((testResult, testIndex) => {
const testLabel = gradingLabel + (testIndex + 1) + ".";
const testStatistic = gradingStatistics.getItemStatistics(testIndex, testLabel);
testStatistic.registerSubmission(testResult.testStatus === 'success' ? 1 : 0, testResult.testStatus === 'notFound');
});
});
});
});
return statistics;
}
exports.computeStatistics = computeStatistics;
class Statistics {
constructor(examStatistics) {
/**
* All submissions, should be similar to number of submitters.
* Includes skipped items.
*/
this.submissions = 0;
this.sumCoverage = 0;
this.sumStudentTests = 0;
this.appliedCorrections = 0;
this.examStatistics = examStatistics;
}
registerSubmission(coverage, studentTests) {
this.submissions++;
this.sumCoverage += coverage;
this.sumStudentTests += studentTests;
}
get averageCoverage() {
if (this.submissions == 0) {
throw new Error("Cannot compute average coverage, no submissions registered");
}
return this.sumCoverage / this.submissions;
}
get averageStudentTests() {
if (this.submissions == 0) {
throw new Error("Cannot compute average student tests, no submissions registered");
}
return this.sumStudentTests / this.submissions;
}
*[Symbol.iterator]() {
const stack = [];
if (this.examStatistics) {
stack.unshift(this.examStatistics);
}
while (stack.length > 0) {
const current = stack.shift();
yield current;
if (current.itemStatistics.length != 0) {
stack.unshift(...current.itemStatistics);
}
}
}
}
exports.Statistics = Statistics;
class ItemStatistics {
constructor(label, title, pointsMax, itemType) {
/**
* Subitems, i.e. exam->tasks, tasks->tests, tests->none
*/
this.itemStatistics = [];
/**
* All submissions, should be similar to number of submitters.
* Includes skipped items.
*/
this.submissions = 0;
this.sumGainedPoints = 0;
this.skippedCount = 0;
this.label = label;
this.title = title;
this.pointsMax = pointsMax;
this.itemType = itemType;
if (this.pointsMax < 0) {
throw new Error(`Inconsistency for ${this.description}, maximum points must be greater or equal 0.`);
}
}
addItemStatistics(itemStatistics) {
this.itemStatistics.push(itemStatistics);
return itemStatistics;
}
/**
* Returns the statistics for the given subitem by index.
* Since the item is added for the first student, errors here means that a student has something no one else had before.
*/
getItemStatistics(index, label, studentResult) {
const itemStatistics = this.itemStatistics[index];
if (!itemStatistics) {
throw new Error(`Inconsistency in ${this.description} found: No ${subItemType(this.itemType)} at index ${index} ('${label}')${studentResult ? " for " + studentResult.userName : ""}.`);
}
if (itemStatistics.label !== label) {
throw new Error(`Inconsistency in ${this.description} found: ${itemStatistics.description} with different name (${label}) at index ${index}${studentResult ? " for " + studentResult.userName : ""}.`);
}
return itemStatistics;
}
registerSubmission(points, skipped) {
this.submissions++;
this.sumGainedPoints += points;
if (skipped) {
this.skippedCount++;
}
}
get successRate() {
if (this.submissions == 0) {
if (this.itemType === 'exam') {
throw new Error(`Cannot compute success rate for ${this.description}, no submissions registered`);
}
else { // may happen if no student has submitted this item
return 0;
}
}
const averagePoints = this.sumGainedPoints / this.submissions;
const successRate = averagePoints / this.pointsMax;
return successRate;
}
get successRateNonSkipped() {
if (this.submissions == 0) {
throw new Error(`Cannot compute success rate (non skipped) for ${this.itemType} ${this.label}, no submissions registered`);
}
const nonSkipped = this.submissions - this.skippedCount;
if (nonSkipped == 0) {
return 0;
}
return this.sumGainedPoints / nonSkipped;
}
get description() {
return `${this.itemType} '${this.label}'`;
}
}
exports.ItemStatistics = ItemStatistics;
//# sourceMappingURL=statistics.js.map