private async Task GenerateAllocations(Message message)
        {
            Guard.ArgumentNotNull(message, nameof(message));

            _logger.Debug("Validating new allocations message");

            _calculationEngineServiceValidator.ValidateMessage(_logger, message);

            GenerateAllocationMessageProperties messageProperties = GetMessageProperties(message);

            string specificationId = messageProperties.SpecificationId;

            messageProperties.GenerateCalculationAggregationsOnly = Job.JobDefinitionId == JobConstants.DefinitionNames.GenerateCalculationAggregationsJob;

            _logger.Information($"Generating allocations for specification id {specificationId} on server '{Environment.MachineName}'");

            Stopwatch prerequisiteStopwatch = Stopwatch.StartNew();

            Task <(byte[], long)> getAssemblyTask = GetAssemblyForSpecification(specificationId, messageProperties.AssemblyETag);
            Task <(IEnumerable <ProviderSummary>, long)>         providerSummaryTask    = GetProviderSummaries(messageProperties);
            Task <(IEnumerable <CalculationSummaryModel>, long)> calculationSummaryTask = GetCalculationSummaries(specificationId);
            Task <(SpecificationSummary, long)> specificationSummaryTask         = GetSpecificationSummary(specificationId);
            Task <(IEnumerable <CalculationAggregation>, long)> aggregationsTask = BuildAggregations(messageProperties);

            await TaskHelper.WhenAllAndThrow(getAssemblyTask, providerSummaryTask, calculationSummaryTask, specificationSummaryTask, aggregationsTask);

            (byte[] assembly, long assemblyLookupElapsedMilliseconds) = getAssemblyTask.Result;
            (IEnumerable <ProviderSummary> summaries, long providerSummaryLookupElapsedMilliseconds) = providerSummaryTask.Result;
            (IEnumerable <CalculationSummaryModel> calculations, long calculationsLookupStopwatchElapsedMilliseconds) = calculationSummaryTask.Result;
            (SpecificationSummary specificationSummary, long specificationSummaryElapsedMilliseconds) = specificationSummaryTask.Result;
            (IEnumerable <CalculationAggregation> aggregations, long aggregationsElapsedMilliseconds) = aggregationsTask.Result;

            prerequisiteStopwatch.Stop();

            int providerBatchSize = _engineSettings.ProviderBatchSize;

            IEnumerable <string> dataRelationshipIds = specificationSummary.DataDefinitionRelationshipIds;

            if (dataRelationshipIds == null)
            {
                throw new InvalidOperationException("Data relationship ids returned null");
            }

            int  totalProviderResults             = 0;
            bool calculationResultsHaveExceptions = false;

            Dictionary <string, List <object> > cachedCalculationAggregationsBatch = CreateCalculationAggregateBatchDictionary(messageProperties);

            for (int i = 0; i < summaries.Count(); i += providerBatchSize)
            {
                Stopwatch calcTiming = Stopwatch.StartNew();

                CalculationResultsModel calculationResults = await CalculateResults(specificationId,
                                                                                    summaries,
                                                                                    calculations,
                                                                                    aggregations,
                                                                                    dataRelationshipIds,
                                                                                    assembly,
                                                                                    messageProperties,
                                                                                    providerBatchSize,
                                                                                    i);

                _logger.Information($"Calculating results complete for specification id {specificationId}");

                long saveCosmosElapsedMs        = -1;
                long queueSearchWriterElapsedMs = -1;
                long saveRedisElapsedMs         = 0;
                long?saveQueueElapsedMs         = null;
                int  savedProviders             = 0;
                int  percentageProvidersSaved   = 0;

                if (calculationResults.ProviderResults.Any())
                {
                    if (messageProperties.GenerateCalculationAggregationsOnly)
                    {
                        PopulateCachedCalculationAggregationsBatch(calculationResults.ProviderResults, cachedCalculationAggregationsBatch, messageProperties);
                        totalProviderResults += calculationResults.ProviderResults.Count();
                    }
                    else
                    {
                        (long saveCosmosElapsedMs, long queueSerachWriterElapsedMs, long saveRedisElapsedMs, long?saveQueueElapsedMs, int savedProviders)processResultsMetrics =
                            await ProcessProviderResults(calculationResults.ProviderResults, specificationSummary, messageProperties, message);

                        saveCosmosElapsedMs        = processResultsMetrics.saveCosmosElapsedMs;
                        queueSearchWriterElapsedMs = processResultsMetrics.queueSerachWriterElapsedMs;
                        saveRedisElapsedMs         = processResultsMetrics.saveRedisElapsedMs;
                        saveQueueElapsedMs         = processResultsMetrics.saveQueueElapsedMs;
                        savedProviders             = processResultsMetrics.savedProviders;

                        totalProviderResults    += calculationResults.ProviderResults.Count();
                        percentageProvidersSaved = savedProviders / totalProviderResults * 100;

                        if (calculationResults.ResultsContainExceptions)
                        {
                            _logger.Warning($"Exception(s) executing specification id '{specificationId}:  {calculationResults.ExceptionMessages}");
                            calculationResultsHaveExceptions = true;
                        }
                    }
                }

                calcTiming.Stop();

                IDictionary <string, double> metrics = new Dictionary <string, double>()
                {
                    { "calculation-run-providersProcessed", calculationResults.PartitionedSummaries.Count() },
                    { "calculation-run-lookupCalculationDefinitionsMs", calculationsLookupStopwatchElapsedMilliseconds },
                    { "calculation-run-providersResultsFromCache", summaries.Count() },
                    { "calculation-run-partitionSize", messageProperties.PartitionSize },
                    { "calculation-run-saveProviderResultsRedisMs", saveRedisElapsedMs },
                    { "calculation-run-runningCalculationMs", calculationResults.CalculationRunMs },
                    { "calculation-run-savedProviders", savedProviders },
                    { "calculation-run-savePercentage ", percentageProvidersSaved },
                    { "calculation-run-specLookup ", specificationSummaryElapsedMilliseconds },
                    { "calculation-run-providerSummaryLookup ", providerSummaryLookupElapsedMilliseconds },
                    { "calculation-run-providerSourceDatasetsLookupMs ", calculationResults.ProviderSourceDatasetsLookupMs },
                    { "calculation-run-assemblyLookup ", assemblyLookupElapsedMilliseconds },
                    { "calculation-run-aggregationsLookup ", aggregationsElapsedMilliseconds },
                    { "calculation-run-prerequisiteMs ", prerequisiteStopwatch.ElapsedMilliseconds },
                };

                if (saveQueueElapsedMs.HasValue)
                {
                    metrics.Add("calculation-run-saveProviderResultsServiceBusMs", saveQueueElapsedMs.Value);
                }

                if (saveCosmosElapsedMs > -1)
                {
                    metrics.Add("calculation-run-batchElapsedMilliseconds", calcTiming.ElapsedMilliseconds);
                    metrics.Add("calculation-run-saveProviderResultsCosmosMs", saveCosmosElapsedMs);
                }
                else
                {
                    metrics.Add("calculation-run-for-tests-ms", calcTiming.ElapsedMilliseconds);
                }

                if (queueSearchWriterElapsedMs > 0)
                {
                    metrics.Add("calculation-run-queueSearchWriterMs", queueSearchWriterElapsedMs);
                }

                _telemetry.TrackEvent("CalculationRunProvidersProcessed",
                                      new Dictionary <string, string>()
                {
                    { "specificationId", specificationId },
                },
                                      metrics
                                      );
            }

            ItemsProcessed = summaries.Count();
            ItemsFailed    = summaries.Count() - totalProviderResults;

            if (calculationResultsHaveExceptions)
            {
                throw new NonRetriableException($"Exceptions were thrown during generation of calculation results for specification '{specificationId}'");
            }
            else
            {
                await CompleteBatch(specificationSummary, messageProperties, cachedCalculationAggregationsBatch);
            }
        }
        public async Task GenerateAllocations(Message message)
        {
            Guard.ArgumentNotNull(message, nameof(message));

            _logger.Information($"Validating new allocations message");

            CalculationEngineServiceValidator.ValidateMessage(_logger, message);

            GenerateAllocationMessageProperties messageProperties = GetMessageProperties(message);

            JobViewModel job = await AddStartingProcessJobLog(messageProperties.JobId);

            if (job == null)
            {
                return;
            }

            messageProperties.GenerateCalculationAggregationsOnly = (job.JobDefinitionId == JobConstants.DefinitionNames.GenerateCalculationAggregationsJob);

            IEnumerable <ProviderSummary> summaries = null;

            _logger.Information($"Generating allocations for specification id {messageProperties.SpecificationId}");

            BuildProject buildProject = await GetBuildProject(messageProperties.SpecificationId);

            byte[] assembly = await _calculationsRepositoryPolicy.ExecuteAsync(() => _calculationsRepository.GetAssemblyBySpecificationId(messageProperties.SpecificationId));

            if (assembly == null)
            {
                string error = $"Failed to get assembly for specification Id '{messageProperties.SpecificationId}'";
                _logger.Error(error);
                throw new RetriableException(error);
            }

            buildProject.Build.Assembly = assembly;

            Dictionary <string, List <decimal> > cachedCalculationAggregationsBatch = CreateCalculationAggregateBatchDictionary(messageProperties);

            _logger.Information($"processing partition index {messageProperties.PartitionIndex} for batch size {messageProperties.PartitionSize}");

            int start = messageProperties.PartitionIndex;

            int stop = start + messageProperties.PartitionSize - 1;

            summaries = await _cacheProviderPolicy.ExecuteAsync(() => _cacheProvider.ListRangeAsync <ProviderSummary>(messageProperties.ProviderCacheKey, start, stop));

            int providerBatchSize = _engineSettings.ProviderBatchSize;

            Stopwatch calculationsLookupStopwatch = Stopwatch.StartNew();
            IEnumerable <CalculationSummaryModel> calculations = await _calculationsRepositoryPolicy.ExecuteAsync(() => _calculationsRepository.GetCalculationSummariesForSpecification(messageProperties.SpecificationId));

            if (calculations == null)
            {
                _logger.Error($"Calculations lookup API returned null for specification id {messageProperties.SpecificationId}");

                throw new InvalidOperationException("Calculations lookup API returned null");
            }
            calculationsLookupStopwatch.Stop();

            IEnumerable <CalculationAggregation> aggregations = await BuildAggregations(messageProperties);

            int totalProviderResults = 0;

            bool calculationResultsHaveExceptions = false;

            for (int i = 0; i < summaries.Count(); i += providerBatchSize)
            {
                Stopwatch calculationStopwatch            = new Stopwatch();
                Stopwatch providerSourceDatasetsStopwatch = new Stopwatch();

                Stopwatch calcTiming = Stopwatch.StartNew();

                CalculationResultsModel calculationResults = await CalculateResults(summaries, calculations, aggregations, buildProject, messageProperties, providerBatchSize, i, providerSourceDatasetsStopwatch, calculationStopwatch);

                _logger.Information($"calculating results complete for specification id {messageProperties.SpecificationId}");

                long saveCosmosElapsedMs = -1;
                long saveSearchElapsedMs = -1;
                long saveRedisElapsedMs  = 0;
                long saveQueueElapsedMs  = 0;

                if (calculationResults.ProviderResults.Any())
                {
                    if (messageProperties.GenerateCalculationAggregationsOnly)
                    {
                        PopulateCachedCalculationAggregationsBatch(calculationResults.ProviderResults, cachedCalculationAggregationsBatch, messageProperties);
                    }
                    else
                    {
                        (long saveCosmosElapsedMs, long saveSearchElapsedMs, long saveRedisElapsedMs, long saveQueueElapsedMs)timingMetrics = await ProcessProviderResults(calculationResults.ProviderResults, messageProperties, message);

                        saveCosmosElapsedMs = timingMetrics.saveCosmosElapsedMs;
                        saveSearchElapsedMs = timingMetrics.saveSearchElapsedMs;
                        saveRedisElapsedMs  = timingMetrics.saveRedisElapsedMs;
                        saveQueueElapsedMs  = timingMetrics.saveQueueElapsedMs;

                        totalProviderResults += calculationResults.ProviderResults.Count();

                        if (calculationResults.ResultsContainExceptions)
                        {
                            if (!calculationResultsHaveExceptions)
                            {
                                calculationResultsHaveExceptions = true;
                            }
                        }
                    }
                }

                calcTiming.Stop();

                IDictionary <string, double> metrics = new Dictionary <string, double>()
                {
                    { "calculation-run-providersProcessed", calculationResults.PartitionedSummaries.Count() },
                    { "calculation-run-lookupCalculationDefinitionsMs", calculationsLookupStopwatch.ElapsedMilliseconds },
                    { "calculation-run-providersResultsFromCache", summaries.Count() },
                    { "calculation-run-partitionSize", messageProperties.PartitionSize },
                    { "calculation-run-providerSourceDatasetQueryMs", providerSourceDatasetsStopwatch.ElapsedMilliseconds },
                    { "calculation-run-saveProviderResultsRedisMs", saveRedisElapsedMs },
                    { "calculation-run-saveProviderResultsServiceBusMs", saveQueueElapsedMs },
                    { "calculation-run-runningCalculationMs", calculationStopwatch.ElapsedMilliseconds },
                };

                if (saveCosmosElapsedMs > -1)
                {
                    metrics.Add("calculation-run-elapsedMilliseconds", calcTiming.ElapsedMilliseconds);
                    metrics.Add("calculation-run-saveProviderResultsCosmosMs", saveCosmosElapsedMs);
                    metrics.Add("calculation-run-saveProviderResultsSearchMs", saveSearchElapsedMs);
                }
                else
                {
                    metrics.Add("calculation-run-for-tests-ms", calcTiming.ElapsedMilliseconds);
                }


                _telemetry.TrackEvent("CalculationRunProvidersProcessed",
                                      new Dictionary <string, string>()
                {
                    { "specificationId", messageProperties.SpecificationId },
                    { "buildProjectId", buildProject.Id },
                },
                                      metrics
                                      );
            }

            if (calculationResultsHaveExceptions)
            {
                await FailJob(messageProperties.JobId, totalProviderResults, "Exceptions were thrown during generation of calculation results");
            }
            else
            {
                await CompleteBatch(messageProperties, cachedCalculationAggregationsBatch, summaries.Count(), totalProviderResults);
            }
        }