public void Build_Has_Four_Weeks_One_Markdown_In_Last_Week() { var weekMin = 11; var weekMax = 14; var markdownWeeks = new[] { 14 }; var prices = new[] { 0.1M, 0.2M, 0.3M, 0.4M, 0.5M, 0.6M, 0.7M, 0.8M }; var priceLadderType = SmPriceLadderType.Percent; var expected = new SmSchedulePricePath { Weeks = new[] { 11, 12, 13, 14 }, Prices = new [] { (decimal?)null, (decimal?)null, (decimal?)null, 0.1M }, LadderType = SmPriceLadderType.Percent, MarkdownCount = 1 }; var result = SmSchedulePricePath.Build(weekMin, weekMax, markdownWeeks, priceLadderType, prices); result.ShouldBeEquivalentTo(expected); }
public void Build_Eight_Weeks_Eight_Markdowns() { var weekMin = 11; var weekMax = 18; var markdownWeeks = new [] { 11, 12, 13, 14, 15, 16, 17, 18 }; var prices = new[] { 0.1M, 0.2M, 0.3M, 0.4M, 0.5M, 0.6M, 0.7M, 0.8M }; var priceLadderType = SmPriceLadderType.Percent; var expected = new SmSchedulePricePath { Weeks = new[] { 11, 12, 13, 14, 15, 16, 17, 18 }, Prices = new decimal?[] { 0.1M, 0.2M, 0.3M, 0.4M, 0.5M, 0.6M, 0.7M, 0.8M }, LadderType = SmPriceLadderType.Percent, MarkdownCount = 8 }; var result = SmSchedulePricePath.Build(weekMin, weekMax, markdownWeeks, priceLadderType, prices); result.ShouldBeEquivalentTo(expected); }
public void Build_Four_Weeks_Three_Markdowns() { var weekMin = 10; var weekMax = 13; var markdownWeeks = new [] { 11, 12, 13 }; var prices = new[] { 0.1M, 0.2M, 0.3M, 0.4M, 0.5M, 0.6M, 0.7M, 0.8M }; var priceLadderType = SmPriceLadderType.Percent; var expected = new SmSchedulePricePath { Weeks = new[] { 10, 11, 12, 13 }, Prices = new [] { (decimal?)null, 0.1M, 0.2M, 0.3M }, LadderType = SmPriceLadderType.Percent, MarkdownCount = 3 }; var result = SmSchedulePricePath.Build(weekMin, weekMax, markdownWeeks, priceLadderType, prices); result.ShouldBeEquivalentTo(expected); }
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)); }