namespace TeleWave.Domain.Programming.Planning; /// /// Показы, добавленные этим же прогоном. Остывание и потолок повторов считаются по ленте, а она на /// момент сборки входа содержит только прошлое: горизонт в неделю строится одним прогоном, и без /// этого счётчика оба правила не видели бы ничего из того, что сами же и поставили. /// /// Ключ — шоу, как и в снимке из базы: у коллекции остывание общее по всем частям, поэтому элемент /// спрашивает историю сразу по всем своим шоу. /// public sealed class RunPlayHistory { private readonly Dictionary> _byShow = []; public void Record(Guid showId, DateTimeOffset startsAtUtc) { if (!_byShow.TryGetValue(showId, out var plays)) _byShow[showId] = plays = []; plays.Add(startsAtUtc); } /// Когда элемент последний раз выходил в этом прогоне; null — ещё не выходил. public DateTimeOffset? LastPlayed(PlanningElement element) { DateTimeOffset? last = null; foreach (var showId in ShowsOf(element)) { if (!_byShow.TryGetValue(showId, out var plays)) continue; foreach (var play in plays) if (last is null || play > last) last = play; } return last; } /// Сколько раз элемент выходил в этом прогоне начиная с момента. public int CountSince(PlanningElement element, DateTimeOffset from) { var count = 0; foreach (var showId in ShowsOf(element)) if (_byShow.TryGetValue(showId, out var plays)) count += plays.Count(play => play >= from); return count; } /// /// Шоу элемента берутся из его единиц: у коллекции их столько же, сколько частей, и это ровно /// тот набор, по которому историю собирает и оркестратор. /// private static IEnumerable ShowsOf(PlanningElement element) => element.Units.Select(unit => unit.ShowId).Distinct(); }