public async ValueTask AddTypingResultAsync(string userSessionId, TextTypingResult textTypingResult)
    {
        var userSession = await _userSessionRepository.FindAsync(userSessionId)
                          .ConfigureAwait(false);

        if (userSession == null)
        {
            throw new InvalidOperationException("User session does not exist.");
        }

        // TODO: Validate that it is currently active session.

        var typingSession = await _typingSessionRepository.FindAsync(userSession.TypingSessionId)
                            .ConfigureAwait(false);

        if (typingSession == null)
        {
            throw new InvalidOperationException("Typing session does not exist.");
        }

        // TODO: Validate that it is type-able (active, etc).

        var typingSessionText = typingSession.GetTypingSessionTextAtIndexOrDefault(
            textTypingResult.TypingSessionTextIndex);

        if (typingSessionText == null)
        {
            throw new InvalidOperationException("Typing session text with this index does not exist in typing session.");
        }

        var text = await _textRepository.FindAsync(typingSessionText.TextId)
                   .ConfigureAwait(false);

        if (text == null)
        {
            throw new InvalidOperationException("Text with such ID does not exist.");
        }

        if (typingSessionText.Value != text.Value)
        {
            throw new InvalidOperationException("Typing session text value differs from the one from the text store. Corrupted state.");
        }

        // If this doesn't throw - validation succeeded.
        await _textTypingResultValidator.ValidateAsync(text.Value, textTypingResult)
        .ConfigureAwait(false);

        userSession.LogResult(textTypingResult);
        await _userSessionRepository.SaveAsync(userSession)
        .ConfigureAwait(false);
    }
Beispiel #2
0
    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);
    }