feat(main): initial commit
This commit is contained in:
0
core/services/__init__.py
Normal file
0
core/services/__init__.py
Normal file
108
core/services/scheduler.py
Normal file
108
core/services/scheduler.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from datetime import datetime, timedelta, date, time, timezone
|
||||
from core.models import Channel, ScheduleTemplate, ScheduleBlock, Airing, MediaItem
|
||||
import random
|
||||
|
||||
class ScheduleGenerator:
|
||||
"""
|
||||
A service that reads the latest ScheduleTemplate and Blocks for a given channel
|
||||
and generates concrete Airings logic based on available matching MediaItems.
|
||||
"""
|
||||
|
||||
def __init__(self, channel: Channel):
|
||||
self.channel = channel
|
||||
|
||||
def generate_for_date(self, target_date: date) -> int:
|
||||
"""
|
||||
Idempotent generation of airings for a specific date on this channel.
|
||||
Returns the number of new Airings created.
|
||||
"""
|
||||
# 1. Get the highest priority active template valid on this date
|
||||
template = ScheduleTemplate.objects.filter(
|
||||
channel=self.channel,
|
||||
is_active=True
|
||||
).filter(
|
||||
# Start date is null or <= target_date
|
||||
valid_from_date__isnull=True
|
||||
).order_by('-priority').first()
|
||||
|
||||
# In a real app we'd construct complex Q objects for the valid dates,
|
||||
# but for PYTV mock we will just grab the highest priority active template.
|
||||
if not template:
|
||||
template = ScheduleTemplate.objects.filter(channel=self.channel, is_active=True).order_by('-priority').first()
|
||||
if not template:
|
||||
return 0
|
||||
|
||||
# 2. Extract day of week mask
|
||||
# Python weekday: 0=Monday, 6=Sunday
|
||||
# Our mask: bit 0 = Monday, bit 6 = Sunday
|
||||
target_weekday_bit = 1 << target_date.weekday()
|
||||
|
||||
blocks = template.scheduleblock_set.all()
|
||||
airings_created = 0
|
||||
|
||||
for block in blocks:
|
||||
# Check if block runs on this day
|
||||
if not (block.day_of_week_mask & target_weekday_bit):
|
||||
continue
|
||||
|
||||
# Naive time combining mapping local time to UTC timeline without specific tz logic for simplicity now
|
||||
start_dt = datetime.combine(target_date, block.start_local_time, tzinfo=timezone.utc)
|
||||
end_dt = datetime.combine(target_date, block.end_local_time, tzinfo=timezone.utc)
|
||||
|
||||
# If the block wraps past midnight (e.g. 23:00 to 02:00)
|
||||
if end_dt <= start_dt:
|
||||
end_dt += timedelta(days=1)
|
||||
|
||||
# Clear existing airings in this window to allow idempotency
|
||||
Airing.objects.filter(
|
||||
channel=self.channel,
|
||||
starts_at__gte=start_dt,
|
||||
starts_at__lt=end_dt
|
||||
).delete()
|
||||
|
||||
# 3. Pull matching Media Items
|
||||
# Simplistic matching: pull items from library matching the block's genre
|
||||
items_query = MediaItem.objects.filter(media_source__library=self.channel.library)
|
||||
if block.default_genre:
|
||||
items_query = items_query.filter(genres=block.default_genre)
|
||||
|
||||
available_items = list(items_query.exclude(item_kind="bumper"))
|
||||
if not available_items:
|
||||
continue
|
||||
|
||||
# Shuffle randomly for basic scheduling variety
|
||||
random.shuffle(available_items)
|
||||
|
||||
# 4. Fill the block
|
||||
current_cursor = start_dt
|
||||
item_index = 0
|
||||
|
||||
while current_cursor < end_dt and item_index < len(available_items):
|
||||
item = available_items[item_index]
|
||||
duration = timedelta(seconds=item.runtime_seconds or 3600)
|
||||
|
||||
# Check if this item fits
|
||||
if current_cursor + duration > end_dt:
|
||||
# Item doesn't strictly fit, but we'll squeeze it in and break if needed
|
||||
# Real systems pad this out or trim the slot.
|
||||
pass
|
||||
|
||||
import uuid
|
||||
Airing.objects.create(
|
||||
channel=self.channel,
|
||||
schedule_template=template,
|
||||
schedule_block=block,
|
||||
media_item=item,
|
||||
starts_at=current_cursor,
|
||||
ends_at=current_cursor + duration,
|
||||
slot_kind="program",
|
||||
status="scheduled",
|
||||
source_reason="template",
|
||||
generation_batch_uuid=uuid.uuid4()
|
||||
)
|
||||
|
||||
current_cursor += duration
|
||||
item_index += 1
|
||||
airings_created += 1
|
||||
|
||||
return airings_created
|
||||
Reference in New Issue
Block a user