Implement template export/import endpoints and enhance grid generation logic
Added new endpoints for exporting and importing grid configurations in TemplateEndpoints, allowing for better management of template data. Enhanced the GenerateGridCommandHandler to support seasonal layers in grid generation, improving scheduling accuracy during holiday periods. Updated related classes and records to accommodate these changes, ensuring a cohesive integration of new features. Improved documentation for clarity and maintainability.
This commit is contained in:
@@ -75,7 +75,9 @@ public class SchedulePlannerTests
|
||||
DateTimeOffset? start = null,
|
||||
int horizonHours = 6,
|
||||
IReadOnlyList<PlanningUnit>? fallback = null,
|
||||
IReadOnlyList<PlanningSlot>? background = null
|
||||
IReadOnlyList<PlanningSlot>? background = null,
|
||||
IReadOnlyList<PadUnit>? pad = null,
|
||||
TimeSpan maxPad = default
|
||||
) =>
|
||||
new(
|
||||
Guid.NewGuid(),
|
||||
@@ -85,9 +87,13 @@ public class SchedulePlannerTests
|
||||
fallback ?? [Unit(1, Guid.NewGuid(), 0)],
|
||||
SegmentSeconds: 2,
|
||||
UtcOffsetMinutes: 0,
|
||||
background ?? []
|
||||
background ?? [],
|
||||
pad ?? [],
|
||||
maxPad
|
||||
);
|
||||
|
||||
private static PadUnit Ad(int minutes) => new(Unit(minutes, Guid.Empty, 0), PlannedItemKind.Ad);
|
||||
|
||||
private static PlanningResult Run(PlanningInput input) =>
|
||||
TeleWave.Domain.Programming.Planning.SchedulePlanner.Plan(input, new FirstAlways());
|
||||
|
||||
@@ -350,6 +356,72 @@ public class SchedulePlannerTests
|
||||
Assert.Equal(element.ElementId, program.Trace.ElementId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pad_FillsSeamBeforeSlotWithAds()
|
||||
{
|
||||
// Слот-якорь начинается через полчаса после конца предыдущего: шов до объявленного времени
|
||||
// на настоящем ТВ занимает реклама, а не ещё одна серия.
|
||||
var anchor = Slot(T0.AddMinutes(30), 30, [Element(1, 30)], isAnchor: true);
|
||||
|
||||
var result = Run(
|
||||
Input(
|
||||
[Slot(T0, 15, [Element(1, 15)]), anchor],
|
||||
horizonHours: 1,
|
||||
pad: [Ad(5)],
|
||||
maxPad: TimeSpan.FromMinutes(30)
|
||||
)
|
||||
);
|
||||
|
||||
var ads = result.Items.Where(i => i.Kind == PlannedItemKind.Ad).ToList();
|
||||
Assert.Equal(3, ads.Count);
|
||||
Assert.Equal(T0.AddMinutes(15), ads[0].StartsAtUtc);
|
||||
// К объявленному времени шов заканчивается: якорь начинается ровно в свои полчаса.
|
||||
Assert.Equal(anchor.TargetStartUtc, ads[^1].EndsAtUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pad_IsCappedSoLongHolesStayProgramming()
|
||||
{
|
||||
// Дыра в час: рекламой закрывается только потолок одного добора, остальное — программы.
|
||||
var background = Slot(T0, 24 * 60, [Element(6, 10)]);
|
||||
|
||||
var result = Run(
|
||||
Input(
|
||||
[Slot(T0.AddHours(1), 30, [Element(1, 30)], isAnchor: true)],
|
||||
horizonHours: 2,
|
||||
pad: [Ad(5)],
|
||||
maxPad: TimeSpan.FromMinutes(10),
|
||||
background: [background]
|
||||
)
|
||||
);
|
||||
|
||||
var ads = result.Items.Where(i => i.Kind == PlannedItemKind.Ad).ToList();
|
||||
Assert.Equal(
|
||||
TimeSpan.FromMinutes(10),
|
||||
ads.Aggregate(TimeSpan.Zero, (sum, a) => sum + (a.EndsAtUtc - a.StartsAtUtc))
|
||||
);
|
||||
Assert.Contains(result.Items, i => i.SlotId == background.SlotId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pad_ClosesTailWhenProgrammeDoesNotFit()
|
||||
{
|
||||
// Остаток в пять минут: серия фона туда не влезает, и хвост занимает реклама, а не филлер.
|
||||
var background = Slot(T0, 24 * 60, [Element(4, 30)]);
|
||||
|
||||
var result = Run(
|
||||
Input(
|
||||
[Slot(T0, 55, [Element(1, 55)])],
|
||||
horizonHours: 1,
|
||||
pad: [Ad(5)],
|
||||
background: [background]
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Single(result.Items, i => i.Kind == PlannedItemKind.Ad);
|
||||
Assert.DoesNotContain(result.Items, i => i.Kind == PlannedItemKind.Fallback);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Background_PlaysInLeftoverInsteadOfEmergencyFiller()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user