public async Task <Result <TrackProgressInfo> > Handle(Contract request, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(request.UserId) || string.IsNullOrEmpty(request.TrackId)) { return(Result.Fail <TrackProgressInfo>("Acesso Negado")); } var userId = ObjectId.Parse(request.UserId); var trackId = ObjectId.Parse(request.TrackId); var trackProgress = await _db .UserTrackProgressCollection .AsQueryable() .Where(x => x.UserId == userId && x.TrackId == trackId) .Select(x => new TrackProgressInfo() { Level = x.Level, TrackId = x.TrackId, Progress = x.Progress, ModulesCompleted = x.ModulesCompleted }) .FirstOrDefaultAsync(); if (trackProgress == null) { //var published = await _db //.TrackCollection //.AsQueryable() //.Where(x => x.Id == trackId) //.Select(x => x.Published) //.FirstOrDefaultAsync(); //if (published) //{ var newTrackProgress = new UserTrackProgress(trackId, userId, 0, 0); await _db.UserTrackProgressCollection.InsertOneAsync( newTrackProgress, cancellationToken : cancellationToken ); return(Result.Ok(new TrackProgressInfo() { Level = newTrackProgress.Level, TrackId = newTrackProgress.TrackId, Progress = newTrackProgress.Progress, ModulesCompleted = newTrackProgress.ModulesCompleted })); //} } return(Result.Ok(trackProgress)); }
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; } }