feat: add find_last_slot_per_block method to schedule repositories and update related logic
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
//! These traits define the interface for data persistence.
|
||||
//! Implementations live in the infra layer.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
@@ -10,7 +12,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::entities::{Channel, GeneratedSchedule, PlaybackRecord, User};
|
||||
use crate::errors::DomainResult;
|
||||
use crate::value_objects::{ChannelId, UserId};
|
||||
use crate::value_objects::{BlockId, ChannelId, MediaItemId, UserId};
|
||||
|
||||
/// An in-app activity event stored in the database for the admin log view.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -98,6 +100,13 @@ pub trait ScheduleRepository: Send + Sync {
|
||||
) -> DomainResult<Vec<PlaybackRecord>>;
|
||||
|
||||
async fn save_playback_record(&self, record: &PlaybackRecord) -> DomainResult<()>;
|
||||
|
||||
/// Return the most recent slot per block_id across ALL schedules for a channel.
|
||||
/// Resilient to any single generation having empty slots for a block.
|
||||
async fn find_last_slot_per_block(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
) -> DomainResult<HashMap<BlockId, MediaItemId>>;
|
||||
}
|
||||
|
||||
/// Repository port for activity log persistence.
|
||||
|
||||
@@ -127,12 +127,22 @@ pub(super) fn fill_sequential<'a>(
|
||||
};
|
||||
|
||||
// Greedily fill the block's time budget in episode order.
|
||||
// Stop at the first episode that doesn't fit — skipping would break ordering.
|
||||
let mut remaining = target_secs;
|
||||
let mut result = Vec::new();
|
||||
for item in ordered {
|
||||
for item in &ordered {
|
||||
if item.duration_secs <= remaining {
|
||||
remaining -= item.duration_secs;
|
||||
result.push(item);
|
||||
result.push(*item);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Edge case: if the very first episode is longer than the entire block,
|
||||
// still include it — the slot builder clips it to block end via .min(end).
|
||||
if result.is_empty() {
|
||||
if let Some(&first) = ordered.first() {
|
||||
result.push(first);
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||
@@ -91,18 +90,15 @@ impl ScheduleEngineService {
|
||||
.map(|s| s.generation + 1)
|
||||
.unwrap_or(1);
|
||||
|
||||
// Build the initial per-block continuity map from the previous generation's
|
||||
// last slot per block. The map is updated as each block occurrence is resolved
|
||||
// within this generation so that the second day of a 48h schedule continues
|
||||
// from where the first day ended.
|
||||
let mut block_continuity: HashMap<BlockId, MediaItemId> = latest_schedule
|
||||
.iter()
|
||||
.flat_map(|s| &s.slots)
|
||||
.fold(HashMap::new(), |mut map, slot| {
|
||||
// keep only the *last* slot per block (slots are sorted ascending)
|
||||
map.insert(slot.source_block_id, slot.item.id.clone());
|
||||
map
|
||||
});
|
||||
// Build the initial per-block continuity map from the most recent slot per
|
||||
// block across ALL schedules. This is resilient to any single generation
|
||||
// having empty slots for a block (e.g. provider returned nothing transiently).
|
||||
// The map is updated as each block occurrence is resolved within this
|
||||
// generation so the second day of a 48h schedule continues from here.
|
||||
let mut block_continuity = self
|
||||
.schedule_repo
|
||||
.find_last_slot_per_block(channel_id)
|
||||
.await?;
|
||||
|
||||
let valid_from = from;
|
||||
let valid_until = from + Duration::hours(48);
|
||||
|
||||
Reference in New Issue
Block a user