Ejemplo n.º 1
0
        public async Task <List <SmCalcProduct> > Load(IMarkdownFunctionSettings settings, int clientId, int scenarioId, int partitionId, int partitionCount)
        {
            var isOptional = partitionId > 1;
            var path       = SmS3Path.ScenarioPartitionPath(SmS3PathName.Output, settings.S3ScenarioBucketName, settings.S3ScenarioPartitionTemplate, scenarioId, partitionId, partitionCount);

            return(await _s3Repository.ReadRecords <SmCalcProduct>(path, isOptional));
        }
Ejemplo n.º 2
0
        public async Task Generate(IMarkdownFunctionSettings settings)
        {
            _logger.Information("Generating model");

            await _ephemeralRepository.GenerateModel(settings.ModelId, async reader =>
            {
                var model               = reader.Read <Model>().FirstOrDefault();
                var decayHierarchy      = reader.Read <DecayHierarchy>().ToList();
                var elasticityHierarchy = reader.Read <ElasticityHierarchy>().ToList();

                var workingSet64 = Process.GetCurrentProcess().WorkingSet64;
                _logger.Debug("Generated model. Working Set is {Size} ({Bytes})", workingSet64.ToOrdinalString(), workingSet64);

                _logger.Information("Writing data to S3");
                var modelPath = SmS3Path.ModelPath(SmS3PathName.Model, settings.S3ModelBucketName, settings.S3ModelTemplate, settings.ModelId, model.ModelRunId);
                await _s3Repository.WriteRecord(modelPath, model);

                var elasticityPath = SmS3Path.ModelPath(SmS3PathName.ElasticityHierarchy, settings.S3ModelBucketName, settings.S3ModelTemplate, settings.ModelId, model.ModelRunId);
                await _s3Repository.WriteRecords(elasticityPath, elasticityHierarchy);

                var decayPath = SmS3Path.ModelPath(SmS3PathName.DecayHierarchy, settings.S3ModelBucketName, settings.S3ModelTemplate, settings.ModelId, model.ModelRunId);
                await _s3Repository.WriteRecords(decayPath, decayHierarchy);
            });

            _logger.Information("Model generation complete");
        }
Ejemplo n.º 3
0
        public static async Task Run(ILogger logger, IMarkdownFunctionSettings settings, Container container)
        {
            logger.Information("Started. Settings: {@Settings}", settings);

            var functionService       = container.GetInstance <IFunctionService>();
            var scenarioResultService = container.GetInstance <IScenarioResultService>();

            await functionService.UploadStart(settings.ScenarioId);

            try
            {
                var results = await scenarioResultService.Load(settings, settings.OrganisationId, settings.ScenarioId, settings.PartitionId, settings.PartitionCount);

                if (results.Any())
                {
                    await scenarioResultService.Upload(settings, settings.OrganisationId, settings.UserId, settings.ScenarioId, settings.PartitionId, settings.PartitionCount, results);
                }
            }
            catch (Exception e)
            {
                await functionService.UploadError(settings.ScenarioId, "Error uploading: " + e);

                throw;
            }

            await functionService.UploadFinish(settings.ScenarioId);

            logger.Information("Finished");
        }
Ejemplo n.º 4
0
        public async Task <SmModelData> GetModelData(IMarkdownFunctionSettings settings)
        {
            var elasticityPath = SmS3Path.ModelPath(SmS3PathName.ElasticityHierarchy, settings);
            var elasticity     = await _s3Repository.ReadRecords <ElasticityHierarchy>(elasticityPath);

            var decayPath = SmS3Path.ModelPath(SmS3PathName.DecayHierarchy, settings);
            var decay     = await _s3Repository.ReadRecords <DecayHierarchy>(decayPath);

            return(SmModelData.Build(elasticity, decay));
        }
