feat: full backend (untested)

This commit is contained in:
Nik L
2026-01-14 11:37:44 -05:00
parent 4a0db63a30
commit d585cf8613
32 changed files with 1317 additions and 57 deletions

View File

@@ -0,0 +1,105 @@
from datetime import datetime, timedelta, timezone
from uuid import UUID
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import BusyBlock, Participant
def get_week_boundaries(reference_date: datetime | None = None) -> tuple[datetime, datetime]:
if reference_date is None:
reference_date = datetime.now(timezone.utc)
days_since_monday = reference_date.weekday()
monday = reference_date - timedelta(days=days_since_monday)
monday = monday.replace(hour=0, minute=0, second=0, microsecond=0)
friday = monday + timedelta(days=4)
friday = friday.replace(hour=23, minute=59, second=59, microsecond=999999)
return monday, friday
async def get_busy_blocks_for_participants(
db: AsyncSession,
participant_ids: list[UUID],
start_time: datetime,
end_time: datetime,
) -> dict[UUID, list[tuple[datetime, datetime]]]:
stmt = select(BusyBlock).where(
BusyBlock.participant_id.in_(participant_ids),
BusyBlock.start_time < end_time,
BusyBlock.end_time > start_time,
)
result = await db.execute(stmt)
blocks = result.scalars().all()
busy_map: dict[UUID, list[tuple[datetime, datetime]]] = {
pid: [] for pid in participant_ids
}
for block in blocks:
busy_map[block.participant_id].append((block.start_time, block.end_time))
return busy_map
def is_participant_free(
busy_blocks: list[tuple[datetime, datetime]],
slot_start: datetime,
slot_end: datetime,
) -> bool:
for block_start, block_end in busy_blocks:
if block_start < slot_end and block_end > slot_start:
return False
return True
async def calculate_availability(
db: AsyncSession,
participant_ids: list[UUID],
reference_date: datetime | None = None,
) -> list[dict]:
week_start, week_end = get_week_boundaries(reference_date)
busy_map = await get_busy_blocks_for_participants(
db, participant_ids, week_start, week_end
)
participants_stmt = select(Participant).where(Participant.id.in_(participant_ids))
participants_result = await db.execute(participants_stmt)
participants = {p.id: p for p in participants_result.scalars().all()}
days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
hours = list(range(9, 18))
slots = []
for day_offset, day_name in enumerate(days):
for hour in hours:
slot_start = week_start + timedelta(days=day_offset, hours=hour)
slot_end = slot_start + timedelta(hours=1)
available_participants = []
for pid in participant_ids:
if is_participant_free(busy_map.get(pid, []), slot_start, slot_end):
participant = participants.get(pid)
if participant:
available_participants.append(participant.name)
total = len(participant_ids)
available_count = len(available_participants)
if available_count == total:
availability = "full"
elif available_count > 0:
availability = "partial"
else:
availability = "none"
slots.append({
"day": day_name,
"hour": hour,
"availability": availability,
"availableParticipants": available_participants,
})
return slots