public async ValueTask <UserTypingStatistics> GenerateUserStatisticsAsync(string userId, string language, TextGenerationType textGenerationType) { var existingStatistics = await _userTypingStatisticsStore.GetUserTypingStatisticsAsync(userId, language, textGenerationType) .ConfigureAwait(false); var userSessions = Enumerable.Empty <UserSession>(); userSessions = existingStatistics != null ? await _userSessionRepository.FindAllForUserFromTypingResultsAsync(userId, existingStatistics.LastHandledResultUtc) .ConfigureAwait(false) : await _userSessionRepository.FindAllForUserAsync(userId) .ConfigureAwait(false); var results = new List <TextAnalysisResult>(); var textsTypedCount = 0; DateTime lastHandledResultUtc = default; foreach (var userSession in userSessions) { var typingSession = await _typingSessionRepository.FindAsync(userSession.TypingSessionId) .ConfigureAwait(false); if (typingSession == null) { throw new InvalidOperationException("Typing session is not found."); } foreach (var textTypingResult in userSession.GetTextTypingResults()) { textsTypedCount++; if (textTypingResult.SubmittedResultsUtc > lastHandledResultUtc) { lastHandledResultUtc = textTypingResult.SubmittedResultsUtc; } var text = typingSession.GetTypingSessionTextAtIndexOrDefault(textTypingResult.TypingSessionTextIndex); if (text == null) { throw new InvalidOperationException("Text is not found in typing session."); } var textEntity = await _textRepository.FindAsync(text.TextId) .ConfigureAwait(false); if (textEntity == null) { throw new InvalidOperationException("Text is not found."); } if (textEntity.Language != language) { continue; } if (textEntity.TextGenerationType != textGenerationType) { continue; // Generate statistics only for requested text generation type. } var textAnalysisResult = await _textTypingResultValidator.ValidateAsync(text.Value, textTypingResult) .ConfigureAwait(false); results.Add(textAnalysisResult); } } if (results.Count == 0) { if (existingStatistics != null) { // No new data yet. return(existingStatistics); } return(new UserTypingStatistics(0, 0, Enumerable.Empty <KeyPairAggregatedData>(), DateTime.UtcNow)); } var aggregatedResult = new TextAnalysisResult( results.Sum(x => x.SpeedCpm) / results.Count, results.SelectMany(x => x.KeyPairs)); var specificKeys = aggregatedResult.KeyPairs.GroupBy(x => new { x.FromKey, x.ShouldBeKey }); var aggregatedData = specificKeys.Select(x => new KeyPairAggregatedData( x.Key.FromKey, x.Key.ShouldBeKey, x.Where(y => y.Type == KeyPairType.Correct).Any() ? x.Where(y => y.Type == KeyPairType.Correct).Average(y => y.Delay) : 0, x.Where(y => y.Type == KeyPairType.Correct).Any() ? x.Where(y => y.Type == KeyPairType.Correct).Min(y => y.Delay) : 0, x.Where(y => y.Type == KeyPairType.Correct).Any() ? x.Where(y => y.Type == KeyPairType.Correct).Max(y => y.Delay) : 0, x.Count(y => y.Type == KeyPairType.Correct), x.Count(y => y.Type == KeyPairType.Mistake))); var result = MergeAndReturnNew(existingStatistics, new TypingReport(aggregatedResult, aggregatedData), textsTypedCount, lastHandledResultUtc); await _userTypingStatisticsStore.SaveAsync(userId, result, language, textGenerationType) .ConfigureAwait(false); return(result); }