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:
Leonid Pershin
2026-07-25 18:44:28 +03:00
parent 358adbff21
commit 8718e8b6bf
36 changed files with 2921 additions and 97 deletions
@@ -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