Техническое задание: KidsConservatory

Статус: целевая спецификация версии 1.0 с поэтапной реализацией, синхронизированная с учебной программой
Формат проекта: Django-монолит
Языки продукта: русский и английский
Назначение: единый источник требований для разработки в Codex.


0. Обязательные правила

  1. Этот документ является основным источником требований.
  2. При противоречии между кодом и ТЗ приоритет имеет ТЗ, если изменение не зафиксировано отдельно.
  3. На первом этапе не использовать: - Docker; - Redis; - Celery; - Cloudflare R2; - микросервисы; - SPA-фреймворки; - автоматическое фоновое транскодирование видео; - автоматические выплаты преподавателям.
  4. Интерфейс строится на Django templates и небольшом количестве обычного JavaScript.
  5. Все денежные суммы хранятся без float: amount_minor + ISO-код валюты.
  6. Все даты и время хранятся как timezone-aware значения в UTC.
  7. Новые зависимости добавляются только при доказанной необходимости.
  8. Реализация выполняется вертикальными срезами: модели → сервисы → формы/представления → шаблоны → тесты.
  9. Простые и явные решения предпочтительнее преждевременной универсальности.
  10. После принятия нового продуктового решения этот документ обновляется.
  11. program.md является источником педагогических правил и результатов обучения.
  12. presentation.md является источником утверждённых продуктовых обещаний.
  13. Это ТЗ реализует программу и продуктовые обещания. Обнаруженное противоречие между тремя документами должно быть разрешено до реализации затронутой функции.
  14. Принятые ранее решения о бессрочном доступе, двух тарифах «Первой ступени», бесплатной демопрограмме и лимите проверок на курс заменены согласованными условиями этого документа: сопровождаемая «Первая ступень» с максимум 12 календарными месяцами, закрытием доступа после выпуска и недельным лимитом проверок на enrollment.

1. Описание продукта

KidsConservatory — двуязычная онлайн-музыкальная школа для детей.

Продукт объединяет:

  1. Публичный сайт школы.
  2. Индивидуальные онлайн-занятия преподаватель–ученик.
  3. Сопровождаемые авторские программы с последовательным открытием уроков и проверкой домашних видеоотчётов.

Основной образовательный продукт MVP — русскоязычная программа «Первая ступень»: 16 уроков, которые родитель или другой взрослый проходит вместе с ребёнком. Содержание и педагогические результаты программы не изменяются платформой.

Масштабирование строится через назначенных школой кураторов. Один куратор ведёт конкретного ребёнка в конкретной программе, а академический руководитель контролирует все проверки и соблюдение методики. Куратор «Первой ступени» не обязан быть преподавателем индивидуальных музыкальных занятий: это отдельная допущенная школой роль.

Школа не является маркетплейсом. Администратор контролирует:

На старте школа небольшая: около двух преподавателей. Архитектура должна быть простой, но допускать рост.


2. Цели MVP

MVP должен позволить:


3. Не входит в MVP

Новый сайт создаётся с нуля.


4. Роли и права

4.1. Родитель

Родитель является владельцем аккаунта.

В терминах MVP родитель — родитель или законный представитель, который отвечает за аккаунт и действия на платформе. Другой сопровождающий взрослый может участвовать в занятиях вместе с ребёнком, но не получает отдельный логин или делегированный доступ.

Может:

Не может:

4.2. Ученик

Ученик — профиль внутри аккаунта родителя, без отдельного логина в MVP.

Поля профиля:

4.3. Преподаватель и куратор

Преподаватель имеет User и TeacherProfile.

Куратор — сотрудник с отдельным CuratorProfile, допущенный к конкретной программе и языку. Он назначается на конкретный CourseEnrollment; наличие TeacherProfile для этого не требуется.

Может:

Не может:

После замены прежний куратор теряет рабочий доступ к enrollment и его сообщениям, но остаётся автором исторических проверок.

4.4. Академический руководитель

Роль реализуется через Django Group и явные permissions. Права не зашиваются в коде роли: наборы прав и их назначение сотрудникам настраиваются через Django Admin.

Может:

Не получает автоматически доступ к платежным данным, ценам и общим системным настройкам, но эти права могут быть выданы через Django Admin.

Основатель школы не зашивается в правила отдельных уроков и может постепенно перейти от роли куратора к роли академического руководителя.

4.5. Администратор

Администратор получает только назначенные ему права. Ни один из перечисленных ниже доступов не является неотзываемым: через Django Admin можно создавать группы, добавлять или снимать model/action permissions и назначать их конкретным сотрудникам.

При полном наборе прав администратор может:


5. Технологический стек

5.1. Использовать

5.2. Не использовать на первом этапе


6. Общая архитектура

flowchart TD
    B[Браузер] --> N[Nginx]
    N --> D[Django + Gunicorn]
    D --> P[(PostgreSQL)]
    D --> M[Локальное media-хранилище]
    D --> T[LiveKit token endpoint]
    B --> LK[LiveKit Server]
    LK --> D
    N --> M

Принципы:


7. Структура репозитория