Ejemplo n.º 5
0
        public static Container RegisterDependancies(IMarkdownFunctionSettings settings, ILambdaContext lambdaContext)
        {
            IAmazonS3     s3Client;
            IAmazonLambda lambdaClient;

            if (lambdaContext != null)
            {
                s3Client     = new AmazonS3Client();
                lambdaClient = new AmazonLambdaClient();
            }
            else
            {
                var awsOptions = settings.Configuration.GetAWSOptions();
                s3Client     = awsOptions.CreateServiceClient <IAmazonS3>();
                lambdaClient = awsOptions.CreateServiceClient <IAmazonLambda>();
            }

            var logger = new LoggerConfiguration()
                         .ReadFrom
                         .Configuration(settings.Configuration)
                         .Enrich.WithProperty("ModelId", settings.ModelId)
                         .Enrich.WithProperty("ModelRunId", settings.ModelRunId)
                         .Enrich.WithProperty("ScenarioId", settings.ScenarioId)
                         .Enrich.WithProperty("OrganisationId", settings.OrganisationId)
                         .Enrich.WithProperty("UserId", settings.UserId)
                         .CreateLogger()
                         .ForContext <Calculate>();

            // Singletons
            var container = new Container();

            container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

            container.RegisterSingleton(logger);
            container.RegisterSingleton(settings);
            container.RegisterSingleton(s3Client);
            container.RegisterSingleton(lambdaClient);
            container.RegisterSingleton <IS3Settings>(settings);
            container.RegisterSingleton <ISqlSettings>(settings);
            container.RegisterSingleton <ICloudWatchSettings>(settings);
            container.RegisterSingleton <IMemoryCache>(new MemoryCache(new MemoryCacheOptions()));

            container.Register <IMarkdownService, MarkdownService>(Lifestyle.Scoped);

            // Scoped
            container.Register <IDbConnectionProvider, DbConnectionProvider>(Lifestyle.Scoped);
            container.Register <IMarkdownSqlContext, MarkdownSqlContext>(Lifestyle.Scoped);
            container.Register <IOrganisationDataProvider, OrganisationDataProvider>(Lifestyle.Scoped);

            Service.Startup.RegisterDependancies(container);

            container.Verify();
            return(container);
        }
Ejemplo n.º 6
0
        public static async Task Run(ILogger logger, IMarkdownFunctionSettings settings, Container container)
        {
            logger.Information("Started. Settings: {@Settings}", settings);

            var functionService = container.GetInstance <IFunctionService>();
            await functionService.PartitionStart(settings.ScenarioId);

            var partitionService   = container.GetInstance <IPartitionService>();
            var scenarioWebService = container.GetInstance <IScenarioWebService>();

            var summary = await scenarioWebService.Get(0, settings.ScenarioId);

            try
            {
                await partitionService.Split(settings, summary.Scenario.Week ?? 10000, summary.Scenario.ScheduleWeekMin, summary.Scenario.ScheduleWeekMax, summary.Scenario.MarkdownCountStartWeek, summary.Scenario.AllowPromoAsMarkdown, summary.Scenario.MinimumPromoPercentage);
            }
            catch (Exception e)
            {
                await functionService.PartitionError(settings.ScenarioId, "Error partitioning: " + e);

                throw;
            }

            await functionService.PartitionFinish(settings.ScenarioId);

            if (settings.Calculate)
            {
                logger.Information("Launching calculate functions");
                var lambdaClient = container.GetInstance <IAmazonLambda>();
                for (var i = 1; i <= settings.PartitionCount; i++)
                {
                    await lambdaClient.InvokeAsync(new InvokeRequest
                    {
                        FunctionName   = settings.FunctionName,
                        InvocationType = "Event",
                        Payload        = JsonConvert.SerializeObject(new Dictionary <string, object>
                        {
                            { "Program", "calc" },
                            { "ModelId", settings.ModelId },
                            { "ModelRunId", settings.ModelRunId },
                            { "ScenarioId", settings.ScenarioId },
                            { "OrganisationId", settings.OrganisationId },
                            { "UserId", settings.UserId },
                            { "PartitionId", i },
                            { "PartitionCount", settings.PartitionCount },
                            { "Upload", settings.Upload }
                        })
                    });
                }
            }
        }
Ejemplo n.º 7
0
 public static SmS3Path ScenarioPartitionPath(SmS3PathName name, IMarkdownFunctionSettings settings, int partitionId)
 {
     return(ScenarioPartitionPath(name, settings.S3ScenarioBucketName, settings.S3ScenarioPartitionTemplate,
                                  settings.ScenarioId, partitionId,
                                  settings.PartitionCount));
 }
