UNPKG

grading

Version:

Grading of student submissions, in particular programming tests.

176 lines 7.79 kB
"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