kidsconservatory/
├── manage.py
├── pyproject.toml
├── README.md
├── .env.example
├── config/
│   ├── settings/
│   │   ├── base.py
│   │   ├── local.py
│   │   └── production.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── apps/
│   ├── accounts/
│   ├── school/
│   ├── scheduling/
│   ├── classrooms/
│   ├── courses/
│   ├── submissions/
│   ├── payments/
│   ├── content/
│   └── notifications/
├── templates/
├── static/
├── locale/
├── tests/
├── scripts/
├── deploy/
│   ├── nginx/
│   ├── systemd/
│   └── livekit/
└── doc/
    ├── KIDSCONSERVATORY_SPEC.md
    ├── presentation.md
    └── program.md

8. Двуязычность

Обязательные языки:

Публичные URL:

/ru/
/en/
/ru/teachers/
/en/teachers/
/ru/courses/
/en/courses/

Интерфейсные строки переводятся через Django gettext.

Контент из базы хранится в translation-моделях:

Один курс остаётся одной сущностью, а не двумя независимыми RU/EN-курсами.

Языковые версии публикуются независимо:

SEO создаётся с нуля:


9. Аккаунты и профили

9.1. Custom User

Создать до первой миграции, наследовать от AbstractUser.

Поля:

Правила:

9.2. ParentProfile

Поля:

9.3. StudentProfile

Поля:

Статусы:

not_requested
pending
approved
rejected
suspended

9.4. TeacherProfile

Поля:

Переводимые биографические поля находятся в TeacherProfileTranslation.

9.5. CuratorProfile

Профиль сотрудника, который сопровождает авторскую программу. Не требует TeacherProfile и публичной страницы.

Поля:

Допуск к конкретной программе и языку задаётся через CourseCuratorEligibility. Публичное описание «Первой ступени» представляет автора и методику; имена кураторов сообщаются семье при старте программы.

9.6. Семейное участие в программе

В одной семье 2–3 ребёнка могут проходить одну программу вместе с родителем. Для каждого ребёнка создаются отдельные StudentProfile, CourseEnrollment, отчёты, проверки, прогресс и выпуск.

FamilyCourseGroup объединяет связанные зачисления и хранит:

Первый ребёнок оплачивает базовую цену выбранного CoursePlan; за каждого следующего ребёнка с индивидуальной проверкой отчётов и обратной связью добавляется процент из snapshot. Для «Первой ступени» начальное значение — 20%. Процент настраивается в админке на уровне плана и не меняет уже созданные заказы. Семейная группа не создаёт общий прогресс и не подменяет отдельное сопровождение детей.


10. Предметы, услуги и цены

10.1. Subject

Поля:

Название переводится через SubjectTranslation.

10.2. TeacherOffering

Конкретная услуга преподавателя.

Поля:

Один преподаватель может иметь несколько услуг с разной длительностью и ценой.

При покупке цена, валюта и длительность копируются в заказ и бронирование.

10.3. LessonPackage

Пакет относится к конкретному TeacherOffering.

Поля:

Пакет занятий одной длительности нельзя использовать для другой длительности.


11. Заявка на индивидуальное обучение

11.1. IndividualLessonApplication

Поля:

Статусы:

pending
approved
rejected
needs_information

Правила:

11.2. StudentTeacherAssignment

Опциональная модель назначения:


12. Автоматический календарь

Администратор задаёт правила, система рассчитывает свободные слоты. Преподаватель расписание не редактирует.

12.1. TeacherAvailabilityRule

Поля:

12.2. TeacherScheduleException

Поля:

Типы:

blocked
available_override

12.3. Расчёт слотов

Сервис должен:

  1. Получить правила преподавателя.
  2. Построить интервалы в timezone правила.
  3. Перевести интервалы в UTC.
  4. Учесть длительность offering и buffers.
  5. Исключить пересечения с бронированиями.
  6. Исключить активные временные hold.
  7. Учесть исключения и регулярные серии.
  8. Учесть минимальный срок предварительного бронирования.
  9. Ограничить горизонт календаря.
  10. Вернуть время в timezone пользователя.

Слоты рассчитываются динамически и не сохраняются заранее.

12.4. Настройки календаря

В SchoolSettings:


13. Бронирования

13.1. LessonBooking

Поля:

Статусы:

hold
awaiting_payment
confirmed
in_progress
completed
cancelled_by_parent
cancelled_by_admin
cancelled_by_teacher
rescheduled
no_show
technical_issue
expired

История бронирований не удаляется физически без административной причины.

Источники:

single_purchase
package_credit
admin_manual
recurring_series

13.2. Защита от двойного бронирования

13.3. Временный hold

Для разового занятия:

Истёкшие hold освобождаются:

13.4. Отмена

До дедлайна:

После дедлайна:

При отмене преподавателем/администратором:

Дедлайн отмены и переноса действует одинаково для родителя и преподавателя: не позднее чем за 24 часа до начала занятия. Поздняя отмена преподавателем допускается только через административную операцию с обязательной причиной; родителю возвращается кредит или полный возврат.

13.5. Перенос

Перенос атомарно:

  1. проверяет дедлайн;
  2. проверяет новый слот;
  3. создаёт новое бронирование;
  4. помечает старое rescheduled;
  5. не списывает кредит повторно.

Опоздание и завершение занятия:


14. Регулярные занятия

14.1. RecurringLessonSeries

Создаёт и редактирует только администратор.

