public void WhenSpotReferencedProductIsNotFoundThenLogWarning()
        {
            // Arrange
            var spot = _fixture
                       .Build <Spot>()
                       .With(p => p.Product, _fixture.Create <string>())
                       .Create();

            IImmutableDictionary <string, Product> productsByExternalReferences = Enumerable.Empty <Product>().IndexListByExternalID();
            var clashesByExternalReferences = ImmutableDictionary <string, Clash> .Empty;
            var logWarningMessages          = new List <string>();

            // Act
            IReadOnlyCollection <Spot> allSpots = new List <Spot> {
                spot
            };

            VerifyModel.VerifySpotProductsReferences(
                _dummySalesAreaName,
                allSpots,
                productsByExternalReferences,
                clashesByExternalReferences,
                logWarningMessages.Add
                );

            // Assert
            Assert.Contains(
                $"Product {spot.Product} for sales area {_dummySalesAreaName} is referenced by " +
                $"spot(s) {spot.ExternalSpotRef} but it does not exist",
                logWarningMessages
                );
        }
        public void WhenSpotProductDoesNotHaveClashThenThatIsValidAndDoNotLogWarning()
        {
            // Arrange
            var product = _fixture
                          .Build <Product>()
                          .Without(p => p.ClashCode)
                          .Create();

            var spot = _fixture
                       .Build <Spot>()
                       .With(p => p.Product, product.Externalidentifier)
                       .Create();

            var productsByExternalReferences = new[] { product }.IndexListByExternalID();

            var clashesByExternalReferences = ImmutableDictionary <string, Clash> .Empty;
            var logWarningMessages          = new List <string>();

            // Act
            IReadOnlyCollection <Spot> allSpots = new List <Spot> {
                spot
            };

            VerifyModel.VerifySpotProductsReferences(
                _dummySalesAreaName,
                allSpots,
                productsByExternalReferences,
                clashesByExternalReferences,
                logWarningMessages.Add
                );

            // Assert
            Assert.Empty(logWarningMessages);
        }
        public void WhenSpotProductClashParentIsNotFoundThenLogWarning()
        {
            // Arrange
            var clash = _fixture
                        .Build <Clash>()
                        .With(p => p.ParentExternalidentifier, _fixture.Create <string>())
                        .Create();

            var product = _fixture
                          .Build <Product>()
                          .With(p => p.ClashCode, clash.Externalref)
                          .Create();

            var spot = _fixture
                       .Build <Spot>()
                       .With(p => p.Product, product.Externalidentifier)
                       .Create();

            var productsByExternalReferences = new[] { product }.IndexListByExternalID();

            var clashesByExternalReferences = new[] { clash }.IndexListByExternalRef();

            var logWarningMessages = new List <string>();

            // Act
            IReadOnlyCollection <Spot> allSpots = new List <Spot> {
                spot
            };

            VerifyModel.VerifySpotProductsReferences(
                _dummySalesAreaName,
                allSpots,
                productsByExternalReferences,
                clashesByExternalReferences,
                logWarningMessages.Add
                );

            // Assert
            Assert.Contains(
                $"Product {product.Externalidentifier} for sales area {_dummySalesAreaName} " +
                $"references clash code {product.ClashCode} but the parent clash " +
                $"{clash.ParentExternalidentifier} does not exist",
                logWarningMessages
                );
        }
        public void WhenSpotProductClashDoesNotHaveParentThenIsValidAndDoNotLogWarning()
        {
            // Arrange
            var clash = _fixture
                        .Build <Clash>()
                        .With(p => p.ParentExternalidentifier, (string)null)
                        .Create();

            var product = _fixture
                          .Build <Product>()
                          .With(p => p.ClashCode, clash.Externalref)
                          .Create();

            var spot = _fixture
                       .Build <Spot>()
                       .With(p => p.Product, product.Externalidentifier)
                       .Create();

            var productsByExternalReferences = new[] { product }.IndexListByExternalID();

            var clashesByExternalReferences = new[] { clash }.IndexListByExternalRef();

            var logWarningMessages = new List <string>();

            // Act
            IReadOnlyCollection <Spot> allSpots = new List <Spot> {
                spot
            };

            VerifyModel.VerifySpotProductsReferences(
                _dummySalesAreaName,
                allSpots,
                productsByExternalReferences,
                clashesByExternalReferences,
                logWarningMessages.Add
                );

            // Assert
            Assert.Empty(logWarningMessages);
        }
