grading
Version:
Grading of student submissions, in particular programming tests.
307 lines • 11.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestResult = exports.GradingResult = exports.TaskResult = exports.CoverageResult = exports.StudentResult = exports.ManualCorrection = exports.Results = exports.gradeFromPoints = exports.GRADE_VALUES = void 0;
exports.GRADE_VALUES = [
// 0 1 2 3 4 5 6 7 8 9 10
"5,0", "4,0", "3,7", "3,3", "3,0", "2,7", "2,3", "2,0", "1,7", "1,3", "1,0"
// 50%
];
function gradeFromPoints(points, schema) {
for (let note = exports.GRADE_VALUES.length - 1; note > 0; note--) {
if (points >= schema[note]) {
return exports.GRADE_VALUES[note];
}
}
return exports.GRADE_VALUES[0];
}
exports.gradeFromPoints = gradeFromPoints;
function gradingTable(pointsMax, requiredPoints) {
if (!requiredPoints) {
requiredPoints = Math.floor(pointsMax / 2);
}
const schema = [];
const stepsBestanden = (pointsMax - (requiredPoints - 1)) / (exports.GRADE_VALUES.length - 1);
schema[0] = 0;
// falls schlechter als 4 (4,3, 4,7 etc); nicht an der BHT
// const stepsDurchgefallen = bestandenAb / 5;
// for (let i = 0; i < 5; i++) {
// schema[i] = Math.floor(stepsDurchgefallen * i);
// }
schema[1] = requiredPoints;
for (let i = 2; i < exports.GRADE_VALUES.length; i++) {
schema[i] = requiredPoints + Math.floor(stepsBestanden * (i - 1));
}
return schema;
}
class Results {
constructor(course, term, examName, maxPoints, minCoverageStatements = 0, penaltiesCoverageMax = 0, extraCoverageMax = 0,
/**
* How many points are necessary to pass the exam.
*/
requiredPoints) {
this.course = course;
this.term = term;
this.examName = examName;
this.maxPoints = maxPoints;
this.minCoverageStatements = minCoverageStatements;
this.penaltiesCoverageMax = penaltiesCoverageMax;
this.extraCoverageMax = extraCoverageMax;
this.requiredPoints = requiredPoints;
this.studentResults = [];
this.appliedCorrections = 0;
this.gradingTable = gradingTable(maxPoints, requiredPoints);
this.dateTime = Date.now();
}
/**
*
* @param rawPoints Punkte ohne Coverage-Korrektur
* @param stmtCoverage Statement-Coverage (des Studenten)
* @returns Korrekturfaktor für Coverage; 0, wenn keine Coverage verlangt (minCoverageStatements undefined oder 0)
*/
coveragePunkte(rawPoints, stmtCoverage) {
if (this.minCoverageStatements <= 0 || this.minCoverageStatements > 100 || this.minCoverageStatements == stmtCoverage) {
return 0;
}
if (stmtCoverage < 0) {
stmtCoverage = 0;
}
if (stmtCoverage > 100) {
stmtCoverage = 100;
}
if (stmtCoverage < this.minCoverageStatements) {
const factor = Math.max(0, stmtCoverage) / this.minCoverageStatements;
// penaltiesCoverageMax = 0 means: no penalty
const deductionMax = Math.min(this.penaltiesCoverageMax, this.maxPoints);
const deduction = (1 - factor) * deductionMax;
return Math.max(-rawPoints, -Math.floor(deduction));
}
else { // stmtCoverage>this.minCoverageStatements
if (this.extraCoverageMax > 0) {
const factor = (stmtCoverage - this.minCoverageStatements) / (100 - this.minCoverageStatements);
const extra = this.extraCoverageMax * factor;
return Math.min(rawPoints, Math.ceil(extra));
}
}
return 0;
}
}
exports.Results = Results;
class Skippable {
constructor() {
this.skipMessage = null;
}
skipped() {
return this.skipMessage ? true : false;
}
}
/**
* Points may be 0 if only a comment is added.
*/
class ManualCorrection {
constructor(points, reason, absolute) {
if (!points) {
this.points = 0;
}
else {
this.points = points;
}
this.reason = reason;
this.absolute = absolute;
}
}
exports.ManualCorrection = ManualCorrection;
class StudentResult extends Skippable {
constructor(submissionId, userName) {
super();
this.submissionId = submissionId;
this.userName = userName;
this.taskResults = [];
this.coverageResult = new CoverageResult();
this.noSubmission = false;
this.checkReportFound = false;
this.studentReportFound = false;
this.gradingDone = false;
this.penaltyResults = [];
this.manualCorrection = [];
this.errorDetails = "";
/**
* sum of points of tasks including manual corrections, computed in computeAndSetRawPunkte
*/
this.pointsRaw = 0;
/**
* actual coverage points, can only computed when rawPoints have been set
*/
this.pointsCoverage = 0;
/**
* points raw + point coverage
*/
this.points = 0;
/**
* grade (1-5, no 4,3 and 4,7), can only be computed when points have been set.
*/
this.grade = "";
/**
* Set to true if manual correction changed the points using absolute points.
*/
this.absolutePoints = false;
this.userTests = false;
this.userTestsSuccess = 0;
this.userTestsFailureOrError = 0;
this.userTestsCount = 0;
this.comment = "";
this.patched = false;
}
addComment(comment) {
if (this.comment.length > 0) {
this.comment += " ";
}
this.comment += comment;
}
addPenalty(gradingResult) {
this.penaltyResults.push(gradingResult);
}
/**
* @returns Returns true, if any penalities exist (i.e. taking actual results into account).
*/
hasPenalties() {
let penaltyPoints = this.penaltyResults.reduce((sum, bew) => sum + bew.points, 0);
return penaltyPoints != 0;
}
computeAndSetRawPunkte() {
this.pointsRaw = this.taskResults.reduce((sum, taskResult) => {
// compute pointsRaw for each task even if it skipped as it may contain manual corrections
taskResult.computeAndSetRawPoints();
return sum + taskResult.points;
}, 0);
if (this.penaltyResults.length > 0) {
this.penaltyResults.forEach(gradingResult => gradingResult.computeAndSetRawPoints());
this.pointsRaw += this.penaltyResults.reduce((sum, gradingResult) => sum + gradingResult.points, 0);
}
this.manualCorrection.forEach(manualCorrection => this.pointsRaw += manualCorrection.points);
if (this.pointsRaw < 0) {
this.pointsRaw = 0;
}
}
get pointsMax() {
return this.taskResults.reduce((sum, task) => sum + task.pointsMax, 0);
}
}
exports.StudentResult = StudentResult;
class CoverageResult extends Skippable {
constructor() {
super();
this.stmtCoverage = 0;
}
}
exports.CoverageResult = CoverageResult;
class TaskResult extends Skippable {
constructor(name, pointsMax) {
super();
this.gradingResults = [];
this.penaltyResults = [];
this.points = 0;
this.manualCorrections = [];
this.images = [];
this.message = null;
/** for statistics */
this.precondition = "none";
this.name = name;
this.pointsMax = pointsMax;
}
addGradingResult(gradingResult) {
this.gradingResults.push(gradingResult);
}
addPenalty(gradingResult) {
this.penaltyResults.push(gradingResult);
}
/**
* @returns Returns true, if any penalities exist (i.e. taking actual results into account).
*/
hasPenalties() {
let penaltyPoints = this.penaltyResults.reduce((sum, bew) => sum + bew.points, 0);
return penaltyPoints != 0;
}
computeAndSetRawPoints() {
this.points = 0;
if (!this.skipped()) {
if (this.gradingResults.length > 0) {
this.gradingResults.forEach(gradingResult => gradingResult.computeAndSetRawPoints());
this.points = this.gradingResults.reduce((sum, gradingResult) => sum + gradingResult.points, 0);
}
if (this.penaltyResults.length > 0) {
this.penaltyResults.forEach(gradingResult => gradingResult.computeAndSetRawPoints());
this.points += this.penaltyResults.reduce((sum, gradingResult) => sum + gradingResult.points, 0);
}
}
this.manualCorrections.forEach(correction => this.points += correction.points);
if (this.points < 0) { // minimum 0 points per task
this.points = 0;
}
}
}
exports.TaskResult = TaskResult;
class GradingResult extends Skippable {
/**
* If the grading is negative (i.e., {@link pointsMax} is less 0), the grading is a penalty.
*/
constructor(text, pointsMax, isBonus) {
super();
this.text = text;
this.pointsMax = pointsMax;
this.isBonus = isBonus;
this.testResults = [];
this.points = 0;
this.images = [];
this.message = null;
}
addTestResult(text, testStatus, message) {
this.testResults.push(new TestResult(text, testStatus, message));
}
/**
* If the grading is negative (i.e., {@link pointsMax} is less 0), the grading is a penalty.
*/
get isPenalty() {
return this.pointsMax < 0;
}
/**
* Computes raw points (and sets them internally to field {@link points}).
*
* If the grading is positive, the number of successfully passed tests is used to compute the raw points.
* If the grading is a penalty (i.e. grading is negative) the number of failed tests are used instead.
*/
computeAndSetRawPoints() {
this.points = 0;
if (!this.isPenalty) {
if (this.skipped()) {
return;
}
const successCount = this.testResults.filter(test => test.testStatus === "success").length;
if (this.testResults.length == 0) {
console.error("Keine Testergebnisse verzeichnet, setze Punkte für Bewertung " + this.text + " auf 0.");
this.points = 0;
}
else {
this.points = Math.ceil(this.pointsMax * successCount / this.testResults.length);
}
}
else {
const failureCount = this.testResults.filter(test => test.testStatus !== "success").length;
if (this.testResults.length == 0) {
this.points = 0;
}
else {
this.points = Math.floor(this.pointsMax * failureCount / this.testResults.length);
}
}
}
}
exports.GradingResult = GradingResult;
class TestResult {
constructor(text, testStatus, message) {
this.text = text;
this.testStatus = testStatus;
this.message = message;
}
}
exports.TestResult = TestResult;
//# sourceMappingURL=results.js.map