Поля:

Статусы:

draft
active
completed
cancelled

Правила:


15. Пакеты и баланс

15.1. LessonPackagePurchase

Поля:

Статусы:

active
used
expired
cancelled

15.2. LessonCreditTransaction

Поля:

Причины:

purchase
booking
cancellation_return
admin_adjustment
refund
expiration

Правила:


16. LiveKit-класс

16.1. Развёртывание

16.2. ClassroomSession

Один-к-одному с LessonBooking.

Поля:

Статусы:

scheduled
open
closed
failed

16.3. Token endpoint

POST /api/classrooms/<booking_uuid>/token/

Проверки:

Выдаётся короткоживущий JWT.

16.4. Музыкальный режим

Режим «Разговор»:

Режим «Музыка»:

16.5. Экран проверки

Перед уроком:

16.6. Webhooks

Сохранять события:

ClassroomAttendanceEvent:

Подпись webhook проверяется.

16.7. Резервная ссылка

Администратор может указать fallback_meeting_url на случай технического сбоя.


17. Курсы

17.1. Course

Поля:

Бесплатная демопрограмма не входит в MVP. До покупки родители знакомятся с программой через публичную презентацию автора и методики, видео о процессе обучения, примеры результатов и отзывы.

Для «Первой ступени» стандартный возраст — 5–9 лет. StudentProfile.birth_date обязателен для всех детей; возраст проверяется на дату активации. Администратор может сделать методически обоснованное исключение с причиной и аудитом.

После появления первого активного enrollment учебная структура этой версии курса замораживается:

17.2. CourseTranslation

Поля:

Статусы публикации:

draft
coming_soon
published

Уникальность:

Публикация проверяется отдельно для каждого языка. Статус coming_soon разрешает публичную маркетинговую страницу и форму интереса, но запрещает покупку, enrollment и доступ к учебному контенту. published разрешён только когда обязательные уроки, задания и media готовы.

17.3. CourseModule

Поля:

Переводы — CourseModuleTranslation.

17.4. CourseLesson

Поля:

Переводы — CourseLessonTranslation.

17.5. LessonMedia

Поля:

Это позволяет хранить разные видео для RU и EN внутри одного курса.

17.6. CourseAttachment

Поля:

17.7. LessonCuratorGuide

Внутренняя методическая карточка, не видимая семье.

Поля:

17.8. CourseCuratorEligibility

Ограничивает назначение кураторами, допущенными школой к конкретной программе и языку.

Поля:

Уникальность: course + curator + language.

Назначение разрешено только активному куратору с активной eligibility нужных course/language. Превышение max_active_enrollments запрещено без явного административного override с причиной и аудитом.

Покупка guided-языка доступна только при наличии хотя бы одного подходящего куратора со свободной capacity. Проверка выполняется повторно перед созданием заказа. Capacity резервируется на время Stripe Checkout, а куратор назначается в течение двух рабочих дней после подтверждения оплаты.

При подсчёте capacity учитываются активные enrollment и незавершённые CourseCapacityReservation.

17.9. CourseCapacityReservation

Резервирует одно место куратора на время оплаты сопровождаемой программы.

Поля:

Статусы:

checkout_hold
paid_pending_assignment
converted
expired
cancelled
refunded

Правила:

17.10. CourseInterest

Минимальная заявка со страницы coming_soon.

Поля:

Правила:

17.11. CourseCapacityWaitlist

Заявка семьи при отсутствии свободной capacity кураторов опубликованной сопровождаемой программы.

Поля:

Статусы:

waiting
contacted
converted
cancelled

Правила:


18. Планы участия

18.1. CoursePlan

Поля:

В MVP все продаваемые планы являются сопровождаемыми. Будущий самостоятельный или интерактивный план потребует отдельного решения и не реализуется скрытым параметром текущего курса.

План определяет:

Для «Первой ступени»:

Политика возврата временно фиксируется в плане и копируется в заказ: в течение 30 календарных дней после активации семья может запросить возврат; сумма рассчитывается администратором с учётом стоимости открытых/пройденных уроков и работы куратора. Автоматический расчёт и юридически окончательная формула не входят в MVP.


19. Зачисление и доступ

19.1. CourseEnrollment

Поля:

Исторических enrollment одного ребёнка на один курс может быть несколько.

Ограничения:

Источники:

purchase
admin_grant
promotion

Статусы доступа:

pending
active
expired
revoked

Статусы сопровождения:

pending_curator
active
expired
completed

Статусы прогресса:

not_started
in_progress
completed

Правила:

19.2. EnrollmentCuratorAssignment

Поля:

У enrollment только одно активное назначение.

Database constraint запрещает более одной строки для enrollment с ended_at IS NULL. CourseEnrollment.current_curator всегда совпадает с куратором активного назначения; оба значения меняются только одним транзакционным сервисом.

Замена выполняется сервисом в одной транзакции:

  1. закрывает прежнее назначение;
  2. создаёт новое;
  3. обновляет current_curator;
  4. передаёт незавершённые проверки новому куратору;
  5. не меняет review_due_at;
  6. сохраняет авторство старых ответов;
  7. закрывает прежнему куратору рабочий доступ;
  8. создаёт AuditLog;
  9. уведомляет родителя и нового куратора.

