using System.Text.Json;
using System.Text.Json.Serialization;
using TeleWave.Domain.Library;
namespace TeleWave.Application.Programming.Templates;
///
/// Окно детского времени: с до во времени канала
/// в эфир идёт только контент не строже . Окно может переходить
/// через полночь (23:00–06:00) — тогда границы сравниваются в обратную сторону.
///
public sealed record AudienceWindow(TimeOnly From, TimeOnly To, ShowAudience MaxAudience)
{
public bool Contains(TimeOnly moment) =>
From <= To ? moment >= From && moment < To : moment >= From || moment < To;
}
///
/// Правила отбора кандидатов на уровне канала (см. 3.8). Это жёсткие фильтры: они отсекают
/// недопустимое до жребия, поэтому не требуют пересборки и не ломают воспроизводимость.
///
/// Область действия — весь канал: дейпарты сюда не заведены намеренно, окна детского времени
/// и так задаются временем, а потолок повторов осмыслен только целиком по каналу.
///
public sealed record PlanningRules(
IReadOnlyList? MaxAudienceByTime = null,
/// Не чаще Max раз за WindowDays суток (null — без ограничения).
RepeatLimitRule? MaxRepeatsInWindow = null,
// ── Пост-проверки: считаются по готовой ленте, дают предупреждения и ничего не переигрывают. ──
/// Потолок врезок в часе, минуты (null — не проверять).
int? MaxBreakMinutesPerHour = null,
/// Потолок доли одного жанра за вещательные сутки, проценты (null — не проверять).
int? MaxGenreSharePercent = null,
/// Потолок доли эфира, отданной фону, проценты (null — не проверять).
int? MaxFallbackSharePercent = null
)
{
private static readonly JsonSerializerOptions Options = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() },
};
/// Возрастной потолок в указанный момент времени канала (null — без ограничения).
public ShowAudience? AudienceAt(TimeOnly moment)
{
if (MaxAudienceByTime is not { Count: > 0 } windows)
return null;
// Пересекающиеся окна разрешаются в пользу строгого: детское время не должно
// отменяться более широким окном, случайно наложенным сверху.
var applicable = windows.Where(w => w.Contains(moment)).Select(w => w.MaxAudience).ToList();
return applicable.Count > 0 ? applicable.Min() : null;
}
public string ToJson() => JsonSerializer.Serialize(this, Options);
public static PlanningRules? FromJson(string? json)
{
if (string.IsNullOrWhiteSpace(json))
return null;
try
{
return JsonSerializer.Deserialize(json, Options);
}
catch (JsonException)
{
return null;
}
}
}
public sealed record RepeatLimitRule(int WindowDays, int Max);