From 38b6160bd22854f858c403b705382e048af46953 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Mon, 27 Jul 2026 00:01:22 +0300 Subject: [PATCH] Refactor JunctionFiller and SchedulePlanner to improve parameter handling by introducing JunctionPlacement record for cleaner code and better readability. Update Directory.Build.props to suppress additional warnings related to API boundaries and unused route parameters, ensuring a more focused analysis during builds. --- backend/Directory.Build.props | 14 +- .../Programming/Planning/JunctionFiller.cs | 42 ++-- .../Programming/Planning/SchedulePlanner.cs | 180 +++++++----------- 3 files changed, 109 insertions(+), 127 deletions(-) diff --git a/backend/Directory.Build.props b/backend/Directory.Build.props index 5a34a55..0c5da06 100644 --- a/backend/Directory.Build.props +++ b/backend/Directory.Build.props @@ -9,6 +9,18 @@ latest false true - $(NoWarn);CA1711;CA1716;CA1848;CA1873 + + $(NoWarn);CA1711;CA1716;CA1848;CA1873;CA1859;ASP0018 diff --git a/backend/src/TeleWave.Domain/Programming/Planning/JunctionFiller.cs b/backend/src/TeleWave.Domain/Programming/Planning/JunctionFiller.cs index c3e887e..026f26f 100644 --- a/backend/src/TeleWave.Domain/Programming/Planning/JunctionFiller.cs +++ b/backend/src/TeleWave.Domain/Programming/Planning/JunctionFiller.cs @@ -18,6 +18,19 @@ public sealed class JunctionHistory _lastPlaced[element.Kind] = moment; } +/// +/// Где ставится стык и между чем. Собрано в один параметр: по отдельности эти четыре значения ехали +/// сквозь всю раскладку и вместе с курсором, пределом и накопителями раздували сигнатуры. +/// +/// Шоу перед стыком, — после; заставке нужна +/// именно пара соседей, её ассет рендерится под неё после сборки ленты. +public sealed record JunctionPlacement( + Guid SlotId, + Guid? FromShowId, + Guid? ToShowId, + bool ElementChanged +); + /// /// Раскладка врезок стыка: реклама, промо, заставка, заполнитель. Ставит только то, что влезает /// целиком до предела (якорь или горизонт) — обрезать врезку нельзя, а перехлёст сдвинул бы якорь. @@ -27,19 +40,12 @@ public sealed class JunctionHistory /// public static class JunctionFiller { - /// - /// Раскладывает стык от . / - /// нужны заставке: её ассет зависит от пары соседей и рендерится после сборки ленты. - /// Возвращает курсор после стыка. - /// + /// Раскладывает стык от . Возвращает курсор после стыка. public static DateTimeOffset Fill( PlanningJunction? junction, DateTimeOffset cursor, DateTimeOffset limit, - Guid slotId, - Guid? fromShowId, - Guid? toShowId, - bool elementChanged, + JunctionPlacement placement, JunctionHistory history, List items, PlanTrace? trace @@ -55,7 +61,7 @@ public static class JunctionFiller foreach (var element in ordered) { - if (element.OnlyOnElementChange && !elementChanged) + if (element.OnlyOnElementChange && !placement.ElementChanged) continue; if (!history.Allows(element, cursor)) continue; @@ -63,8 +69,8 @@ public static class JunctionFiller var placedAt = cursor; cursor = element.Kind == JunctionElementKind.Bumper - ? PlaceBumper(element, cursor, limit, slotId, fromShowId, toShowId, items, trace) - : PlaceUnits(element, cursor, limit, slotId, items, trace); + ? PlaceBumper(element, cursor, limit, placement, items, trace) + : PlaceUnits(element, cursor, limit, placement.SlotId, items, trace); if (cursor > placedAt) history.Record(element, placedAt); @@ -81,9 +87,7 @@ public static class JunctionFiller PlanningJunctionElement element, DateTimeOffset cursor, DateTimeOffset limit, - Guid slotId, - Guid? fromShowId, - Guid? toShowId, + JunctionPlacement placement, List items, PlanTrace? trace ) @@ -97,14 +101,14 @@ public static class JunctionFiller Guid.Empty, cursor, end, - toShowId, + placement.ToShowId, null, - slotId, + placement.SlotId, PlannedItemKind.Bumper, trace, element.BumperTemplateId, - fromShowId, - toShowId + placement.FromShowId, + placement.ToShowId ) ); return end; diff --git a/backend/src/TeleWave.Domain/Programming/Planning/SchedulePlanner.cs b/backend/src/TeleWave.Domain/Programming/Planning/SchedulePlanner.cs index 2e2cfdd..1c3007f 100644 --- a/backend/src/TeleWave.Domain/Programming/Planning/SchedulePlanner.cs +++ b/backend/src/TeleWave.Domain/Programming/Planning/SchedulePlanner.cs @@ -14,20 +14,35 @@ public static class SchedulePlanner { private const int IterationBackstop = 100_000; + /// + /// Накопители одного прогона: собираемая лента, новые курсоры слотов, предупреждения, история + /// врезок и шоу последней поставленной единицы. Всё это протаскивалось через сигнатуры десятком + /// параметров (включая ref), хотя принадлежит прогону целиком, а не отдельному шагу. + /// + /// История врезок общая на прогон намеренно: «не чаще раза в полчаса» должно работать и через + /// границу слота. + /// + private sealed class PlanningRun(PlanningInput input, IRandomSource random) + { + public PlanningInput Input { get; } = input; + public IRandomSource Random { get; } = random; + public List Items { get; } = []; + public List Cursors { get; } = []; + public List Warnings { get; } = []; + public JunctionHistory Junctions { get; } = new(); + + /// Шоу последней поставленной единицы — по нему стык понимает, сменился ли элемент. + public Guid? PreviousShowId { get; set; } + } + public static PlanningResult Plan(PlanningInput input, IRandomSource random) { - var items = new List(); - var cursors = new List(); - var warnings = new List(); + var run = new PlanningRun(input, random); var slots = input.Slots.OrderBy(s => s.TargetStartUtc).ToList(); var cursor = input.StartUtc; var iterations = 0; - // История врезок общая на прогон: «не чаще раза в полчаса» должно работать и через границу слота. - var junctions = new JunctionHistory(); - Guid? previousShowId = null; - for (var i = 0; i < slots.Count && cursor < input.HorizonEndUtc; i++) { if (iterations++ > IterationBackstop) @@ -42,32 +57,20 @@ public static class SchedulePlanner var nextAnchor = FindNextAnchor(slots, i + 1); - cursor = OpenSlot(slot, cursor, input, items, warnings, out var trace); + cursor = OpenSlot(slot, cursor, run, out var trace); if (cursor >= input.HorizonEndUtc) break; - cursor = FillSlot( - slot, - cursor, - nextAnchor, - input, - random, - items, - cursors, - warnings, - trace, - junctions, - ref previousShowId - ); + cursor = FillSlot(slot, cursor, nextAnchor, trace, run); } // Хвост до горизонта закрывает фон: лента обязана быть непрерывной, иначе живой край // упрётся в дыру. if (cursor < input.HorizonEndUtc) - cursor = FillWithFallback(cursor, input.HorizonEndUtc, input, items, null); + cursor = FillWithFallback(cursor, input.HorizonEndUtc, run, null); if (cursor < input.HorizonEndUtc && input.FallbackUnits.Count == 0) - warnings.Add( + run.Warnings.Add( new PlanningWarning( PlanningWarningKind.FallbackEmpty, null, @@ -75,7 +78,7 @@ public static class SchedulePlanner ) ); - return new PlanningResult(items, cursors, warnings); + return new PlanningResult(run.Items, run.Cursors, run.Warnings); } /// @@ -85,9 +88,7 @@ public static class SchedulePlanner private static DateTimeOffset OpenSlot( PlanningSlot slot, DateTimeOffset cursor, - PlanningInput input, - List items, - List warnings, + PlanningRun run, out PlanTrace trace ) { @@ -97,7 +98,7 @@ public static class SchedulePlanner { // До целевого времени ещё есть место — закрываем его фоном. Для якоря это обязательно, // для обычного слота тоже: иначе он начнётся раньше объявленного в программе времени. - cursor = FillWithFallback(cursor, slot.TargetStartUtc, input, items, slot.SlotId); + cursor = FillWithFallback(cursor, slot.TargetStartUtc, run, slot.SlotId); } else if (slot.SnapToMinutes is { } snap && snap > 0) { @@ -106,7 +107,7 @@ public static class SchedulePlanner var target = RoundUp(cursor, TimeSpan.FromMinutes(snap)); if (target - cursor <= TimeSpan.FromMinutes(slot.MaxDriftMinutes)) { - var afterFill = FillWithFallback(cursor, target, input, items, slot.SlotId); + var afterFill = FillWithFallback(cursor, target, run, slot.SlotId); snapped = afterFill > cursor; cursor = afterFill; } @@ -114,7 +115,7 @@ public static class SchedulePlanner var drift = (int)Math.Round((cursor - slot.TargetStartUtc).TotalMinutes); if (Math.Abs(drift) > slot.MaxDriftMinutes) - warnings.Add( + run.Warnings.Add( new PlanningWarning( PlanningWarningKind.DriftExceeded, slot.SlotId, @@ -140,17 +141,11 @@ public static class SchedulePlanner PlanningSlot slot, DateTimeOffset cursor, DateTimeOffset? nextAnchor, - PlanningInput input, - IRandomSource random, - List items, - List cursors, - List warnings, PlanTrace trace, - JunctionHistory junctions, - ref Guid? previousShowId + PlanningRun run ) { - var limit = Min(input.HorizonEndUtc, nextAnchor); + var limit = Min(run.Input.HorizonEndUtc, nextAnchor); switch (slot.SlotKind) { @@ -159,30 +154,17 @@ public static class SchedulePlanner return FillWithFallback( cursor, Min(slot.TargetEndUtc, limit), - input, - items, + run, slot.SlotId, PlannedItemKind.SignOff, trace ); case SlotKind.Repeat: - return FillRepeat(slot, cursor, limit, input, items, warnings, trace); + return FillRepeat(slot, cursor, limit, trace, run); default: - return FillContent( - slot, - cursor, - limit, - input, - random, - items, - cursors, - warnings, - trace, - junctions, - ref previousShowId - ); + return FillContent(slot, cursor, limit, trace, run); } } @@ -190,29 +172,21 @@ public static class SchedulePlanner PlanningSlot slot, DateTimeOffset cursor, DateTimeOffset limit, - PlanningInput input, - List items, - List warnings, - PlanTrace trace + PlanTrace trace, + PlanningRun run ) { var units = slot.RepeatUnits ?? []; if (units.Count == 0) { - warnings.Add( + run.Warnings.Add( new PlanningWarning( PlanningWarningKind.RepeatSourceEmpty, slot.SlotId, "В источнике повтора ничего не нашлось — слот закрыт фоном." ) ); - return FillWithFallback( - cursor, - Min(slot.TargetEndUtc, limit), - input, - items, - slot.SlotId - ); + return FillWithFallback(cursor, Min(slot.TargetEndUtc, limit), run, slot.SlotId); } var slotEnd = Min(slot.TargetEndUtc, limit); @@ -220,7 +194,7 @@ public static class SchedulePlanner { if (cursor + unit.Duration > slotEnd) break; - items.Add(Program(unit, cursor, slot.SlotId, trace)); + run.Items.Add(Program(unit, cursor, slot.SlotId, trace)); cursor += unit.Duration; } @@ -231,31 +205,19 @@ public static class SchedulePlanner PlanningSlot slot, DateTimeOffset cursor, DateTimeOffset limit, - PlanningInput input, - IRandomSource random, - List items, - List cursors, - List warnings, PlanTrace trace, - JunctionHistory junctions, - ref Guid? previousShowId + PlanningRun run ) { - var pick = ElementSelector.Select(slot, cursor, random); + var pick = ElementSelector.Select(slot, cursor, run.Random); if (pick is null) { - warnings.Add(NoCandidatesWarning(slot)); - return FillWithFallback( - cursor, - Min(slot.TargetEndUtc, limit), - input, - items, - slot.SlotId - ); + run.Warnings.Add(NoCandidatesWarning(slot)); + return FillWithFallback(cursor, Min(slot.TargetEndUtc, limit), run, slot.SlotId); } if (pick.CooldownExhausted) - warnings.Add( + run.Warnings.Add( new PlanningWarning( PlanningWarningKind.CooldownExhausted, slot.SlotId, @@ -283,8 +245,8 @@ public static class SchedulePlanner && !FitsEntirely(element, unitIndex, cursor, budgetEnd) ) { - cursors.Add(CursorUpdate(slot, element, unitIndex)); - return FillWithFallback(cursor, budgetEnd, input, items, slot.SlotId); + run.Cursors.Add(CursorUpdate(slot, element, unitIndex)); + return FillWithFallback(cursor, budgetEnd, run, slot.SlotId); } while (unitIndex < element.Units.Count && cursor < limit) @@ -297,12 +259,14 @@ public static class SchedulePlanner slot.JunctionBetween, cursor, limit, - slot.SlotId, - previousShowId, - unit.ShowId, - elementChanged: previousShowId != unit.ShowId, - junctions, - items, + new JunctionPlacement( + slot.SlotId, + run.PreviousShowId, + unit.ShowId, + ElementChanged: run.PreviousShowId != unit.ShowId + ), + run.Junctions, + run.Items, slotTrace ); @@ -313,12 +277,12 @@ public static class SchedulePlanner if (!WithinBudget(slot, placed, accumulated, cursor, unit, budgetEnd)) break; - items.Add(Program(unit, cursor, slot.SlotId, slotTrace, CollectionOf(element))); + run.Items.Add(Program(unit, cursor, slot.SlotId, slotTrace, CollectionOf(element))); cursor += unit.Duration; accumulated += unit.Duration; unitIndex++; placed++; - previousShowId = unit.ShowId; + run.PreviousShowId = unit.ShowId; } // Врезки в конце блока ставятся до добора фоном: иначе реклама оказалась бы после заполнителя. @@ -327,20 +291,22 @@ public static class SchedulePlanner slot.JunctionAfter, cursor, limit, - slot.SlotId, - previousShowId, - null, - elementChanged: true, - junctions, - items, + new JunctionPlacement( + slot.SlotId, + run.PreviousShowId, + null, + ElementChanged: true + ), + run.Junctions, + run.Items, slotTrace ); - cursors.Add(CursorUpdate(slot, element, unitIndex)); + run.Cursors.Add(CursorUpdate(slot, element, unitIndex)); // Недобор до целевого конца закрываем фоном — только для слотов, чей бюджет привязан ко времени. if (slot.BlockMode == SlotBlockMode.FillSlot && cursor < budgetEnd) - cursor = FillWithFallback(cursor, budgetEnd, input, items, slot.SlotId); + cursor = FillWithFallback(cursor, budgetEnd, run, slot.SlotId); return cursor; } @@ -416,14 +382,14 @@ public static class SchedulePlanner private static DateTimeOffset FillWithFallback( DateTimeOffset from, DateTimeOffset until, - PlanningInput input, - List items, + PlanningRun run, Guid? slotId, PlannedItemKind kind = PlannedItemKind.Fallback, PlanTrace? trace = null ) { - if (input.FallbackUnits.Count == 0 || until <= from) + var fallback = run.Input.FallbackUnits; + if (fallback.Count == 0 || until <= from) return from; var cursor = from; @@ -432,13 +398,13 @@ public static class SchedulePlanner while (cursor < until && guard++ < IterationBackstop) { - var unit = input.FallbackUnits[index % input.FallbackUnits.Count]; + var unit = fallback[index % fallback.Count]; index++; if (unit.Duration <= TimeSpan.Zero || cursor + unit.Duration > until) break; - items.Add( + run.Items.Add( new PlannedItem( unit.MediaAssetId, cursor,