Skip to content

Data model

All data lives in a single Cloudflare D1 (SQLite) database. This page describes the entities in product terms — not every column, but what each thing is and how they connect.

The big picture

erDiagram
  USERS ||--o{ ENROLLMENTS : "joins"
  CLASSES ||--o{ ENROLLMENTS : "has"
  USERS ||--o| STUDENT_PROFILES : "has"
  USERS ||--o| TEACHER_PROFILES : "has"
  CLASSES ||--o{ SCHEDULE_SESSIONS : "generates"
  SCHEDULE_SESSIONS ||--o{ ATTENDANCE : "records"
  USERS ||--o{ TUITION : "owes"
  TUITION ||--o{ TUITION_PAYMENTS : "receives"
  CLASSES ||--o{ TUITION : "bills"
  USERS ||--o{ SKILL_ATTEMPTS : "makes"
  ASSIGNMENTS ||--o{ ASSIGNMENT_TARGETS : "targets"
  USERS ||--o{ ASSIGNMENT_TARGETS : "receives"

Entities by domain

People & structure

Entity What it is
Users Every account, with a role (student / teacher / admin) and an active flag.
Student profiles / Teacher profiles Extra detail — entry/current/target band and parent contact for students; specialties for teachers.
Classes A course: name, code, level, target band, capacity, tuition fee, weekly schedule, assigned teacher, status.
Enrollments Which student is in which class (active/dropped). Enrolling now also raises a tuition charge from the class fee.

Schedule & attendance

Entity What it is
Schedule sessions Individual class meetings, generated from a class's weekly schedule.
Attendance Per-student, per-session mark: present / late / absent / makeup, with who marked it.

Money (manual bookkeeping)

Entity What it is
Tuition A charge owed by a student (amount, paid amount, due date, status: unpaid / partial / paid / overdue). Overdue is derived from the due date at read time.
Tuition payments Each payment recorded against a charge; also writes a matching cashflow "income" row.
Cashflow The center's cash in/out ledger (income/expense with category), independent of any student.

Learning content & results

Entity What it is
Reading / Listening / Writing / Speaking tests The question bank content per skill, each publishable or draft.
Materials Syllabus, slides, handouts with inline content and visibility.
Skill attempts Every student submission across the 4 skills — answers, band, status, feedback, and behavior signals, all in one row.
Assignments / Assignment targets A homework assignment and its per-student progress (status, band, what's done).
Content levels / Student levels The 8-level gating system.
Student activity A per-student feed of everything they did, powering the profile timeline.

AI & settings

Entity What it is
AI usage Every metered AI call (feature, units) — the basis for the monthly budget caps and the manager's usage report.
AI tutor messages Saved tutor conversations.
App settings Center-wide toggles: AI Tutor on/off, pronunciation cap, etc.

Two design choices worth knowing

Behavior rides inside the attempt

Integrity and behavior signals (time, tab-switches, paste attempts, audio play/seek) are stored inside the attempt's feedback, not in a separate table — so adding richer tracking needed no schema change.

Overdue is computed, not stored

Tuition "overdue" status is derived from the due date when the ledger is read (in the center's local time), so it's always current without a nightly job. Charges are stored as unpaid / partial / paid.

Indexes & integrity

Secondary indexes cover the hot query paths (sessions by class and date, enrollments and attendance by student, attempts by user and test) so lists stay fast as history grows. D1 does not enforce foreign keys at runtime, so referential cleanup (e.g. removing a test's satellite rows) is handled in application code and migrations.

Backups

D1 provides point-in-time recovery (Time Travel), so the database can be restored to an earlier moment — the practical safety net against a bad bulk operation. See Deploy & runbook.