Scheduling & Slots
Availability System
Section titled “Availability System”Teachers define their availability using multiple layers:
Named Schedules
Section titled “Named Schedules”availability_schedules — Named weekly sets (e.g., “Summer”, “Winter”). Each teacher can have multiple schedules with isDefault and isManuallyActive flags.
Weekly Rules
Section titled “Weekly Rules”availability_rules — Recurring time slots per day of week. Linked to a specific schedule. Times stored in teacher’s timezone.
Overrides
Section titled “Overrides”availability_overrides — One-off modifications:
- time_off: Block specific dates/times
- extra_availability: Add one-time availability
Activations
Section titled “Activations”schedule_activations — Date-range activations for schedules. The slot engine resolves which schedule applies for each date.
Slot Engine Algorithm
Section titled “Slot Engine Algorithm”The slot engine calculates available booking slots using 10 steps:
Inputs
Section titled “Inputs”- Active schedules + rules (resolved via activations)
- Overrides (time off, extra availability)
services.sessionDurationMinutesteacher.bufferMinutes— Gap between sessionsteacher.minNoticeHours— Minimum advance booking- Google Calendar Free/Busy — Busy blocks
- Existing sessions in DB (scheduled/hold)
viewer_timezone— Student’s IANA timezonepreferredHours— Student preferences (optional)
Algorithm
Section titled “Algorithm”1. Resolve which schedule applies for each date via activations2. Load rules for active schedules + overrides3. Generate candidate slots (teacher TZ → UTC)4. Filter: remove slots where start < now + minNoticeHours5. Fetch Google Calendar Free/Busy6. Fetch existing DB sessions7. Merge busy blocks (Google + DB + buffer)8. Remove overlapping slots9. Convert to viewer timezone10. Mark isPreferred based on student's preferred hours11. Return: [{ startUtc, endUtc, startLocal, endLocal, isPreferred }]Student Preferred Hours
Section titled “Student Preferred Hours”Students can set preferred booking times:
- Time range: e.g., 09:00 - 18:00
- Days: Optional weekday filter (ISO 1-7)
- Stored in
students.preferredStartTime/preferredEndTime/preferredDays - Slots matching preferences get
isPreferred: true - All slots remain available — preferences are visual guidance only
Session Lifecycle
Section titled “Session Lifecycle”Status Workflow
Section titled “Status Workflow”hold → pending_confirmation → scheduled → completed → cancelled_by_student → cancelled_by_teacher → cancelled_system → no_show_student → no_show_teacher → rescheduled → pending_reviewAuto-Complete
Section titled “Auto-Complete”BullMQ job scheduled at session.endsAt + gracePeriod:
- AUTO_COMPLETE mode: Session → completed, credit CONSUME
- PENDING_REVIEW mode: Session → pending_review, notify teacher
Teacher can override: COMPLETED / NO_SHOW_STUDENT / NO_SHOW_TEACHER / CANCELED
Cancellation Policy
Section titled “Cancellation Policy”The Policy Engine evaluates cancellation rules:
PolicyEngine.evaluateCancellation(): → Resolves policy: service-specific → teacher default → allow all → Actions: full_refund, partial_penalty, forfeit, block, allow_freeStudents get preview endpoints for dry-run evaluation before cancel/reschedule.
Session Satellites
Section titled “Session Satellites”class_sessions is decomposed into a core table + 3 satellite tables:
| Satellite | Fields |
|---|---|
session_content | summary, teacherNotes, studentMood, rating, templateId |
session_payment | stripeCheckoutSessionId, amountPaid, currency |
session_calendar_sync | googleCalendarEventId, meetingUrl, syncStatus |
Access via Drizzle with: clause. Use upsertSessionContent() and upsertSessionCalendarSync() helpers.
Group Sessions
Section titled “Group Sessions”When services.maxParticipants > 1:
class_sessions.studentIdis null- Each participant tracked in
session_participantsjunction table - Independent credit tracking per student
- Per-student mood feedback