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); }