Ejemplo n.º 8
0
 public static SmS3Path ScenarioPath(SmS3PathName name, IMarkdownFunctionSettings settings)
 {
     return(ScenarioPath(name, settings.S3ScenarioBucketName, settings.S3ScenarioTemplate,
                         settings.ModelId, settings.ModelRunId, settings.ScenarioId));
 }
Ejemplo n.º 9
0
 public static SmS3Path ModelPath(SmS3PathName name, IMarkdownFunctionSettings settings)
 {
     return(ModelPath(name, settings.S3ModelBucketName, settings.S3ModelTemplate, settings.ModelId, settings.ModelRunId));
 }
Ejemplo n.º 10
0
 public static async Task Run(ILogger logger, IMarkdownFunctionSettings settings, Container container)
 {
     logger.Information("Started. Settings: {@Settings}", settings);
     var modelService = container.GetInstance <IModelService>();
     await modelService.Generate(settings);
 }
Ejemplo n.º 11
0
 public async Task Upload(IMarkdownFunctionSettings settings, int clientId, int userId, int scenarioId, int partitionId, int partitionCount, List <SmCalcProduct> results)
 {
     var seededHash = MurmurHash.Create128(seed: (uint)clientId);
     var entities   = Build(seededHash, partitionId, partitionCount, results, userId);
     await _recommendationProductRepository.Write(clientId : clientId, scenarioId : scenarioId, partitionId : partitionId, entities : entities);
 }
Ejemplo n.º 12
0
 public async Task Upload(IMarkdownFunctionSettings settings, SmRecommendationProductSummary product, int revisionId, List <SmCalcRecommendation> recommendations, int userId)
 {
     var seededHash = MurmurHash.Create128(seed: (uint)product.ClientId);
     var entities   = Build(seededHash, product, recommendations, userId);
     await _recommendationRepository.Write(clientId : product.ClientId, scenarioId : product.ScenarioId, revisionId : revisionId, partitionId : product.PartitionNumber, productId : product.ProductId, entities : entities);
 }
Ejemplo n.º 13
0
        public async Task Split(IMarkdownFunctionSettings settings, int week, int scheduleWeekMin, int scheduleWeekMax, int markdownCountStartWeek, bool allowPromoAsMarkdown, decimal minimumPromoPercentage)
        {
            _logger.Information("Deleting old partition records");

            var scenarioBasePath = SmS3Path.ScenarioPath(SmS3PathName.ScenarioBase, settings);
            await _s3Repository.DeletePath(scenarioBasePath);

            _logger.Information("Updating partition records");
            var productTotals = new int[settings.PartitionCount];

            await _ephemeralRepository.GetScenarioData(settings.ModelRunId, settings.ScenarioId, week, scheduleWeekMin, scheduleWeekMax, markdownCountStartWeek,
                                                       settings.PartitionCount, allowPromoAsMarkdown, minimumPromoPercentage, async reader =>
            {
                _logger.Information("Writing common data to S3");

                var header     = reader.Read <ScenarioHeader>().Single();
                var headerPath = SmS3Path.ScenarioPath(SmS3PathName.ScenarioHeader, settings);
                await _s3Repository.WriteRecord(headerPath, header);

                var scenarioPath = SmS3Path.ScenarioPath(SmS3PathName.Scenario, settings);
                var scenario     = reader.Read <Scenario>().Single();
                await _s3Repository.WriteRecord(scenarioPath, scenario);

                var priceLadderValues    = reader.Read <PriceLadderValue>().ToList();
                var priceLadderValuePath = SmS3Path.ScenarioPath(SmS3PathName.PriceLadderValue, settings);
                await _s3Repository.WriteRecords(priceLadderValuePath, priceLadderValues);

                var hierarchy     = reader.Read <Hierarchy>().ToList();
                var hierarchyPath = SmS3Path.ScenarioPath(SmS3PathName.Hierarchy, settings);
                await _s3Repository.WriteRecords(hierarchyPath, hierarchy);

                var hierarchySellThrough     = reader.Read <HierarchySellThrough>().ToList();
                var hierarchySellThroughPath = SmS3Path.ScenarioPath(SmS3PathName.HierarchySellThrough, settings);
                await _s3Repository.WriteRecords(hierarchySellThroughPath, hierarchySellThrough);

                _logger.Information("Writing partition data to S3");

                var products = reader.Read <Product>().ChunkBy(x => x.PartitionNumber).ToList();
                if (!products.Any())
                {
                    throw new Exception("GetScenarioData returned no products");
                }

                foreach (var chunk in products)
                {
                    productTotals[chunk.Key - 1] = chunk.Count();
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.Product, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductHierarchy>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductHierarchy, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductPriceLadder>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductPriceLadder, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductSalesTax>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductSalesTax, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductParameterValues>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductParameterValues, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductMarkdownConstraint>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductMarkdownConstraint, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductSalesFlexFactor>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.SalesFlexFactor, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductMinimumAbsolutePriceChange>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductMinimumAbsolutePriceChange, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductWeekParameterValues>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductWeekParameterValues, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                foreach (var chunk in reader.Read <ProductWeekMarkdownTypeParameterValues>().ChunkBy(x => x.PartitionNumber))
                {
                    var path = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductWeekMarkdownTypeParameterValues, settings, chunk.Key);
                    await _s3Repository.WriteRecords(path, chunk);
                }

                var workingSet64 = Process.GetCurrentProcess().WorkingSet64;
                _logger.Debug("Wrote scenario data. Working Set is {Size} ({Bytes})",
                              workingSet64.ToOrdinalString(), workingSet64);
            });

            _logger.Information("Split complete");
        }