19.3. Синхронизация состояний

Команда:

python manage.py sync_course_enrollments

запускается systemd timer и является идемпотентной. Она:


20. Прогресс

20.1. CourseLessonProgress

Поля:

Статусы:

locked
available
in_progress
completed
passed

Правила:


21. Видеоотчёты

21.1. CourseAssignment

Поля:

title и instructions находятся в CourseAssignmentTranslation.

21.2. AssignmentSubmission

Поля:

Статусы:

draft
submitted
under_review
revision_requested
passed
closed

Уникальность: один AssignmentSubmission на enrollment + assignment.

Одновременно у submission может быть только одна версия в queued или under_review. Новую версию можно отправить только после формального revision_requested предыдущей версии.

21.3. Недельный лимит

21.4. SubmissionVersion

Поля:

Статусы версии:

draft
queued
under_review
reviewed
cancelled

Ограничения:


22. Проверка, общение и выпуск

22.1. SubmissionReview

Поля:

Решения:

revision_requested
passed

Для каждой отправленной версии допускается одно формальное решение. Обычные уточнения относятся к учебному диалогу и не являются полноценной проверкой.

submission_version уникален в SubmissionReview. Повторная отправка одного и того же решения идемпотентна и не создаёт второй review или повторное открытие урока.

22.2. TimestampComment

Поля:

При клике видеоплеер переходит к нужной секунде.

Правила:

22.3. SLA проверки

При отправке версии review_due_at рассчитывается из review_sla_working_days_snapshot. Для «Первой ступени» это четыре рабочих дня с момента успешной постановки в очередь. Основные операционные дни проверки — понедельник и четверг; видео можно отправлять в любой день.

Для MVP:

SchoolNonWorkingDay:

22.4. CourseMessage

Простой асинхронный учебный диалог без WebSocket и общего мессенджера.

Поля:

Правила:

22.5. EnrollmentStaffNote

Внутренние методические заметки не смешиваются с сообщениями семье.

Поля:

Доступ: текущий куратор, академический руководитель и администратор.

22.6. GraduationRecord

Поля:

enrollment является OneToOneField: у одного enrollment не более одной выпускной записи.

Статусы:

pending
preparing
ready
revoked

Правила:


23. Локальное хранение видео

23.1. Каталоги

/srv/kidsconservatory/media/
├── public/
├── courses/
├── submissions/
├── teacher_replies/
├── originals/
├── converted/
└── thumbnails/

Файлы не хранятся в Git.

23.2. VideoAsset

Поля:

Назначения:

course_video
course_example
student_submission
teacher_reply
lesson_attachment

course_example с участием ребёнка допускается только при наличии зафиксированного действующего согласия законного представителя. Приватный student_submission никогда не превращается в учебный пример автоматически.

Статусы:

uploaded
validating
ready
requires_conversion
invalid
deleted

23.3. Ограничения

В админке:

Курс может переопределить максимальную длительность. Срок хранения учебных отчётов и видеоответов не сокращается администратором ниже срока доступа конкретного enrollment.

23.4. Загрузка

В MVP используется multipart-загрузка через Django.

Требования:

Возобновляемая chunked-загрузка откладывается.

23.5. Форматы

Курсовые видео:

Пользовательские видео:

23.6. Просмотр и скачивание

Пользователи и преподаватели:

Администратор:

Это контроль доступа, а не DRM.

23.7. Срок хранения

Для student_submission и teacher_reply:

Команда:

python manage.py cleanup_expired_media

Запускается cron/systemd timer.

Удаление:

  1. проверяет срок;
  2. удаляет физический файл;
  3. ставит статус;
  4. сохраняет AuditLog;
  5. не ломает историю проверки.

Снятие программы с продажи или перевод Course.active = false не удаляет мастер-файлы и не отзывает существующий доступ. Курсовые материалы хранятся как минимум до максимального access_ends_at всех неотозванных enrollment; пользовательский доступ определяется сроком конкретного enrollment.

Резервные копии имеют отдельный ограниченный срок хранения. Удалённый из основной системы файл не должен сохраняться в backup бессрочно.

23.8. CourseExampleConsent

Отдельно фиксирует основание для использования видео ребёнка как учебного примера.

Поля:

Правила:


24. Платежи

Production-провайдер — Stripe Checkout. Основная валюта — EUR. Stripe Connect и автоматические выплаты не используются.

Для разработки допускается manual/fake payment backend.

24.1. Order

Поля:

Статусы:

draft
pending
paid
failed
cancelled
refunded
partially_refunded

24.2. OrderItem

Типы:

course_plan
single_lesson
lesson_package

Поля:

Правила:

Для семейной покупки один Order может содержать отдельные course_plan позиции для каждого ребёнка. Первая позиция использует базовую цену, каждая следующая — процентную надбавку из CoursePlan.additional_child_surcharge_percent; расчёт и список детей фиксируются в metadata и не меняются задним числом.

24.3. Payment

Поля:

24.4. Refund

Поля:

Возврат по сопровождаемой программе в MVP оформляется администратором вручную в пределах 30 дней после activated_at. Причина и рассчитанные удержания за открытые/пройденные уроки и работу куратора обязательны в Refund.reason и AuditLog; итог не может превышать сумму платежа. Детальная формула будет утверждена отдельно и затем заменит временную конфигурацию CoursePlan.refund_calculation_config.

