High Horizon Current Implementation Logic Analysis

This is an Obsidian operations/development reference document summarizing the logic actually implemented in the current code of /home/kitae/high-horizon/english-online-class. It is based only on the state confirmed in the code, not on product descriptions or plans. Secret files such as .env were not viewed according to instructions.

1. One-Line Summary

High Horizon is an online IELTS English tutoring server based on Django 5.1. The current implementation consists of Google-only login, a post-login consent gate, teacher profiles/search, 1:1 and group booking, Google Calendar/Meet integration scaffolding, lesson reports/reviews, course packages and partial refunds, payment scaffolding, announcements, and legal pages.

Key evidence:

2. Runtime/Deployment Structure

2.1 Server Stack

Evidence:

2.2 Security/Environment Defaults

Evidence:

3. URL/App Boundaries

3.1 Fixed Paths

Evidence:

3.2 User Paths With Language Prefix

Because of i18n_patterns(prefix_default_language=True), user-facing paths take the /ko/... and /en/... forms.

Evidence:

4. Full Domain Model Map

erDiagram
  User ||--o| TeacherProfile : has
  User ||--o{ TeacherApplication : submits
  User ||--o{ ConsentRecord : grants
  User ||--o{ AvailabilitySlot : teaches
  User ||--o{ Booking : books
  User ||--o{ CoursePackage : owns

  TeacherProfile }o--|| User : profile_user
  AvailabilitySlot ||--o{ Booking : contains
  Booking ||--o| Review : reviewed_by_student
  Booking ||--o| LessonReport : reported_by_teacher
  Booking ||--o{ MakeupCredit : source
  MakeupCredit ||--o| Booking : redeemed_booking

  CoursePackage ||--o{ CoursePackageSession : has
  CoursePackageSession ||--o| AvailabilitySlot : creates_slot
  CoursePackage ||--o{ Enrollment : enrolls
  Enrollment ||--o{ Booking : creates_confirmed_bookings

  Payment }o--o| Booking : single_booking_target
  Payment }o--o| Enrollment : package_target
  Payment ||--o{ Refund : refunds
  Promotion ||--o{ Payment : discounts

Important constraints:

Evidence:

5.1 User Roles

accounts.User is a custom user model with student, teacher, and staff roles. New Google signups are students by the model default, and teacher/staff authority is a structure where operations promotes the user.

User fields:

Evidence:

5.2 Google-Only Login

Evidence:

5.3 Post-Login Consent Gate

Login itself and consent collection are separated. ConsentGateMiddleware redirects authenticated users to /consent/ if they do not have the latest required consent versions.

Required consent:

Optional consent:

The current versions are all v1.0. Consent records are one row per (user, doc_type), and the latest decision is overwritten with update_or_create.

Evidence:

5.4 Development Auto-Login

Works only when DEV_AUTOLOGIN=True and Google OAuth credentials are absent. The demo role can be changed with ?as=teacher or ?as=student, and required consent is automatically granted to the demo user. Because it bypasses real authentication, this is a development convenience feature that must not be enabled for production data.

Evidence:

5.5 Timezone/Language Middleware

Evidence:

6. Teacher Profile, Teacher Search, Application

6.1 Teacher Profile

TeacherProfile is the teacher's public profile. Main fields are headline, bio, experience, languages, country, photo URL, intro video URL, external profile URL, and public visibility.

External URLs are used only as supporting trust signals, and model comments and tests exist so they are displayed near the intro card rather than near booking/payment CTAs.

Evidence:

teacher_list shows only teachers with public profiles and actual supply. Supply is one of the following.

Filters:

Sorting:

Evidence:

6.3 Teacher Detail

Teacher detail only looks up users with the teacher role. If the profile is not public, everyone except the owner gets 404. The detail includes the following.

Evidence:

6.4 Teacher Application/Review

Logged-in users can submit teacher applications, and duplicate submission is blocked when there is a pending application. Staff or superusers approve/reject applications. On approval, the applicant role is changed to teacher and a TeacherProfile is created.

Evidence:

7. Booking Logic

7.1 Booking Model

AvailabilitySlot is a time slot opened by a teacher.

Booking states:

Booking.is_active is true only for requested/confirmed.

Evidence:

7.2 Slot Creation/Deletion

Teachers create single availability times at /booking/availability/. AvailabilitySlotForm validates the following.

Deletion is allowed only for future slots that are not course sessions and have no bookings/requests.

Evidence:

7.3 Student Booking Request

request_booking locks the slot row with select_for_update() and checks the following.

1:1:

Group:

Evidence:

7.4 Teacher Approval/Decline

1:1 approval:

Decline:

Evidence:

7.5 Student Cancellation

Student cancellation is possible only for active bookings. If it was confirmed, booked_count is reduced by 1 and calendar sync is scheduled. Cancelling a requested booking does not touch seat count.

Evidence:

7.6 Public Slot Browsing

browse_slots gathers future, not-full, non-course-session slots from all teachers and shows them in the calendar UI. For logged-in users, their own slots are excluded. If the selected date is invalid, it falls back to the earliest available date.

Evidence:

7.7 Wanted Time Request

Users can submit a TimeRequest when there are no open slots or no desired time. This request stores only whether it has been processed, and automatic slot creation is not implemented.

Evidence:

8. Google Calendar/Meet Integration

Calendar/Meet is designed as a central Workspace service account rather than per-user OAuth.

Activation conditions:

Behavior:

Evidence:

9. Email/Resend Notifications

Booking event email is implemented only for web/email. There is no channel such as Kakao/Alimtalk.

Notification types:

Send failures are only caught/logged so they do not break booking state transitions. The default email backend is console, and if RESEND_API_KEY exists, it switches to core.email.ResendBackend.

Evidence:

10. Teacher Cancellation and Make-Up Credit

10.1 Make-Up Credit Model

MakeupCredit is a same-teacher make-up credit issued to a student when a confirmed future session is cancelled for a teacher-side reason.

Statuses:

Default expiry is 30 days after creation. is_redeemable is true when active and expires_at > now.

Evidence:

10.2 Teacher Cancellation

When a teacher cancels a confirmed future booking:

It is rejected if the booking is not confirmed or the lesson has already started.

Evidence:

10.3 Make-Up Credit Use

Using a make-up credit must satisfy all of the following conditions.

On success, a confirmed booking is created, booked_count += 1, and the make-up credit becomes redeemed.

Evidence:

11. Reviews and Lesson Reports

11.1 Student Reviews

Only the student of a confirmed booking can write a review, and only after the slot start time has passed. There is one review per booking. Teacher average rating and review count are aggregated in teacher_rating().

Evidence:

11.2 Teacher Lesson Reports

Teachers can create/edit reports for confirmed bookings they are responsible for. IELTS band fields are in the 0-9 range and only 0.5 increments are allowed.

Students can see reports and the latest overall band at /booking/progress/.

Evidence:

12. Course Packages

12.1 Package Model/Constraints

CoursePackage is a teacher-owned course product.

Main constraints:

Recurrence patterns:

Evidence:

12.2 Price Calculation

Package price is not entered manually and is calculated from platform-wide PricingConfig.

Evidence:

12.3 Session/Slot Creation

generate_sessions() converts the package's local date/time and timezone into UTC slots and creates CoursePackageSession and AvailabilitySlot.

Behavior/validation:

Evidence:

12.4 Package Creation/Edit/Close/Delete

Evidence:

12.5 Public Package List/Enrollment

The public list shows status=OPEN packages in start_date/start_time order, and logged-in users do not see their own packages.

Enrollment conditions:

On successful enrollment:

In the current code, course package enrollment is a flow where test payment is immediately completed regardless of PAYMENTS_ENABLED.

Evidence:

13. Payment

13.1 Payment Model

Payment is connected to exactly one of a single booking or a package enrollment.

Statuses:

provider:

Additional fields:

Evidence:

13.2 Single Booking Payment Flow

If PAYMENTS_ENABLED=False, the start payment view does nothing and returns to My Bookings after a "preparing" message.

If enabled:

Evidence:

13.3 Live Payment Not-Implemented Boundary

Actual Toss/Stripe API calls are still NotImplementedError. If credentials are set and the provider becomes configured, the current implementation has no real checkout/verify and raises an exception. The README also states TODO(real) implementation as a go-live condition.

Evidence:

13.4 Promotion

Promotion supports percent/fixed discounts and all/single/package scopes. best_for() chooses the promotion that results in the lowest final amount among promotions matching the active period/scope.

Evidence:

14. Refund

14.1 Refund Model

Refund belongs to Payment and optionally connects to Enrollment. It stores refund basis, consumed session count, idempotency key, and provider ref.

Evidence:

14.2 Refund Quote

refund_quote() chooses the most favorable amount for the consumer among three methods.

Consumed sessions are calculated as the number of confirmed bookings within at + 24 hours. In other words, confirmed lessons that have already passed or are within 24 hours are treated as consumed for refund calculation.

Evidence:

14.3 Refund Execution

refund_enrollment() uses the idempotency key enrollment:{id}:refund:v1. If a completed refund already exists, it returns it as-is.

On execution:

Evidence:

15.1 Announcement

Announcement exposure has Announcement.active() as the single decision point.

Exposure conditions:

The global banner context processor and announcement list view use the same active query.

Evidence:

terms/privacy/refund only render templates. They are consent gate exception paths and can be accessed before consent.

Evidence:

16. Admin Operations Screen

Core objects managed in Admin:

PricingConfig is restricted like a singleton for add, and deletion is prohibited.

Evidence:

17. Test Coverage Map

Current tests directly cover the following logic.

Evidence:

18. Current Not-Implemented/Caution Boundaries

Clear remaining boundaries based on the code:

Evidence:

19. File Entry Points When Changing

When changing booking policy:

When changing package/refund policy:

When changing pricing/promotion/payment policy:

When changing login/consent/permission policy:

When changing teacher visibility/search/application workflow:

When changing announcements/legal/landing:

20. State Transition Summary

20.1 Single 1:1 Booking

stateDiagram-v2
  [*] --> requested: student request
  requested --> confirmed: teacher approve
  requested --> declined: teacher decline or competitor loses
  requested --> cancelled: student cancel
  confirmed --> cancelled: student cancel
  confirmed --> teacher_cancelled: teacher future cancel
  teacher_cancelled --> [*]: makeup credit issued

20.2 Group Booking

stateDiagram-v2
  [*] --> confirmed: student request, auto-confirm
  confirmed --> cancelled: student cancel
  confirmed --> teacher_cancelled: teacher future cancel

20.3 Course Package Enrollment

stateDiagram-v2
  [*] --> pending: enroll starts
  pending --> active: confirmed bookings + stub paid payment
  active --> partially_refunded: partial refund
  active --> refunded: full refund
  partially_refunded --> [*]
  refunded --> [*]

21. What Operators Should Especially Remember