Ejemplo n.º 14
0
        public static async Task Run(ILogger logger, IMarkdownFunctionSettings settings, Container container)
        {
            logger.Information("Running. Settings: {@Settings}", settings);

            var canUpload          = false;
            var statsInterval      = TimeSpan.FromSeconds(10);
            var cancellationSource = new CancellationTokenSource();
            var cancellationToken  = cancellationSource.Token;

            var calcService     = container.GetInstance <IMarkdownService>();
            var functionService = container.GetInstance <IFunctionService>();
            var scenarioService = container.GetInstance <IScenarioService>();
            var scheduleService = container.GetInstance <IScheduleService>();

            try
            {
                // Inform AppDb we've started
                logger.Information("Calculate start (getting data and scenarios)");
                await functionService.CalculateStart(settings.ScenarioId, settings.PartitionCount, settings.PartitionId);

                // Todo: exclude products where markdown count > max markdown
                var model = await scenarioService.GetModelData(settings);

                var data = await scenarioService.GetScenarioData(settings, settings.PartitionId);

                var scenario   = data.Item1;
                var products   = data.Item2;
                var modelId    = settings.ModelId;
                var revisionId = 0;

                var scheduleOptions = new SmScheduleOptions
                {
                    WeekMin                 = scenario.ScheduleWeekMin,
                    WeekMax                 = scenario.ScheduleWeekMax,
                    WeeksAllowed            = scenario.ScheduleMask,
                    ExcludeConsecutiveWeeks = true
                };
                logger.Information("Getting schedules with {@Options}", scheduleOptions);
                var schedules = scheduleService.GetSchedules(scheduleOptions);
                logger.Information($"Got {schedules.Count} schedules");

                var decayHierarchies      = model.DecayHierarchies;
                var elasticityHierarchies = model.ElasticityHierarchies;

                var recommendationResults = new ConcurrentBag <SmCalcProduct>();

                // Setup calculation
                var stats = new CalculationStatistics(logger, statsInterval);
                stats.AddTotalProductCount(products.Count);

                stats.Start();
                logger.Information("Product loop start");
                {
                    await functionService.CalculateUpdate(
                        settings.ScenarioId,
                        settings.PartitionCount,
                        settings.PartitionId,
                        products.Count,
                        stats.ProductCount,
                        0,
                        stats.PricePaths,
                        (int)stats.HierarchyErrorCount);

                    // Start
                    Parallel.ForEach(products,
                                     x =>
                    {
                        stats.StartCalculation();
                        var product = calcService.Calculate(scenario, modelId, revisionId, schedules, decayHierarchies, elasticityHierarchies, x, null, cancellationToken);
                        stats.FinishCalculation();
                        stats.AddProducts(1L);
                        stats.AddPricePaths(product.ScheduleCrossProductCount);
                        stats.AddRecommendations(product.Recommendations.Count);
                        recommendationResults.Add(product);
                    });
                }
                logger.Information("Product loop finish");
                stats.Stop();

                // Save data
                logger.Information("Calculate save.");
                var s3Path = SmS3Path.ScenarioPartitionPath(SmS3PathName.Output, settings);
                await calcService.Save(recommendationResults.ToList(), s3Path);

                logger.Information("Calculate saved.");

                // Inform AppliationDb we've finished
                var productRate         = stats.ProductCount / stats.ElapsedSeconds;
                var productCount        = stats.ProductCount;
                var pricePathCount      = stats.PricePaths;
                var hierarchyErrorCount = stats.HierarchyErrorCount;

                var finishResult = await functionService.CalculateFinish(settings.ScenarioId, settings.PartitionCount, settings.PartitionId,
                                                                         products.Count, productCount, productRate, pricePathCount, (int)hierarchyErrorCount);

                // Detect finish state
                logger.Information("Finish state: {@Model}", finishResult);
                if (finishResult.SuccessCount == finishResult.FunctionInstanceTotal)
                {
                    canUpload = true;
                    logger.Information("This function is the last partition, where UploadQueue == Total");
                }

                logger.Information("Finished");
            }
            catch (Exception ex)
            {
                cancellationSource.Cancel();

                await functionService.CalculateError(settings.ScenarioId, settings.PartitionCount, settings.PartitionId, ex.ToString());

                throw;
            }

            if (canUpload && settings.Upload)
            {
                logger.Information("Launching upload function");
                var lambdaClient = container.GetInstance <IAmazonLambda>();
                for (var i = 1; i <= settings.PartitionCount; i++)
                {
                    await lambdaClient.InvokeAsync(new InvokeRequest
                    {
                        FunctionName   = settings.FunctionName,
                        InvocationType = "Event",
                        Payload        = JsonConvert.SerializeObject(new Dictionary <string, object>
                        {
                            { "Program", "upload" },
                            { "ScenarioId", settings.ScenarioId },
                            { "OrganisationId", settings.OrganisationId },
                            { "UserId", settings.UserId },
                            { "PartitionId", i },
                            { "PartitionCount", settings.PartitionCount }
                        })
                    }, cancellationToken);
                }
            }
        }