24.5. StripeWebhookEvent

Сохраняет доставку Stripe webhook отдельно от Payment, поскольку один платёж может породить несколько событий.

Поля:

Статусы:

received
processed
ignored
failed

Правила:


25. Уведомления без Celery

25.1. Notification

Поля:

Статусы:

pending
sent
failed
cancelled

Команда:

python manage.py send_pending_notifications

Запускается cron/systemd timer.

Минимальные события:


26. Django Admin

Django Admin — основная операционная панель MVP.

Для академического руководителя создаётся отдельная защищённая серверная страница обзора проверок. Это не SPA и не отдельная административная система.

Требования:

Обязательные действия:

Управление доступами обязательно включает Django Groups, model permissions и отдельные action permissions. Основатель, академический руководитель, операционный администратор, преподаватель и куратор являются настраиваемыми предустановками, а не жёстко зафиксированными ролями.

Академический обзор обязан поддерживать:


27. SchoolSettings

Singleton-модель.

Поля:

Изменения настроек журналируются.


28. Безопасность и приватность

Обязательно:

Детские данные:

28.1. AuditLog

Поля:

Журналировать:


29. Нефункциональные требования

29.1. Производительность

Целевая нагрузка:

Требования:

29.2. Надёжность

29.3. Доступность интерфейса

29.4. Браузеры

Актуальные:

LiveKit отдельно тестируется на Safari и Chrome.


30. Развёртывание без Docker

На VPS:

Сервисы:

kidsconservatory-web.service
livekit.service

Периодические команды:

send_pending_notifications
send_lesson_reminders
send_course_deadline_reminders
send_review_sla_reminders
sync_course_enrollments
expire_booking_holds
expire_course_capacity_reservations
cleanup_expired_media
cleanup_orphaned_uploads
backup_postgres
backup_media

Каталоги:

/srv/kidsconservatory/app
/srv/kidsconservatory/venv
/srv/kidsconservatory/media
/srv/kidsconservatory/static
/etc/kidsconservatory/
/var/log/kidsconservatory/

.env.example:

DJANGO_SETTINGS_MODULE=
DJANGO_SECRET_KEY=
DJANGO_ALLOWED_HOSTS=
DATABASE_URL=
DEFAULT_FROM_EMAIL=
EMAIL_HOST=
EMAIL_PORT=
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
LIVEKIT_URL=
LIVEKIT_API_KEY=
LIVEKIT_API_SECRET=
LIVEKIT_WEBHOOK_SECRET=
PAYMENT_PROVIDER=
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
MEDIA_ROOT=

31. Тестирование

Обязательные тесты:

Критические сценарии:

  1. Нельзя забронировать один слот дважды.
  2. Баланс не уходит ниже нуля.
  3. Своевременная отмена возвращает кредит.
  4. Поздняя отмена не возвращает кредит.
  5. Перенос не списывает кредит дважды.
  6. Изменение цены не меняет старый заказ.
  7. Изменение плана не меняет snapshot и сроки старого enrollment.
  8. Родитель не видит чужого ребёнка или чужой учебный диалог.
  9. Пользователь не использует admin download.
  10. Token endpoint отказывает вне разрешённого окна.
  11. Повторный payment webhook не дублирует покупку или enrollment.
  12. DST обрабатывается корректно.
  13. draft/coming_soon язык нельзя купить или открыть как учебный контент; его публичную страницу можно посмотреть.
  14. Форма интереса к coming_soon идемпотентна, требует согласия и не создаёт enrollment.
  15. Enrollment до назначения куратора не активирует сроки.
  16. У enrollment только одно активное назначение куратора.
  17. Замена сохраняет историю и review_due_at, меняет permissions и отправляет уведомление.
  18. Прежний куратор после замены не видит отчёты и сообщения enrollment.
  19. Родитель не может установить уроку passed.
  20. revision_requested не открывает следующий урок.
  21. passed атомарно и ровно один раз открывает следующий урок.
  22. Параллельные решения не создают двойной прогресс или два GraduationRecord.
  23. Исправленная версия учитывается в недельном лимите, но не создаёт новое оплаченное задание.
  24. Черновик и повторный HTTP-запрос не расходуют недельный слот.
  25. Третья отправка за неделю запрещена даже при гонке запросов.
  26. Лимит независим для двух enrollment одной семьи.
  27. Новая неделя начинается в понедельник timezone школы, включая DST.
  28. SLA правильно пропускает выходные и SchoolNonWorkingDay.
  29. Замена куратора не перезапускает SLA.
  30. Академический руководитель видит все проверки и фильтры, куратор — только действующие назначения.
  31. Сообщения и вложения изолированы по enrollment.
  32. Финальный passed создаёт выпускную запись и закрывает доступ к материалам программы.
  33. GraduationRecord.ready требует диплом и нотную запись.
  34. Родитель видит только выпускные документы своих детей.
  35. Детский course_example нельзя опубликовать без действующего согласия; отзыв согласия закрывает публикацию.
  36. sync_course_enrollments идемпотентно применяет только expiry без продления сроков.
  37. Email является единственным логином и уникален без учёта регистра.
  38. Неподтверждённый email не может купить курс или забронировать занятие.
  39. После completed, expired или revoked создаётся новый enrollment того же ребёнка без изменения истории старого.
  40. Второй одновременно незавершённый enrollment того же ребёнка на тот же курс запрещён даже при гонке запросов.
  41. OrderItem содержит ровно одну явную целевую связь, соответствующую item_type.
  42. Семейная покупка создаёт отдельный enrollment на каждого ребёнка и применяет зафиксированную надбавку к каждому дополнительному ребёнку.
  43. Пересекающиеся busy-интервалы одного преподавателя или ребёнка отклоняются PostgreSQL.
  44. Stripe Checkout Session и booking hold согласованы по времени; webhook в технический резерв подтверждает тот же booking.
  45. Повторный Stripe Event ID не выполняет бизнес-операцию повторно, а порядок webhook не влияет на результат.
  46. Успешная оплата слота, который невозможно подтвердить из-за исключительного сбоя, создаёт полный возврат.
  47. Регулярная серия создаёт ровно число занятий пакета или полностью откатывается при конфликте.
  48. Checkout сопровождаемой программы недоступен без свободной capacity куратора.
  49. Две одновременные покупки последнего места не превышают max_active_enrollments.
  50. Оплаченный резерв capacity сохраняется до назначения куратора.
  51. Запрос возврата в первые 30 дней после активации создаёт аудируемый расчёт с удержаниями по временной политике.
  52. Не прикреплённые к доменной сущности незавершённые загрузки удаляются через 24 часа.
  53. Заявка ожидания capacity не создаёт заказ, оплату, резерв или enrollment и не дублируется для того же ребёнка.
  54. Права академического руководителя и администратора меняются через Django Admin и применяются без изменения кода.
  55. Ученик может войти в класс до 15 минут после начала; продление занятия не превышает 30 минут и не пересекает следующий busy-интервал.

