public async Task <ArtistWorkReport> AggregateDataAsync(Guid artistId) { // Get artist albums/works var artist = await _artistService.GetArtistAsync(artistId); artist.Works = await _artistService.GetArtistWorksAsync(artistId); var report = new ArtistWorkReport { ArtistId = artistId, Name = artist.Name }; if (artist.Works == null || !artist.Works.Any()) { return(report); } int sum = 0, validValuesCount = 0; ArtistWork minWorkInfo = null, maxWorkInfo = null; var syncLock = new object(); _cacheService.InitializeArtistWorks(artistId); await artist.Works.ParallelForEachAsync(async (work) => { // retrieve lyrics work.Lyrics = await _lyricsService.SearchAsync(artist.Name, work.Title); // calculate number of words work.WordCount = _wordCounterService.Count(work.Lyrics); // keep a copy so we can do further operations on the data without interogating the APIs _cacheService.AddArtistWork(artistId, work); // skip from the report songs that we couldn't find lyrics for if (work.WordCount == 0) { return; } lock (syncLock) { // keep the sum and number of values to calculate the average sum += work.WordCount; validValuesCount++; // keep info about the song with shortest number of words if (minWorkInfo == null || work.WordCount < minWorkInfo.WordCount) { minWorkInfo = work; } // keep info about the song with maximum number of words if (maxWorkInfo == null || work.WordCount > maxWorkInfo.WordCount) { maxWorkInfo = work; } } }); if (validValuesCount > 0) { report.AverageWords = sum / validValuesCount; report.TotalSongs = artist.Works.Count; report.SongsConsidered = validValuesCount; report.MinWords = minWorkInfo; report.MaxWords = maxWorkInfo; // using https://www.mathsisfun.com/data/standard-deviation-formulas.html var stdDevSum = artist.Works .Where(x => x.WordCount > 0) .Sum(x => Math.Pow(x.WordCount - report.AverageWords, 2)); report.Variance = Math.Round(stdDevSum / validValuesCount, 2); report.StandardDeviation = Math.Round(Math.Sqrt(report.Variance), 2); } return(report); }