Exemplo n.º 5
0
        Execute(DateTimeRange weekBeingSmoothed)
        {
            string salesAreaName = _salesArea.Name;

            string auditMessageForSalesAreaNameAndBatchStartEndDate =
                $"for sales area {salesAreaName} ({Log(weekBeingSmoothed)})";

            RaiseInfoAndDebug($"Smoothing batch {auditMessageForSalesAreaNameAndBatchStartEndDate}");

            var batchThreadOutput = new SmoothBatchOutput();

            foreach (var item in PrepareSmoothFailureMessageCollection(_threadSafeCollections.SmoothFailureMessages))
            {
                batchThreadOutput.SpotsByFailureMessage.Add(item);
            }

            foreach (var item in PrepareSmoothOutputWithSmoothPasses(_smoothPasses))
            {
                batchThreadOutput.OutputByPass.Add(item);
            }

            int countBreaksWithPreviousSpots   = 0;
            int countBreaksWithNoPreviousSpots = 0;

            var programmes = new List <Programme>();
            var allSpots   = new List <Spot>();
            var spots      = new List <Spot>();

            IImmutableList <Break> breaksForTheWeekBeingSmoothed = ImmutableList <Break> .Empty;
            IImmutableList <RatingsPredictionSchedule> ratingsPredictionSchedules = ImmutableList <RatingsPredictionSchedule> .Empty;

            IReadOnlyDictionary <string, SpotPlacement> spotPlacementsByExternalRef;

            try
            {
#pragma warning disable HAA0101 // Array allocation for params parameter
                Parallel.Invoke(
                    () => programmes.AddRange(LoadProgrammesToSmooth(weekBeingSmoothed, salesAreaName)),
                    () =>
                {
                    var results = LoadSpotsToSmooth(weekBeingSmoothed, salesAreaName);
                    allSpots.AddRange(results.allSpots);
                    spots.AddRange(results.unbookedSpots);
                },
                    () => ratingsPredictionSchedules    = LoadRatingsPredictionSchedules(weekBeingSmoothed, salesAreaName),
                    () => breaksForTheWeekBeingSmoothed = LoadBreaksToSmooth(weekBeingSmoothed, salesAreaName)
                    );
#pragma warning restore HAA0101 // Array allocation for params parameter

                void RaiseInfoForCounts(string message) =>
                RaiseInfo($"Read {message} {auditMessageForSalesAreaNameAndBatchStartEndDate}");

                RaiseInfoForCounts($"{Log(programmes.Count)} programmes");
                RaiseInfoForCounts($"{Log(allSpots.Count)} client picked spots");
                RaiseInfoForCounts($"{Log(spots.Count)} unbooked spots");
                RaiseInfoForCounts($"{Log(ratingsPredictionSchedules.Count)} ratings prediction schedules");
                RaiseInfoForCounts($"{Log(breaksForTheWeekBeingSmoothed.Count)} breaks");

                spotPlacementsByExternalRef = LoadSmoothPreviousSpotPlacements(allSpots);
            }
            catch (Exception exception)
            {
                throw new Exception(
                          $"Error loading smooth data {auditMessageForSalesAreaNameAndBatchStartEndDate})",
                          exception
                          );
            }

            VerifyModel.VerifySpotProductsReferences(
                salesAreaName,
                allSpots,
                _threadSafeCollections.ProductsByExternalRef,
                _threadSafeCollections.ClashesByExternalRef,
                RaiseWarning
                );

            // Default all spots to no placement attempt, will reduce
            // the list as we attempt, generate Smooth failures at the
            // end for any where no attempt was made to place it (E.g.
            // no breaks or progs)
            IReadOnlyDictionary <Guid, SpotInfo> spotInfos = SpotInfo.Factory(
                allSpots,
                _threadSafeCollections.ProductsByExternalRef,
                _threadSafeCollections.ClashesByExternalRef
                );

            IReadOnlyDictionary <string, Break> breaksByExternalRef =
                Break.IndexListByExternalId(breaksForTheWeekBeingSmoothed);

            var filterService = new SponsorshipRestrictionFilterService(
                _threadSafeCollections.SponsorshipRestrictions);

            IReadOnlyList <SmoothSponsorshipTimeline> timelines = filterService
                                                                  .GetSponsorshipRestrictionTimeline(
                weekBeingSmoothed,
                salesAreaName);

            ISmoothSponsorshipTimelineManager timelineManager = new SmoothSponsorshipTimelineManager(timelines);
            timelineManager.SetupTimelineRunningTotals(breaksForTheWeekBeingSmoothed);

            IReadOnlyCollection <Product> products = _threadSafeCollections
                                                     .ProductsByExternalRef.Values.ToList();

            IReadOnlyCollection <Clash> clashes = _threadSafeCollections
                                                  .ClashesByExternalRef.Values.ToList();

            var smoothFailures      = new List <SmoothFailure>();
            var recommendations     = new List <Recommendation>();
            var spotIdsUsedForBatch = new HashSet <Guid>();

            foreach (Programme oneProgramme in programmes.OrderBy(p => p.StartDateTime))
            {
                var(programmeBreaks, programmeSpots) = GetProgrammeBreaksAndSpots(
                    oneProgramme,
                    allSpots,
                    breaksForTheWeekBeingSmoothed);

                // The running total and maximums allowed will add up
                // within the service as we can't access the variables
                // outside of the programme loop from inside the event
                // handlers (they're in a different scope).
                var sponsorshipRestrictionService =
                    SponsorshipRestrictionService.Factory(
                        spotInfos,
                        filterService,
                        timelineManager,
                        oneProgramme,
                        RaiseException);

                var smoothOneProgramme = new SmoothOneProgramme(
                    oneProgramme,
                    programmeBreaks,
                    programmeSpots,
                    spotInfos,
                    _runId,
                    _salesArea,
                    _processorDateTime,
                    _smoothDiagnostics,
                    ratingsPredictionSchedules,
                    _threadSafeCollections,
                    _clashExposureCountService,
                    sponsorshipRestrictionService,
                    products,
                    clashes,
                    programmes,
                    RaiseInfo,
                    RaiseException);

                var smoothProgramme = smoothOneProgramme.Execute(
                    batchThreadOutput,
                    _smoothPasses,
                    breaksByExternalRef,
                    spotPlacementsByExternalRef,
                    breaksForTheWeekBeingSmoothed,
                    spotIdsUsedForBatch);

                countBreaksWithPreviousSpots   += smoothProgramme.BreaksWithPreviousSpots;
                countBreaksWithNoPreviousSpots += smoothProgramme.BreaksWithoutPreviousSpots;

                _saveSmoothChanges.SaveSmoothedSpots(smoothProgramme.SpotsToBatchSave);

                smoothFailures.AddRange(smoothProgramme.SmoothFailures);
                recommendations.AddRange(smoothProgramme.Recommendations);

                bool SpotNotSetDueToExternalCampaignRef(Spot s) =>
                _smoothConfiguration.ExternalCampaignRefsToExclude.Contains(s.ExternalCampaignNumber) &&
                !s.IsBooked();

                batchThreadOutput.SpotsNotSetDueToExternalCampaignRef +=
                    programmeSpots.Count(SpotNotSetDueToExternalCampaignRef);

                // Copy the final values of the sponsorship restriction
                // calculations and running totals into the outer
                // variables so the next iteration can use them. This
                // seems to be the only way to do this at the minute,
                // until I think of something else.
                timelineManager = sponsorshipRestrictionService.TimelineManager;
            } // End of foreach programme loop

            string batchStartEndDateForLogging = Log(weekBeingSmoothed);

            var recommendationsForUnplacedSpots = _smoothRecommendationsFactory
                                                  .CreateRecommendationsForUnplacedSpots(
                allSpots,
                _salesArea,
                _processorDateTime
                );

            RaiseInfo(
                $"Created {Log(recommendationsForUnplacedSpots.Count)} recommendations " +
                $"for unplaced spots {auditMessageForSalesAreaNameAndBatchStartEndDate}");

            recommendations.AddRange(recommendationsForUnplacedSpots);
            batchThreadOutput.Recommendations += recommendations.Count;

            _saveSmoothChanges.SaveSpotPlacements(
                _processorDateTime,
                allSpots,
                spotPlacementsByExternalRef,
                spotInfos,
                batchStartEndDateForLogging
                );

            _saveSmoothChanges.SaveSmoothFailures(
                _runId,
                salesAreaName,
                _smoothFailuresFactory,
                smoothFailures,
                spots,
                spotInfos,
                batchStartEndDateForLogging
                );

            UpdateSmoothOutputForSpotsByFailureMessage(
                batchThreadOutput.SpotsByFailureMessage,
                smoothFailures,
                batchStartEndDateForLogging
                );

            batchThreadOutput.Failures += smoothFailures.Count;

            var unusedSpotIdsForBatch = new HashSet <Guid>();
            AddUnusedSpotsToUnusedSpotsCollection(spotIdsUsedForBatch, unusedSpotIdsForBatch, allSpots);

            spotIdsUsedForBatch.CopyDistinctTo(batchThreadOutput.UsedSpotIds);
            unusedSpotIdsForBatch.CopyDistinctTo(batchThreadOutput.UnusedSpotIds);

            _saveSmoothChanges.SaveSmoothRecommendations(
                _firstScenarioId,
                auditMessageForSalesAreaNameAndBatchStartEndDate,
                recommendations
                );

            RaiseInfo(
                $"Smoothed batch {auditMessageForSalesAreaNameAndBatchStartEndDate}: " +
                $"Breaks with no prev spots={countBreaksWithNoPreviousSpots.ToString()}, " +
                $"Breaks with prev spots={countBreaksWithPreviousSpots.ToString()})"
                );

            return(batchThreadOutput, recommendations, smoothFailures);
        }