32. Основные маршруты

Публичные:

/<lang>/
/<lang>/teachers/
/<lang>/teachers/<slug>/
/<lang>/courses/
/<lang>/courses/<slug>/
/<lang>/courses/<slug>/interest/
/<lang>/courses/<slug>/capacity-waitlist/
/<lang>/individual-lessons/
/<lang>/about/
/<lang>/faq/
/<lang>/contact/

Кабинет родителя:

/<lang>/account/
/<lang>/account/children/
/<lang>/account/lessons/
/<lang>/account/packages/
/<lang>/account/courses/
/<lang>/account/courses/<enrollment_uuid>/
/<lang>/account/courses/<enrollment_uuid>/lessons/<lesson_uuid>/
/<lang>/account/courses/<enrollment_uuid>/messages/
/<lang>/account/courses/<enrollment_uuid>/graduation/
/<lang>/account/submissions/
/<lang>/account/orders/
/<lang>/account/settings/

Кабинет преподавателя:

/<lang>/teacher/
/<lang>/teacher/schedule/
/<lang>/teacher/students/
/<lang>/teacher/submissions/
/<lang>/teacher/submissions/<uuid>/
/<lang>/teacher/enrollments/<enrollment_uuid>/
/<lang>/teacher/enrollments/<enrollment_uuid>/messages/
/<lang>/teacher/reviews/
/<lang>/teacher/classrooms/<booking_uuid>/

Кабинет академического руководителя:

/<lang>/academic/reviews/
/<lang>/academic/reviews/<review_id>/

JS endpoints:

GET  /api/calendar/available-slots/
POST /api/bookings/create/
POST /api/bookings/<uuid>/cancel/
POST /api/bookings/<uuid>/reschedule/
POST /api/classrooms/<uuid>/token/
POST /api/submission-versions/<uuid>/submit/
POST /api/video-position/
POST /payments/stripe/webhook/

Отправка версии на проверку идемпотентна. Формальные решения куратора выполняются обычной защищённой POST-формой через service layer.


33. Пользовательские сценарии

Покупка и активация «Первой ступени»

Программа
→ регистрация/вход
→ ребёнок
→ подтверждённый email
→ проверка и резерв capacity куратора
→ заказ
→ Stripe Checkout в EUR
→ enrollment pending_curator
→ назначение куратора не позднее двух рабочих дней
→ activated_at
→ первый урок

Если capacity отсутствует, checkout не открывается и семья оставляет заявку ожидания без оплаты. После завершения, истечения или отзыва предыдущего enrollment новый заказ создаёт отдельное повторное зачисление и не изменяет историю старого.

Сопровождаемое прохождение

→ урок
→ черновик видео
→ резервирование недельного слота
→ ответ куратора не позднее четырёх рабочих дней
→ revision_requested → исправленная версия → повторная проверка
→ passed → следующий урок
→ после урока 16 GraduationRecord
→ диплом и нотная запись

Разовое занятие

Подтверждённый ученик
→ услуга
→ слот
→ hold 30 минут + 5 минут технического резерва
→ Stripe Checkout в EUR
→ подтверждение
→ LiveKit

Пакет

Пакет
→ оплата
→ баланс
→ бронирование
→ списание кредита
→ возврат при своевременной отмене

Регулярная серия

