private static void CheckProductClashExposureCount( IReadOnlyDictionary <string, Clash> clashesByExternalRef, IClashExposureCountService clashExposureCountService, SmoothFailureMessagesForSpotsCollection result, IReadOnlyDictionary <string, int> spotChildClashCodeCount, Spot spot, SpotInfo spotInfo, IReadOnlyCollection <Spot> childProductClashSpots) { if (!clashesByExternalRef.TryGetValue(spotInfo.ProductClashCode, out Clash childClash)) { return; } int exposureCount = clashExposureCountService.Calculate( childClash.Differences, (childClash.DefaultPeakExposureCount, childClash.DefaultOffPeakExposureCount), (spot.StartDateTime, spot.SalesArea) ); if (exposureCount > 0 && childProductClashSpots.Count + spotChildClashCodeCount[spotInfo.ProductClashCode] > exposureCount) { result.Add(spot.Uid, SmoothFailureMessages.T1_ProductClash); } }
private static SmoothFailureMessagesForSpotsCollection RemoveDuplicateFailureMessages( IReadOnlyCollection <Spot> spotsForBreak, SmoothFailureMessagesForSpotsCollection messages) { var result = new SmoothFailureMessagesForSpotsCollection(); foreach (var spotUid in spotsForBreak.Select(s => s.Uid)) { result.InitialiseForSpot(spotUid); foreach (var failure in messages[spotUid].Failures .OrderBy(f => Convert.ToInt32(f.FailureMessage))) { if (result[spotUid].Failures.Any(f => f.FailureMessage == failure.FailureMessage)) { continue; } result.Add( spotUid, failure.FailureMessage, failure.Restriction); } } return(result); }
private static void IndicateIfMultipartTopTailSameBreakSpotsCannotBeAdded( SmoothFailureMessagesForSpotsCollection result, IReadOnlyCollection <Spot> multipartSpots) { bool haveFailureMessages = multipartSpots.Any( spot => result[spot.Uid].Failures.Count > 0 ); if (!haveFailureMessages) { return; } bool isTopTailMultipartSpots = multipartSpots.Any(spot => spot.MultipartSpot == MultipartSpotTypes.TopTail); bool isSameBreakMultipartSpots = multipartSpots.Any(spot => spot.MultipartSpot == MultipartSpotTypes.SameBreak); if (isTopTailMultipartSpots) { foreach (var spot in multipartSpots) { result.Add(spot.Uid, SmoothFailureMessages.T1_CantAddTopAndTailToSameBreak); } } else if (isSameBreakMultipartSpots) { foreach (var spot in multipartSpots) { result.Add(spot.Uid, SmoothFailureMessages.T1_CantAddSameBreakToSameBreak); } } }
private static void ValidateWithSpotRestrictions( SmoothBreak theSmoothBreak, Programme programme, SalesArea salesArea, IReadOnlyCollection <Spot> spotsForBreak, SmoothResources smoothResources, IReadOnlyCollection <Break> breaksBeingSmoothed, IReadOnlyCollection <Programme> scheduleProgrammes, SmoothFailureMessagesForSpotsCollection result) { var anyRestrictions = CheckRestrictionsForSpots( spotsForBreak, theSmoothBreak.TheBreak, programme, salesArea, smoothResources, breaksBeingSmoothed, scheduleProgrammes); foreach (var item in anyRestrictions) { foreach (var failure in item.Value.Failures) { result.Add(item.Key, failure.FailureMessage, failure.Restriction); } } }
private static void ValidateNonMultipartSpots( IReadOnlyList <SmoothBreak> progSmoothBreaks, SpotPositionRules breakPositionRules, SpotPositionRules requestedPositionInBreakRules, CanAddSpotService canAddSpotService, SmoothFailureMessagesForSpotsCollection result, IReadOnlyCollection <Spot> nonMultipartSpots) { // Determine which break we want to position the spot in if possible foreach (var spot in nonMultipartSpots) { bool canAddSpotAtBreakPosition = canAddSpotService .CanAddSpotWithBreakRequestForAtomicSpotWithDefaultPosition( spot.BreakRequest, progSmoothBreaks, breakPositionRules); if (!canAddSpotAtBreakPosition) { result.Add(spot.Uid, SmoothFailureMessages.T1_BreakPosition); } // Determine if we can add at requested position in break if (!String.IsNullOrEmpty(spot.RequestedPositioninBreak) && !canAddSpotService.CanAddSpotAtRequestedPosition(spot.RequestedPositioninBreak, requestedPositionInBreakRules)) { result.Add(spot.Uid, SmoothFailureMessages.T1_RequestedPositionInBreak); } } }
HasClashExceptionIncludesAndExcludes( SmoothBreak theSmoothBreak, bool respectClashExceptions, SmoothResources smoothResources, SmoothFailureMessagesForSpotsCollection result, Spot spot, Guid spotUid) { (bool spotHasClashExceptionIncludes, bool spotHasClashExceptionExcludes)clashExceptionResults = (false, false); if (!respectClashExceptions) { return(clashExceptionResults); } var checkClashExceptionsResults = smoothResources .ClashExceptionChecker.CheckClashExceptions(theSmoothBreak, spot); clashExceptionResults.spotHasClashExceptionIncludes = checkClashExceptionsResults .Any(cer => cer.ClashException.IncludeOrExclude == IncludeOrExclude.I); clashExceptionResults.spotHasClashExceptionExcludes = checkClashExceptionsResults .Any(cer => cer.ClashException.IncludeOrExclude == IncludeOrExclude.E); if (clashExceptionResults.spotHasClashExceptionIncludes) { result.Add(spotUid, SmoothFailureMessages.T1_ProductClash); } return(clashExceptionResults); }
private static void AssertFailureMessage( SmoothFailureMessagesForSpotsCollection res, SmoothFailureMessages message) { _ = res.Should().ContainSingle(); _ = res.First().Value.Failures.Should().ContainSingle(); _ = res.First().Value.Failures[0].FailureMessage.Should().Be(message); }
public void SingleItemInSingleItemOutNoRestriction() { // Arrange var target = new SmoothFailureMessagesForSpotsCollection(); var spotUid = Guid.NewGuid(); const SmoothFailureMessages specimen = SmoothFailureMessages.T1_BreakPosition; // Act target.Add(spotUid, specimen); // Assert _ = target.Should().ContainSingle(becauseArgs: null); _ = target[spotUid].Failures[0].FailureMessage.Should().Be(specimen, becauseArgs: null); }
private static void CheckProductClashRulesWhenClashLimitsAreAllowed( IReadOnlyDictionary <Guid, SpotInfo> spotInfos, IReadOnlyDictionary <string, Clash> clashesByExternalRef, SmoothResources smoothResources, IClashExposureCountService clashExposureCountService, SmoothFailureMessagesForSpotsCollection result, IReadOnlyDictionary <string, int> spotChildClashCodeCount, IReadOnlyDictionary <string, int> spotParentClashCodeCount, IReadOnlyCollection <Spot> spotsAlreadyInTheBreak, Spot spot, SpotInfo spotInfo, IReadOnlyCollection <Spot> childProductClashSpots) { if (childProductClashSpots.Count > 0 && !String.IsNullOrEmpty(spotInfo.ProductClashCode)) { CheckProductClashExposureCount( clashesByExternalRef, clashExposureCountService, result, spotChildClashCodeCount, spot, spotInfo, childProductClashSpots); } // Check parent clashes var parentProductClashSpots = smoothResources.ProductClashChecker .GetProductClashesForSingleSpot( spot, spotsAlreadyInTheBreak, spotInfos, ClashCodeLevel.Parent) .ToList(); if (parentProductClashSpots.Count > 0 && !String.IsNullOrEmpty(spotInfo.ParentProductClashCode)) { CheckProductParentClashExposure( clashesByExternalRef, clashExposureCountService, result, spotParentClashCodeCount, spot, spotInfo, parentProductClashSpots); } }
public void SingleItemInSingleItemOutIncludingRestrictionInitialisedCollection() { // Arrange var target = new SmoothFailureMessagesForSpotsCollection(); var spotUid = Guid.NewGuid(); var restriction = new Mock <Restriction>().Object; const SmoothFailureMessages specimen = SmoothFailureMessages.T1_BreakPosition; // Act target.InitialiseForSpot(spotUid); target.Add(spotUid, specimen, restriction); // Assert _ = target.Should().ContainSingle(becauseArgs: null); _ = target[spotUid].Failures[0].FailureMessage.Should().Be(specimen, becauseArgs: null); }
private static SmoothFailureMessagesForSpotsCollection CheckRestrictionsForSpots( IReadOnlyCollection <Spot> spotsForBreak, Break theBreak, Programme programme, SalesArea salesArea, SmoothResources smoothResources, IReadOnlyCollection <Break> breaksBeingSmoothed, IReadOnlyCollection <Programme> scheduleProgrammes) { var result = new SmoothFailureMessagesForSpotsCollection(); foreach (var spot in spotsForBreak) { var restrictionCheckerResults = smoothResources .RestrictionChecker.CheckRestrictions( programme, theBreak, spot, salesArea, breaksBeingSmoothed, scheduleProgrammes ); foreach (var restrictionCheckerResult in restrictionCheckerResults .Where(r => r.Reason != RestrictionReasons.None) ) { var failureMessage = Map(restrictionCheckerResult.Reason); if (failureMessage == SmoothFailureMessages.T0_NoFailure) { continue; } result.Add( spot.Uid, failureMessage, restrictionCheckerResult.Restriction ); } } return(result); }
private static void ValidateWithSponsorshipRestrictions( SmoothBreak theSmoothBreak, SponsorshipRestrictionService sponsorshipRestrictionsService, SmoothFailureMessagesForSpotsCollection result, IReadOnlyCollection <Spot> spotsAlreadyInTheBreak, Spot spot) { Break theBreak = theSmoothBreak.TheBreak; IReadOnlyCollection <(Guid spotUid, SmoothFailureMessages failureMessage)> serviceResult = sponsorshipRestrictionsService.CheckSponsorshipRestrictions( spot, theBreak.ExternalBreakRef, theBreak.ScheduledDate, theBreak.Duration, spotsAlreadyInTheBreak ); foreach ((Guid spotUid, SmoothFailureMessages failureMessage) in serviceResult) { result.Add(spotUid, failureMessage); } }
private static void ValidateMultipartSpots( IReadOnlyList <SmoothBreak> progSmoothBreaks, SpotPositionRules breakPositionRules, SpotPositionRules requestedPositionInBreakRules, bool canSplitMultipartSpotsOverBreaks, CanAddSpotService canAddSpotService, SmoothFailureMessagesForSpotsCollection result, IReadOnlyCollection <Spot> spotsAlreadyInTheBreak, IReadOnlyList <Spot> multipartSpots) { if (multipartSpots.Count == 0) { return; } foreach (var spot in multipartSpots) { bool canAddSpotAtBreakPosition = canAddSpotService .CanAddSpotWithBreakRequestForMultipartSpotWithDefaultPosition( spot.BreakRequest, progSmoothBreaks, breakPositionRules); if (!canAddSpotAtBreakPosition) { result.Add(spot.Uid, SmoothFailureMessages.T1_BreakPosition); } // Determine if we can add at requested position in break // TODO: Check that multipart Spots are all linked if (!canAddSpotService.CanAddMultipartSpotAtRequestedPosition( spot.MultipartSpot, spot.MultipartSpotPosition, requestedPositionInBreakRules)) { result.Add(spot.Uid, SmoothFailureMessages.T1_RequestedPositionInBreak); } } // For the multipart spot type then check if this is the break // that it must be added to if (canSplitMultipartSpotsOverBreaks) { return; } // Multipart spots can't be split over breaks, ensure that // break will contain all linked multipart spots if (multipartSpots[0].MultipartSpot == MultipartSpotTypes.TopTail && multipartSpots.Count < 2) { // We're not placing both spots, ensure that this break // has linked spot. Get all linked spots that have been // placed in this break. var linkedSpotsPlacedInBreak = BreakUtilities.GetLinkedMultipartSpots( multipartSpots[0], spotsAlreadyInTheBreak, includeInputSpotInOutput: false ); if (linkedSpotsPlacedInBreak.Count == 0) { // Break doesn't contain linked spots, can't add to // this break foreach (var spot in multipartSpots) { result.Add( spot.Uid, SmoothFailureMessages.T1_CantAddTopAndTailToSameBreak); } } } }
/// <summary> /// Evaluate whether spots can be added to break. We consider time /// remaining, break type, product clashes, campaign clashes, spot end /// time (some spots may required to be position in the first N mins of /// the programme), sponsor rules. /// </summary> public static SmoothFailureMessagesForSpotsCollection ValidateAddSpots( SmoothBreak theSmoothBreak, Programme programme, SalesArea salesArea, IReadOnlyCollection <Spot> spotsForBreak, IReadOnlyDictionary <Guid, SpotInfo> spotInfos, IReadOnlyList <SmoothBreak> progSmoothBreaks, ProductClashRules productClashRule, bool respectCampaignClash, bool respectSpotTime, bool respectRestrictions, bool respectClashExceptions, SpotPositionRules breakPositionRules, SpotPositionRules requestedPositionInBreakRules, IReadOnlyDictionary <string, Clash> clashesByExternalRef, bool canSplitMultipartSpotsOverBreaks, SmoothResources smoothResources, IReadOnlyCollection <Break> breaksBeingSmoothed, IReadOnlyCollection <Programme> scheduleProgrammes, IClashExposureCountService clashExposureCountService, SponsorshipRestrictionService sponsorshipRestrictionsService) { var result = new SmoothFailureMessagesForSpotsCollection(theSmoothBreak); foreach (Guid spotUid in spotsForBreak.Select(s => s.Uid)) { result.InitialiseForSpot(spotUid); } if (!IsSufficientRemainingDurationToAddSpots( theSmoothBreak.RemainingAvailability, spotsForBreak)) { foreach (Guid spotUid in spotsForBreak.Select(s => s.Uid)) { result.Add( spotUid, SmoothFailureMessages.T1_InsufficentRemainingDuration); } } var(spotChildClashCodeCount, spotParentClashCodeCount) = ClashCodesForSpotsCount(spotsForBreak, spotInfos); // Check basics of whether we can add this spot to the break, // correct break type, sufficient time remaining, product clashes, // campaign clashes var spotsAlreadyInTheBreak = theSmoothBreak.SmoothSpots .ConvertAll(s => s.Spot); var canAddSpotService = CanAddSpotService.Factory(theSmoothBreak); foreach (Spot spot in spotsForBreak) { ValidateWithSponsorshipRestrictions( theSmoothBreak, sponsorshipRestrictionsService, result, spotsAlreadyInTheBreak, spot); Guid spotUid = spot.Uid; if (!canAddSpotService.CanAddSpotWithBreakType(spot.BreakType)) { result.Add(spotUid, SmoothFailureMessages.T1_InvalidBreakType); } if (respectSpotTime && !canAddSpotService.CanAddSpotWithTime(spot.StartDateTime, spot.EndDateTime)) { result.Add(spotUid, SmoothFailureMessages.T1_InvalidSpotTime); } var(spotHasClashExceptionIncludes, spotHasClashExceptionExcludes) = HasClashExceptionIncludesAndExcludes( theSmoothBreak, respectClashExceptions, smoothResources, result, spot, spotUid); bool shouldCheckProductClashRules = ( productClashRule == ProductClashRules.NoClashes || productClashRule == ProductClashRules.LimitOnExposureCount ) && !spotHasClashExceptionIncludes && !spotHasClashExceptionExcludes; if (shouldCheckProductClashRules) { var childProductClashSpots = smoothResources.ProductClashChecker .GetProductClashesForSingleSpot( spot, spotsAlreadyInTheBreak, spotInfos, ClashCodeLevel.Child); switch (productClashRule) { case ProductClashRules.NoClashes: var output = CheckProductClashRulesWhenNoClashesAreAllowed( childProductClashSpots); if (output != SmoothFailureMessages.T0_NoFailure) { result.Add(spotUid, output); } break; case ProductClashRules.LimitOnExposureCount: CheckProductClashRulesWhenClashLimitsAreAllowed( spotInfos, clashesByExternalRef, smoothResources, clashExposureCountService, result, spotChildClashCodeCount, spotParentClashCodeCount, spotsAlreadyInTheBreak, spot, spotInfos[spotUid], childProductClashSpots); break; } } if (respectCampaignClash && smoothResources.CampaignClashChecker .GetCampaignClashesForNewSpots( new List <Spot> { spot }, spotsAlreadyInTheBreak) .Count > 0) { result.Add(spotUid, SmoothFailureMessages.T1_CampaignClash); } } var multipartSpots = spotsForBreak .Where(s => s.IsMultipartSpot) .ToList(); var nonMultipartSpots = spotsForBreak .Except(multipartSpots) .ToList(); if (nonMultipartSpots.Count > 0) { ValidateNonMultipartSpots( progSmoothBreaks, breakPositionRules, requestedPositionInBreakRules, canAddSpotService, result, nonMultipartSpots); } if (multipartSpots.Count > 0) { ValidateMultipartSpots( progSmoothBreaks, breakPositionRules, requestedPositionInBreakRules, canSplitMultipartSpotsOverBreaks, canAddSpotService, result, spotsAlreadyInTheBreak, multipartSpots); } if (respectRestrictions) { ValidateWithSpotRestrictions( theSmoothBreak, programme, salesArea, spotsForBreak, smoothResources, breaksBeingSmoothed, scheduleProgrammes, result); } IndicateIfMultipartTopTailSameBreakSpotsCannotBeAdded( result, multipartSpots); return(RemoveDuplicateFailureMessages(spotsForBreak, result)); }