src/app/models/lrs/studentExerciseStatistics.ts
import { ExerciseStatistics } from "../gamification-lrs";
import { CabriActivity } from "../cabri-activity";
import { CabriDataService } from "src/app/services/cabri-data.service";
import { cloneDeep } from "lodash";
import { LmsService } from "src/app/services/lms.service";
import { Journey, JourneysByStatus, journeyStatus } from "../journey";
export enum ExerciseProgressionLength {
number = 1
}
class StatisticsByCompetency {
[category: string]: Array<object>;
}
export class AssignationStatistics {
[journeyId: string]: {
perCorrectAnswers?: number;
};
}
export class StudentExerciseStatistics {
statistics: ExerciseStatistics;
exerciseStatistics: ExerciseStatistics;
statisticsByCompetency;
cabriService: CabriDataService;
currentActivity: CabriActivity;
selectedGrade: string;
userId: string;
constructor(statistics: ExerciseStatistics, userId?: string, cabriService?: CabriDataService, currentActivity?: CabriActivity) {
this.statistics = statistics;
this.userId = userId;
if (cabriService) {
this.cabriService = cabriService;
this.selectedGrade = cabriService.exercices.selected;
}
if (currentActivity) {
this.currentActivity = currentActivity;
this.statisticsByCompetency = this.gatherStatisticsByActivity();
}
}
/**
* Gather all statistics by competency and by activity (without taking care of the mode)
*/
private gatherStatisticsByActivity(): StatisticsByCompetency {
this.exerciseStatistics = cloneDeep(this.statistics);
/**
* Remove duplicate items and keep only statistics with at least one verb passed during the session
*/
const filterStatistics = (object,index,selfArray) => {
if((index === selfArray.findIndex(t => {
return t.exerciseId === object.exerciseId && t.progression !== null
})) && object.passed > 0 || object.passedWithHelp > 0){
return true
}else{
return false
}
}
if (this.userId && this.exerciseStatistics[this.userId]) {
// remove duplicates items with same exerciseId
for (const userId in this.exerciseStatistics) {
if (userId) {
for (const activityId in this.exerciseStatistics[userId]) {
if (activityId) {
for (const category in this.exerciseStatistics[userId][activityId]) {
if (category) {
this.exerciseStatistics[userId][activityId][category] = this.exerciseStatistics[userId][activityId][
category
].filter((ex, index, self) => {
return filterStatistics(ex, index, self)
});
}
}
}
}
}
}
const uniqueCategory = {};
for (const categoryId in this.exerciseStatistics[this.userId][this.currentActivity.id]) {
if (categoryId) {
if (!uniqueCategory[categoryId]) {
uniqueCategory[categoryId] = [];
}
const res = this.exerciseStatistics[this.userId][this.currentActivity.id][categoryId];
uniqueCategory[categoryId] = uniqueCategory[categoryId].concat(res);
}
}
/** Sort result per correct rate answer and keep the best rate with removing other results */
for (const categoryId in uniqueCategory) {
if (categoryId) {
uniqueCategory[categoryId] = uniqueCategory[categoryId].sort((a, b) => b.perGoodAnswers - a.perGoodAnswers);
uniqueCategory[categoryId] = uniqueCategory[categoryId].filter((ex, index, self) => {
return (
index ===
self.findIndex(t => {
return t.exerciseId === ex.exerciseId;
})
);
});
}
}
return uniqueCategory;
}
}
/**
* Count number of completed exercises
*/
get countCompletedExercises(): { [category: string]: { numberCompleted: number } } {
const result = {};
for (const categoryId in this.statisticsByCompetency) {
if (categoryId) {
const resultFinal = this.statisticsByCompetency[categoryId].filter(ex => {
return ex.completed && this.selectedGrade === ex.grade;
});
if (resultFinal.length > 0) {
result[categoryId] = { numberCompleted: resultFinal.length };
} else {
result[categoryId] = { numberCompleted: 0 };
}
}
}
this._addNotExistingCategory(result, { numberCompleted: 0 });
return result;
}
/**
* Get history of all story statistics / keeping the best session result based on exercise
*/
getStoryStatistics(): { [idExercice: string]: { shooting: number; star: number; moon: number; averageGoodAnswer: number } } {
const statisticsBestResultByExercise: {
[idExercice: string]: { shooting: number; star: number; moon: number; averageGoodAnswer: number };
} = {};
for (const userId in this.statistics) {
if (userId === this.userId) {
for (const activityId in this.statistics[userId]) {
if (activityId) {
for (const exerciseInfo in this.statistics[userId][activityId]) {
if (exerciseInfo) {
this.statistics[userId][activityId][exerciseInfo].forEach(result => {
if (result?.story) {
if (!statisticsBestResultByExercise[result.exerciseId]) {
statisticsBestResultByExercise[result.exerciseId] = {
shooting: result.passed,
star: result.passedWithHelp,
moon: result.failed,
averageGoodAnswer: result.perGoodAnswers
};
} else {
if (
result.perGoodAnswers > statisticsBestResultByExercise[result.exerciseId].averageGoodAnswer
) {
statisticsBestResultByExercise[result.exerciseId] = {
shooting: result.passed,
star: result.passedWithHelp,
moon: result.failed,
averageGoodAnswer: result.perGoodAnswers
};
}
}
}
});
}
}
}
}
}
}
return statisticsBestResultByExercise;
}
/**
* Get history of all story statistics / keeping the best session result based on exercise
*/
getAssignationStatistics(): { [idExercice: string]: { shooting: number; star: number; moon: number; averageGoodAnswer: number } } {
const statisticsBestResultByExercise: {
[idExercice: string]: { shooting: number; star: number; moon: number; averageGoodAnswer: number };
} = {};
for (const userId in this.statistics) {
if (userId) {
for (const activityId in this.statistics[userId]) {
if (activityId) {
for (const exerciseInfo in this.statistics[userId][activityId]) {
if (exerciseInfo) {
this.statistics[userId][activityId][exerciseInfo].forEach(result => {
if (result?.story) {
if (!statisticsBestResultByExercise[result.exerciseId]) {
statisticsBestResultByExercise[result.exerciseId] = {
shooting: result.passed,
star: result.passedWithHelp,
moon: result.failed,
averageGoodAnswer: result.perGoodAnswers
};
} else {
if (
result.perGoodAnswers > statisticsBestResultByExercise[result.exerciseId].averageGoodAnswer
) {
statisticsBestResultByExercise[result.exerciseId] = {
shooting: result.passed,
star: result.passedWithHelp,
moon: result.failed,
averageGoodAnswer: result.perGoodAnswers
};
}
}
}
});
}
}
}
}
}
}
return statisticsBestResultByExercise;
}
/**
* Get history of all assignation journey statistics / keeping the best session result based on exercise
*/
getTotalAssignationStatistics(allJourneysByStatuses: JourneysByStatus[], completedJourneysKeyName: string) {
let completedJourneys: Journey[];
const journeyStatistics: AssignationStatistics = {};
if (allJourneysByStatuses?.length > 0) {
for (const currentJourneyByStatus in allJourneysByStatuses[0]) {
if (currentJourneyByStatus === completedJourneysKeyName) {
if (allJourneysByStatuses[0][currentJourneyByStatus].items?.length > 0) {
completedJourneys = allJourneysByStatuses[0][currentJourneyByStatus].items;
}
}
}
}
if (completedJourneys) {
completedJourneys.forEach(currentJourney => {
if(!journeyStatistics[currentJourney.id] || journeyStatistics[currentJourney.id].perCorrectAnswers < currentJourney.correctAnswers){
journeyStatistics[currentJourney.id] = { perCorrectAnswers: currentJourney.correctAnswers };
}
});
return journeyStatistics;
}
}
/**
* Better rate of correct answers
*/
get averageRateCorrectAnswers(): { [category: string]: number } {
const result = {};
for (const categoryId in this.statisticsByCompetency) {
if (categoryId) {
let allCompletedExercises;
allCompletedExercises = this.statisticsByCompetency[categoryId].filter(ex => {
return ex.completed && this.selectedGrade === ex.grade;
});
if (allCompletedExercises.length > 0) {
let totalResult = 0;
for (let i = 0; i < allCompletedExercises.length; i++) {
totalResult += allCompletedExercises[i].perGoodAnswers;
if (i === allCompletedExercises.length - 1) {
totalResult = totalResult / allCompletedExercises.length;
result[categoryId] = Math.floor(totalResult);
}
}
}
}
}
this._addNotExistingCategory(result, 0);
return result;
}
/**
* Get each exercise better rate of correct answer
*/
get exercisePerGoodAnswer(): { [exerciseId: string]: number } {
const result = {};
for (const categoryId in this.statisticsByCompetency) {
if (categoryId) {
this.statisticsByCompetency[categoryId].forEach(ex => {
if (!ex.completed) {
return;
}
if (!result[ex.exerciseId]) {
result[ex.exerciseId] = {};
result[ex.exerciseId].result = "";
result[ex.exerciseId].progressedUntil = "";
result[ex.exerciseId].decimalValue = "";
result[ex.exerciseId].status = "";
}
result[ex.exerciseId].result = ex.perGoodAnswers;
const progressed = ExerciseProgressionLength.number * (ex.perGoodAnswers / 100);
result[ex.exerciseId].progressedUntil = Math.ceil(progressed);
result[ex.exerciseId].decimalValue = progressed % 1;
});
}
}
return result;
}
/**
* Get total number of awards by activity(shootings/normal/moons)
*/
get totalNumberAwardsByActivity(): { [category: string]: { totalShootings: number; totalNormals: number; totalMoons: number } } {
const result = {};
for (const categoryId in this.statisticsByCompetency) {
if (categoryId) {
let totalShootings = 0;
let totalNormals = 0;
let totalMoons = 0;
const level = this.statisticsByCompetency[categoryId].filter(ex => {
return ex.grade === this.selectedGrade;
});
if (level?.length > 0) {
level.forEach(ex => {
totalShootings += ex.passed;
totalNormals += ex.passedWithHelp;
totalMoons += ex.failed;
result[categoryId] = { totalShootings, totalNormals, totalMoons };
});
}
}
}
this._addNotExistingCategory(result, { totalShootings: 0, totalNormals: 0, totalMoons: 0 });
return result;
}
/**
* Get total number of awards(shootings/normal/moons) for Kidaia.
*/
get totalNumberAwards(): { shooting: number } {
let shooting = 0;
let result = { shooting };
try {
if (!this.userId) {
throw new Error("User id not defined");
}
if (!this.statistics[this.userId]) {
// throw new Error(`User id ${this.userId} has no any normal or shooting stars`);
}
for (const activityId in this.statistics[this.userId]) {
if (activityId) {
for (const categoryId in this.statistics[this.userId][activityId]) {
if (categoryId) {
this.statistics[this.userId][activityId][categoryId].forEach(ex => {
shooting += ex.passed + ex.passedWithHelp;
result = { shooting };
});
}
}
}
}
} catch (err) {
console.warn("student statistics error", err);
}
return result;
}
/**
* Add the category with initial value if not any data has been found
*/
private _addNotExistingCategory(result: { [category: string]: number }, defaultResult) {
for (const categoryName in this.cabriService.categories[this.cabriService.exercices.selected]) {
if (!result.hasOwnProperty(categoryName)) {
result[categoryName] = defaultResult;
}
}
}
}