Администратор
→ оплаченный пакет с фиксированным числом уроков
→ ученик, преподаватель и offering
→ еженедельное время
→ атомарное создание ровно оплаченного числа бронирований
→ отображение в кабинете

34. Порядок реализации

Каждый этап завершается работающим вертикальным срезом, тестами и обновлением документации.

Этап 0. Основа, публичный сайт и аккаунты

Этап 1. «Первая ступень»

Этап 2. LiveKit: инфраструктурная проверка

Этап 3. Индивидуальный календарь, пакеты и платежи

Этап 4. LiveKit-класс

Этап 5. Академический контроль и выпуск

Этап 6. Production hardening


35. Критерии приёмки MVP

  1. Сайт работает на RU и EN.
  2. Администратор создаёт преподавателей, услуги и разные цены.
  3. Родитель создаёт несколько детей.
  4. Родитель подаёт заявку.
  5. Только администратор подтверждает ученика.
  6. Администратор задаёт расписание.
  7. Родитель видит слоты в своём timezone.
  8. Работает разовое занятие.
  9. Работает пакет и баланс.
  10. Работают отмена и перенос.
  11. Администратор создаёт регулярную серию.
  12. Занятие открывает LiveKit-класс.
  13. Посторонний не получает токен.
  14. RU-версия программы публикуется независимо, а незавершённая EN-версия показывает coming_soon.
  15. У enrollment есть один текущий куратор с историей назначений.
  16. Сроки enrollment начинаются только после назначения куратора и открытия первого урока.
  17. Следующий урок открывается только после решения passed.
  18. revision_requested оставляет следующий урок закрытым.
  19. Отчёт можно отправить в любой день; основные дни проверки — понедельник и четверг.
  20. Первичная и повторная проверки учитываются в лимите две проверки в неделю на enrollment.
  21. Повторная версия не создаёт новое оплаченное задание.
  22. Каждая принятая в очередь версия получает ответ не позднее четырёх рабочих дней.
  23. Куратор заменяется без согласования, с сохранением истории и автоматическим уведомлением.
  24. Общий диалог enrollment и обсуждение задания работают внутри платформы.
  25. Срок программы составляет до 12 календарных месяцев с активации; завершить её можно раньше.
  26. Финальный passed создаёт выпускной процесс и закрывает доступ к материалам курса.
  27. Родитель получает защищённый доступ к диплому и нотной записи.
  28. Можно загрузить видео до 60 минут или значения настройки.
  29. Куратор отвечает текстом, видео и таймкодами.
  30. Пользователь не видит штатного скачивания.
  31. Администратор скачивает оригинал.
  32. Файлы после истечения доступа и не прикреплённые загрузки после 24 часов удаляет management command.
  33. Критические операции покрыты тестами.
  34. Проект разворачивается без Docker.
  35. База и media резервируются вне VPS.
  36. Видео ребёнка публикуется как учебный пример только при действующем согласии.
  37. Форма интереса к английской версии сохраняет уникальную заявку с согласием и уведомляет администратора.
  38. Email является единственным логином и подтверждается до покупки или бронирования.
  39. После завершения, истечения или отзыва курса ребёнок может купить его повторно с новым enrollment и сохранением старой истории.
  40. Одновременно у ребёнка не более одного незавершённого enrollment одного курса.
  41. Production-оплата работает через Stripe Checkout в EUR.
  42. Разовый слот удерживается 30 минут и ещё пять минут только для технического завершения платежа.
  43. Повторный Stripe webhook не дублирует оплату, заказ, booking, пакет, enrollment или возврат.
  44. Пересечения преподавателя и ребёнка запрещены PostgreSQL с учётом буферов.
  45. Регулярная серия содержит фиксированное число занятий оплаченного пакета и не бывает бессрочной.
  46. Продажа программы недоступна без свободной capacity куратора.
  47. Capacity резервируется на время checkout, а куратор назначается не позднее двух рабочих дней после оплаты.
  48. Если назначение в срок невозможно, родитель выбирает согласованное ожидание или полный возврат.
  49. Семейное участие создаёт отдельный enrollment и обратную связь для каждого ребёнка; надбавка за каждого дополнительного ребёнка составляет 20% snapshot-цены плана.
  50. Запрос возврата в течение 30 дней после активации обрабатывается вручную с аудируемыми удержаниями за открытые уроки и работу куратора.
  51. Доступы сотрудников настраиваются через Django Admin.
  52. Ученик может присоединиться с опозданием до 15 минут, а занятие завершается позднее не более чем на 30 минут без пересечения расписания.
  53. При отсутствии capacity родитель оставляет уникальную заявку ожидания без оплаты и возвращается на обычный checkout после появления места.

36. Решения до production

Нужно выбрать:


37. Будущие расширения

Redis

Добавлять при необходимости:

Celery

Добавлять при:

Cloudflare R2

Добавлять при:

Docker

Добавлять при:

Отдельный LiveKit VPS

Добавлять при:


38. Инструкции для Codex

