private static decimal CalculateUserGrade(Module module, UserModuleProgress moduleProgress, List <UserSubjectProgressItem> userProgress, decimal levelsCount) { switch (module.ModuleGradeType) { case ModuleGradeTypeEnum.Percentage: decimal badgeValue = 0; if (moduleProgress == null) { return(badgeValue); } switch (moduleProgress.Level) { case 1: badgeValue = (decimal)0.3; break; case 2: badgeValue = (decimal)0.6; break; case 3: badgeValue = (decimal)0.9; break; case 4: badgeValue = (decimal)1.0; break; } var totalAnswers = new List <bool>(); foreach (List <bool> userAnswerList in userProgress.Select(x => x.Answers).ToList()) { totalAnswers.AddRange(userAnswerList); } var correctAnswers = totalAnswers.Where(x => x).ToList(); if (correctAnswers.Count == 0 || totalAnswers.Count == 0 || badgeValue == 0) { return(0); } return(((decimal)correctAnswers.Count / (totalAnswers.Count > 0 ? totalAnswers.Count : 1)) * badgeValue); case ModuleGradeTypeEnum.SubjectsLevel: default: if (userProgress == null || userProgress.Count == 0) { return(0); } var levelSum = userProgress.Select(x => x.Level).Sum(); if (levelSum == 0 || module.Subjects == null || module.Subjects.Count == 0 || levelsCount == 0) { return(0); } return(levelSum / (module.Subjects.Count * levelsCount)); } }
private async Task SetModuleProgress(List <Module> modules, List <Track> tracks, List <UserProgress> userTracks, ObjectId userId, UserInfoDB userInfo, CancellationToken token) { var moduleId = ObjectId.Parse(userInfo.IdModuloEvento); var currentModule = modules.FirstOrDefault(t => t.Id == moduleId); if (currentModule != null) { await _db.ModuleGradeCollection.InsertOneAsync( ModuleGrade.Create( ObjectId.Parse(userInfo.IdModuloEvento), userId, CellIsEmpty(userInfo.Pontuacao) ? 0 : decimal.Parse(userInfo.Pontuacao.Replace(".", ",")), CellIsEmpty(userInfo.Presenca) ? 0 : decimal.Parse(userInfo.Presenca.Replace(".", ",")) ).Data, cancellationToken : token ); int currentLevel = GetLevelByGrade(userInfo.Pontuacao); var progressesToAdd = new List <UserSubjectProgress>(); if (currentModule.Subjects != null && currentModule.Subjects.Count > 0) { foreach (var subject in currentModule.Subjects) { var progress = UserSubjectProgress.Create(moduleId, subject.Id, userId); progress.Level = currentLevel; progress.Points = 100 * currentLevel; progressesToAdd.Add(progress); } await _db.UserSubjectProgressCollection.InsertManyAsync( progressesToAdd, cancellationToken : token ); } var moduleProgress = UserModuleProgress.Create(moduleId, userId); moduleProgress.Level = currentLevel; moduleProgress.Points = progressesToAdd.Sum(p => p.Points); await _db.UserModuleProgressCollection.InsertOneAsync( moduleProgress, cancellationToken : token ); var track = tracks.FirstOrDefault(t => userTracks.Any(ut => ut.Id == t.Id) && t.ModulesConfiguration.Any(m => m.ModuleId == moduleId) ); if (track != null) { var trackProgress = await _db.UserTrackProgressCollection .AsQueryable() .Where(t => t.TrackId == track.Id && t.UserId == userId) .FirstOrDefaultAsync(); if (trackProgress != null) { int modulesCompleted = 0; var trackConfig = track.ModulesConfiguration.FirstOrDefault(m => m.ModuleId == moduleId); if (trackConfig != null && currentLevel > trackConfig.Level) { modulesCompleted++; trackProgress.ModulesCompleted.Add(moduleId); trackProgress.Progress = (decimal)modulesCompleted / (decimal)track.ModulesConfiguration.Count; await _db.UserTrackProgressCollection.ReplaceOneAsync( t => t.Id == trackProgress.Id, trackProgress, cancellationToken : token ); } } } } }
private async Task <TrackItem> GetStudentsInfo( TrackItem track, ObjectId moduleId, List <Student> trackStudents, List <UserModuleProgress> progresses, List <UserFile> userFiles ) { track.Students = new List <StudentItem>(); track.StudentsProgress = new List <StudentsProgressItem>(); track.WrongConcepts = new List <WrongConcept>(); track.ViewedContents = new List <ViewedContentItem>(); track.SubjectsProgress = new List <SubjectsProgressItem>(); track.UserFiles = new List <UserFile>(); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = null }); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = 0 }); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = 1 }); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = 2 }); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = 3 }); track.StudentsProgress.Add(new StudentsProgressItem { Count = 0, Level = 4 }); var trackModule = track.ModulesConfiguration.First(m => m.ModuleId == moduleId ); track.ModulesConfiguration = new List <TrackModuleItem> { trackModule }; foreach (var student in trackStudents) { if (student.WrongConcepts != null && student.WrongConcepts.Any(x => x.ModuleId == moduleId)) { track.WrongConcepts.AddRange( student.WrongConcepts.Where(x => x.ModuleId == moduleId).ToList() ); } UserModuleProgress userProgress = null; int level = 0; if (progresses.Count > 0) { userProgress = progresses.FirstOrDefault(p => p.UserId == student.Id ); if (userProgress == null) { track.StudentsProgress.First(x => x.Level == null).Count++; } else { level = userProgress.Level; track.StudentsProgress.First(x => x.Level == level).Count++; } } var newStud = new StudentItem { Id = student.Id, ImageUrl = student.ImageUrl, Name = student.Name, Objective = userProgress == null ? 0 : userProgress.Progress, Finished = userProgress != null && userProgress.Level > trackModule.Level, UserFiles = userFiles.Where(x => x.CreatedBy == student.Id).OrderByDescending(x => x.CreatedAt).FirstOrDefault() }; if (userProgress != null) { newStud.Level = level; } track.Students.Add(newStud); } var dbModule = await GetModuleById(moduleId); track.ModuleTitle = dbModule.Title; foreach (var subject in dbModule.Subjects) { var progressColl = _db.Database.GetCollection <UserSubjectProgress>("UserSubjectProgress"); var progQuery = await progressColl.FindAsync(x => x.ModuleId == moduleId ); var subjectProgresses = await progQuery.ToListAsync(); decimal subjectProgressSum = subjectProgresses.Where(p => p.SubjectId == subject.Id && trackStudents .Select(s => s.Id) .Contains(p.UserId) ).Sum(p => p.Level); track.StudentsCount = trackStudents.Count(); if (track.StudentsCount > 0) { track.SubjectsProgress.Add(new SubjectsProgressItem { Level = subjectProgressSum / (decimal)track.StudentsCount, SubjectTitle = subject.Title }); } foreach (var content in subject.Contents) { string contentId = content.Id.ToString(); var studentsIds = trackStudents.Select(s => s.Id); var actionsColl = _db.Database.GetCollection <Action>("Actions"); var actionsQuery = await actionsColl.FindAsync(x => x.ContentId == contentId && x.Description == "content-access" && studentsIds.Contains(x.CreatedBy) ); var actions = await actionsQuery.ToListAsync(); var actionsCount = actions.GroupBy(c => c.CreatedBy).Count(); if (actionsCount > 0) { track.ViewedContents.Add(new ViewedContentItem { ContentTitle = content.Title, ContentType = content.Type, Count = actionsCount }); } } } track.WrongConcepts = track.WrongConcepts .GroupBy(c => new { c.Concept, c.ModuleId }) .Select(g => new WrongConcept { Concept = g.First().Concept, ModuleId = g.First().ModuleId, ModuleName = g.First().ModuleName, Count = g.Sum(c => c.Count) }) .OrderByDescending(wc => wc.Count) .ToList(); return(track); }
public async Task <Result <UserAnswerInfo> > Handle(Contract request, CancellationToken cancellationToken) { try { var moduleId = ObjectId.Parse(request.ModuleId); var subjectId = ObjectId.Parse(request.SubjectId); var userId = ObjectId.Parse(request.UserId); var questionId = ObjectId.Parse(request.QuestionId); var answerId = ObjectId.Parse(request.AnswerId); var progress = await _db .UserSubjectProgressCollection .AsQueryable() .FirstOrDefaultAsync(x => x.ModuleId == moduleId && x.UserId == userId && x.SubjectId == subjectId, cancellationToken: cancellationToken); if (progress == null) { return(Result.Fail <UserAnswerInfo>("Por favor reinicie a avaliação.")); } if (progress.Level == 4) { return(Result.Fail <UserAnswerInfo>("Você já alcançou o nível máximo.")); } var question = progress.Questions.FirstOrDefault(x => x.QuestionId == questionId); if (question == null) { return(Result.Fail <UserAnswerInfo>("Questão não encontrada. Por favor reinicie a avaliação.")); } if (question.HasAnsweredCorrectly) { return(Result.Fail <UserAnswerInfo>("Questão já respondida corretamente.")); } question.HasAnsweredCorrectly = question.CorrectAnswerId == answerId; question.Answered = true; question.AnsweredCount += 1; var dbQuestion = await _db.QuestionCollection.AsQueryable().FirstAsync(x => x.Id == questionId, cancellationToken: cancellationToken); var applicableQuestionLevel = progress.Level == 3 ? 2 : progress.Level; var answer = dbQuestion.Answers.First(x => x.Id == answerId); var newAnswer = new UserAnswer() { AnswerDate = DateTimeOffset.UtcNow, QuestionId = questionId, AnswerPoints = answer.Points, AnswerId = answerId, CorrectAnswerId = question.CorrectAnswerId, CorrectAnswer = question.HasAnsweredCorrectly, QuestionText = dbQuestion.Text.Length > 1000 ? dbQuestion.Text.Substring(1000) : dbQuestion.Text, AnswerText = answer.Description, Order = progress.Answers.Count, Level = applicableQuestionLevel }; progress.Answers.Add(newAnswer); if (!question.HasAnsweredCorrectly && dbQuestion.Concepts != null && dbQuestion.Concepts.Count > 0) { newAnswer.QuestionConcepts = dbQuestion.Concepts?.Select(c => c.Name).Aggregate((f, s) => f + "," + s); if (answer.Concepts != null && answer.Concepts.Any(x => !x.IsRight)) { request.Concepts = answer.Concepts.Where(x => !x.IsRight).Select(x => x.Concept) .ToList(); newAnswer.AnswerWrongConcepts = request.Concepts.Aggregate((f, s) => f + "," + s); await SaveStudentWrongConcepts(userId, request, cancellationToken); } } /* * Validação de progresso * - Quantidade de respostas a serem validadas: * - Total = MIN(Tres perguntas * total de conceitos do assunto, Total de perguntas do BDQ, Limite de Perguntas do BDQ - se houver) * - Formula para passar de nivel: * passou = SUM(Pontuação das respostas dentro do total de perguntas válidas) / Pontuação Máxima (Total * 2) > Média necessária do nivel * - A média volante será o "Total", ou seja, a ordem de respostas certas e erradas deverá ser guardada, mas só vai contar as últimas "Total" */ newAnswer.TotalDbQuestionNumber = await _db.QuestionCollection.AsQueryable().CountAsync(x => x.ModuleId == moduleId && x.SubjectId == subjectId && x.Level == applicableQuestionLevel, cancellationToken: cancellationToken); var dbSubject = await _db.ModuleCollection.AsQueryable() .Where(x => x.Id == moduleId) .Select(x => x.Subjects.First(s => s.Id == subjectId)) .FirstAsync(cancellationToken); newAnswer.TotalConceptNumber = dbSubject.Concepts.Count; newAnswer.LevelPercent = progress.Level == 3 ? 1M : dbSubject.UserProgresses.First(x => x.Level == progress.Level).Percentage; newAnswer.TotalQuestionNumber = newAnswer.TotalDbQuestionNumber < newAnswer.TotalConceptNumber * 3 ? newAnswer.TotalDbQuestionNumber : newAnswer.TotalConceptNumber * 3; var module = await _db.ModuleCollection.AsQueryable() .Where(m => m.Id == moduleId) .FirstOrDefaultAsync(cancellationToken); if (module != null && module.QuestionsLimit.HasValue) { newAnswer.ModuleQuestionsLimit = module.QuestionsLimit; newAnswer.TotalQuestionNumber = newAnswer.TotalQuestionNumber < module.QuestionsLimit.Value ? newAnswer.TotalQuestionNumber : module.QuestionsLimit.Value; } newAnswer.MaxPoints = newAnswer.TotalQuestionNumber * 2; newAnswer.TotalAnswers = progress.Answers.Count(x => x.Level == applicableQuestionLevel); newAnswer.InitWindow = newAnswer.TotalAnswers.Value - newAnswer.TotalQuestionNumber.Value > 0 ? newAnswer.TotalAnswers.Value - newAnswer.TotalQuestionNumber.Value : 0; newAnswer.EndWindow = newAnswer.TotalQuestionNumber.Value > newAnswer.TotalAnswers.Value ? newAnswer.TotalAnswers.Value : newAnswer.TotalQuestionNumber.Value; var accountableAnswers = progress.Answers .Where(x => x.Level == applicableQuestionLevel) .ToList() .GetRange(newAnswer.InitWindow.Value, newAnswer.EndWindow.Value); newAnswer.TotalAccountablePoints = accountableAnswers.Sum(x => x.AnswerPoints); newAnswer.TotalAccountablePoints = newAnswer.TotalAccountablePoints < 0 ? 0 : newAnswer.TotalAccountablePoints; // se já respondeu todas as perguntas corretamente e não tem mais como responder outras perguntas, segue a vida e passa o cara de nivel newAnswer.HasAnsweredAllLevelQuestionsCorrectly = !progress.Questions.Any(x => x.HasAnsweredCorrectly == false && x.Level == applicableQuestionLevel); newAnswer.TotalApplicablePoints = !newAnswer.HasAnsweredAllLevelQuestionsCorrectly.Value ? newAnswer.TotalAccountablePoints : newAnswer.MaxPoints; newAnswer.AbsoluteProgress = (decimal)newAnswer.TotalApplicablePoints / (decimal)newAnswer.MaxPoints; newAnswer.OriginalLevel = progress.Level; if (progress.Level < 2 && newAnswer.AbsoluteProgress >= newAnswer.LevelPercent) // Valida os niveis iniciante e intermediario { progress.Level++; progress.Progress = 0; //progress.PassPercentage = 1; } else if (progress.Level == 2 && newAnswer.AbsoluteProgress >= newAnswer.LevelPercent && newAnswer.AbsoluteProgress < 1) // Valida se continua na validação para expert { progress.Level++; progress.Progress = newAnswer.AbsoluteProgress.Value; //progress.PassPercentage = 1; } else if (progress.Level >= 2 && newAnswer.AbsoluteProgress >= 1) // Valida se foi direto para expert { progress.Level = 4; progress.Progress = 1; //progress.PassPercentage = 0; } else { //progress.Progress = newAnswer.AbsoluteProgress.Value; //decimal notAnswered = progress.Questions.Count(x => x.Level == applicableQuestionLevel && x.AnsweredCount == 0) * 2; //decimal notAnsweredCorrectly = progress.Questions.Count(x => x.Level == applicableQuestionLevel && !x.HasAnsweredCorrectly && x.AnsweredCount == 1); //if (notAnswered == 0 && notAnsweredCorrectly == 0) //{ // progress.PassPercentage = 0; //} //else //{ // var pass = (notAnswered + notAnsweredCorrectly) / (decimal)newAnswer.MaxPoints; // progress.PassPercentage = pass > 1 ? 1 : pass; //} progress.Progress = newAnswer.AbsoluteProgress.Value / (progress.Level < 3 ? newAnswer.LevelPercent.Value : 1); } newAnswer.FinalLevel = progress.Level; // newAnswer.PassPercentage = progress.PassPercentage; // progress.Points = (100 * progress.Level) + (progress.Level < 4 && newAnswer.FinalLevel == newAnswer.Level ? (int)(100 * (decimal)newAnswer.AbsoluteProgress) : 0); progress.Points = (100 * progress.Level) + (progress.Level < 4 ? (int)(100 * progress.Progress) : 0); await _db.UserSubjectProgressCollection.ReplaceOneAsync(x => x.Id == progress.Id, progress, cancellationToken : cancellationToken); var result = new UserAnswerInfo() { HasAnsweredCorrectly = question.HasAnsweredCorrectly, HasAchievedNewLevel = newAnswer.OriginalLevel < progress.Level, LevelAchieved = progress.Level, Concepts = dbQuestion.Answers.First(x => x.Id == answerId).Concepts, // PassPercentage = progress.PassPercentage, Progress = progress.Progress }; // Passou de nível if (result.HasAchievedNewLevel) { // Atualizar Modulo var userModuleProgress = await _db.UserModuleProgressCollection .AsQueryable() .FirstOrDefaultAsync(x => x.ModuleId == moduleId && x.UserId == userId, cancellationToken: cancellationToken); var create = userModuleProgress == null; if (create) { userModuleProgress = UserModuleProgress.Create(moduleId, userId); } var subjects = await _db.ModuleCollection .AsQueryable() .Where(x => x.Id == moduleId) .Select(x => x.Subjects) .FirstAsync(cancellationToken: cancellationToken); var subjectProgress = await _db.UserSubjectProgressCollection .AsQueryable() .Where(x => x.ModuleId == moduleId && x.UserId == userId) .ToListAsync(cancellationToken); var ids = subjects.Select(x => x.Id); subjectProgress = subjectProgress.Where(x => ids.Contains(x.SubjectId)).ToList(); var minLevel = subjectProgress.Count == subjects.Count ? subjectProgress.Select(x => x.Level).Min() : 0; var nextLevelProgress = subjectProgress.Count(x => x.Level <= minLevel) + (subjects.Count - subjectProgress.Count); userModuleProgress.Level = minLevel; userModuleProgress.Progress = (decimal)(subjects.Count - nextLevelProgress) / (decimal)subjects.Count; userModuleProgress.Points = subjectProgress.Sum(x => x.Points); if (userModuleProgress.Level == 4) { userModuleProgress.CompletedAt = DateTimeOffset.Now; } if (create) { await _db.UserModuleProgressCollection.InsertOneAsync(userModuleProgress, cancellationToken : cancellationToken); } else { await _db.UserModuleProgressCollection.ReplaceOneAsync(x => x.Id == userModuleProgress.Id, userModuleProgress, cancellationToken : cancellationToken); } // Atualizar trilha var userTracksProgress = await _db.UserTrackProgressCollection .AsQueryable() .Where(x => x.UserId == userId) .ToListAsync(cancellationToken);; var tracks = await _db.TrackCollection.AsQueryable() .ToListAsync(cancellationToken);; var userModulesProgress = await _db.UserModuleProgressCollection .AsQueryable() .Where(x => x.UserId == userId) .Select(x => new { x.ModuleId, x.Level }) .ToListAsync(cancellationToken); foreach (var track in tracks) { if (track.ModulesConfiguration.All(x => x.ModuleId != moduleId)) { continue; } var usrProgress = userTracksProgress.FirstOrDefault(x => x.TrackId == track.Id); var createTp = usrProgress == null; if (createTp) { usrProgress = new UserTrackProgress(track.Id, userId, 0, 0); } var modulesCompleted = 0; usrProgress.ModulesCompleted = new List <ObjectId>(); foreach (var cfg in track.ModulesConfiguration) { var mod = userModulesProgress.FirstOrDefault(x => x.ModuleId == cfg.ModuleId && x.Level > cfg.Level); if (mod != null) { modulesCompleted++; usrProgress.ModulesCompleted.Add(mod.ModuleId); } } usrProgress.Progress = (decimal)modulesCompleted / (decimal)track.ModulesConfiguration.Count; if (usrProgress.Progress >= 1) { usrProgress.CompletedAt = DateTimeOffset.Now; } if (createTp) { await _db.UserTrackProgressCollection.InsertOneAsync(usrProgress, cancellationToken : cancellationToken); } else { await _db.UserTrackProgressCollection.ReplaceOneAsync( x => x.Id == usrProgress.Id, usrProgress, cancellationToken : cancellationToken); } } } else { // não passou de nível, var nextQuestionResult = await StartExamCommand.Handler.GetNextQuestion(progress, cancellationToken, _db); if (nextQuestionResult.IsFailure) { return(Result.Fail <UserAnswerInfo>(nextQuestionResult.Error)); } result.NextQuestion = nextQuestionResult.Data; } return(Result.Ok(result)); } catch (Exception err) { Console.WriteLine(err.Message); throw; } }