Пример #1
0
        private static SmDepth GetDiscountLadderDepth(SmProduct product)
        {
            var priceLadder = product.PriceLadder;

            var currentMarkdownDepth       = 0M;
            var currentDiscountLadderDepth = (decimal?)null;

            var priceDelta = product.OriginalSellingPrice - product.CurrentSellingPrice;

            if (priceDelta > 0)
            {
                currentMarkdownDepth = priceDelta / product.OriginalSellingPrice;
                if (currentMarkdownDepth < priceLadder.Values.First())
                {
                    currentDiscountLadderDepth = priceLadder.Values.First();
                }
                else if (currentMarkdownDepth > priceLadder.Values.Last())
                {
                    currentDiscountLadderDepth = priceLadder.Values.Last();
                }
                else
                {
                    currentDiscountLadderDepth = priceLadder.Values.FirstOrDefault(x => x >= currentMarkdownDepth);
                }
            }

            return(new SmDepth
            {
                MarkdownDepth = currentMarkdownDepth,
                DiscountLadderDepth = currentDiscountLadderDepth
            });
        }
Пример #2
0
        public SmCalcProduct Calculate(
            SmScenario scenario,
            int modelId,
            int revisionId,
            List <SmDenseSchedule> schedules,
            Dictionary <int, SmDecayHierarchy> decayHierarchies,
            Dictionary <int, SmElasticityHierarchy> elasticityHierarchies,
            SmProduct product,
            List <decimal> revisedDiscounts     = null,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            // Calculate the current markdown depth and ladder depth
            var depth = GetDiscountLadderDepth(product);

            var result = new SmCalcProduct(scenario, modelId, product, schedules, depth);

            // Return OK if there are no schedules for this product
            if (!schedules.Any())
            {
                _logger.Warning("No schedules for {ProductId}", product.ProductId);
                return(result.Ok(ProductState.NoSchedules));
            }

            // Return Fatal recommendation for bad CSPs
            if (product.CurrentSellingPrice <= 0)
            {
                _logger.Warning("Product {ProductId} CSP is <= 0", product.ProductId);
                return(result.Fatal(ProductState.InvalidCsp));
            }

            // Return Fatal if we can't resolve the decay hierarchy
            if (!decayHierarchies.TryGetValue(product.HierarchyId, out SmDecayHierarchy decayHierarchy))
            {
                _logger.Warning("Can't find Decay Hierarchy by HierarchyId {HierarchyId} for Product {ProductId}", product.HierarchyId, product.ProductId);
                return(result.Fatal(ProductState.InvalidDecayHierarchy));
            }

            // Return Fatal if we can't resolve the elasticity hierarchy
            if (!elasticityHierarchies.TryGetValue(product.HierarchyId, out SmElasticityHierarchy elasticityHierarchy))
            {
                _logger.Warning("Can't find Elasticity Hierarchy by HierarchyId {HierarchyId} for Product {ProductId}", product.HierarchyId, product.ProductId);
                return(result.Fatal(ProductState.InvalidElasticityHierarchy));
            }

            var recommendations = new List <SmCalcRecommendation>();

            foreach (var schedule in schedules)
            {
                // Apply the product mask to this schedule and skip of not aligned
                if (product.ProductScheduleMask != null && (schedule.ScheduleNumber & product.ProductScheduleMask.Value) != 0)
                {
                    result.ScheduleProductMaskFilterCount++;
                    continue;
                }

                // Skip products where the max markdown is exceeded
                if (product.ProductMaxMarkdown != null && product.ProductMaxMarkdown.Value >= schedule.MarkdownCount)
                {
                    result.ScheduleMaxMarkdownFilterCount++;
                    continue;
                }

                if (product.ProductHasExceededFlowlineThreshold == 1)
                {
                    result.ScheduleExceededFlowlineThresholdFilterCount++;
                    continue;
                }

                var crossProduct = GetCrossProduct(schedule, product.PriceLadder);
                result.ScheduleCrossProductCount = crossProduct.Length;

                foreach (var path in crossProduct)
                {
                    var prices         = path.Prices.Select(x => x ?? 0).ToArray();
                    var recommendation = new SmCalcRecommendation(scenario, schedule, prices, revisionId);

                    var calculateResult = CalculatePricePath(
                        product: ref result,
                        recommendation: ref recommendation,
                        scenario: scenario,
                        modelId: modelId,
                        revisionId: revisionId,
                        decayHierarchy: decayHierarchy,
                        elasticityHierarchy: elasticityHierarchy,
                        priceLadder: product.PriceLadder,
                        scheduleId: schedule.ScheduleNumber,
                        schedulePricePath: path);

                    if (calculateResult)
                    {
                        // Get top 10 by Total Revenue
                        recommendations.InsertAfter(recommendation, 10, x => recommendation.TotalRevenue > x.TotalRevenue);
                    }

                    cancellationToken.ThrowIfCancellationRequested();
                }
            }

            // For initial runs, calculate CSP (where revision id = 0)
            if (revisionId == 0)
            {
                var noChangeSchedule = SmDenseSchedule.NoMarkdowns(scenario.ScheduleWeekMin, scenario.ScheduleWeekMax);
                var cspPricePath     = SmSchedulePricePath.Build(noChangeSchedule.WeekMin, noChangeSchedule.WeekMax, noChangeSchedule.MarkdownWeeks, product.PriceLadder.Type, product.PriceLadder.Values);

                var prices            = cspPricePath.Prices.Select(x => x ?? 0).ToArray();
                var cspRecommendation = new SmCalcRecommendation(scenario, noChangeSchedule, prices, revisionId, isCsp: true);

                var cspResult = CalculatePricePath(
                    product: ref result,
                    recommendation: ref cspRecommendation,
                    scenario: scenario,
                    modelId: modelId,
                    revisionId: revisionId,
                    decayHierarchy: decayHierarchy,
                    elasticityHierarchy: elasticityHierarchy,
                    priceLadder: product.PriceLadder,
                    scheduleId: noChangeSchedule.ScheduleNumber,
                    schedulePricePath: cspPricePath);

                if (cspResult)
                {
                    recommendations.InsertAfter(cspRecommendation, recommendations.Count + 1, x => cspRecommendation.TotalRevenue > x.TotalRevenue, true);
                }

                result.ScheduleCount++;
            }

            if (!recommendations.Any())
            {
                _logger.Warning("No recommendations made for {ProductId}", product.ProductId);
                return(result.Ok());
            }

            // Set rank, store and set stats
            var results = new List <SmCalcRecommendation>();

            foreach (var ordered in recommendations.Select((x, i) => new { recomendation = x, index = i }))
            {
                var recommendation = ordered.recomendation;
                recommendation.Rank = recommendations.Count - ordered.index;
                results.Add(recommendation);
            }

            ExpandProjectionWeeks(ref result, ref results, scenario, revisedDiscounts);
            return(result.Ok(results));
        }
Пример #3
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));
        }