/// <summary> /// Determines whether this Spot instance can be placed within the requested /// break or container. /// </summary> /// <param name="spot">The spot.</param> /// <param name="progSmoothBreaks">The prog smooth breaks.</param> /// <param name="breakPositionRules">The break position rules.</param> /// <param name="respectSpotTime">if set to <c>true</c>, respect spot time.</param> /// <param name="validBreaksForSpotTime">The valid breaks for spot time.</param> /// <param name="isRestrictedSpotTime">if set to <c>true</c>, is restricted spot time.</param> /// <param name="progSmoothBreak">The prog smooth break.</param> /// <returns> /// <c>true</c> if this Spot instance can be placed within the break or container requested; otherwise, <c>false</c>. /// </returns> public static bool CanSpotBePlacedInRequestedBreakOrContainer( Spot spot, IReadOnlyList <SmoothBreak> progSmoothBreaks, SpotPositionRules breakPositionRules, bool respectSpotTime, IOrderedEnumerable <SmoothBreak> validBreaksForSpotTime, bool isRestrictedSpotTime, SmoothBreak progSmoothBreak) { var canAddSpotService = CanAddSpotService.Factory(progSmoothBreak); string spotBreakOrContainerRequest = spot.BreakRequest; if (ContainerReference.TryParse(spotBreakOrContainerRequest, out ContainerReference cr)) { spotBreakOrContainerRequest = cr.ToString(); } if (canAddSpotService.CanAddSpotWithBreakRequest(spotBreakOrContainerRequest, progSmoothBreaks, breakPositionRules)) { return(true); } else if (IsBreakWithinSpotTimeRestriction(respectSpotTime, isRestrictedSpotTime, validBreaksForSpotTime, progSmoothBreak)) { return(true); } return(false); }
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); } } }
/// <summary> /// <para>Returns list of scenarios to try.</para> /// <para> /// We may need to improve the way that we work out the best scenario /// using the ImpactScore property. /// </para> /// </summary> /// <param name="spotsForBreak"></param> /// <param name="progSmoothBreaks"></param> /// <returns></returns> public IReadOnlyCollection <SmoothScenario> GetSmoothScenarios( IReadOnlyCollection <Spot> spotsForBreak, IReadOnlyList <SmoothBreak> progSmoothBreaks, IReadOnlyCollection <SmoothFailureMessagesForSpotsCollection> validateAddSpotsToBreakResults, SpotPositionRules breakPositionRules, bool respectSpotTime) { var smoothScenarios = new List <SmoothScenario>(); smoothScenarios.AddRange( GetSmoothScenariosForSponsoredSpots( spotsForBreak, progSmoothBreaks, validateAddSpotsToBreakResults, lastSmoothScenarioSequence: 0, breakPositionRules, respectSpotTime ) ); // Add scenario with no actions. Set it has the highest impact so // that we ideally pick one of the scenarios from above. smoothScenarios.Add( new SmoothScenario { Id = Guid.NewGuid(), Sequence = smoothScenarios.Count + 1, Priority = Int32.MaxValue }); return(smoothScenarios .OrderBy(ss => ss.Sequence) .ToList()); }
public override bool CanAddSpotWithBreakRequestForMultipartSpotWithDefaultPosition( string spotPositionRequest, IReadOnlyList <SmoothBreak> programmeBreaks, SpotPositionRules breakPositionRules) { return(CanAddSpotWithBreakRequest( spotPositionRequest, programmeBreaks, breakPositionRules)); }
/// <summary> /// <para> Adds spots to break. If multiple spots are being /// added then they're typically related. E.g. TOP & TAIL. /// </para> /// <para> The break sequence will be updated later when the break has /// been filled, will be updated to start from 1. For specific positions /// (FIB, SIB etc) then the break sequence is assigned as a large positive /// or negative number and everything else is inserted between. /// </para> /// </summary> public static List <SmoothSpot> AddSpotsToBreak( SmoothBreak smoothBreak, int smoothPassSequence, int smoothPassIterationSequence, IReadOnlyCollection <Spot> spots, SpotPositionRules passRequestedPositionInBreakRules, bool canMoveSpotToOtherBreak, ICollection <Guid> spotIdsUsed, string bestBreakFactorGroupName, IReadOnlyDictionary <Guid, SpotInfo> spotInfos, SponsorshipRestrictionService sponsorshipRestrictionService ) { var smoothSpots = new List <SmoothSpot>(); foreach (Spot spot in spots) { IReadOnlyDictionary <string, bool> hasSpotPositions = smoothBreak.GetSpotPositions(); int breakSeq = SpotPositioning.GetBreakSequenceNumber( smoothBreak, passRequestedPositionInBreakRules, spot, hasSpotPositions ); // Default break sequence to middle of break if (breakSeq == 0) { breakSeq = SpotPositioning.GetBreakSeqForMiddleOfBreak(smoothBreak); } smoothSpots.Add( smoothBreak.AddSpot( spot, smoothPassSequence, smoothPassIterationSequence, breakSeq, currentSpot: true, canMoveSpotToOtherBreak, bestBreakFactorGroupName, spotInfos[spot.Uid].ExternalBreakRefAtRunStart ) ); sponsorshipRestrictionService.TriggerRecalculationOfAllowedRestrictionLimits( SpotAction.AddSpot, spot, smoothBreak.TheBreak ); FlagSpotAsUsed(spot, spotIdsUsed); } return(smoothSpots); }
/// <inheritdoc/> public override bool CanAddSpotWithBreakRequest( string spotPositionRequest, IReadOnlyList <SmoothBreak> programmeBreaks, SpotPositionRules breakPositionRules) { if (String.IsNullOrWhiteSpace(spotPositionRequest)) { return(true); } if (ContainerReference.TryParse( _smoothBreak.TheBreak.ExternalBreakRef, out ContainerReference breakContainerReference)) { if (breakContainerReference.Equals(spotPositionRequest)) { return(true); } } if (!Int32.TryParse(spotPositionRequest, out int requestedContainerNumber)) { return(false); } // Note: this should never happen but it's better to check to stop // index out of range exceptions. if (programmeBreaks.Count == 0) { return(false); } // All smooth breaks have a link back to their smooth programme, so // just use the first one. var breakContainer = programmeBreaks[0].SmoothProgramme.BreakContainers; requestedContainerNumber = GetActualBreakPositionFromRelativePosition( requestedContainerNumber, breakContainer.Count); return(CanAddSpotAtBreakPosition( requestedContainerNumber, breakContainerReference.ContainerNumber, breakPositionRules)); }
public SmoothPassDefaultIteration( int sequence, bool respectSpotTime, bool respectCampaignClash, ProductClashRules productClashRules, SpotPositionRules breakPositionRules, SpotPositionRules requestedPositionInBreakRules, bool respectRestrictions, bool respectClashExceptions) { Sequence = sequence; RespectSpotTime = respectSpotTime; RespectCampaignClash = respectCampaignClash; ProductClashRules = productClashRules; BreakPositionRules = breakPositionRules; RequestedPositionInBreakRules = requestedPositionInBreakRules; RespectRestrictions = respectRestrictions; RespectClashExceptions = respectClashExceptions; }
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)); }
/// <summary> /// <para> /// Get SmoothScenario instances for sponsored spots. We try and return scenarios. /// </para> /// <para> /// Unfortunately we have some business rules regarding sponsor spots /// coded here. We should try and make it more data driven later on. The /// business rules are that the first sponsor spot is placed in break /// #1, the next in break #2. /// </para> /// </summary> private IReadOnlyCollection <SmoothScenario> GetSmoothScenariosForSponsoredSpots( IReadOnlyCollection <Spot> spotsForBreak, IReadOnlyList <SmoothBreak> progSmoothBreaks, IReadOnlyCollection <SmoothFailureMessagesForSpotsCollection> validateAddSpotsToBreakResults, int lastSmoothScenarioSequence, SpotPositionRules breakPositionRules, bool respectSpotTime) { if (progSmoothBreaks.Count == 0) { return(new List <SmoothScenario>()); } var sponsoredSpots = spotsForBreak .Where(s => s.Sponsored); if (!sponsoredSpots.Any()) { return(new List <SmoothScenario>()); } var sponsoredSpot = sponsoredSpots.First(); var validBreaksForSpotTime = progSmoothBreaks .Where(sb => { var canAddSpotService = CanAddSpotService.Factory(sb); return(canAddSpotService.CanAddSpotWithTime(sponsoredSpot.StartDateTime, sponsoredSpot.EndDateTime)); }) .OrderBy(sb => sb.TheBreak.ScheduledDate); if (!validBreaksForSpotTime.Any()) { return(new List <SmoothScenario>()); } bool isRestrictedSpotTime = validBreaksForSpotTime.First().Position != 1 || validBreaksForSpotTime.Last().Position != progSmoothBreaks[progSmoothBreaks.Count - 1].Position; var smoothScenarios = new List <SmoothScenario>(); var listOfSpotValidations = validateAddSpotsToBreakResults.ToList(); var listOfProgrammeSmoothBreaks = progSmoothBreaks.ToList(); // Check each break, see if there are sponsored spots that we could move foreach (var progSmoothBreak in progSmoothBreaks) { // Lists of groups of spots moved out of break, used for // preventing duplicate scenarios which is inefficient var externalSpotRefsMovedOutOfBreakByGroup = new HashSet <string>(); // Get results for adding spot to break in its current state var validateAddSpotsToBreakResult = listOfSpotValidations[listOfProgrammeSmoothBreaks.IndexOf(progSmoothBreak)]; // Check if any failures. Ignore if T1_BreakPosition which // happens if break requested and this break is not allowed If // break position not allowed then don't move any spots. This // would happen if a break request was specified and this isn't // the requested break if (validateAddSpotsToBreakResult[sponsoredSpot.Uid].Failures.Count > 0 && // Failures prevent spot being added !validateAddSpotsToBreakResult[sponsoredSpot.Uid].Failures.Any(f => f.FailureMessage == SmoothFailureMessages.T1_BreakPosition)) { var spotGroupsToMove = new List <Spot[]>(); // Only consider moving spots from this break if any of the // following conditions are true: // - No break request and first or last break (as per // business rules). // - Requested break and this is requested break. // - Spot has restricted times and break is within it. bool addScenariosToFixFailures = false; if (!HasBreakRequest(sponsoredSpot) && ( progSmoothBreak.Position == listOfProgrammeSmoothBreaks[0].Position || progSmoothBreak.Position == progSmoothBreaks[progSmoothBreaks.Count - 1].Position ) ) { addScenariosToFixFailures = true; } else if (HasBreakRequest(sponsoredSpot)) { addScenariosToFixFailures = SpotUtilities.CanSpotBePlacedInRequestedBreakOrContainer( sponsoredSpot, progSmoothBreaks, breakPositionRules, respectSpotTime, validBreaksForSpotTime, isRestrictedSpotTime, progSmoothBreak); } else if (SpotUtilities.IsBreakWithinSpotTimeRestriction( respectSpotTime, isRestrictedSpotTime, validBreaksForSpotTime, progSmoothBreak)) { addScenariosToFixFailures = true; } if (addScenariosToFixFailures) { // See if we can find spots to move out of the break, // check failures foreach (var failure in validateAddSpotsToBreakResult[sponsoredSpot.Uid].Failures) { switch (failure.FailureMessage) { case SmoothFailureMessages.T1_CampaignClash: MoveCampaignClashSpots(spotsForBreak, sponsoredSpot, progSmoothBreak, spotGroupsToMove); break; case SmoothFailureMessages.T1_InsufficentRemainingDuration: MoveSpotsToIncreaseBreakAvailability(spotsForBreak, sponsoredSpot, progSmoothBreak, spotGroupsToMove); break; case SmoothFailureMessages.T1_ProductClash: MoveProductClashSpots(spotsForBreak, sponsoredSpot, progSmoothBreak, spotGroupsToMove); break; case SmoothFailureMessages.T1_RequestedPositionInBreak: MoveRequestedPositionInBreakSpots(spotsForBreak, sponsoredSpot, progSmoothBreak, spotGroupsToMove); break; } } } // Add scenario to move group of spots, avoid duplicating // scenarios for same spot foreach (var spotGroupToMove in spotGroupsToMove) { string externalSpotRefsForGroup = SpotUtilities.GetListOfSpotExternalReferences( ",", spotGroupToMove.ToList()); if (!externalSpotRefsMovedOutOfBreakByGroup.Contains(externalSpotRefsForGroup)) { const int PriorityBase2 = 1_000_000; var smoothScenario = new SmoothScenario() { Id = Guid.NewGuid(), Sequence = lastSmoothScenarioSequence + 1, Priority = PriorityBase2 + (100_000 - spotGroupToMove[0].Preemptlevel) }; smoothScenario.Actions.Add( new SmoothActionMoveSpotToUnplaced( sequence: 1, spotGroupToMove.Select(s => s.ExternalSpotRef) ) ); smoothScenarios.Add(smoothScenario); lastSmoothScenarioSequence = smoothScenarios.Last().Sequence; _ = externalSpotRefsMovedOutOfBreakByGroup.Add(externalSpotRefsForGroup); } } } } return(smoothScenarios);