Skip to content

Scheduling & Slots

Teachers define their availability using multiple layers:

availability_schedules — Named weekly sets (e.g., “Summer”, “Winter”). Each teacher can have multiple schedules with isDefault and isManuallyActive flags.

availability_rules — Recurring time slots per day of week. Linked to a specific schedule. Times stored in teacher’s timezone.

availability_overrides — One-off modifications:

  • time_off: Block specific dates/times
  • extra_availability: Add one-time availability

schedule_activations — Date-range activations for schedules. The slot engine resolves which schedule applies for each date.

The slot engine calculates available booking slots using 10 steps:

  1. Active schedules + rules (resolved via activations)
  2. Overrides (time off, extra availability)
  3. services.sessionDurationMinutes
  4. teacher.bufferMinutes — Gap between sessions
  5. teacher.minNoticeHours — Minimum advance booking
  6. Google Calendar Free/Busy — Busy blocks
  7. Existing sessions in DB (scheduled/hold)
  8. viewer_timezone — Student’s IANA timezone
  9. preferredHours — Student preferences (optional)
1. Resolve which schedule applies for each date via activations
2. Load rules for active schedules + overrides
3. Generate candidate slots (teacher TZ → UTC)
4. Filter: remove slots where start < now + minNoticeHours
5. Fetch Google Calendar Free/Busy
6. Fetch existing DB sessions
7. Merge busy blocks (Google + DB + buffer)
8. Remove overlapping slots
9. Convert to viewer timezone
10. Mark isPreferred based on student's preferred hours
11. Return: [{ startUtc, endUtc, startLocal, endLocal, isPreferred }]

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
hold → pending_confirmation → scheduled → completed
→ cancelled_by_student
→ cancelled_by_teacher
→ cancelled_system
→ no_show_student
→ no_show_teacher
→ rescheduled
→ pending_review

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

The Policy Engine evaluates cancellation rules:

PolicyEngine.evaluateCancellation():
→ Resolves policy: service-specific → teacher default → allow all
→ Actions: full_refund, partial_penalty, forfeit, block, allow_free

Students get preview endpoints for dry-run evaluation before cancel/reschedule.

class_sessions is decomposed into a core table + 3 satellite tables:

SatelliteFields
session_contentsummary, teacherNotes, studentMood, rating, templateId
session_paymentstripeCheckoutSessionId, amountPaid, currency
session_calendar_syncgoogleCalendarEventId, meetingUrl, syncStatus

Access via Drizzle with: clause. Use upsertSessionContent() and upsertSessionCalendarSync() helpers.

When services.maxParticipants > 1:

  • class_sessions.studentId is null
  • Each participant tracked in session_participants junction table
  • Independent credit tracking per student
  • Per-student mood feedback