Техническое задание: KidsConservatory
Статус: целевая спецификация версии 1.0 с поэтапной реализацией, синхронизированная с учебной программой
Формат проекта: Django-монолит
Языки продукта: русский и английский
Назначение: единый источник требований для разработки в Codex.
0. Обязательные правила
- Этот документ является основным источником требований.
- При противоречии между кодом и ТЗ приоритет имеет ТЗ, если изменение не зафиксировано отдельно.
- На первом этапе не использовать: - Docker; - Redis; - Celery; - Cloudflare R2; - микросервисы; - SPA-фреймворки; - автоматическое фоновое транскодирование видео; - автоматические выплаты преподавателям.
- Интерфейс строится на Django templates и небольшом количестве обычного JavaScript.
- Все денежные суммы хранятся без
float:amount_minor+ ISO-код валюты. - Все даты и время хранятся как timezone-aware значения в UTC.
- Новые зависимости добавляются только при доказанной необходимости.
- Реализация выполняется вертикальными срезами: модели → сервисы → формы/представления → шаблоны → тесты.
- Простые и явные решения предпочтительнее преждевременной универсальности.
- После принятия нового продуктового решения этот документ обновляется.
program.mdявляется источником педагогических правил и результатов обучения.presentation.mdявляется источником утверждённых продуктовых обещаний.- Это ТЗ реализует программу и продуктовые обещания. Обнаруженное противоречие между тремя документами должно быть разрешено до реализации затронутой функции.
- Принятые ранее решения о бессрочном доступе, двух тарифах «Первой ступени», бесплатной демопрограмме и лимите проверок на курс заменены согласованными условиями этого документа: сопровождаемая «Первая ступень» с максимум 12 календарными месяцами, закрытием доступа после выпуска и недельным лимитом проверок на enrollment.
1. Описание продукта
KidsConservatory — двуязычная онлайн-музыкальная школа для детей.
Продукт объединяет:
- Публичный сайт школы.
- Индивидуальные онлайн-занятия преподаватель–ученик.
- Сопровождаемые авторские программы с последовательным открытием уроков и проверкой домашних видеоотчётов.
Основной образовательный продукт MVP — русскоязычная программа «Первая ступень»: 16 уроков, которые родитель или другой взрослый проходит вместе с ребёнком. Содержание и педагогические результаты программы не изменяются платформой.
Масштабирование строится через назначенных школой кураторов. Один куратор ведёт конкретного ребёнка в конкретной программе, а академический руководитель контролирует все проверки и соблюдение методики. Куратор «Первой ступени» не обязан быть преподавателем индивидуальных музыкальных занятий: это отдельная допущенная школой роль.
Школа не является маркетплейсом. Администратор контролирует:
- преподавателей;
- цены;
- расписание;
- подтверждение новых учеников;
- назначение преподавателей;
- программы и планы участия;
- кураторов и их нагрузку;
- академический контроль качества;
- отмены и переносы;
- платежи;
- права доступа;
- сроки хранения видео.
На старте школа небольшая: около двух преподавателей. Архитектура должна быть простой, но допускать рост.
2. Цели MVP
MVP должен позволить:
- создать новый сайт на русском и английском;
- публиковать преподавателей с разными услугами и ценами;
- регистрировать родителя;
- использовать подтверждённый email как единственный логин;
- создавать несколько профилей детей;
- подавать заявку на индивидуальное обучение;
- подтверждать нового ученика администратором;
- показывать автоматический календарь свободных слотов;
- продавать разовое занятие и пакет занятий;
- бронировать, переносить и отменять занятия;
- проводить урок через self-hosted LiveKit;
- публиковать и продавать учебные программы;
- продавать сопровождаемую программу «Первая ступень»;
- повторно зачислять ребёнка на ту же программу после завершения, истечения или отзыва предыдущего enrollment без удаления истории;
- активировать программу после назначения куратора;
- открывать следующий урок только после решения куратора
passed; - загружать домашние видеоотчёты;
- поддерживать исправленные версии без новой оплаты за задание;
- фиксировать основные дни проверки: понедельник и четверг, не ограничивая дни отправки отчёта;
- ограничивать очередь двумя полноценными проверками в календарную неделю на enrollment;
- обеспечивать срок ответа в четыре рабочих дня;
- назначать и заменять куратора с сохранением истории и автоматическим уведомлением;
- отвечать текстом, видео и комментариями с таймкодами;
- вести учебные диалоги внутри платформы;
- предоставлять срок прохождения до 12 календарных месяцев с возможностью завершить программу раньше;
- закрывать доступ к материалам и учебному общению после успешного выпуска;
- поддерживать семейное участие нескольких детей с отдельными отчётами и обратной связью;
- выдавать диплом и нотную запись после завершения;
- давать академическому руководителю обзор всех проверок с фильтрами;
- управлять операционной частью через Django Admin.
3. Не входит в MVP
- пробные индивидуальные занятия;
- бесплатная демопрограмма и бесплатное зачисление в курс;
- подписочная модель курсов;
- маркетплейс преподавателей;
- самостоятельное изменение расписания преподавателем;
- автоматические выплаты преподавателям;
- Stripe Connect или аналогичная инфраструктура;
- запись LiveKit-занятий;
- групповые занятия;
- мобильное приложение;
- React/Vue/Angular;
- социальные функции;
- общий realtime-мессенджер вне учебных диалогов enrollment;
- геймификация;
- DRM;
- автоматическое фоновое кодирование видео;
- сложная аналитика просмотра;
- перенос старых URL и SEO;
- обязательная миграция старого контента один-в-один;
- полноценная английская версия 16 уроков и англоязычное сопровождение.
- отдельный электронный договор участника и сбор комплексных согласий до оплаты; временная политика возврата хранится в настройках плана и администратор обрабатывает её вручную.
Новый сайт создаётся с нуля.
4. Роли и права
4.1. Родитель
Родитель является владельцем аккаунта.
В терминах MVP родитель — родитель или законный представитель, который отвечает за аккаунт и действия на платформе. Другой сопровождающий взрослый может участвовать в занятиях вместе с ребёнком, но не получает отдельный логин или делегированный доступ.
Может:
- регистрироваться и входить;
- управлять своим профилем;
- создавать профили детей;
- покупать курсы для выбранного ребёнка или семейной группы;
- подавать заявку на индивидуальные занятия;
- после подтверждения бронировать занятия;
- покупать разовые занятия и пакеты;
- видеть расписание;
- отменять и переносить занятия по правилам;
- входить в онлайн-класс;
- смотреть курсы;
- загружать черновики и отправлять версии видеоотчётов на проверку;
- смотреть ответы куратора;
- вести учебный диалог внутри enrollment;
- видеть сроки сопровождения, доступа и ответа;
- получать диплом и нотную запись после выпуска;
- видеть историю заказов.
Не может:
- менять расписание преподавателя;
- назначать или менять куратора;
- устанавливать уроку статус
passed; - видеть чужих учеников;
- скачивать видеоотчёты и видеоответы;
- видеть финансовые данные школы.
4.2. Ученик
Ученик — профиль внутри аккаунта родителя, без отдельного логина в MVP.
Поля профиля:
- имя;
- дата рождения — обязательно;
- предпочитаемый язык;
- часовой пояс;
- музыкальный опыт;
- цели;
- заметки родителя;
- статус допуска к индивидуальным занятиям.
4.3. Преподаватель и куратор
Преподаватель имеет User и TeacherProfile.
Куратор — сотрудник с отдельным CuratorProfile, допущенный к конкретной программе и языку. Он назначается на конкретный CourseEnrollment; наличие TeacherProfile для этого не требуется.
Может:
- преподаватель видит собственное расписание, учеников и входит в свои LiveKit-занятия;
- куратор видит только enrollment, на которых он является текущим куратором;
- видеть назначенные видеоотчёты и крайний срок ответа;
- отвечать текстом;
- прикреплять видеоответ;
- создавать комментарии с таймкодами;
- вести учебный диалог с родителем;
- запрашивать исправление;
- устанавливать решение
passed, которое открывает следующий урок.
Не может:
- редактировать расписание;
- менять цены;
- подтверждать новых учеников;
- назначать себя куратором;
- видеть учеников других преподавателей;
- отменять
passedбез административной операции; - видеть платежные данные;
- скачивать видео через обычный интерфейс.
После замены прежний куратор теряет рабочий доступ к enrollment и его сообщениям, но остаётся автором исторических проверок.
4.4. Академический руководитель
Роль реализуется через Django Group и явные permissions. Права не зашиваются в коде роли: наборы прав и их назначение сотрудникам настраиваются через Django Admin.
Может:
- видеть все enrollment, отчёты, версии и проверки;
- фильтровать проверки по курсу, уроку, куратору, языку, статусу, сроку, просрочке и числу попыток;
- видеть внутренние методические материалы;
- оставлять внутренние комментарии;
- отмечать сложные случаи;
- подключаться к проверке;
- при отдельном permission
courses.can_override_submission_reviewвыполнять формальное решение с обязательной причиной иAuditLog.
Не получает автоматически доступ к платежным данным, ценам и общим системным настройкам, но эти права могут быть выданы через Django Admin.
Основатель школы не зашивается в правила отдельных уроков и может постепенно перейти от роли куратора к роли академического руководителя.
4.5. Администратор
Администратор получает только назначенные ему права. Ни один из перечисленных ниже доступов не является неотзываемым: через Django Admin можно создавать группы, добавлять или снимать model/action permissions и назначать их конкретным сотрудникам.
При полном наборе прав администратор может:
- управлять пользователями и ролями;
- подтверждать новых учеников;
- назначать преподавателей;
- управлять расписанием;
- создавать исключения и отпуска;
- создавать услуги, цены и пакеты;
- создавать курсы и планы участия;
- назначать и атомарно заменять куратора;
- управлять последовательным открытием уроков через сервисные операции;
- контролировать недельные лимиты и SLA;
- загружать диплом и нотную запись;
- управлять заказами и платежами;
- корректировать баланс занятий;
- отменять и переносить занятия;
- скачивать оригиналы видео;
- задавать ограничения загрузки и контролировать рассчитанный срок хранения;
- запускать ручную конвертацию видео;
- просматривать журнал действий.
5. Технологический стек
5.1. Использовать
- Python 3.12+;
- Django 5.2 LTS;
- PostgreSQL;
- Django templates;
- Django Forms;
- vanilla JavaScript;
- HTML5 video;
- Nginx;
- Gunicorn;
- systemd;
- self-hosted LiveKit Server;
- Stripe Checkout;
- Git;
ffprobe;ffmpegтолько для ручных/синхронных операций;- cron или systemd timers.
5.2. Не использовать на первом этапе
- Docker;
- Redis;
- Celery;
- Cloudflare R2;
- Kubernetes;
- RabbitMQ;
- SPA;
- отдельный Node.js backend;
- автоматический FFmpeg worker.
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
Принципы:
- один репозиторий;
- один Django-проект;
- одна PostgreSQL база;
- логическое разделение на Django apps;
- бизнес-логика размещается в service-функциях;
- закрытые видео отдаёт Nginx через
X-Accel-Redirect; - Django проверяет доступ, но не стримит большие файлы через Python;
- LiveKit передаёт аудио и видео, Django выдаёт токены и хранит бизнес-состояние.
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. Двуязычность
Обязательные языки:
ru;en.
Публичные URL:
/ru/
/en/
/ru/teachers/
/en/teachers/
/ru/courses/
/en/courses/
Интерфейсные строки переводятся через Django gettext.
Контент из базы хранится в translation-моделях:
TeacherProfileTranslation;SubjectTranslation;CourseTranslation;CourseModuleTranslation;CourseLessonTranslation;CourseAssignmentTranslation;PageTranslation;FAQTranslation.
Один курс остаётся одной сущностью, а не двумя независимыми RU/EN-курсами.
Языковые версии публикуются независимо:
- публичный сайт и интерфейс доступны на RU и EN;
CourseTranslation.publication_statusимеет значенияdraft,coming_soon,published;- продавать или открывать можно только
published-версию с готовыми обязательными уроками, заданиями и media; - «Первая ступень» в MVP публикуется на русском;
- английская страница до готовности показывает
coming_soonи форму интереса; - язык сохраняется в enrollment;
- для сопровождаемой программы смена языка выполняется администратором только при опубликованном контенте и наличии подходящего куратора.
SEO создаётся с нуля:
- title и description для каждого языка;
hreflang;- sitemap;
- canonical;
- robots.txt;
- локализованные slug;
- Open Graph.
9. Аккаунты и профили
9.1. Custom User
Создать до первой миграции, наследовать от AbstractUser.
Поля:
- email — обязательный, единственный логин;
- preferred_language;
- timezone;
- email_verified_at — nullable;
- is_active;
- staff/superuser flags.
Правила:
- поле
usernameне используется и удаляется до первой миграции; USERNAME_FIELD = "email";- email нормализуется и уникален без учёта регистра;
- уникальность обеспечивается ограничением PostgreSQL на
Lower("email"), а не только формой; - покупка и бронирование доступны только после подтверждения email;
- смена email сбрасывает подтверждение и требует новой проверки;
- восстановление пароля выполняется по email;
- токены подтверждения и восстановления одноразовые и ограничены по времени.
9.2. ParentProfile
Поля:
- user;
- phone;
- country;
- musical_education;
- preferred_contact_method;
- consent flags;
- created_at;
- updated_at.
9.3. StudentProfile
Поля:
- parent;
- first_name;
- last_name — необязательно;
- birth_date;
- preferred_language;
- timezone;
- musical_experience;
- learning_goals;
- parent_notes;
- individual_lesson_status;
- approved_at;
- approved_by;
- created_at;
- updated_at.
Статусы:
not_requested
pending
approved
rejected
suspended
9.4. TeacherProfile
Поля:
- user;
- public_slug;
- photo;
- active;
- timezone;
- sort_order;
- public_visibility.
Переводимые биографические поля находятся в TeacherProfileTranslation.
9.5. CuratorProfile
Профиль сотрудника, который сопровождает авторскую программу. Не требует TeacherProfile и публичной страницы.
Поля:
- user;
- active;
- internal_notes;
- onboarding_status;
- created_at;
- updated_at.
Допуск к конкретной программе и языку задаётся через CourseCuratorEligibility. Публичное описание «Первой ступени» представляет автора и методику; имена кураторов сообщаются семье при старте программы.
9.6. Семейное участие в программе
В одной семье 2–3 ребёнка могут проходить одну программу вместе с родителем. Для каждого ребёнка создаются отдельные StudentProfile, CourseEnrollment, отчёты, проверки, прогресс и выпуск.
FamilyCourseGroup объединяет связанные зачисления и хранит:
- parent;
- course;
- primary_enrollment;
- additional_child_surcharge_percent_snapshot;
- created_at.
Первый ребёнок оплачивает базовую цену выбранного CoursePlan; за каждого следующего ребёнка с индивидуальной проверкой отчётов и обратной связью добавляется процент из snapshot. Для «Первой ступени» начальное значение — 20%. Процент настраивается в админке на уровне плана и не меняет уже созданные заказы. Семейная группа не создаёт общий прогресс и не подменяет отдельное сопровождение детей.
10. Предметы, услуги и цены
10.1. Subject
Поля:
- code;
- active;
- sort_order.
Название переводится через SubjectTranslation.
10.2. TeacherOffering
Конкретная услуга преподавателя.
Поля:
- teacher;
- subject;
- duration_minutes;
- amount_minor;
- currency;
- language_ru;
- language_en;
- min_student_age — необязательно;
- max_student_age — необязательно;
- buffer_before_minutes;
- buffer_after_minutes;
- active;
- created_at;
- updated_at.
Один преподаватель может иметь несколько услуг с разной длительностью и ценой.
При покупке цена, валюта и длительность копируются в заказ и бронирование.
10.3. LessonPackage
Пакет относится к конкретному TeacherOffering.
Поля:
- offering;
- title;
- lesson_count;
- amount_minor;
- currency;
- validity_days — nullable;
- active;
- sort_order.
Пакет занятий одной длительности нельзя использовать для другой длительности.
11. Заявка на индивидуальное обучение
11.1. IndividualLessonApplication
Поля:
- student;
- desired_subject;
- preferred_teacher — необязательно;
- goals;
- experience;
- preferred_days_text;
- preferred_times_text;
- status;
- admin_comment;
- reviewed_by;
- reviewed_at;
- created_at.
Статусы:
pending
approved
rejected
needs_information
Правила:
- подтверждает только администратор;
- до подтверждения бронирование недоступно;
- покупка курса подтверждения не требует;
- администратор может ограничить доступные услуги.
11.2. StudentTeacherAssignment
Опциональная модель назначения:
- student;
- teacher;
- allowed_offerings;
- active;
- notes;
- assigned_by;
- assigned_at.
12. Автоматический календарь
Администратор задаёт правила, система рассчитывает свободные слоты. Преподаватель расписание не редактирует.
12.1. TeacherAvailabilityRule
Поля:
- teacher;
- weekday;
- local_start_time;
- local_end_time;
- timezone;
- valid_from;
- valid_until — nullable;
- active.
12.2. TeacherScheduleException
Поля:
- teacher;
- starts_at;
- ends_at;
- exception_type;
- comment;
- created_by.
Типы:
blocked
available_override
12.3. Расчёт слотов
Сервис должен:
- Получить правила преподавателя.
- Построить интервалы в timezone правила.
- Перевести интервалы в UTC.
- Учесть длительность offering и buffers.
- Исключить пересечения с бронированиями.
- Исключить активные временные hold.
- Учесть исключения и регулярные серии.
- Учесть минимальный срок предварительного бронирования.
- Ограничить горизонт календаря.
- Вернуть время в timezone пользователя.
Слоты рассчитываются динамически и не сохраняются заранее.
12.4. Настройки календаря
В SchoolSettings:
- default_timezone;
- booking_horizon_days;
- minimum_booking_notice_hours;
- cancellation_deadline_hours;
- reschedule_deadline_hours;
- booking_hold_minutes;
- classroom_join_before_minutes;
- classroom_join_after_minutes.
- late_student_join_grace_minutes = 15;
- max_lesson_overrun_minutes = 30;
13. Бронирования
13.1. LessonBooking
Поля:
- uuid;
- teacher;
- student;
- offering;
- recurring_series — nullable;
- starts_at;
- ends_at;
- busy_starts_at;
- busy_ends_at;
- buffer_before_minutes_snapshot;
- buffer_after_minutes_snapshot;
- status;
- source_type;
- package_purchase — nullable;
- amount_minor_snapshot;
- currency_snapshot;
- duration_minutes_snapshot;
- cancellation_reason;
- cancelled_by;
- cancelled_at;
- original_booking — nullable;
- hold_expires_at — nullable;
- payment_finalization_deadline — nullable;
- stripe_checkout_session_id — nullable, unique;
- created_at;
- updated_at.
Статусы:
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. Защита от двойного бронирования
busy_starts_at = starts_at - buffer_before_minutes_snapshot;busy_ends_at = ends_at + buffer_after_minutes_snapshot;- snapshots буферов сохраняются в бронировании вместе с длительностью;
- операции выполняются в
transaction.atomic(); - перед записью повторно проверяется пересечение и блокируются затрагиваемые записи;
- расширение PostgreSQL
btree_gistдобавляется миграцией; - обязательный
ExclusionConstraintзапрещает пересечение[busy_starts_at, busy_ends_at)для одного преподавателя; - второй обязательный
ExclusionConstraintзапрещает пересечение того же диапазона для одного ребёнка; - ограничения применяются к блокирующим статусам
hold,awaiting_payment,confirmed,in_progress; IntegrityErrorперехватывается и преобразуется в понятную ошибку занятого слота;- сервисная проверка нужна для сообщения пользователю, но база остаётся окончательной защитой от гонок.
13.3. Временный hold
Для разового занятия:
- hold создаётся до перехода в Stripe Checkout;
- Stripe Checkout Session и hold действуют 30 минут;
payment_finalization_deadlineустанавливается на пять минут позжеhold_expires_at, чтобы учесть доставку webhook;- до
payment_finalization_deadlineслот остаётся занятым; - для курса и пакета временный hold не создаётся;
- для слота разрешены только способы оплаты с немедленным подтверждением;
- успешная оплата подтверждает booking одним идемпотентным сервисом, который может быть вызван success-страницей и webhook;
- если платёж подтверждён, но booking невозможно подтвердить из-за исключительного сбоя, создаётся полный автоматический возврат и уведомление администратору и родителю.
Истёкшие hold освобождаются:
- лениво при расчёте календаря;
- management command по cron/systemd timer.
13.4. Отмена
До дедлайна:
- пакетный кредит возвращается;
- для разовой покупки создаётся возврат или административная обработка.
После дедлайна:
- занятие считается использованным, если администратор не сделал исключение.
При отмене преподавателем/администратором:
- кредит возвращается или оформляется полный возврат.
Дедлайн отмены и переноса действует одинаково для родителя и преподавателя: не позднее чем за 24 часа до начала занятия. Поздняя отмена преподавателем допускается только через административную операцию с обязательной причиной; родителю возвращается кредит или полный возврат.
13.5. Перенос
Перенос атомарно:
- проверяет дедлайн;
- проверяет новый слот;
- создаёт новое бронирование;
- помечает старое
rescheduled; - не списывает кредит повторно.
Опоздание и завершение занятия:
- ученик может присоединиться к классу до 15 минут после
starts_at; - это не сдвигает плановое
ends_atавтоматически; - преподаватель или администратор может завершить фактическое занятие позже, но не более чем на 30 минут и только если это не создаёт пересечение следующего
busy-интервала; ClassroomSessionхранитactual_started_atиactual_ended_at; любое продление сверх плана журналируется.
14. Регулярные занятия
14.1. RecurringLessonSeries
Создаёт и редактирует только администратор.
Поля:
- student;
- teacher;
- offering;
- weekday;
- local_start_time;
- timezone;
- starts_on;
- occurrence_count;
- generated_count;
- status;
- package_purchase;
- notes;
- created_by.
Статусы:
draft
active
completed
cancelled
Правила:
- серия не является подпиской;
- серия всегда относится к оплаченной покупке пакета с фиксированным числом занятий;
occurrence_countне превышает доступное число кредитов покупки;- открытая бесконечная серия в MVP запрещена;
- сервис в одной транзакции создаёт ровно
occurrence_countотдельныхLessonBookingи списывает по одному кредиту на каждое; - последняя дата должна укладываться в
valid_untilпокупки пакета; - если любая дата конфликтует, вся операция откатывается без частичной серии;
- после генерации всех занятий
generated_count = occurrence_count, но серия остаётсяactiveдо завершения/отмены всех входящих бронирований; - после перехода всех входящих бронирований в конечные статусы серия становится
completed; - изменение серии не меняет завершённые занятия;
- отмена одной даты не отменяет серию;
- своевременный перенос сохраняет тот же кредит;
- своевременная отмена возвращает кредит, но не создаёт новую дату автоматически;
- массовые изменения будущих дат доступны только администратору;
- новый пакет создаёт отдельное продолжение серии с новой покупкой и собственными бронированиями.
15. Пакеты и баланс
15.1. LessonPackagePurchase
Поля:
- student;
- package;
- order_item;
- purchased_count;
- remaining_count;
- valid_until;
- status;
- created_at.
Статусы:
active
used
expired
cancelled
15.2. LessonCreditTransaction
Поля:
- package_purchase;
- delta;
- reason;
- booking;
- created_by;
- created_at.
Причины:
purchase
booking
cancellation_return
admin_adjustment
refund
expiration
Правила:
- баланс не может быть отрицательным;
- списание выполняется в транзакции с блокировкой;
- каждое движение сохраняется;
- администратор видит полную историю.
16. LiveKit-класс
16.1. Развёртывание
- self-hosted LiveKit на основном VPS;
- без Docker;
- без Redis в single-node режиме;
- systemd service;
- TLS/reverse proxy через Nginx;
- необходимые UDP/TCP порты;
- запись занятий отключена.
16.2. ClassroomSession
Один-к-одному с LessonBooking.
Поля:
- booking;
- room_name;
- status;
- fallback_meeting_url;
- created_at;
- closed_at.
Статусы:
scheduled
open
closed
failed
16.3. Token endpoint
POST /api/classrooms/<booking_uuid>/token/
Проверки:
- пользователь является нужным родителем или преподавателем;
- бронирование подтверждено;
- наступило разрешённое время входа;
- занятие не отменено;
- room name соответствует бронированию.
Выдаётся короткоживущий JWT.
16.4. Музыкальный режим
Режим «Разговор»:
- echo cancellation on;
- noise suppression on;
- auto gain control on.
Режим «Музыка»:
- echo cancellation off;
- noise suppression off;
- auto gain control off;
- stereo, если доступно;
- повышенный audio bitrate;
- рекомендация использовать наушники.
16.5. Экран проверки
Перед уроком:
- выбор камеры;
- выбор микрофона;
- индикатор уровня;
- проверка воспроизведения;
- выбор режима;
- обработка ошибок доступа к устройствам.
16.6. Webhooks
Сохранять события:
- room started;
- participant joined;
- participant left;
- room finished.
ClassroomAttendanceEvent:
- classroom;
- participant_user;
- participant_role;
- event_type;
- occurred_at;
- raw_payload.
Подпись webhook проверяется.
16.7. Резервная ссылка
Администратор может указать fallback_meeting_url на случай технического сбоя.
17. Курсы
17.1. Course
Поля:
- uuid;
- slug_base;
- active;
- cover_image;
- min_student_age — nullable;
- max_student_age — nullable;
- sort_order;
- created_at;
- updated_at.
Бесплатная демопрограмма не входит в MVP. До покупки родители знакомятся с программой через публичную презентацию автора и методики, видео о процессе обучения, примеры результатов и отзывы.
Для «Первой ступени» стандартный возраст — 5–9 лет. StudentProfile.birth_date обязателен для всех детей; возраст проверяется на дату активации. Администратор может сделать методически обоснованное исключение с причиной и аудитом.
После появления первого активного enrollment учебная структура этой версии курса замораживается:
- нельзя удалять или менять порядок обязательных модулей, уроков и заданий;
- нельзя менять
required/published, если это изменит условия завершения существующих enrollment; - исправление текста и media допускается с аудитом, если не меняет состав обязательной программы;
- существенное изменение учебного состава в MVP оформляется как новая версия/новый
Course, а не применяется задним числом.
17.2. CourseTranslation
Поля:
- course;
- language;
- title;
- slug;
- short_description;
- description;
- seo_title;
- seo_description;
- publication_status.
Статусы публикации:
draft
coming_soon
published
Уникальность:
- course + language;
- language + slug.
Публикация проверяется отдельно для каждого языка. Статус coming_soon разрешает публичную маркетинговую страницу и форму интереса, но запрещает покупку, enrollment и доступ к учебному контенту. published разрешён только когда обязательные уроки, задания и media готовы.
17.3. CourseModule
Поля:
- course;
- position;
- published.
Переводы — CourseModuleTranslation.
17.4. CourseLesson
Поля:
- module;
- position;
- published;
- is_preview;
- required;
- estimated_minutes;
- created_at;
- updated_at.
Переводы — CourseLessonTranslation.
17.5. LessonMedia
Поля:
- lesson;
- language;
- video_asset;
- subtitles_file — nullable;
- transcript — nullable.
Это позволяет хранить разные видео для RU и EN внутри одного курса.
17.6. CourseAttachment
Поля:
- lesson;
- language — nullable;
- title;
- file;
- position;
- visible.
17.7. LessonCuratorGuide
Внутренняя методическая карточка, не видимая семье.
Поля:
- lesson;
- language;
- acceptance_criteria;
- common_errors;
- review_guidance;
- escalation_guidance;
- active;
- updated_at.
17.8. CourseCuratorEligibility
Ограничивает назначение кураторами, допущенными школой к конкретной программе и языку.
Поля:
- course;
- curator;
- language;
- active;
- max_active_enrollments — nullable;
- approved_by;
- approved_at.
Уникальность: course + curator + language.
Назначение разрешено только активному куратору с активной eligibility нужных course/language. Превышение max_active_enrollments запрещено без явного административного override с причиной и аудитом.
Покупка guided-языка доступна только при наличии хотя бы одного подходящего куратора со свободной capacity. Проверка выполняется повторно перед созданием заказа. Capacity резервируется на время Stripe Checkout, а куратор назначается в течение двух рабочих дней после подтверждения оплаты.
При подсчёте capacity учитываются активные enrollment и незавершённые CourseCapacityReservation.
17.9. CourseCapacityReservation
Резервирует одно место куратора на время оплаты сопровождаемой программы.
Поля:
- course;
- language;
- student;
- eligibility;
- status;
- expires_at;
- stripe_checkout_session_id — nullable;
- order — nullable;
- enrollment — nullable;
- created_at;
- converted_at — nullable.
Статусы:
checkout_hold
paid_pending_assignment
converted
expired
cancelled
refunded
Правила:
- создаётся транзакционно с блокировкой подходящей
CourseCuratorEligibility; - проверяет
max_active_enrollmentsс учётом активных резервов; - действует 35 минут: 30 минут Stripe Checkout и пять минут технического резерва;
- после успешной оплаты становится
paid_pending_assignmentи продолжает занимать capacity; - при назначении куратора связывается с enrollment и становится
converted; - если свободной capacity нет, оплата недоступна и семье показывается форма ожидания;
- куратор назначается в течение двух рабочих дней после подтверждения оплаты;
- если назначение невозможно в срок, родителю предлагается согласованное ожидание или полный возврат;
- ожидание не продлевается без явного согласия родителя;
- истёкшие неоплаченные резервы освобождает идемпотентная management command.
17.10. CourseInterest
Минимальная заявка со страницы coming_soon.
Поля:
- course;
- language;
- email;
- parent — nullable;
- consent_to_contact;
- created_at;
- notified_at — nullable.
Правила:
- форма доступна только для
coming_soon; - согласие на контакт обязательно;
- повторная заявка с тем же course/language/email не создаёт дубликат;
- администратор получает уведомление;
- заявка не создаёт enrollment и не обещает дату запуска.
17.11. CourseCapacityWaitlist
Заявка семьи при отсутствии свободной capacity кураторов опубликованной сопровождаемой программы.
Поля:
- course;
- language;
- parent;
- student;
- email;
- consent_to_contact;
- status;
- created_at;
- contacted_at — nullable.
Статусы:
waiting
contacted
converted
cancelled
Правила:
- форма доступна только для
publishedguided-языка, когда checkout закрыт из-за capacity; - email берётся из подтверждённого аккаунта родителя;
- повторная активная заявка на
student + course + languageне создаёт дубликат; - заявка не создаёт заказ, платёж, резерв capacity или enrollment;
- появление места уведомляет администратора, но не запускает автоматическое списание;
- семья возвращается на обычный checkout и подтверждает покупку самостоятельно.
18. Планы участия
18.1. CoursePlan
Поля:
- course;
- title — внутреннее название для админки, не публичный переводимый текст;
- amount_minor;
- currency;
- duration_months;
- weekly_review_limit;
- review_sla_working_days;
- additional_child_surcharge_percent;
- refund_window_days;
- refund_policy_text;
- refund_calculation_config JSON;
- active;
- sort_order.
В MVP все продаваемые планы являются сопровождаемыми. Будущий самостоятельный или интерактивный план потребует отдельного решения и не реализуется скрытым параметром текущего курса.
План определяет:
- первый урок открывается при активации;
- следующий урок открывается только после решения куратора
passed; - назначается один текущий куратор;
- обязательные задания и необходимые исправления, отправленные в период программы, входят в программу;
- скорость сопровождения ограничивается недельным лимитом, а не балансом купленных отчётов;
- текстовые, видео- и таймкод-ответы.
Для «Первой ступени»:
duration_months = 12;weekly_review_limit = 2;review_sla_working_days = 4;additional_child_surcharge_percent = 20;refund_window_days = 30.
Политика возврата временно фиксируется в плане и копируется в заказ: в течение 30 календарных дней после активации семья может запросить возврат; сумма рассчитывается администратором с учётом стоимости открытых/пройденных уроков и работы куратора. Автоматический расчёт и юридически окончательная формула не входят в MVP.
19. Зачисление и доступ
19.1. CourseEnrollment
Поля:
- uuid;
- student;
- course;
- plan;
- order_item — nullable только для административного зачисления или промо-акции с аудитом;
- source;
- language;
- access_status;
- support_status;
- progress_status;
- current_curator — nullable;
- enrolled_at;
- activated_at — nullable;
- access_ends_at — nullable до активации;
- duration_months_snapshot;
- weekly_review_limit_snapshot;
- review_sla_working_days_snapshot;
- additional_child_surcharge_percent_snapshot;
- completed_at — nullable;
- revoked_at — nullable.
Исторических enrollment одного ребёнка на один курс может быть несколько.
Ограничения:
- одновременно допускается только один незавершённый enrollment на
student + course; - частичный
UniqueConstraintприменяется к enrollment со статусом доступаpending/activeи прогрессомnot_started/in_progress; - после
completed,expiredилиrevokedможно создать новый платный enrollment; - новый enrollment получает собственные сроки, куратора, прогресс и историю;
- предыдущий enrollment не очищается и не переиспользуется;
- после успешного завершения доступ закрывается, а повторное платное зачисление получает новую историю сроков и сопровождения.
Источники:
purchase
admin_grant
promotion
Статусы доступа:
pending
active
expired
revoked
Статусы сопровождения:
pending_curator
active
expired
completed
Статусы прогресса:
not_started
in_progress
completed
Правила:
- платное зачисление после оплаты получает
support_status = pending_curator; - родитель обязан выбрать принадлежащий ему профиль ребёнка;
- enrollment активируется после назначения куратора и открытия первого урока;
- сроки считаются с
activated_at, а не с оплаты; - месяц означает календарный месяц в
SchoolSettings.default_timezone, а не фиксированные 30 дней; - прибавление месяцев сохраняет локальное время активации, а отсутствующее число месяца заменяется последним календарным днём соответствующего месяца;
- рассчитанный локальный момент окончания сохраняется как timezone-aware UTC;
- при активации условия плана копируются в snapshot-поля;
- изменение
CoursePlanне меняет существующий enrollment; - срок программы составляет не более 12 календарных месяцев с активации; самостоятельное продление и заморозка не предусмотрены;
- при финальном
passedenrollment получаетcompleted_at,support_status = completed,access_status = expired; доступ к урокам, сообщениям и учебным видео закрывается; - при наступлении
access_ends_atнезавершённый enrollment получаетsupport_status = expiredиaccess_status = expired; новые отправки и открытие уроков запрещены.
19.2. EnrollmentCuratorAssignment
Поля:
- enrollment;
- curator;
- started_at;
- ended_at — nullable;
- assigned_by;
- replacement_reason;
- created_at.
У enrollment только одно активное назначение.
Database constraint запрещает более одной строки для enrollment с ended_at IS NULL. CourseEnrollment.current_curator всегда совпадает с куратором активного назначения; оба значения меняются только одним транзакционным сервисом.
Замена выполняется сервисом в одной транзакции:
- закрывает прежнее назначение;
- создаёт новое;
- обновляет
current_curator; - передаёт незавершённые проверки новому куратору;
- не меняет
review_due_at; - сохраняет авторство старых ответов;
- закрывает прежнему куратору рабочий доступ;
- создаёт
AuditLog; - уведомляет родителя и нового куратора.
19.3. Синхронизация состояний
Команда:
python manage.py sync_course_enrollments
запускается systemd timer и является идемпотентной. Она:
- переводит сопровождение и доступ в
expiredпо срокам; - не меняет финансовую или учебную историю.
20. Прогресс
20.1. CourseLessonProgress
Поля:
- enrollment;
- lesson;
- status;
- unlocked_at — nullable;
- started_at;
- completed_at;
- passed_at — nullable;
- passed_by — nullable;
- last_position_seconds — nullable.
Статусы:
locked
available
in_progress
completed
passed
Правила:
- родитель не может установить
passed; passedустанавливает текущий куратор или администратор;- решение
passedпроверяет прохождение всех обязательных заданий урока и атомарно открывает следующий опубликованный урок; revision_requestedне открывает следующий урок;- после последнего обязательного урока
passedзавершает enrollment и ровно один раз создаёт выпускную запись; - куратор не может отменить
passed; административный откат требует причины и аудита; - позиция видео может сохраняться JavaScript-запросом;
- сложная аналитика просмотра не нужна.
21. Видеоотчёты
21.1. CourseAssignment
Поля:
- lesson;
- active;
- required;
- requires_video;
- sort_order.
title и instructions находятся в CourseAssignmentTranslation.
21.2. AssignmentSubmission
Поля:
- uuid;
- enrollment;
- assignment;
- status;
- current_version;
- closed_at;
- retention_until;
- created_at;
- updated_at.
Статусы:
draft
submitted
under_review
revision_requested
passed
closed
Уникальность: один AssignmentSubmission на enrollment + assignment.
Одновременно у submission может быть только одна версия в queued или under_review. Новую версию можно отправить только после формального revision_requested предыдущей версии.
21.3. Недельный лимит
- черновик можно загрузить в любой день, он не резервирует проверку;
- каждая первичная или исправленная версия при отправке в очередь резервирует один недельный слот;
- исправленная версия не создаёт новое оплаченное задание;
- лимит считается отдельно для каждого enrollment;
- неделя — понедельник–воскресенье в timezone школы;
- при исчерпании
weekly_review_limit_snapshotследующую версию можно хранить как черновик, но нельзя отправить до новой недели; для «Первой ступени» snapshot равен двум; - отправка выполняется транзакционно с блокировкой enrollment;
- повторный HTTP-запрос или повторная отправка одной версии не расходует слот;
- файл должен иметь
VideoAsset.status = readyдо резервирования слота; - техническую отмену queued-версии выполняет только администратор с причиной; она освобождает слот и не уменьшает право семьи;
- изменение лимита существующего enrollment возможно только административной операцией с аудитом.
21.4. SubmissionVersion
Поля:
- submission;
- number;
- video_asset;
- student_comment;
- uploaded_at;
- active;
- submitted_for_review_at — nullable;
- review_due_at — nullable;
- quota_week_start — nullable;
- quota_released_at — nullable;
- review_status.
Статусы версии:
draft
queued
under_review
reviewed
cancelled
Ограничения:
submission + numberуникальны;- одну версию нельзя поставить в очередь дважды;
cancelledдопустим только для технической/административной отмены до формального решения;- недельный подсчёт игнорирует версии с
quota_released_at; - поля времени очереди принадлежат версии, а submission отражает только агрегированное состояние текущей версии.
22. Проверка, общение и выпуск
22.1. SubmissionReview
Поля:
- submission;
- submission_version;
- curator;
- text;
- video_asset — nullable;
- decision;
- created_at;
- updated_at.
Решения:
revision_requested
passed
Для каждой отправленной версии допускается одно формальное решение. Обычные уточнения относятся к учебному диалогу и не являются полноценной проверкой.
submission_version уникален в SubmissionReview. Повторная отправка одного и того же решения идемпотентна и не создаёт второй review или повторное открытие урока.
22.2. TimestampComment
Поля:
- review;
- timestamp_seconds;
- text;
- position.
При клике видеоплеер переходит к нужной секунде.
Правила:
- куратор выбирает текст, видео или оба формата;
- таймкоды необязательны;
- проверяет только текущий куратор, академический руководитель с
courses.can_override_submission_reviewили администратор; - куратор не меняет лимиты;
- изменения журналируются.
22.3. SLA проверки
При отправке версии review_due_at рассчитывается из review_sla_working_days_snapshot. Для «Первой ступени» это четыре рабочих дня с момента успешной постановки в очередь. Основные операционные дни проверки — понедельник и четверг; видео можно отправлять в любой день.
Для MVP:
- рабочие дни — понедельник–пятница в timezone школы;
- даты из
SchoolNonWorkingDayисключаются; - просрочка определяется по
review_due_at; - замена куратора не перезапускает срок;
- приближение срока и просрочка видны куратору и академическому руководителю.
SchoolNonWorkingDay:
- date;
- title.
22.4. CourseMessage
Простой асинхронный учебный диалог без WebSocket и общего мессенджера.
Поля:
- enrollment;
- submission — nullable;
- author — nullable для системного сообщения;
- body;
- attachment — nullable, закрытый файл;
- created_at.
Правила:
submission = nullозначает общий диалог enrollment;- иначе сообщение относится к конкретной работе;
- участники — родитель, текущий куратор и администратор;
- академический руководитель видит обсуждения, связанные с проверками; общий организационный диалог доступен ему только при явной эскалации или дополнительном permission;
- новый куратор видит историю;
- прежний куратор после замены теряет доступ;
- сообщения и вложения приватны;
- в MVP сообщение нельзя бесследно удалить;
- email содержит уведомление и ссылку, но не текст консультации как основной канал.
22.5. EnrollmentStaffNote
Внутренние методические заметки не смешиваются с сообщениями семье.
Поля:
- enrollment;
- submission — nullable;
- author;
- body;
- created_at.
Доступ: текущий куратор, академический руководитель и администратор.
22.6. GraduationRecord
Поля:
- enrollment;
- status;
- diploma_file — nullable;
- composition_score_file — nullable;
- created_at;
- ready_at — nullable;
- prepared_by — nullable.
enrollment является OneToOneField: у одного enrollment не более одной выпускной записи.
Статусы:
pending
preparing
ready
revoked
Правила:
- создаётся ровно один раз после
passedпоследнего обязательного урока; - в MVP диплом и нотная запись готовятся вручную;
- статус
readyразрешён только при наличии обоих файлов; - файлы выдаются родителю через защищённый endpoint;
- готовые выпускные документы остаются доступны независимо от доступа к материалам курса, пока запись не отозвана или данные не удалены по юридическому основанию;
- административный откат финального
passedпереводитpending/preparingзапись вrevoked; - после
readyоткат запрещён, пока администратор отдельной операцией не отзовёт выпуск с причиной; файлы скрываются, но история сохраняется; - после
readyотправляется уведомление.
23. Локальное хранение видео
23.1. Каталоги
/srv/kidsconservatory/media/
├── public/
├── courses/
├── submissions/
├── teacher_replies/
├── originals/
├── converted/
└── thumbnails/
Файлы не хранятся в Git.
23.2. VideoAsset
Поля:
- uuid;
- owner;
- purpose;
- file;
- original_filename;
- mime_type;
- codec_video;
- codec_audio;
- size_bytes;
- duration_seconds;
- width;
- height;
- status;
- conversion_error;
- created_at;
- retention_until;
- deleted_at.
Назначения:
course_video
course_example
student_submission
teacher_reply
lesson_attachment
course_example с участием ребёнка допускается только при наличии зафиксированного действующего согласия законного представителя. Приватный student_submission никогда не превращается в учебный пример автоматически.
Статусы:
uploaded
validating
ready
requires_conversion
invalid
deleted
23.3. Ограничения
В админке:
- максимальная длительность, по умолчанию 60 минут;
- максимальный размер файла;
- разрешённые расширения;
- разрешённые MIME types.
Курс может переопределить максимальную длительность. Срок хранения учебных отчётов и видеоответов не сокращается администратором ниже срока доступа конкретного enrollment.
23.4. Загрузка
В MVP используется multipart-загрузка через Django.
Требования:
TemporaryFileUploadHandler;- большой файл не загружается целиком в RAM;
- лимит размера на Nginx и Django;
- после загрузки запуск
ffprobe; - не доверять расширению и MIME;
- при ошибке удалять незавершённый файл.
Возобновляемая chunked-загрузка откладывается.
23.5. Форматы
Курсовые видео:
- MP4;
- H.264;
- AAC;
- Fast Start.
Пользовательские видео:
- MP4 и MOV;
- проверка кодеков;
- совместимый файл получает
ready; - несовместимый получает
requires_conversion; - администратор запускает management command ручной конвертации;
- автоматизация добавляется позднее.
23.6. Просмотр и скачивание
Пользователи и преподаватели:
- смотрят онлайн;
- не видят кнопку скачивания;
- получают защищённый endpoint;
- файл отдаёт Nginx через
X-Accel-Redirect; Content-Disposition: inline.
Администратор:
- имеет отдельный download endpoint;
- получает
Content-Disposition: attachment.
Это контроль доступа, а не DRM.
23.7. Срок хранения
Для student_submission и teacher_reply:
retention_untilрассчитывается по политике хранения послеaccess_ends_atили досрочного выпуска связанного enrollment;- все отправленные и исправленные версии, видеоответы и таймкоды сохраняются весь период доступа;
- успешное завершение программы закрывает пользовательский доступ и устанавливает срок удаления связанных учебных видео по политике retention;
- незавершённый файл, не прикреплённый к черновику или другой доменной сущности, удаляется через 24 часа;
- курсовые мастер-видео не получают срок одного enrollment;
- выпускные документы хранятся по правилам
GraduationRecord, независимо от срока курса.
Команда:
python manage.py cleanup_expired_media
Запускается cron/systemd timer.
Удаление:
- проверяет срок;
- удаляет физический файл;
- ставит статус;
- сохраняет AuditLog;
- не ломает историю проверки.
Снятие программы с продажи или перевод Course.active = false не удаляет мастер-файлы и не отзывает существующий доступ. Курсовые материалы хранятся как минимум до максимального access_ends_at всех неотозванных enrollment; пользовательский доступ определяется сроком конкретного enrollment.
Резервные копии имеют отдельный ограниченный срок хранения. Удалённый из основной системы файл не должен сохраняться в backup бессрочно.
23.8. CourseExampleConsent
Отдельно фиксирует основание для использования видео ребёнка как учебного примера.
Поля:
- video_asset;
- child_reference — nullable для исторических материалов;
- guardian_name;
- scope;
- evidence_file — nullable;
- granted_at;
- revoked_at — nullable;
- recorded_by;
- notes.
Правила:
course_exampleможно опубликовать только при действующем согласии с подходящим scope;- проверка согласия выполняется сервисом;
- отзыв согласия немедленно закрывает публикацию примера;
- приватный submission и его копия не меняют purpose без отдельной административной операции и аудита;
- маркетинговое использование и использование внутри курса являются разными scope.
24. Платежи
Production-провайдер — Stripe Checkout. Основная валюта — EUR. Stripe Connect и автоматические выплаты не используются.
Для разработки допускается manual/fake payment backend.
24.1. Order
Поля:
- uuid;
- parent;
- status;
- amount_minor;
- currency;
- provider;
- provider_order_id;
- stripe_checkout_session_id — nullable;
- created_at;
- paid_at;
- cancelled_at.
Статусы:
draft
pending
paid
failed
cancelled
refunded
partially_refunded
24.2. OrderItem
Типы:
course_plan
single_lesson
lesson_package
Поля:
- order;
- item_type;
- course_plan — nullable;
- lesson_booking — nullable;
- lesson_package — nullable;
- title_snapshot;
- quantity;
- unit_amount_minor;
- total_amount_minor;
- currency;
- metadata JSON.
Правила:
GenericForeignKeyне используется;- для
course_planзаполнено толькоcourse_plan; - для
single_lessonзаполнено толькоlesson_booking; - для
lesson_packageзаполнено толькоlesson_package; CheckConstraintтребует ровно одну целевую связь, соответствующуюitem_type;- целевые связи используют
on_delete=PROTECT; - изменение исходного объекта не меняет snapshot заказа.
Для семейной покупки один Order может содержать отдельные course_plan позиции для каждого ребёнка. Первая позиция использует базовую цену, каждая следующая — процентную надбавку из CoursePlan.additional_child_surcharge_percent; расчёт и список детей фиксируются в metadata и не меняются задним числом.
24.3. Payment
Поля:
- order;
- provider;
- provider_payment_id;
- status;
- amount_minor;
- currency;
- raw_payload;
- created_at;
- confirmed_at.
24.4. Refund
Поля:
- payment;
- amount_minor;
- status;
- reason;
- provider_refund_id;
- created_at.
Возврат по сопровождаемой программе в MVP оформляется администратором вручную в пределах 30 дней после activated_at. Причина и рассчитанные удержания за открытые/пройденные уроки и работу куратора обязательны в Refund.reason и AuditLog; итог не может превышать сумму платежа. Детальная формула будет утверждена отдельно и затем заменит временную конфигурацию CoursePlan.refund_calculation_config.
24.5. StripeWebhookEvent
Сохраняет доставку Stripe webhook отдельно от Payment, поскольку один платёж может породить несколько событий.
Поля:
- event_id — unique;
- event_type;
- object_id;
- payload;
- status;
- received_at;
- processed_at — nullable;
- error_message;
Статусы:
received
processed
ignored
failed
Правила:
- подтверждение оплаты создаёт платное enrollment ровно один раз;
- guided enrollment после оплаты ожидает назначения куратора и не активирует сроки автоматически;
- запрос создания Stripe Checkout Session использует idempotency key, связанный с заказом;
- подпись Stripe webhook проверяется по исходному телу запроса;
- Stripe Event ID сохраняется в
StripeWebhookEventи не обрабатывается повторно; - обработка не зависит от порядка событий и при необходимости получает актуальный объект через Stripe API;
- повторный webhook не создаёт вторую покупку, enrollment, пакет, booking или возврат;
- обработчик быстро сохраняет событие/результат и возвращает
2xx; бизнес-операция остаётся идемпотентной; - денежные снимки не меняются;
- возврат не удаляет историю;
- для разового занятия подтверждение оплаты и booking выполняются по правилам hold из раздела 13.3;
- для guided-курса оплата возможна только с действующим
CourseCapacityReservation; - успешная оплата переводит резерв capacity в
paid_pending_assignment; - для курса и пакета hold календарного слота не используется.
25. Уведомления без Celery
25.1. Notification
Поля:
- user;
- channel;
- template_code;
- payload;
- status;
- scheduled_at;
- sent_at;
- error_message;
- attempts;
- created_at.
Статусы:
pending
sent
failed
cancelled
Команда:
python manage.py send_pending_notifications
Запускается cron/systemd timer.
Минимальные события:
- регистрация;
- подтверждение email;
- новая заявка интереса к
coming_soonпрограмме; - результат заявки;
- покупка;
- отсутствие capacity и заявка ожидания;
- приближение/нарушение четырёхдневного срока проверки;
- предложение ожидания или полного возврата при невозможности назначения;
- подтверждение бронирования;
- перенос;
- отмена;
- напоминание;
- куратор назначен или заменён;
- новый отчёт куратору;
- версия принята в очередь;
- готовая проверка родителю;
- запрос исправления;
- урок пройден и открыт следующий;
- новое учебное сообщение;
- приближение
review_due_atи просрочка; - сопровождение и доступ скоро заканчиваются;
- диплом и нотная запись готовы;
- техническое сообщение.
26. Django Admin
Django Admin — основная операционная панель MVP.
Для академического руководителя создаётся отдельная защищённая серверная страница обзора проверок. Это не SPA и не отдельная административная система.
Требования:
- list filters;
- search;
- readonly исторические поля;
- inlines переводов и планов участия;
- admin actions;
- подтверждение опасных операций;
- понятные русские названия.
Обязательные действия:
- подтвердить/отклонить заявку;
- назначить преподавателя;
- назначить или заменить куратора;
- выполнить административный откат
passedс причиной; - контролировать просроченные проверки;
- загрузить диплом и нотную запись;
- создать/изменить серию;
- вернуть кредит;
- отменить бронирование;
- отметить технический сбой;
- скачать оригинал;
- запустить ручную конвертацию;
- просмотреть срок хранения;
- повторить уведомление;
- вручную подтвердить оплату в development/manual режиме.
Управление доступами обязательно включает Django Groups, model permissions и отдельные action permissions. Основатель, академический руководитель, операционный администратор, преподаватель и куратор являются настраиваемыми предустановками, а не жёстко зафиксированными ролями.
Академический обзор обязан поддерживать:
- фильтры по курсу, уроку, куратору, языку и статусу;
- фильтры по дате отправки и
review_due_at; - отдельный фильтр просроченных работ;
- номер версии/попытки;
- переход к отчёту, формальной проверке и внутренним заметкам;
- пагинацию и индексы без полного перебора таблиц.
27. SchoolSettings
Singleton-модель.
Поля:
- school_name;
- default_timezone;
- default_currency;
- booking_horizon_days;
- minimum_booking_notice_hours;
- booking_hold_minutes = 30;
- payment_finalization_grace_minutes = 5;
- cancellation_deadline_hours;
- reschedule_deadline_hours;
- classroom_join_before_minutes;
- classroom_join_after_minutes;
- late_student_join_grace_minutes = 15;
- max_lesson_overrun_minutes = 30;
- max_submission_duration_minutes = 60;
- max_submission_size_mb;
- orphan_upload_retention_hours = 24;
- media_backup_retention_days;
- default_review_sla_working_days = 4;
- review_week_starts_on = "monday";
- support_email;
- support_phone;
- maintenance_message;
- updated_at.
Изменения настроек журналируются.
28. Безопасность и приватность
Обязательно:
- CSRF;
- HTTPS;
- secure cookies;
- HSTS после проверки;
- rate limit для login/reset/upload/token endpoint;
- permission checks;
- закрытые media без публичных URL;
- секреты в environment variables;
DEBUG=False;- security headers;
- audit log.
Детские данные:
- аккаунт принадлежит родителю;
- видео приватны по умолчанию;
- маркетинговое использование требует отдельного согласия;
- примеры работ детей в курсе требуют отдельного действующего согласия законного представителя;
- приватный отчёт не становится учебным примером автоматически;
- преподаватель видит только назначенных учеников;
- прежний куратор после замены теряет доступ к enrollment;
- академический руководитель видит все проверки по явному permission;
- учебные сообщения хранятся внутри платформы и изолированы по enrollment;
- штатное скачивание преподавателем запрещено;
- запись LiveKit выключена;
- предусмотрен административный процесс удаления данных.
28.1. AuditLog
Поля:
- actor;
- action;
- target_model;
- target_id;
- metadata;
- ip_address;
- created_at.
Журналировать:
- смену цен;
- назначение и замену куратора;
- решение
passedи административный откат; - изменение сроков только по предусмотренным правилам и с аудитом;
- публикацию языковой версии;
- загрузку выпускных документов;
- отмены и переносы;
- изменение баланса;
- скачивание администратором;
- удаление видео;
- ручную оплату;
- отзыв доступа.
29. Нефункциональные требования
29.1. Производительность
Целевая нагрузка:
- несколько десятков активных пользователей;
- несколько одновременных занятий;
- небольшое число одновременных загрузок.
Требования:
- серверный ответ обычной страницы до 500 мс без внешних сервисов;
- календарь без полного перебора всех записей;
- индексы по status, starts_at, teacher, student и foreign keys;
- индексы проверки по status, review_due_at, quota_week_start, current_curator, course и lesson;
- пагинация.
29.2. Надёжность
- ежедневный backup PostgreSQL;
- media backup;
- backup вне основного VPS;
- проверка восстановления;
- Django health endpoint;
- LiveKit health check;
- мониторинг диска;
- журнал ошибок.
29.3. Доступность интерфейса
- адаптивность;
- клавиатурная навигация;
- корректные labels;
- базовая поддержка screen reader;
- контраст;
- текстовые ошибки форм.
29.4. Браузеры
Актуальные:
- Chrome;
- Safari;
- Firefox;
- Edge;
- мобильный Safari;
- мобильный Chrome.
LiveKit отдельно тестируется на Safari и Chrome.
30. Развёртывание без Docker
На VPS:
- Ubuntu/Debian;
- Nginx;
- Python virtualenv;
- Gunicorn;
- PostgreSQL;
- LiveKit binary;
- systemd units;
- cron/systemd timers;
- локальное media.
Сервисы:
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. Тестирование
Обязательные тесты:
- models;
- services;
- permissions;
- forms;
- views;
- webhook idempotency;
- timezone/DST;
- booking conflicts;
- package balance;
- media access;
- translations.
Критические сценарии:
- Нельзя забронировать один слот дважды.
- Баланс не уходит ниже нуля.
- Своевременная отмена возвращает кредит.
- Поздняя отмена не возвращает кредит.
- Перенос не списывает кредит дважды.
- Изменение цены не меняет старый заказ.
- Изменение плана не меняет snapshot и сроки старого enrollment.
- Родитель не видит чужого ребёнка или чужой учебный диалог.
- Пользователь не использует admin download.
- Token endpoint отказывает вне разрешённого окна.
- Повторный payment webhook не дублирует покупку или enrollment.
- DST обрабатывается корректно.
draft/coming_soonязык нельзя купить или открыть как учебный контент; его публичную страницу можно посмотреть.- Форма интереса к
coming_soonидемпотентна, требует согласия и не создаёт enrollment. - Enrollment до назначения куратора не активирует сроки.
- У enrollment только одно активное назначение куратора.
- Замена сохраняет историю и
review_due_at, меняет permissions и отправляет уведомление. - Прежний куратор после замены не видит отчёты и сообщения enrollment.
- Родитель не может установить уроку
passed. revision_requestedне открывает следующий урок.passedатомарно и ровно один раз открывает следующий урок.- Параллельные решения не создают двойной прогресс или два
GraduationRecord. - Исправленная версия учитывается в недельном лимите, но не создаёт новое оплаченное задание.
- Черновик и повторный HTTP-запрос не расходуют недельный слот.
- Третья отправка за неделю запрещена даже при гонке запросов.
- Лимит независим для двух enrollment одной семьи.
- Новая неделя начинается в понедельник timezone школы, включая DST.
- SLA правильно пропускает выходные и
SchoolNonWorkingDay. - Замена куратора не перезапускает SLA.
- Академический руководитель видит все проверки и фильтры, куратор — только действующие назначения.
- Сообщения и вложения изолированы по enrollment.
- Финальный
passedсоздаёт выпускную запись и закрывает доступ к материалам программы. GraduationRecord.readyтребует диплом и нотную запись.- Родитель видит только выпускные документы своих детей.
- Детский
course_exampleнельзя опубликовать без действующего согласия; отзыв согласия закрывает публикацию. sync_course_enrollmentsидемпотентно применяет только expiry без продления сроков.- Email является единственным логином и уникален без учёта регистра.
- Неподтверждённый email не может купить курс или забронировать занятие.
- После
completed,expiredилиrevokedсоздаётся новый enrollment того же ребёнка без изменения истории старого. - Второй одновременно незавершённый enrollment того же ребёнка на тот же курс запрещён даже при гонке запросов.
OrderItemсодержит ровно одну явную целевую связь, соответствующуюitem_type.- Семейная покупка создаёт отдельный enrollment на каждого ребёнка и применяет зафиксированную надбавку к каждому дополнительному ребёнку.
- Пересекающиеся
busy-интервалы одного преподавателя или ребёнка отклоняются PostgreSQL. - Stripe Checkout Session и booking hold согласованы по времени; webhook в технический резерв подтверждает тот же booking.
- Повторный Stripe Event ID не выполняет бизнес-операцию повторно, а порядок webhook не влияет на результат.
- Успешная оплата слота, который невозможно подтвердить из-за исключительного сбоя, создаёт полный возврат.
- Регулярная серия создаёт ровно число занятий пакета или полностью откатывается при конфликте.
- Checkout сопровождаемой программы недоступен без свободной capacity куратора.
- Две одновременные покупки последнего места не превышают
max_active_enrollments. - Оплаченный резерв capacity сохраняется до назначения куратора.
- Запрос возврата в первые 30 дней после активации создаёт аудируемый расчёт с удержаниями по временной политике.
- Не прикреплённые к доменной сущности незавершённые загрузки удаляются через 24 часа.
- Заявка ожидания capacity не создаёт заказ, оплату, резерв или enrollment и не дублируется для того же ребёнка.
- Права академического руководителя и администратора меняются через Django Admin и применяются без изменения кода.
- Ученик может войти в класс до 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. Основа, публичный сайт и аккаунты
- репозиторий, Django, PostgreSQL и settings;
- custom User с единственным логином по подтверждённому email;
- parent и student profiles;
- регистрация, вход и восстановление;
- templates/static/locale;
- публичные страницы, преподаватели, услуги, каталог курсов;
- RU/EN, SEO;
- SchoolSettings, CI и базовые тесты.
Этап 1. «Первая ступень»
- модели course/module/lesson/assignment и RU-контент;
- Stripe Checkout core, EUR, Order/Payment/Refund;
- guided plan и snapshots условий;
- eligibility и
CourseCapacityReservation; - семейная покупка,
FamilyCourseGroupи надбавка за дополнительного ребёнка; - покупка, enrollment
pending_curatorи повторное зачисление; - назначение/замена куратора в течение двух рабочих дней;
- активация и первый урок;
- gated progress;
- VideoAsset, upload и
ffprobe; - submissions, версии, недельный лимит и SLA;
revision_requested/passedи открытие следующего урока;- текст, видео, таймкоды и учебные диалоги;
- 12-месячный срок, закрытие доступа после выпуска и политика хранения видео;
- временная 30-дневная политика возврата с ручным расчётом;
- минимальные permissions академического руководителя и AuditLog;
- уведомления и end-to-end тесты среза.
Этап 2. LiveKit: инфраструктурная проверка
- self-hosted server;
- systemd, TLS, домен и TURN;
- проверка сети и health check;
- прототип token endpoint;
- проверка устройств и музыкального режима в Safari/Chrome;
- без окончательной связи с booking до этапа 4.
Этап 3. Индивидуальный календарь, пакеты и платежи
- заявки и admin approval;
- availability, exceptions, timezone и slot calculation;
busy_starts_at/busy_ends_atи обязательные exclusion constraints;- Stripe Checkout для разового занятия;
- 30-минутный hold и пятиминутный технический резерв;
- пакеты с фиксированным числом занятий и баланс;
- конечные регулярные серии;
- отмена, перенос и возвраты;
- правило 24 часов, вход с опозданием до 15 минут и продление занятия до 30 минут;
- permissions, уведомления и тесты конкурентных сценариев.
Этап 4. LiveKit-класс
- окончательный token endpoint, связанный с
LessonBooking; - classroom UI;
- разрешённое окно входа;
- режимы «Разговор» и «Музыка»;
- webhooks посещаемости;
- fallback URL;
- end-to-end тест занятия.
Этап 5. Академический контроль и выпуск
- специализированный обзор проверок с фильтрами;
- внутренние заметки и эскалации;
- контроль просрочки;
- выпускная задача;
- диплом и нотная запись;
- permissions, аудит, уведомления и тесты.
Этап 6. Production hardening
- timers и повторная доставка уведомлений;
- backups и проверка восстановления;
- security, privacy и retention backup;
- monitoring сайта, LiveKit и диска;
- deploy docs;
- проверка Stripe live mode.
35. Критерии приёмки MVP
- Сайт работает на RU и EN.
- Администратор создаёт преподавателей, услуги и разные цены.
- Родитель создаёт несколько детей.
- Родитель подаёт заявку.
- Только администратор подтверждает ученика.
- Администратор задаёт расписание.
- Родитель видит слоты в своём timezone.
- Работает разовое занятие.
- Работает пакет и баланс.
- Работают отмена и перенос.
- Администратор создаёт регулярную серию.
- Занятие открывает LiveKit-класс.
- Посторонний не получает токен.
- RU-версия программы публикуется независимо, а незавершённая EN-версия показывает
coming_soon. - У enrollment есть один текущий куратор с историей назначений.
- Сроки enrollment начинаются только после назначения куратора и открытия первого урока.
- Следующий урок открывается только после решения
passed. revision_requestedоставляет следующий урок закрытым.- Отчёт можно отправить в любой день; основные дни проверки — понедельник и четверг.
- Первичная и повторная проверки учитываются в лимите две проверки в неделю на enrollment.
- Повторная версия не создаёт новое оплаченное задание.
- Каждая принятая в очередь версия получает ответ не позднее четырёх рабочих дней.
- Куратор заменяется без согласования, с сохранением истории и автоматическим уведомлением.
- Общий диалог enrollment и обсуждение задания работают внутри платформы.
- Срок программы составляет до 12 календарных месяцев с активации; завершить её можно раньше.
- Финальный
passedсоздаёт выпускной процесс и закрывает доступ к материалам курса. - Родитель получает защищённый доступ к диплому и нотной записи.
- Можно загрузить видео до 60 минут или значения настройки.
- Куратор отвечает текстом, видео и таймкодами.
- Пользователь не видит штатного скачивания.
- Администратор скачивает оригинал.
- Файлы после истечения доступа и не прикреплённые загрузки после 24 часов удаляет management command.
- Критические операции покрыты тестами.
- Проект разворачивается без Docker.
- База и media резервируются вне VPS.
- Видео ребёнка публикуется как учебный пример только при действующем согласии.
- Форма интереса к английской версии сохраняет уникальную заявку с согласием и уведомляет администратора.
- Email является единственным логином и подтверждается до покупки или бронирования.
- После завершения, истечения или отзыва курса ребёнок может купить его повторно с новым enrollment и сохранением старой истории.
- Одновременно у ребёнка не более одного незавершённого enrollment одного курса.
- Production-оплата работает через Stripe Checkout в EUR.
- Разовый слот удерживается 30 минут и ещё пять минут только для технического завершения платежа.
- Повторный Stripe webhook не дублирует оплату, заказ, booking, пакет, enrollment или возврат.
- Пересечения преподавателя и ребёнка запрещены PostgreSQL с учётом буферов.
- Регулярная серия содержит фиксированное число занятий оплаченного пакета и не бывает бессрочной.
- Продажа программы недоступна без свободной capacity куратора.
- Capacity резервируется на время checkout, а куратор назначается не позднее двух рабочих дней после оплаты.
- Если назначение в срок невозможно, родитель выбирает согласованное ожидание или полный возврат.
- Семейное участие создаёт отдельный enrollment и обратную связь для каждого ребёнка; надбавка за каждого дополнительного ребёнка составляет 20% snapshot-цены плана.
- Запрос возврата в течение 30 дней после активации обрабатывается вручную с аудируемыми удержаниями за открытые уроки и работу куратора.
- Доступы сотрудников настраиваются через Django Admin.
- Ученик может присоединиться с опозданием до 15 минут, а занятие завершается позднее не более чем на 30 минут без пересечения расписания.
- При отсутствии capacity родитель оставляет уникальную заявку ожидания без оплаты и возвращается на обычный checkout после появления места.
36. Решения до production
Нужно выбрать:
- срок действия пакетов;
- максимальный размер видео;
- booking horizon;
- интервалы напоминаний;
- юридически окончательную формулу возврата, заменяющую временный ручной расчёт;
- календарь праздничных и иных нерабочих дней для SLA;
- срок подготовки диплома и нотной записи;
- формат нотной записи и кто выбирает сочинение;
- наличие согласий на перенос существующих видео детей в учебные примеры;
- LiveKit domain/TURN;
- email provider;
- внешнее хранилище backup.
37. Будущие расширения
Redis
Добавлять при необходимости:
- кэш календаря;
- distributed locks;
- multi-process coordination;
- LiveKit multi-node.
Celery
Добавлять при:
- автоматическом транскодировании;
- большой очереди email;
- тяжёлых фоновых задачах.
Cloudflare R2
Добавлять при:
- нехватке локального диска;
- необходимости direct upload;
- внешнем media storage;
- масштабировании web-сервера.
Docker
Добавлять при:
- росте команды;
- сложности воспроизведения окружения;
- появлении нескольких worker-сервисов.
Отдельный LiveKit VPS
Добавлять при:
- росте параллельных комнат;
- влиянии медиатрафика на сайт;
- необходимости независимого обслуживания.
38. Инструкции для Codex
Codex должен:
- Читать это ТЗ перед архитектурными изменениями.
- Не добавлять запрещённые технологии без команды.
- Не переписывать соседние модули без необходимости.
- Создавать миграции при изменении моделей.
- Добавлять тесты вместе с функциональностью.
- Использовать транзакции для оплат, бронирований и балансов.
- Использовать service layer для бизнес-операций.
- Проверять permissions.
- Не использовать
floatдля денег. - Не использовать naive datetime.
- Не отдавать закрытые media по публичному URL.
- Не удалять финансовую историю каскадом.
- Не привязывать доменную модель к платёжному провайдеру.
- Использовать
FileField/VideoAsset, а не ручные абсолютные пути. - Запускать релевантные тесты после изменений.
- При неоднозначности фиксировать допущение.
- Предпочитать простой явный код.
- Обновлять ТЗ после новых решений.
- Не позволять родителю устанавливать
passedдля guided-урока. - Не расходовать недельный слот при сохранении черновика.
- Изменять куратора, сроки и прогресс только через сервисные функции с аудитом.
- Не смешивать сообщения семье с внутренними заметками сотрудников.
- Проверять согласие перед публикацией детского видео как учебного примера.
- Не использовать
GenericForeignKeyдляOrderItem. - Не подтверждать booking только по return URL Stripe; использовать общий идемпотентный сервис и проверенный webhook.
- Не заменять database exclusion constraints одной только проверкой Python.
- Не создавать checkout сопровождаемой программы без действующего резерва capacity куратора.
- Не сокращать срок хранения учебных видео ниже срока, рассчитанного по политике 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. Изменения относительно предыдущей версии
- Бесплатная демопрограмма, бесплатное зачисление и связанные маршруты, модели, сценарии, тесты и этап реализации удалены из MVP. До покупки используются публичные материалы: видео об авторе и методике, примеры результатов и отзывы.
- «Первая ступень» теперь имеет единый срок до 12 календарных месяцев с активации. Автоматическая заморозка на 60 дней, дополнительные продления и разделение на шесть месяцев сопровождения и 12 месяцев материалов удалены.
- При успешном завершении программы доступ к её урокам, учебным видео и диалогам закрывается; выпускные документы остаются доступны в защищённом разделе.
- Срок ответа на отчёт изменён с двух до четырёх рабочих дней. Отчёты принимаются в любой день, а понедельник и четверг зафиксированы как основные операционные дни проверок; недельный лимит двух полноценных проверок сохранён.
- В публичной коммуникации «Первой ступени» используется роль «куратор». Куратор отделён от преподавателя индивидуальных занятий: добавлены
CuratorProfile,CourseCuratorEligibilityиEnrollmentCuratorAssignment. - Дата рождения ребёнка стала обязательной. Добавлены музыкальное образование родителя и семейное участие: отдельное enrollment, отчёты и обратная связь для каждого ребёнка, базовая цена для первого и настраиваемая надбавка
+20%для каждого следующего. - Для возврата временно зафиксировано ручное правило: запрос возможен в течение 30 дней после активации, сумма учитывает открытые/пройденные уроки и работу куратора. Окончательная юридическая формула отложена.
- Права сотрудников больше не предполагаются неявно по роли: группы, model permissions и action permissions настраиваются в Django Admin. Академическому руководителю можно выдать доступ к ценам или платежам отдельным правом.
- Для индивидуальных занятий добавлены правила: отмена/перенос не позднее чем за 24 часа для семьи и преподавателя, вход ребёнка с опозданием до 15 минут, фактическое завершение не более чем на 30 минут позже без пересечения расписания.
- Отдельный электронный договор и комплексное принятие условий до оплаты пока не входят в объём MVP; они не блокируют временную операционную политику возвратов.
Конец спецификации.