Ejemplo n.º 15
0
        public async Task <Tuple <SmScenario, List <SmProduct> > > GetScenarioData(IMarkdownFunctionSettings settings, int partitionId, IEnumerable <int> specificProductIds = null)
        {
            var isOptional = partitionId > 1;

            // Scenario
            var scenarioPath = SmS3Path.ScenarioPath(SmS3PathName.Scenario, settings);
            var scenario     = await _s3Repository.ReadRecord <Scenario>(scenarioPath);

            var sellThroughPath = SmS3Path.ScenarioPath(SmS3PathName.HierarchySellThrough, settings);
            var sellThrough     = await _s3Repository.ReadRecords <HierarchySellThrough>(sellThroughPath);

            if (!sellThrough.Any())
            {
                _logger.Warning("No sell through targets in partition");
            }

            var priceLadderValuePath = SmS3Path.ScenarioPath(SmS3PathName.PriceLadderValue, settings);
            var priceLadderValue     = await _s3Repository.ReadRecords <PriceLadderValue>(priceLadderValuePath);

            if (!priceLadderValue.Any())
            {
                _logger.Error("No price ladder values in partition");
            }

            // Product
            var productPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.Product, settings);
            var product     = await _s3Repository.ReadRecords <Product>(productPath, isOptional);

            if (specificProductIds != null)
            {
                product = product.Where(x => specificProductIds.Contains(x.ProductId)).ToList();
            }
            if (!product.Any())
            {
                _logger.Error("No products in partition");
            }

            var productHierarchyPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductHierarchy, settings);
            var productHierarchy     = await _s3Repository.ReadRecords <ProductHierarchy>(productHierarchyPath, isOptional);

            if (!productHierarchy.Any())
            {
                _logger.Error("No product hierarchies in partition");
            }

            var productPriceLadderPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductPriceLadder, settings);
            var productPriceLadder     = await _s3Repository.ReadRecords <ProductPriceLadder>(productPriceLadderPath, isOptional);

            if (!productPriceLadder.Any())
            {
                _logger.Error("No product price ladders in partition");
            }

            var productSalesTaxPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductSalesTax, settings);
            var productSalesTax     = await _s3Repository.ReadRecords <ProductSalesTax>(productSalesTaxPath, isOptional);

            if (!productSalesTax.Any())
            {
                _logger.Warning("No product sales tax in partition");
            }

            var productParameterValuesPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductParameterValues, settings);
            var productParameterValues     = await _s3Repository.ReadRecords <ProductParameterValues>(productParameterValuesPath, isOptional);

            if (!productParameterValues.Any())
            {
                _logger.Warning("No product parameter values in partition");
            }

            var productMarkdownConstraintPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductMarkdownConstraint, settings);
            var productMarkdownConstraint     = await _s3Repository.ReadRecords <ProductMarkdownConstraint>(productMarkdownConstraintPath, true);

            if (!productMarkdownConstraint.Any())
            {
                _logger.Warning("No product markdown constraint values in partition");
            }

            var salesFlexFactorPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.SalesFlexFactor, settings);
            var salesFlexFactor     = await _s3Repository.ReadRecords <ProductSalesFlexFactor>(salesFlexFactorPath, isOptional);

            if (!salesFlexFactor.Any())
            {
                _logger.Warning("No sales flex factor values in partition");
            }

            var productMinimumAbsolutePriceChangePath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductMinimumAbsolutePriceChange, settings);
            var productMinimumAbsolutePriceChange     = await _s3Repository.ReadRecords <ProductMinimumAbsolutePriceChange>(productMinimumAbsolutePriceChangePath, true);

            if (!productMinimumAbsolutePriceChange.Any())
            {
                _logger.Warning("No product minimum absolute price change values in partition");
            }

            var productWeekParameterValuesPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductWeekParameterValues, settings);
            var productWeekParameterValues     = await _s3Repository.ReadRecords <ProductWeekParameterValues>(productWeekParameterValuesPath, true);

            if (!productWeekParameterValues.Any())
            {
                _logger.Warning("No product week parameter values in partition");
            }

            var productWeekMarkdownTypeParameterValuesPath = SmS3Path.ScenarioPartitionPath(SmS3PathName.ProductWeekMarkdownTypeParameterValues, settings);
            var productWeekMarkdownTypeParameterValues     = await _s3Repository.ReadRecords <ProductWeekMarkdownTypeParameterValues>(productWeekMarkdownTypeParameterValuesPath, true);

            if (!productWeekMarkdownTypeParameterValues.Any())
            {
                _logger.Warning("No product week parameter values in partition");
            }

            // Sell through lookup
            var sellThroughLookup = sellThrough.Any() ? sellThrough.ToDictionary(x => x.HierarchyId, x => x.Value) : new Dictionary <int, decimal>();

            // Sales tax lookup
            var productSalesTaxLookup =
                productSalesTax.Any()
                    ? productSalesTax.ToDictionary(x => x.ProductId, x => Tuple.Create(x.Week, x.Rate))
                    : new Dictionary <int, Tuple <int, decimal> >();

            var products = SmProduct.Build(_logger, product, productHierarchy, productPriceLadder, priceLadderValue,
                                           sellThroughLookup, productSalesTaxLookup, productParameterValues, productMarkdownConstraint, salesFlexFactor, productMinimumAbsolutePriceChange, productWeekParameterValues, productWeekMarkdownTypeParameterValues);

            return(Tuple.Create(SmScenario.Build(scenario), products));
        }