import {Injectable} from '@angular/core'; import {MiniLessonMetrics, ExerciseData, sum} from 'ox-types'; @Injectable({ providedIn: 'root' }) export abstract class ScoreStarsService { // public abstract calculateScore(metrics: MicroLessonMetrics): void; // TODO public calculateScore(metrics: MiniLessonMetrics, minScore: number = 500, maxScore: number = 10000) { // const exerciseCount = metrics.exerciseMetrics.length; const exerciseCount = metrics.expandableInfo.exercisesData.length; const parameters = this.getStandardMetricsByExerciseCount(exerciseCount); let calculatedPoints = 0; // let calculatedBonus = 0; // metrics.exerciseMetrics.forEach(exerciseMetric => { metrics.expandableInfo.exercisesData.forEach(exerciseMetric => { // console.log(this.calculateScoreForMetric(exerciseMetric, parameters)); const aux = this.calculateScoreForMetric(exerciseMetric, parameters); calculatedPoints += aux.points; // calculatedBonus += aux.bonusPoints; }); metrics.pointsScore = Math.max(minScore, Math.round(calculatedPoints)); metrics.bonusScore = Math.round(metrics.pointsScore / 9); // metrics.bonusScore = Math.round(calculatedBonus); metrics.score = Math.round(Math.min(Math.max(minScore, metrics.bonusScore + metrics.pointsScore), maxScore)); // throw new Error('Falta hacer esto de las metricas!!'); // metrics.score =; } protected getStandardMetricsByExerciseCount(exerciseCount: number) { const totalBonusScore = 1000; const totalPointsScore = 9000; if (exerciseCount <= 5) { return new ScoreParameters(totalPointsScore / exerciseCount, [70, 20, 20], totalBonusScore / exerciseCount); } else { return new ScoreParameters(totalPointsScore / exerciseCount, [60, 25, 25], totalBonusScore / exerciseCount); } } public calculateStars(metrics: MiniLessonMetrics): void { if (!metrics.score) { throw new Error('You can\' t set the star count if you haven\'t set the score!'); } metrics.stars = this.defaultStarCalculator(metrics.score); } private defaultStarCalculator(score: number): number { if (score >= 9000) { return 5; } if (score >= 7500) { return 4; } if (score >= 6000) { return 3; } if (score >= 3000) { return 2; } return 1; } // TODO protected calculateScoreForMetric(exerciseMetric: ExerciseData, parameters: ScoreParameters): { points: number, bonusPoints: number } { // this should be 0 or 1 // const correctCount = exerciseMetric.answers.filter(x => x.correctness === 'right').length; const correctCount = exerciseMetric.userInput.answers.filter(x => x.parts.every(part => part.correctness === 'correct')).length; if (correctCount !== 0 && correctCount !== 1) { // if there is more than one correct answer for the same exercise, something is wrong // console.warn('There was a strange amount of common answers in exercise metric.', correctCount); throw new Error('There was a strange amount of common answers in exercise metric. count: ' + correctCount); } let exerciseScore = 0; if (correctCount >= 1) { exerciseScore = parameters.exerciseValue; } // if one part of the answer is wrong, we consider the whole answer wrong, at least for noew const wrongCount = exerciseMetric.userInput.answers.filter((x => x.parts.some(part => part.correctness === 'wrong'))).length; const wrongPenalty = (sum(parameters.errorPercentages.slice(0, wrongCount)) / 100) * parameters.exerciseValue; // const hintPenalty = (this.getHintPercentagePenalty(exerciseMetric.hints.length) / 100) * parameters.exerciseValue; const hintPenalty = (this.getHintPercentagePenalty(exerciseMetric.userInput.requestedHints) / 100) * parameters.exerciseValue; exerciseScore -= wrongPenalty; exerciseScore -= hintPenalty; // Todo review bonus points console.log('Review bonus points!'); // let bonusPoints = 0; // const timeUsed = secondsBetweenDates(exerciseMetric.initialTime, exerciseMetric.finishTime); // if (timeUsed <= exerciseMetric.bonusTime.freeTime) { // bonusPoints = parameters.bonusTime; // } else if (timeUsed <= exerciseMetric.bonusTime.maxTimeToBonus) { // const secondsOverFreeTime = exerciseMetric.bonusTime.maxTimeToBonus - exerciseMetric.bonusTime.freeTime; // const valuePerSecond = parameters.bonusTime / secondsOverFreeTime; // bonusPoints = (exerciseMetric.bonusTime.maxTimeToBonus - timeUsed) * valuePerSecond; // } if (exerciseScore > parameters.exerciseValue + parameters.bonusTime) { throw new Error('There was an error while exercise metrics was being calculated.'); } return {points: exerciseScore, bonusPoints: 0}; } protected getHintPercentagePenalty(count: number) { switch (count) { case 0: return 0; case 1: return 35; case 2: return 40; default: return 45; } } } export class ScoreParameters { constructor(public exerciseValue: number, public errorPercentages: number[], public bonusTime: number) { } }