app.seed

Database seed module.

Populates the database with rooms and time slots for February, March, and April 2026. Each day gets a random subset of rooms, and each room gets hourly time slots from 08:00 to 18:00.

This module is idempotent — it skips seeding if rooms already exist.

  1"""
  2Database seed module.
  3
  4Populates the database with rooms and time slots for February, March,
  5and April 2026. Each day gets a random subset of rooms, and each room
  6gets hourly time slots from 08:00 to 18:00.
  7
  8This module is idempotent — it skips seeding if rooms already exist.
  9"""
 10
 11import random
 12from datetime import date, time, timedelta
 13
 14from loguru import logger
 15from sqlmodel import select
 16from sqlmodel.ext.asyncio.session import AsyncSession
 17
 18from app.database import engine
 19from app.models.booking import TimeSlot
 20from app.models.room import Room
 21
 22ROOMS = [
 23    {"name": "A-101", "capacity": 10},
 24    {"name": "A-102", "capacity": 15},
 25    {"name": "A-201", "capacity": 20},
 26    {"name": "A-202", "capacity": 8},
 27    {"name": "A-203", "capacity": 30},
 28    {"name": "B-101", "capacity": 12},
 29    {"name": "B-102", "capacity": 25},
 30    {"name": "B-201", "capacity": 6},
 31    {"name": "B-301", "capacity": 40},
 32    {"name": "C-101", "capacity": 18},
 33]
 34
 35# Hourly slots from 08:00 to 18:00
 36SLOT_HOURS = [(time(h, 0), time(h + 1, 0)) for h in range(8, 18)]
 37
 38# Months to seed: (year, month)
 39SEED_MONTHS = [(2026, 2), (2026, 3), (2026, 4)]
 40
 41# Deterministic seed so the data is reproducible
 42RNG_SEED = 2026
 43
 44
 45# ── Helpers ────────────────────────────────────────────────────────────────
 46
 47
 48def _days_in_month(year: int, month: int) -> list[date]:
 49    """Return all dates in a given month."""
 50    first = date(year, month, 1)
 51    if month == 12:
 52        last = date(year + 1, 1, 1)
 53    else:
 54        last = date(year, month + 1, 1)
 55    return [first + timedelta(days=i) for i in range((last - first).days)]
 56
 57
 58# ── Main seed function ─────────────────────────────────────────────────────
 59
 60
 61async def seed_rooms_and_slots() -> None:
 62    """
 63    Populate the database with rooms and time slots.
 64
 65    Idempotent: if any rooms already exist, the function returns early.
 66    """
 67    async with AsyncSession(engine) as session:
 68        # Check if rooms already exist
 69        existing = (await session.exec(select(Room).limit(1))).first()
 70        if existing:
 71            logger.info("Rooms already seeded — skipping.")
 72            return
 73
 74        rng = random.Random(RNG_SEED)
 75
 76        # 1. Create all rooms
 77        room_objects: list[Room] = []
 78        for room_data in ROOMS:
 79            room = Room(**room_data)
 80            session.add(room)
 81            room_objects.append(room)
 82
 83        await session.flush()  # assigns IDs
 84
 85        # 2. For each day in each month, pick a random subset of rooms
 86        #    and create hourly time slots for them.
 87        all_slots: list[TimeSlot] = []
 88
 89        for year, month in SEED_MONTHS:
 90            for day in _days_in_month(year, month):
 91                # Pick 1–len(rooms) rooms available this day
 92                num_rooms = rng.randint(1, len(room_objects))
 93                day_rooms = rng.sample(room_objects, num_rooms)
 94
 95                for room in day_rooms:
 96                    for start, end in SLOT_HOURS:
 97                        slot = TimeSlot(
 98                            room_id=room.id,  # type: ignore[arg-type]
 99                            slot_date=day,
100                            start_time=start,
101                            end_time=end,
102                        )
103                        all_slots.append(slot)
104
105        session.add_all(all_slots)
106        await session.commit()
107
108        logger.info(
109            f"Seeded {len(room_objects)} rooms and {len(all_slots)} time slots "
110            f"across {len(SEED_MONTHS)} months."
111        )
ROOMS = [{'name': 'A-101', 'capacity': 10}, {'name': 'A-102', 'capacity': 15}, {'name': 'A-201', 'capacity': 20}, {'name': 'A-202', 'capacity': 8}, {'name': 'A-203', 'capacity': 30}, {'name': 'B-101', 'capacity': 12}, {'name': 'B-102', 'capacity': 25}, {'name': 'B-201', 'capacity': 6}, {'name': 'B-301', 'capacity': 40}, {'name': 'C-101', 'capacity': 18}]
SLOT_HOURS = [(datetime.time(8, 0), datetime.time(9, 0)), (datetime.time(9, 0), datetime.time(10, 0)), (datetime.time(10, 0), datetime.time(11, 0)), (datetime.time(11, 0), datetime.time(12, 0)), (datetime.time(12, 0), datetime.time(13, 0)), (datetime.time(13, 0), datetime.time(14, 0)), (datetime.time(14, 0), datetime.time(15, 0)), (datetime.time(15, 0), datetime.time(16, 0)), (datetime.time(16, 0), datetime.time(17, 0)), (datetime.time(17, 0), datetime.time(18, 0))]
SEED_MONTHS = [(2026, 2), (2026, 3), (2026, 4)]
RNG_SEED = 2026
async def seed_rooms_and_slots() -> None:
 62async def seed_rooms_and_slots() -> None:
 63    """
 64    Populate the database with rooms and time slots.
 65
 66    Idempotent: if any rooms already exist, the function returns early.
 67    """
 68    async with AsyncSession(engine) as session:
 69        # Check if rooms already exist
 70        existing = (await session.exec(select(Room).limit(1))).first()
 71        if existing:
 72            logger.info("Rooms already seeded — skipping.")
 73            return
 74
 75        rng = random.Random(RNG_SEED)
 76
 77        # 1. Create all rooms
 78        room_objects: list[Room] = []
 79        for room_data in ROOMS:
 80            room = Room(**room_data)
 81            session.add(room)
 82            room_objects.append(room)
 83
 84        await session.flush()  # assigns IDs
 85
 86        # 2. For each day in each month, pick a random subset of rooms
 87        #    and create hourly time slots for them.
 88        all_slots: list[TimeSlot] = []
 89
 90        for year, month in SEED_MONTHS:
 91            for day in _days_in_month(year, month):
 92                # Pick 1–len(rooms) rooms available this day
 93                num_rooms = rng.randint(1, len(room_objects))
 94                day_rooms = rng.sample(room_objects, num_rooms)
 95
 96                for room in day_rooms:
 97                    for start, end in SLOT_HOURS:
 98                        slot = TimeSlot(
 99                            room_id=room.id,  # type: ignore[arg-type]
100                            slot_date=day,
101                            start_time=start,
102                            end_time=end,
103                        )
104                        all_slots.append(slot)
105
106        session.add_all(all_slots)
107        await session.commit()
108
109        logger.info(
110            f"Seeded {len(room_objects)} rooms and {len(all_slots)} time slots "
111            f"across {len(SEED_MONTHS)} months."
112        )

Populate the database with rooms and time slots.

Idempotent: if any rooms already exist, the function returns early.