예제 #1
0
        /// <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);
                }
            }
        }
예제 #3
0
        /// <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());
        }
예제 #4
0
 public override bool CanAddSpotWithBreakRequestForMultipartSpotWithDefaultPosition(
     string spotPositionRequest,
     IReadOnlyList <SmoothBreak> programmeBreaks,
     SpotPositionRules breakPositionRules)
 {
     return(CanAddSpotWithBreakRequest(
                spotPositionRequest,
                programmeBreaks,
                breakPositionRules));
 }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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));
        }
예제 #7
0
 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));
        }
예제 #10
0
        /// <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);