3. Schedule — RoomSlot·CardioSlot·MentorBlock·FixedSlot
슬롯 스케줄링 시스템. 4B 세션 포맷, 5A 예약 정책 반영.
RoomSlot
Purpose: 방 60분 단위 슬롯. 30분 stagger (정각 4방 + 30분 4방). Related PRDs: 🏢 예약 시스템 · 🏢 세션 시스템 Lifecycle: cron 생성 → available → reserved → completed
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() | PK |
| roomId | String | ✓ | - | FK → Room |
| storeId | String | ✓ | - | FK → Store (denorm) |
| startAt | DateTime | ✓ | - | 60분 슬롯 시작 (정각/30분) |
| status | SlotStatus | ✓ | available | available/reserved/completed |
| reservedByMemberId | String? | - | null | 예약 잡힌 회원 |
Validation
- (roomId, startAt) unique
- startAt 분 = 0 또는 30 (stagger 강제)
- startAt > now (과거 슬롯 신규 생성 ❌)
State Transitions
stateDiagram-v2
[*] --> available: cron 생성
available --> reserved: 회원 예약
reserved --> available: 취소 (48h+ 전)
reserved --> completed: 세션 완료
available --> [*]: 시간 경과 (자동 archive)
Indexes
[roomId, startAt](unique)[startAt, status]— 가용 슬롯 조회
Common Queries
- 가용 슬롯:
WHERE status='available' AND startAt > NOW() - 회원 다음 예약:
WHERE reservedByMemberId=? AND startAt > NOW() ORDER BY startAt
Edge Cases
- 방 maintenance 전환 시 → 미래 available 슬롯 자동 cancel
- 슬롯 생성 cron: 매일 자정에 14일 후 슬롯 추가
CardioSlot
Purpose: 카디오 자리 30분 단위 슬롯. Related PRDs: 같음 Lifecycle: 동일 (available → reserved → completed)
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() | PK |
| seatId | String | ✓ | - | FK → CardioSeat |
| storeId | String | ✓ | - | FK (denorm) |
| startAt | DateTime | ✓ | - | 30분 슬롯 시작 |
| status | SlotStatus | ✓ | available | |
| reservedByMemberId | String? | - | null |
Validation
- (seatId, startAt) unique
- 분 = 0 또는 30
- RoomSlot.startAt = CardioSlot.startAt + 30분 (회원 예약 시 연속 점유 필수)
Indexes
[seatId, startAt](unique)[startAt, status]— 가용 카디오 자리 조회
Common Queries
- 특정 시각 카디오 자리 가용 수:
COUNT WHERE startAt=? AND status='available'
Edge Cases
- 카디오 기계 고장 (CardioSeat.status=maintenance): 그 자리 미래 슬롯 자동 cancel
- 카디오 부족 시 (6 자리 만석): 회원 예약 자동 매칭 시 거절
MentorBlock
Purpose: 멘토가 직접 오픈하는 30분 단위 가능 시간. 1시간 = 2 블록 = 2 회원 cover. Related PRDs: 💪 슬롯 오픈 · 🏢 예약 시스템 Lifecycle: 멘토 open → assigned → completed (또는 cancelled)
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() | PK |
| mentorId | String | ✓ | - | FK |
| startAt | DateTime | ✓ | - | 30분 단위 |
| status | BlockStatus | ✓ | open | open/assigned/completed/cancelled |
| assignedSessionId | String? | - | null | 매칭된 Session |
| assignedAt | DateTime? | - | null | |
| cancelledAt | DateTime? | - | null | |
| cancellationReason | String? | - | null | |
| penaltyApplied | Boolean | ✓ | false | 6h 이내 취소 시 true |
Validation
- (mentorId, startAt) unique
- 분 = 0 또는 30
- 취소 시 cancelledAt 기록 의무
- 6h 이내 취소 = penaltyApplied=true 강제
State Transitions
stateDiagram-v2
[*] --> open: 멘토 오픈
open --> assigned: 회원 매칭
assigned --> completed: 세션 완료
open --> cancelled: 멘토 닫기 (48h+)
assigned --> open: 회원 취소 (다시 매칭 대기)
assigned --> cancelled: 멘토 6h 이내 취소 (패널티)
Indexes
[mentorId, startAt](unique)[startAt, status]— 매칭 시 가능한 블록 검색 (status=open)[mentorId, status]— 멘토 일정 조회
Common Queries
- 매칭 가능 블록:
WHERE status='open' AND startAt > NOW() + 1h(당일 임박 매칭은 별도) - 멘토 이번 격주 매칭 수:
WHERE mentorId=? AND status='completed' AND startAt BETWEEN ?
Edge Cases
- 동일 시각 두 블록 (1시간 stagger cover) 동시 매칭 시도 → 시스템이 동시 할당
- 멘토 노쇼 (T+15 미체크인): status=completed → 정산에서 차감 + 회원 보상
- 멘토 6h 이내 취소: penaltyApplied=true → MentorPayout 차감 + 회원 보상
FixedSlot
Purpose: 회원의 매주 고정 슬롯 (피크 6-10pm). 매주 자동 예약. Related PRDs: 👤 예약 · 💪 슬롯 · 🏢 예약 시스템 Lifecycle: 회원 설정 → 매주 자동 매칭 → (선택) 변경 / 해제
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() | PK |
| memberId | String | ✓ | - | FK |
| weekday | Int | ✓ | - | 0-6 (월~일) |
| hour | Int | ✓ | - | 6-22 (운영 시간 내) |
| minute | Int | ✓ | - | 0 또는 30 |
| active | Boolean | ✓ | true | |
| lastMentorId | String? | - | null | 이전 멘토 (우선 매칭) |
| createdAt | DateTime | ✓ | now() | |
| lastChangedAt | DateTime? | - | null | 월 1회 변경 제한 |
Validation
- (memberId, weekday, hour, minute) unique (한 회원 = 1 슬롯)
- minute = 0 또는 30
- 피크 시간만 (hour 18-22) — Phase 1 가설
- 변경 = 월 1회 제한 (lastChangedAt + 30일 후 가능)
State Transitions
stateDiagram-v2
[*] --> active: 회원 신청
active --> inactive: 회원 해제 / 멤버십 종료
active --> active_changed: 회원 변경 (월 1회)
inactive --> [*]
Indexes
[memberId, weekday, hour, minute](unique)[active]— cron 매주 자동 예약 시 활성 슬롯 조회
Common Queries
- 활성 고정 슬롯:
WHERE active = true - 다음 주 자동 예약 대상:
WHERE active=true AND ... cron job
Edge Cases
- 이전 멘토(lastMentorId) 다음 주 슬롯 안 오픈 → AI 다른 멘토 매칭 + 회원 알림
- 회원 일시정지 → active=false (재개 시 true)
- 멤버십 만료 → active=false 자동 전환
- 동일 시각 다른 회원의 고정 슬롯과 충돌 시 — 방 8개 stagger 운영으로 동시 가능 (방 매칭 알고리즘이 처리)
📘 사용 PRD
👤 세션 진행 · 👤 예약 · 💪 세션 진행 · 💪 슬롯 · 🏢 세션 시스템 · 🏢 예약 시스템
| 2026-05-13 | 초안 — Schedule 4 모델 상세 명세 |