Enhance channel management: add support for preferred weight multipliers and preferred hours in channel shows, update related data models and API endpoints, and implement validation for new properties. Refactor scheduling logic to utilize preferred hours for weight adjustments during show planning.
This commit is contained in:
@@ -40,7 +40,8 @@ public static class SchedulePlanner
|
||||
var pick = WeightedPick(candidates, random);
|
||||
|
||||
// ТВ-заставка на переходе. Из подходящих подблоков (по правилу показа vs контексту)
|
||||
// резервируем слот выбранного блока — ассет подставит оркестратор.
|
||||
// резервируем слот выбранного блока — ассет подставит оркестратор. Само появление
|
||||
// ограничено мин. интервалом и вероятностью для типа перехода (смена шоу / между блоками).
|
||||
if (
|
||||
prevShowId is { } prev
|
||||
&& input.Bumpers is { Enabled: true } bumper
|
||||
@@ -51,9 +52,14 @@ public static class SchedulePlanner
|
||||
)
|
||||
)
|
||||
{
|
||||
var bumperStart = cursor;
|
||||
if (TryPlaceBumper(entries, bumper, prev, pick.ShowId, random, ref nextBumper, ref cursor))
|
||||
lastBumperAt = bumperStart;
|
||||
var isShowChange = prev != pick.ShowId;
|
||||
var chance = isShowChange ? bumper.ShowChangeChance : bumper.EpisodeChangeChance;
|
||||
if (RollChance(chance, random))
|
||||
{
|
||||
var bumperStart = cursor;
|
||||
if (TryPlaceBumper(entries, bumper, prev, pick.ShowId, random, ref nextBumper, ref cursor))
|
||||
lastBumperAt = bumperStart;
|
||||
}
|
||||
}
|
||||
|
||||
var blockStart = cursor;
|
||||
@@ -121,6 +127,9 @@ public static class SchedulePlanner
|
||||
case BumperSelection.Random:
|
||||
variant = eligible[random.Next(eligible.Count)];
|
||||
break;
|
||||
case BumperSelection.WeightedRandom:
|
||||
variant = WeightedPickVariant(eligible, random);
|
||||
break;
|
||||
case BumperSelection.AlwaysFirst:
|
||||
variant = eligible[0];
|
||||
break;
|
||||
@@ -158,6 +167,38 @@ public static class SchedulePlanner
|
||||
_ => true,
|
||||
};
|
||||
|
||||
/// <summary>Прошла ли проверка вероятности появления (chance 0..1). 1 — всегда, 0 — никогда.</summary>
|
||||
private static bool RollChance(double chance, IRandomSource random)
|
||||
{
|
||||
if (chance >= 1.0)
|
||||
return true;
|
||||
if (chance <= 0.0)
|
||||
return false;
|
||||
return random.Next(10000) < (int)Math.Round(chance * 10000);
|
||||
}
|
||||
|
||||
/// <summary>Взвешенный случайный выбор подблока по <see cref="PlannerBumperVariant.Weight"/> (нулевые веса → равновероятно).</summary>
|
||||
private static PlannerBumperVariant WeightedPickVariant(
|
||||
List<PlannerBumperVariant> eligible,
|
||||
IRandomSource random
|
||||
)
|
||||
{
|
||||
var total = eligible.Sum(v => Math.Max(0, v.Weight));
|
||||
if (total <= 0)
|
||||
return eligible[random.Next(eligible.Count)];
|
||||
|
||||
var roll = random.Next(total);
|
||||
var acc = 0;
|
||||
foreach (var v in eligible)
|
||||
{
|
||||
acc += Math.Max(0, v.Weight);
|
||||
if (roll < acc)
|
||||
return v;
|
||||
}
|
||||
|
||||
return eligible[^1];
|
||||
}
|
||||
|
||||
private static List<(PlannerShow Show, int Weight)> ResolvePolicy(
|
||||
DateTimeOffset moment,
|
||||
PlannerInput input,
|
||||
@@ -182,12 +223,25 @@ public static class SchedulePlanner
|
||||
// Override ссылается на пустые/неготовые шоу — откатываемся к базовой ротации.
|
||||
}
|
||||
|
||||
var hour = moment.UtcDateTime.Hour;
|
||||
return input.Shows
|
||||
.Where(s => s.Weight > 0 && s.EpisodeAssetIds.Count > 0)
|
||||
.Select(s => (s, s.Weight))
|
||||
.Select(s => (s, EffectiveWeight(s, hour)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>Вес шоу с учётом предпочтительных часов: в окне — усиливается множителем, иначе базовый.</summary>
|
||||
private static int EffectiveWeight(PlannerShow show, int hour)
|
||||
{
|
||||
if (
|
||||
show.PreferredWeightMultiplier > 1
|
||||
&& show.PreferredHours is { Count: > 0 } windows
|
||||
&& windows.Any(w => w.Contains(hour))
|
||||
)
|
||||
return show.Weight * show.PreferredWeightMultiplier;
|
||||
return show.Weight;
|
||||
}
|
||||
|
||||
private static PlannerShow WeightedPick(
|
||||
List<(PlannerShow Show, int Weight)> candidates,
|
||||
IRandomSource random
|
||||
|
||||
@@ -8,9 +8,17 @@ public sealed record PlannerShow(
|
||||
BlockMode BlockMode,
|
||||
int BlockValue,
|
||||
IReadOnlyList<Guid> EpisodeAssetIds,
|
||||
int NextEpisodeIndex
|
||||
int NextEpisodeIndex,
|
||||
IReadOnlyList<PlannerHourWindow>? PreferredHours = null,
|
||||
int PreferredWeightMultiplier = 1
|
||||
);
|
||||
|
||||
/// <summary>Окно предпочтительных часов [StartHour, EndHour) суток (UTC) для планировщика.</summary>
|
||||
public sealed record PlannerHourWindow(int StartHour, int EndHour)
|
||||
{
|
||||
public bool Contains(int hour) => hour >= StartHour && hour < EndHour;
|
||||
}
|
||||
|
||||
/// <summary>Override в терминах планировщика: окно + режим + шоу с весами.</summary>
|
||||
public sealed record PlannerOverride(
|
||||
DateTimeOffset StartsAtUtc,
|
||||
@@ -25,23 +33,28 @@ public sealed record PlannerOverrideShow(Guid ShowId, int Weight);
|
||||
/// Политика ТВ-заставок на переходах. Планировщик из подходящих подблоков (<see cref="Variants"/>,
|
||||
/// фильтр по <see cref="PlannerBumperVariant.Trigger"/> и контексту перехода) выбирает один по стратегии
|
||||
/// <see cref="Selection"/> и резервирует слот длины его блока. Ассет подставляет оркестратор.
|
||||
/// <see cref="ShowChangeChance"/>/<see cref="EpisodeChangeChance"/> — вероятность самого появления
|
||||
/// заставки на смене шоу / между блоками одного шоу (0..1).
|
||||
/// </summary>
|
||||
public sealed record PlannerBumperConfig(
|
||||
bool Enabled,
|
||||
TimeSpan MinInterval,
|
||||
BumperSelection Selection,
|
||||
IReadOnlyList<PlannerBumperVariant> Variants
|
||||
IReadOnlyList<PlannerBumperVariant> Variants,
|
||||
double ShowChangeChance = 1.0,
|
||||
double EpisodeChangeChance = 1.0
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Подблок заставки в терминах планировщика: id варианта + id родительского блока (стиль/звук) +
|
||||
/// длительность слота (кратна сегменту) + правило показа.
|
||||
/// длительность слота (кратна сегменту) + правило показа + вес (для <see cref="BumperSelection.WeightedRandom"/>).
|
||||
/// </summary>
|
||||
public sealed record PlannerBumperVariant(
|
||||
Guid VariantId,
|
||||
Guid TemplateId,
|
||||
TimeSpan Duration,
|
||||
BumperTrigger Trigger
|
||||
BumperTrigger Trigger,
|
||||
int Weight = 1
|
||||
);
|
||||
|
||||
/// <summary>Полный вход планировщика для одного прогона по каналу.</summary>
|
||||
|
||||
Reference in New Issue
Block a user