Codex должен:

  1. Читать это ТЗ перед архитектурными изменениями.
  2. Не добавлять запрещённые технологии без команды.
  3. Не переписывать соседние модули без необходимости.
  4. Создавать миграции при изменении моделей.
  5. Добавлять тесты вместе с функциональностью.
  6. Использовать транзакции для оплат, бронирований и балансов.
  7. Использовать service layer для бизнес-операций.
  8. Проверять permissions.
  9. Не использовать float для денег.
  10. Не использовать naive datetime.
  11. Не отдавать закрытые media по публичному URL.
  12. Не удалять финансовую историю каскадом.
  13. Не привязывать доменную модель к платёжному провайдеру.
  14. Использовать FileField/VideoAsset, а не ручные абсолютные пути.
  15. Запускать релевантные тесты после изменений.
  16. При неоднозначности фиксировать допущение.
  17. Предпочитать простой явный код.
  18. Обновлять ТЗ после новых решений.
  19. Не позволять родителю устанавливать passed для guided-урока.
  20. Не расходовать недельный слот при сохранении черновика.
  21. Изменять куратора, сроки и прогресс только через сервисные функции с аудитом.
  22. Не смешивать сообщения семье с внутренними заметками сотрудников.
  23. Проверять согласие перед публикацией детского видео как учебного примера.
  24. Не использовать GenericForeignKey для OrderItem.
  25. Не подтверждать booking только по return URL Stripe; использовать общий идемпотентный сервис и проверенный webhook.
  26. Не заменять database exclusion constraints одной только проверкой Python.
  27. Не создавать checkout сопровождаемой программы без действующего резерва capacity куратора.
  28. Не сокращать срок хранения учебных видео ниже срока, рассчитанного по политике retention enrollment.

39. Архитектурное резюме

flowchart LR
    Parent[Родитель] --> Student[Ребёнок]
    Student --> App[Заявка]
    App --> Approval[Подтверждение]
    Approval --> Booking[Бронирование]
    Booking --> LiveKit[LiveKit]

    Parent --> Order[Заказ программы]
    Order --> Capacity[Резерв capacity]
    Capacity --> Stripe[Stripe Checkout EUR]
    Stripe --> Enrollment[Guided enrollment]
    Admin --> Curator[Назначение куратора]
    Curator --> Enrollment
    Enrollment --> Lesson[Открытый урок]
    Lesson --> Submission[Видеоотчёт]
    Submission --> Review[Проверка куратора]
    Review -->|revision_requested| Submission
    Review -->|passed| NextLesson[Следующий урок]
    Academic[Академический руководитель] --> Review
    NextLesson -->|после урока 16| Graduation[Диплом и нотная запись]

    Admin[Администратор] --> Schedule[Расписание]
    Admin --> Teacher[Преподаватели и цены]
    Admin --> Course[Программы RU / EN coming soon]

Ключевые цепочки:

TeacherOffering
→ Calendar Slot
→ LessonBooking
→ Order/Package Credit
→ LiveKit Classroom
Course
→ CoursePlan
→ Order
→ CourseCapacityReservation
→ Stripe Checkout
→ CourseEnrollment
→ EnrollmentCuratorAssignment
→ CourseLessonProgress
→ AssignmentSubmission
→ SubmissionVersion
→ SubmissionReview
→ GraduationRecord

40. Изменения относительно предыдущей версии

  1. Бесплатная демопрограмма, бесплатное зачисление и связанные маршруты, модели, сценарии, тесты и этап реализации удалены из MVP. До покупки используются публичные материалы: видео об авторе и методике, примеры результатов и отзывы.
  2. «Первая ступень» теперь имеет единый срок до 12 календарных месяцев с активации. Автоматическая заморозка на 60 дней, дополнительные продления и разделение на шесть месяцев сопровождения и 12 месяцев материалов удалены.
  3. При успешном завершении программы доступ к её урокам, учебным видео и диалогам закрывается; выпускные документы остаются доступны в защищённом разделе.
  4. Срок ответа на отчёт изменён с двух до четырёх рабочих дней. Отчёты принимаются в любой день, а понедельник и четверг зафиксированы как основные операционные дни проверок; недельный лимит двух полноценных проверок сохранён.
  5. В публичной коммуникации «Первой ступени» используется роль «куратор». Куратор отделён от преподавателя индивидуальных занятий: добавлены CuratorProfile, CourseCuratorEligibility и EnrollmentCuratorAssignment.
  6. Дата рождения ребёнка стала обязательной. Добавлены музыкальное образование родителя и семейное участие: отдельное enrollment, отчёты и обратная связь для каждого ребёнка, базовая цена для первого и настраиваемая надбавка +20% для каждого следующего.
  7. Для возврата временно зафиксировано ручное правило: запрос возможен в течение 30 дней после активации, сумма учитывает открытые/пройденные уроки и работу куратора. Окончательная юридическая формула отложена.
  8. Права сотрудников больше не предполагаются неявно по роли: группы, model permissions и action permissions настраиваются в Django Admin. Академическому руководителю можно выдать доступ к ценам или платежам отдельным правом.
  9. Для индивидуальных занятий добавлены правила: отмена/перенос не позднее чем за 24 часа для семьи и преподавателя, вход ребёнка с опозданием до 15 минут, фактическое завершение не более чем на 30 минут позже без пересечения расписания.
  10. Отдельный электронный договор и комплексное принятие условий до оплаты пока не входят в объём MVP; они не блокируют временную операционную политику возвратов.

Конец спецификации.