wire gap_filler into schedule engine, API, MCP

This commit is contained in:
2026-07-12 14:08:55 +02:00
parent 8edd7a9c0b
commit 79e975f057
9 changed files with 243 additions and 3 deletions

View File

@@ -9,7 +9,8 @@ use crate::models::{
ScheduledSlot,
};
use crate::ports::{ChannelQuery, LibraryQuery, ScheduleCommand, ScheduleQuery};
use crate::value_objects::{BlockId, ChannelId, FillStrategy, InterstitialRule, LibrarySearchFilter, MediaItemId, MediaRole, RotationPolicy, Weekday};
use crate::models::MediaItem;
use crate::value_objects::{BlockId, ChannelId, FillStrategy, InterstitialRule, LibrarySearchFilter, MediaFilter, MediaItemId, MediaRole, RotationPolicy, Weekday};
mod fill;
mod rotation;
@@ -153,6 +154,13 @@ impl ScheduleEngineService {
slots.sort_by_key(|s| s.start_at());
if let Some(gap_filter) = channel.gap_filler() {
let filler_items = self.query_gap_fillers(gap_filter).await?;
if !filler_items.is_empty() {
Self::fill_gaps(&mut slots, &filler_items, valid_from, valid_until);
}
}
let schedule = GeneratedSchedule::new(
channel_id,
valid_from,
@@ -415,6 +423,71 @@ impl ScheduleEngineService {
Ok(slots)
}
async fn query_gap_fillers(&self, gap_filter: &MediaFilter) -> DomainResult<Vec<MediaItem>> {
let filter =
media_filter_to_library_search(gap_filter).with_role(MediaRole::Interstitial);
let (items, _) = self.library_query.search(&filter).await?;
Ok(items)
}
fn fill_gaps(
slots: &mut Vec<ScheduledSlot>,
fillers: &[MediaItem],
valid_from: DateTime<Utc>,
valid_until: DateTime<Utc>,
) {
if fillers.is_empty() {
return;
}
let gap_block_id = BlockId::generate();
let mut gap_slots: Vec<ScheduledSlot> = Vec::new();
let mut filler_idx = 0usize;
let mut boundaries: Vec<(DateTime<Utc>, DateTime<Utc>)> = Vec::new();
if slots.is_empty() {
boundaries.push((valid_from, valid_until));
} else {
if slots[0].start_at() > valid_from {
boundaries.push((valid_from, slots[0].start_at()));
}
for pair in slots.windows(2) {
if pair[1].start_at() > pair[0].end_at() {
boundaries.push((pair[0].end_at(), pair[1].start_at()));
}
}
if let Some(last) = slots.last() {
if last.end_at() < valid_until {
boundaries.push((last.end_at(), valid_until));
}
}
}
for (gap_start, gap_end) in boundaries {
let mut cursor = gap_start;
while cursor < gap_end {
let filler = &fillers[filler_idx % fillers.len()];
let filler_end =
(cursor + Duration::seconds(filler.duration_secs() as i64)).min(gap_end);
if filler_end <= cursor {
break;
}
gap_slots.push(ScheduledSlot::new(
cursor,
filler_end,
filler.clone(),
gap_block_id,
));
cursor = filler_end;
filler_idx += 1;
}
}
slots.append(&mut gap_slots);
slots.sort_by_key(|s| s.start_at());
}
}
fn media_filter_to_library_search(filter: &crate::value_objects::MediaFilter) -> LibrarySearchFilter {