iqplay — Техническое задание на MVP
Версия 2.1 · 2026-08-14
1. Обзор и цели MVP
1.1. Что это
iqplay — веб-приложение для любителей тенниса. Игрок ведёт рейтинг по шкале NTRP, участвует в турнирах и фиксирует результаты матчей с обоюдным подтверждением. Турниры — платформенные и однодневные, их создаёт пользователь с ролью «организатор». Матчи — одиночные и парные. Рейтинг — динамический: система предсказывает исход матча по разнице рейтингов и корректирует оценку по фактическому счёту (полный механизм — §6.4).
1.2. Проблема и аудитория
- Аудитория: любители тенниса (RU-рынок), уровень NTRP 2.0–4.5, играющие в клубах и парках, желающие объективно мерить прогресс и находить равных соперников.
- Проблема: разрозненные результаты, нет единого объективного рейтинга, нет удобного способа организовать турнир в клубе и спланировать игры.
1.3. Цели MVP
- Игрок получает осмысленный NTRP-рейтинг после калибровочных матчей и видит его динамику.
- Игрок находит соперников в общей ленте, вызывает на матч, фиксирует одиночные и парные результаты с фото.
- Организатор создаёт однодневный турнир (3 формата, одиночный/парный), собирает заявки, назначает пары и проводит его.
- Платформенный админ модерирует пользователей и турниры.
1.4. Не цели MVP (см. §11)
Сообщества и гостевой доступ, Double Elimination, отдельный парный рейтинг, push-уведомления, реальные выплаты призов, нативные приложения, многоязычность, формат Round Robin.
2. Роли и матрица прав
| Возможность | Игрок | Организатор | Глоб. админ |
|---|---|---|---|
| Регистрация с самооценкой NTRP, профиль, история рейтинга | ✅ | ✅ | ✅ |
| Запись матча (одиночного/парного) с фото, вызов, подтверждение/оспаривание | ✅ | ✅ | ✅ |
| Лента платформы (матчи + турниры), каталог турниров | ✅ | ✅ | ✅ |
| Заявка на участие в турнире | ✅ | ✅ | ✅ |
| Создание турниров, приём заявок, назначение пар, результаты, споры своих турниров | — | ✅ | ✅ |
| Выдача/снятие роли «организатор» | — | — | ✅ |
| Глоб. модерация: люди, турниры, баны, audit-log | — | — | ✅ |
Примечания: - Роль «организатор» выдаёт глобальный
админ (тумблер на admin/person). - Роли игрока
накопительные: «организатор» — тоже игрок. -
Глобальный админ работает в отдельном тёмном UI
(/admin, см. §5.6), одна роль
super-admin, иерархии нет.
3. Глоссарий и ключевые сущности
| Термин | Значение |
|---|---|
| NTRP | National Tennis Rating Program — шкала 1.0–7.0, шаг 0.5 (отображаемый уровень) |
r (internal rating) |
непрерывная внутренняя оценка силы на оси NTRP (2 знака) |
RD (rating deviation) |
мера неопределённости рейтинга (Glicko-логика) |
| Калибровка / Подтверждён | статусы зрелости рейтинга; стартуют с обязательной самооценки при регистрации (см. §6.4 «Жизненный цикл») |
| Турнир | однодневное соревнование одного из 3 форматов (playoff/groups/swiss), одиночное или парное |
| Товарищеский матч | матч вне турнира, требует обоюдного подтверждения |
| Турнирный матч | матч внутри турнира, результат проставляет организатор |
| Вызов (challenge) | приглашение сыграть с предложенными датой/местом |
| Заявка (entry) | запрос игрока на участие в турнире, требует одобрения организатора |
| Пара | команда из двух игроков в парном матче/турнире; в турнире состав пары назначает организатор |
| Санкция (suspension) | набор ограничений, наложенный глоб. админом |
Полный перечень сущностей данных — в §9.
4. Сквозные сценарии
Сценарии end-to-end, связывающие модули. Каждый — последовательность экранов и системных действий.
4.1. Онбординг и первый рейтинг
Регистрация (auth) → объяснение шкалы и
обязательная самооценка уровня
(onboarding, сидирует r с макс.
RD) → калибровочные матчи → после порога (≈5 матчей,
RD ниже порога) статус становится «Подтверждён»,
показывается NTRP (profile-me).
4.2. Найти соперника и сыграть
profile-other / поиск → «Вызвать» → вызов с датой/местом
→ соперник принимает/переносит (match-challenge) →
согласовано, событие в календаре (calendar-me) → после игры
один из игроков фиксирует счёт (match-record) → второй
подтверждает или оспаривает (match-confirm) →
confirmed → пересчёт рейтинга обоих, запись в историю
(rating-history).
4.3. Подать заявку и сыграть турнир
Игрок находит турнир в каталоге/ленте (home,
tournament) → «Подать заявку»
(tournament-apply) → организатор принимает
(tournament-entries) → для парного турнира организатор
назначает пары → жеребьёвка → игрок видит сетку и расписание.
4.4. Провести турнир
Организатор → мастер из 4 шагов: тип — одиночный/парный
(tournament-create-type) → число участников
(tournament-create-count) → формат из трёх —
playoff/groups/swiss (tournament-create-format) →
параметры: дата (один день), best-of-N, приз, приём заявок
(tournament-create-params) → приём заявок
(tournament-entries) → [парный: назначение пар] →
жеребьёвка (tournament-draw) → сетка → организатор
проставляет результаты → победитель.
4.5. Модерация спорного матча
Игрок оспаривает результат товарищеского матча
(match-confirm → Disputed) → матч попадает в очередь
платформенного админа (/admin, §5.6)
→ админ решает (засчитать как есть / исправить счёт / аннулировать) →
состояние матча и рейтинг обновляются. Споры по турнирным результатам
решает организатор турнира в пределах своего турнира.
5. Функциональные требования по модулям
Формат: каждый модуль — список требований
FR-<модуль>-<n>. Экраны модуля перечислены;
детальные спецификации экранов — в §6.
5.1. Аутентификация и аккаунт
Экраны: auth, onboarding.
- FR-AUTH-1. Регистрация по email + пароль (пароль хранится как argon2/bcrypt-хеш).
- FR-AUTH-2. Вход по email + пароль. Восстановление пароля по email-ссылке.
- FR-AUTH-3. Вход/привязка через Telegram (Telegram Login Widget, OAuth). Привязка к существующему аккаунту по email при совпадении.
- FR-AUTH-4. Сессия — HTTP-only cookie; logout инвалидирует сессию.
- FR-AUTH-5. Онбординг после регистрации: объяснение
NTRP-шкалы и обязательная самооценка уровня (выбор на
шкале 1.0–7.0, шаг 0.5) — сидирует стартовый
rс максимальнымRD. Без выбора уровня онбординг не завершается. - FR-AUTH-6. Профиль аккаунта: имя, аватар (загрузка в объектное хранилище), email, привязка Telegram.
5.2. Профиль и NTRP-рейтинг
Экраны: profile-me, profile-other,
rating-history, rating-leaderboard.
- FR-PROF-1. Свой профиль: карточка рейтинга (NTRP + тренд), статистика (Победы/Поражения, матчи), «Мои матчи» (только матчи с участием пользователя), блок «Ближайшее событие». Списков сообществ и discover-блоков нет.
- FR-PROF-2. Чужой профиль: рейтинг, H2H («личные встречи» с текущим игроком), кнопка «Вызвать на матч».
- FR-PROF-3. Статусы рейтинга: Калибровка (ориентировочное «≈3.5»), Подтверждён (число + тренд). NQ-статус отсутствует — самооценка при регистрации обязательна. Логика статусов — §6.4 «Жизненный цикл».
- FR-PROF-4. История рейтинга: график
rво времени + событийные точки матчей (вверх/вниз), фильтр периода. - FR-PROF-5. Тренд считается как Δ
rза период (30/90 дней), цвет success/danger. - FR-PROF-6. Пересчёт рейтинга — серверный, по триггерам §6.4 «Триггеры пересчёта»; клиент только отображает.
- FR-PROF-7. Общий рейтинг
(
rating-leaderboard): лидерборд игроков, отсортированный поrпо убыванию. Переключатель одиночный / парный (в парном ранжируются пары по рейтингу пары). Фильтры: география (город / страна) и «мой уровень» (окно ±0.5 NTRP). Строка каждого игрока — позиция, имя, район, W–L,r. Своя позиция подсвечена (.lb-row.me) и продублирована в карточке «Твоя позиция» вверху; вход — из карточки рейтинга вprofile-me. Данные — серверные, пагинация/lazy-load длинного списка.
5.3. Турниры
Экраны: tournament-create-type,
tournament-create-count,
tournament-create-format,
tournament-create-params, tournament,
tournament-apply, tournament-entries,
tournament-doubles, tournament-draw,
tournament-bracket-single,
tournament-groups-playoff, tournament-swiss,
tournament-calendar.
- FR-TOUR-1. Создание (роль «организатор») —
мастер из 4 шагов: (1) тип — одиночный/парный
(
tournament-create-type); (2) число участников (tournament-create-count); (3) формат из трёх — Плей-офф + утешительный, Группы → платина/золото, Швейцарская система — с инлайн-настройкой (tournament-create-format); (4) параметры — дата (1 день), best-of-N, призовой фонд, приём заявок (tournament-create-params). - FR-TOUR-2. Турниры платформенные: каталог виден всем зарегистрированным.
- FR-TOUR-3. Заявки: игрок подаёт заявку
(
applied), организатор принимает (accepted) или отклоняет (declined); статусы турнираdraft → registration → active → completed. - FR-TOUR-4. Жеребьёвка/посев по внутреннему рейтингу (алгоритм §6.7); генерация структуры сетки/групп.
- FR-TOUR-5. Визуализация: playoff — олимпийская сетка + утешительная; groups — таблицы групп + платиновый и золотой плей-офф; swiss — таблица очков + пары по турам.
- FR-TOUR-6. Проведение: организатор проставляет результаты турнирных матчей (без подтверждения второго игрока); сетка автоматически прогрессирует (§6.7 «Автоперевод»).
- FR-TOUR-7. Standings групп с тай-брейками (§6.8); определение выходящих в плей-офф.
- FR-TOUR-8. Календарь турнира: расписание матчей по датам/кортам (список / по кортам), статусы матчей (scheduled/live/completed).
- FR-TOUR-9. Обзор турнира: стадии, ближайшие матчи, призовой фонд, победитель по завершении.
- FR-TOUR-10. Парный турнир: заявки индивидуальные;
после приёма заявок организатор формирует пары из принятых игроков;
посев и сетка — по парам (рейтинг пары = среднее
rпартнёров). - FR-TOUR-11. Жеребьёвка
(
tournament-draw): после приёма заявок организатор запускает жеребьёвку — посев по рейтингу, предпросмотр структуры; «Пережеребьёвка» меняет только порядок равных по рейтингу; «Зафиксировать» лочит (drawLocked) и генерирует структуру, турнир →active. - FR-TOUR-12. Правка пар швейцарки: до старта тура организатор может вручную переставить игроков в парах поверх авто-жеребьёвки; запрет повторов — подсказка, но переопределяется; правки логируются.
5.4. Матчи
Экраны: home, matches,
match-record, match-confirm,
match-invite.
- FR-MATCH-1. Главная
(
home): большая кнопка «Записать матч» + лента платформы — последние подтверждённые матчи, карточки турниров (анонс/активные) и посты-новости (блог платформы и рекламные посты с внешней ссылкой; ведутся из админки, FR-ADM-8) вперемешку по времени. Над лентой — фильтр Все / Новости / Матчи / Мои события. Полный список матчей —matches. - FR-MATCH-2. Запись товарищеского матча: выбор
соперника, ввод счёта по сетам (табло), формат, место/время; отправка →
статус
pending. Переключатель «Парный матч»: выбор партнёра и двух соперников; подтверждает любой игрок команды соперников. - FR-MATCH-3. Подтверждение: второй игрок видит
pending-матч, подтверждает (→confirmed, пересчёт рейтинга) или оспаривает (→disputed, в очередь админа). - FR-MATCH-4. Приглашение/вызов
(
match-invite): создание вызова с предложенными датой/местом/типом и комментарием. - FR-MATCH-5. Состояния матча:
pending/confirmed/disputed/tournament(всегда confirmed). Перечень состояний — §6.6. - FR-MATCH-6. Валидация счёта: корректность по выбранному формату best-of-N (число сетов, тай-брейки), запрет нелегальных счётов.
- FR-MATCH-7. Только
confirmedиtournamentматчи влияют на рейтинг;pending/disputed— нет. - FR-MATCH-8. К матчу можно приложить до 3 фото (объектное хранилище); фото видны в карточке матча и на экране подтверждения.
- FR-MATCH-9. Полный список матчей
(
matches): фильтр Все / Турниры / Товарищеские. Одиночные и парные матчи показываются вместе внутри каждой категории — отдельного фильтра «Парные» нет, тип матча помечается на карточке.
5.5. События и календари
Экраны: calendar-me, tournament-calendar,
match-challenge. Обновления: блок «Ближайшее событие» в
profile-me, кнопки 📅 в
tournament/match-confirm, ссылка в
matches.
- FR-EVT-1. Календарь игрока: мини-сетка месяца (точки на днях с событиями) + agenda по датам, фильтры (Все / Вызовы / Матчи / Турниры).
- FR-EVT-2. Типы событий:
challenge(pending-вызов),friendly(подтверждённый товарищеский),tournament_match,tournament_milestone(дедлайн регистрации/старт/финал). Статусы:pending/scheduled/live/completed. - FR-EVT-3. Принять вызов
(
match-challenge): 3 Возможных варианта — Принять / Предложить другое время (drawer пересогласования: дата+время+место+заметка) / Отказаться. Поддерживается одна итерация контр-предложения. - FR-EVT-4. «Добавить в системный календарь» —
генерация
.ics(кнопка 📅 у ближайших матчей/событий). - FR-EVT-5. Календарь турнира: матчи по датам/кортам, статусы; tap → карточка матча.
5.6. Админ-консоль (платформенная)
Экраны: admin/login, admin/dashboard,
admin/people, admin/person,
admin/communities, admin/community,
admin/audit-log, admin/news,
admin/suspend. Отдельный тёмный UI, desktop-first.
- FR-ADM-1. Отдельный вход админа (email+пароль+2FA-код).
- FR-ADM-2. Dashboard: метрики (игроков, турниров, активных санкций, новых регистраций за 7 дней), свежие админ-действия, очередь спорных матчей.
- FR-ADM-3. Таблица людей: фильтры, поиск, мульти-select, bulk-действия; детальный экран игрока содержит тумблер роли «организатор».
- FR-ADM-4. Гибкая модель санкций: чек-боксы
ограничений для игрока (
cannot_login,cannot_record_matches,cannot_join_tournaments,hidden_from_search,cannot_create_tournaments— для организаторов), длительность (до даты / бессрочно), причина (категория + обязательный комментарий + внутренняя заметка). - FR-ADM-5. Состояния сущности:
active/suspended/deleted(soft-delete). - FR-ADM-6. Audit-log: хронология всех админ-действий с фильтрами; запись при каждом действии.
- FR-ADM-7. Применение санкций влияет на доступ соответствующего пользователя в основном приложении.
- FR-ADM-8. Новости и блог (
admin/news): CRUD постов в ленту приложения. Поля — заголовок, текст, изображение, тип (новость / реклама), внешняя ссылка (обязательна для рекламы, ведёт наружу и помечается «Реклама»), статус (черновик / опубликован / по дате), закрепление вверху блока «Новости». Посты попадают в лентуhome(FR-MATCH-1) и фильтруются вкладкой «Новости».
5.7. Уведомления
- FR-NOTIF-1. In-app уведомления (колокольчик + бейдж): подтверди матч, входящий вызов, заявка на турнир принята/отклонена, твой матч оспорен/решён, турнирные вехи.
- FR-NOTIF-2. Доставка — websockets. Push-уведомления — вне MVP.
FR-NOTIF-3. Email — транзакционные (подтверждение регистрации, сброс пароля). Бан-уведомления.
6. Карта экранов и спецификации
Шаблон спецификации экрана:
файл мокапа · назначение · доступ · ключевые элементы · состояния · действия и переходы · данные (читает/пишет) · валидации.
Дизайн каждого экрана уже зафиксирован в мокапах mockups/ —
ТЗ описывает поведение и данные, не переопределяя визуал.
Полная карта — 35 экранов (26 мобильных + 9 админских):
| # | Файл | Модуль | Назначение | Доступ |
|---|---|---|---|---|
| 1 | auth.html |
Auth | Вход / Регистрация (табы) | Все |
| 2 | onboarding.html |
Auth | NTRP-шкала + обязательная самооценка | Игрок (новый) |
| 3 | home.html |
Главная | Лента платформы + CTA «Записать матч» | Игрок |
| 4 | profile-me.html |
Профиль | Свой профиль + «Мои матчи» | Игрок |
| 5 | profile-other.html |
Профиль | Чужой профиль, H2H, вызов | Игрок |
| 6 | rating-history.html |
Профиль | График NTRP + события | Игрок |
| 7 | rating-leaderboard.html |
Профиль | Общий рейтинг игроков (одиночный/парный, фильтры) | Игрок |
| 8 | tournament-create-type.html |
Турниры | Мастер шаг 1: тип (одиночный/парный) | Организатор |
| 9 | tournament-create-count.html |
Турниры | Мастер шаг 2: число участников | Организатор |
| 10 | tournament-create-format.html |
Турниры | Мастер шаг 3: формат (playoff/groups/swiss) | Организатор |
| 11 | tournament-create-params.html |
Турниры | Мастер шаг 4: дата, best-of-N, приз | Организатор |
| 12 | tournament.html |
Турниры | Обзор турнира + «Подать заявку» | Игрок |
| 13 | tournament-apply.html |
Турниры | Заявка игрока на турнир | Игрок |
| 14 | tournament-entries.html |
Турниры | Заявки: приём/отказ + назначение пар | Организатор |
| 15 | tournament-doubles.html |
Турниры | Парный турнир: пары + сетка пар | Игрок |
| 16 | tournament-draw.html |
Турниры | Жеребьёвка: посев, пережеребьёвка, фиксация | Организатор |
| 17 | tournament-bracket-single.html |
Турниры | Сетка: плей-офф + утешительный | Игрок |
| 18 | tournament-groups-playoff.html |
Турниры | Группы + платина/золото плей-офф | Игрок |
| 19 | tournament-swiss.html |
Турниры | Швейцарка: таблица очков + пары по турам | Игрок |
| 20 | tournament-calendar.html |
События | Расписание матчей турнира | Игрок |
| 21 | matches.html |
Матчи | Полный список матчей | Игрок |
| 22 | match-record.html |
Матчи | Запись матча: табло, парный, фото | Игрок |
| 23 | match-confirm.html |
Матчи | Подтверждение / оспаривание + фото | Игрок |
| 24 | match-invite.html |
Матчи | Создание вызова | Игрок |
| 25 | match-challenge.html |
События | Принять вызов (3 CTA + drawer) | Игрок |
| 26 | calendar-me.html |
События | Календарь игрока | Игрок |
| 27–35 | admin/*.html (9 экранов, вкл. news.html —
новости/блог) |
Админ | Как в v1.0 + тумблер «организатор» + блог | Глоб. админ |
admin/communities.html /
admin/community.html — задел под модерацию сообществ (см.
роадмап, §11); в MVP не используются.
Ниже — спецификации ключевых экранов. Алгоритм, который экран использует, описан тут же, рядом с экраном — документ самодостаточен. Остальные экраны следуют тому же шаблону; визуал — в мокапах.
6.1. auth.html
- Состояния: табы Вход/Регистрация; ошибки валидации (неверный email/пароль, занятый email); загрузка.
- Действия: Войти → главная (
home); Зарегистрироваться →onboarding; «Войти через Telegram» → OAuth-флоу. - Данные: пишет
User(регистрация), создаёт сессию. - Валидации: email-формат, минимальная длина пароля, уникальность email.
6.2. onboarding.html
- Назначение: объяснить NTRP-шкалу;
обязательная самооценка уровня (шаг 0.5, 1.0–7.0)
сидирует стартовый
rс максимальнымRD— без неё онбординг не завершается. - Переход: → главная (
home); статус сразу «Калибровка», калибровочные матчи уточняют рейтинг (алгоритм — §6.4).
6.3. profile-me.html /
profile-other.html
profile-me— Элементы: карточка рейтинга (NTRP/тренд или калибровка), статистика (W/L, матчи), «Мои матчи» (только с участием пользователя), блок «Ближайшее событие» (→calendar-me).profile-me— Состояния: калибровка («≈3.5», приглушённо); подтверждён; пустые секции.profile-other— Элементы: рейтинг, H2H («личные встречи»), кнопка «Вызвать на матч», ожидаемый счёт и вероятность победы против текущего игрока (предсказание — см. §6.4 «Предсказание»).- Данные: читает
User,RatingEvent,Match, ближайшееCalendarEvent.
6.4. rating-history.html + алгоритм
рейтинга NTRP
Экран: график r во времени + событийные
точки матчей (↑/↓) из RatingEvent, фильтр периода (30 / 90
дней / всё). Тренд = Δr за период, цвет
success/danger.
Рейтинг — динамический движок в духе UTR / ITF WTN / USTA dynamic NTRP: перед матчем предсказывает исход по разнице рейтингов, после матча сравнивает фактический счёт с предсказанием и сдвигает рейтинг на величину ошибки, с поправкой на уверенность, свежесть и вес матча. Константы — ориентир, калибруются на реальных данных.
Двухслойная модель. У игрока хранится не одно число, а три:
| Величина | Что это | Диапазон |
|---|---|---|
r — внутренний рейтинг |
непрерывная оценка силы на оси NTRP, до 2 знаков (напр.
3.62) |
1.00 – 7.00 |
RD — отклонение (uncertainty) |
насколько система уверена в r (Glicko-2 rating
deviation) |
~1.0 (новичок) → ~0.12 (устоявшийся) |
| NTRP — отображаемый уровень | r, округлённый до ближайшего 0.5 |
1.0 – 7.0 шаг 0.5 |
UI оперирует NTRP; предсказания и пересчёт работают
на r; RD управляет скоростью
изменений и переходом Калибровка → Подтверждён.
Шкала NTRP (полная). Отображаемый уровень — число из этой шкалы; в UI показывается только число и тренд, без «маркетинговых» названий уровней. Описания ниже — адаптация официальной таблицы USTA NTRP (General Characteristics of Various NTRP Playing Levels); используются в онбординге для самооценки и как справочник. Целевая аудитория iqplay — 2.0–4.5.
| NTRP | Характеристика игрока |
|---|---|
| 1.0 | Только начинает играть в теннис. |
| 1.5 | Небольшой опыт; работает в основном над тем, чтобы вводить мяч в игру. |
| 2.0 | Нужен игровой опыт; заметные пробелы в ударах, но знаком с базовыми позициями в одиночке и паре. |
| 2.5 | Учится оценивать, куда летит мяч (покрытие корта пока слабое); держит короткий медленный розыгрыш с равным соперником. |
| 3.0 | Достаточно стабилен на мячах средней силы, но не во всех ударах; не хватает контроля направления, глубины и мощи. |
| 3.5 | Улучшилась стабильность и контроль направления на умеренных мячах, но не хватает глубины и разнообразия; агрессивнее у сетки, лучше покрытие корта, развивается взаимодействие в паре. |
| 4.0 | Надёжные удары с контролем направления и глубины с обеих сторон; уверенно использует свечи, смэши, укороченные, удары с лёта; подачей иногда вынуждает ошибку; в паре видно взаимодействие. |
| 4.5 | Освоил мощь и вращение, справляется с темпом; уверенная работа ног, контроль глубины, меняет тактику под соперника; первая подача с силой и точностью, вторая — с расстановкой. |
| 5.0 | Хорошо читает игру, часто имеет коронный удар, вокруг которого строит игру; выигрывает очко или вынуждает ошибку с коротких мячей, уверенно завершает у сетки. |
| 5.5 | Мощь и/или стабильность — главное оружие; варьирует тактику и стиль, надёжен в стрессовых ситуациях. |
| 6.0 | Прошёл интенсивную подготовку для национальных турниров (юниорский/студенческий уровень); рейтинг NTRP уже не нужен — говорят результаты. |
| 6.5 – 7.0 | Игрок международного / профессионального уровня. |
Жизненный цикл.
Регистрация ──▶ обязательная самооценка ──▶ Калибровка ──RD ниже порога──▶ Подтверждён
сидирует r с макс. RD (provisional) │
быстрый пересчёт долгий простой
(высокий K) ─▶ RD растёт ─▶ обратно в provisional
| Статус | Условие | Поведение |
|---|---|---|
| Калибровка | < 5 матчей / 10 сетов, RD высок |
NTRP ориентировочный («≈3.5»), задан самооценкой; K
большой — рейтинг движется быстро |
| Подтверждён | RD ниже порога |
стабильный NTRP с трендом |
| Реактивация | простой > ~6 мес. | RD подрастает → первые матчи после паузы двигают
сильнее |
Предсказание (используется здесь, на
profile-other, match-challenge,
match-record). Из разницы рейтингов — ожидаемая доля геймов
игрока A (margin-based):
E_A = 1 / (1 + 10^( −(r_A − r_B) / S )) S ≈ 1.7 (пунктов NTRP)
| Δ = r_A − r_B | Ожидаемая доля геймов A | ≈ Ожидаемый счёт сета |
|---|---|---|
| 0.00 | 50 % | 6:6 — равная игра |
| 0.25 | 58 % | ~6:4 |
| 0.50 | 66 % | ~6:3 |
| 1.00 | 80 % | ~6:2 |
| 1.50 | 88 % | ~6:1 |
| 2.00 | 94 % | ~6:0 |
Сравнение и пересчёт. Фактическая доля геймов — посетово (как в WTN, чтобы один «баранкой» взятый сет не перекосил картину):
A_A = среднее по сетам ( геймы_выиграны / геймы_в_сете )
Δr_A = K_eff · w · (A_A − E_A)
r_A ← r_A + Δr_A r_B ← r_B − Δr_A (приблизительно нулевая сумма)
A_A > E_A— сыграл лучше ожидания →rрастёт; работает даже при поражении (ждали 6:1, проиграл 4:6 → рост).A_A < E_A→rпадает у фаворита, растёт у андердога.K_eff = K_base · f(RD)— ориентир ~0.25 (provisional) → ~0.06 (подтверждён). Каждый матч уменьшаетRD, простой — увеличивает.
Вес матча w ∈ (0,1] — произведение
факторов: формат (best-of-3 > короткий сет);
соревновательность (разрыв > ~2 ступеней NTRP
малоинформативен → вес затухает); надёжность соперника
(vs подтверждённого > vs provisional); свежесть
(окно ~30 матчей / 12 мес., экспоненц. затухание, полураспад ~3 мес.);
повторы соперника (анти-фарм, w_rep —
формула в «Целостности» ниже).
Парные матчи. Рейтинг пары = среднее r
партнёров; RD пары = среднее RD. Предсказание
E считается по разнице рейтингов пар той же формулой. Сдвиг
Δr = K_eff · (w × 0.5) · (A − E) делится поровну
между партнёрами (и зеркально — у соперников). Пониженный вес
×0.5 отражает меньшую информативность парного счёта для индивидуальной
силы. Отдельного парного рейтинга в MVP нет.
Что влияет: счёт по геймам/сетам и рейтинги
соперников до матча. Не влияет напрямую: факт
«победа/поражение» сам по себе (важна разница с ожиданием), возраст,
место в таблице. Товарищеский учитывается только после обоюдного
подтверждения (pending не считается, disputed
заморожен); турнирный — сразу; незавершённые — только доигранные сеты;
парные — с весом ×0.5 (см. «Парные матчи»).
Триггеры пересчёта. Переход матча в
confirmed (товарищеский) или проставление турнирного
результата организатором → вычислить E и A,
обновить r/RD обоих игроков (для парного —
обоих партнёров с обеих сторон, см. «Парные матчи»), записать
RatingEvent (before/after, expected, actual, weight) для
истории. Базово — инкрементально в момент подтверждения; полный пересчёт
(после исправления/аннулирования организатором) — фоновая задача,
перепроигрывающая RatingEvent хронологически. Рост
RD при простое — ночной джоб.
Отображение и тренд. NTRP = ближайшие 0.5 от
r; у самой границы — подсказка «почти 4.0». Тренд —
Δr за период, цвет
success/danger.
Целостность (anti-abuse). Результаты против только
что зарегистрированного соперника (RD на максимуме, ещё не
откалиброван) весят ≈0; большой разрыв между отображаемым и динамическим
рейтингом — флаг на ревью админа (сэндбэггинг/буст). Два предохранителя
заданы формулами — они особенно важны в калибровке, где
K_eff велик и рейтинг двигается быстро:
- Анти-фарм — убывающий вес повторов. В произведение
wвходит факторw_rep = max(0.5^(n−1), 0.1), гдеn— номер матча с тем же соперником (для парного — с той же парой соперников) за скользящие 30 дней: 1-й матч — вес 1.0, 2-й — 0.5, 3-й — 0.25, 4-й и далее — 0.1. Вне окна счётчик обнуляется. Серия матчей с одним «удобным» соперником быстро перестаёт двигать рейтинг. - Дисконт разгромов. Ошибка предсказания сверх порога
неожиданности учитывается частично:
e = A − E,e′ = sign(e) · ( min(|e|, T) + β · max(|e| − T, 0) )приT ≈ 0.25,β ≈ 0.5; в формуле пересчёта вместо(A − E)используетсяe′. Аномально разгромный (или аномально провальный) результат двигает рейтинг не на всю ошибку — избыток сверхTдисконтируется вполовину. Поверх — жёсткий кап на сдвиг за один матч:|Δr| ≤ 0.15(калибровка) /0.05(подтверждён).
Константы (ориентир, калибруется на реальных
данных): S ≈ 1.7;
K_base ≈ 0.25 → 0.06 по RD;
RD₀ ≈ 1.0 / RD_floor ≈ 0.12; порог
подтверждения ≈ 5 матчей / 10 сетов и RD < порога; окно
свежести ~30 матчей / 12 мес., полураспад ~3 мес.; анти-фарм
w_rep = max(0.5^(n−1), 0.1), окно повторов 30 дней; дисконт
разгромов T ≈ 0.25, β ≈ 0.5; кап сдвига
|Δr| ≤ 0.15 (калибровка) / 0.05
(подтверждён).
Методология-ориентир: UTR · ITF WTN · USTA dynamic NTRP · Elo.
6.5. match-record.html
- Элементы: переключатель «Парный матч» (выбор партнёра + двух соперников вместо одного), выбор соперника(-ов) (поиск по игрокам), табло счёта по сетам, выбор формата best-of-N, место/время, комментарий, до 3 фото (FR-MATCH-8), превью изменения рейтинга по введённому счёту (предсказание/пересчёт — §6.4, для парного — §6.4 «Парные матчи»).
- Состояния: пошаговый выбор; ошибка нелегального счёта; отправка.
- Действия: Отправить →
Match(status=pending), уведомление сопернику(-ам). - Данные: пишет
Match+MatchSet. - Валидации: легальность счёта по формату best-of-N (FR-MATCH-6); жизненный цикл — §6.6.
6.6. match-confirm.html + жизненный цикл
матча
- Элементы: карточка матча со счётом, превью изменения рейтинга, кнопки Подтвердить / Оспорить.
- Действия: Подтвердить →
confirmed+ пересчёт (§6.4 «Триггеры пересчёта»); Оспорить →disputed, в очередь платформенного админа. - Данные: читает
Match; пишет статус.
Жизненный цикл матча:
Товарищеский:
created ──отправка──▶ pending ──подтверждение B──▶ confirmed ──▶ [пересчёт §6.4]
│
└──оспаривание B──▶ disputed ──решение платформ. админа──▶ confirmed | corrected | void
Турнирный:
created ──результат от организатора──▶ confirmed ──▶ [пересчёт §6.4, прогресс сетки §6.7]
- Кто действует:
pending— только игрок B (подтвердить/оспорить);disputed— только платформенный админ; турнирный — только организатор. voidне влияет на рейтинг;corrected— платформенный админ задаёт верный счёт, далее какconfirmed.
6.7. Турниры: создание, сетки, посев и прогресс
Экраны: tournament-create-type (шаг 1:
одиночный/парный); tournament-create-count (шаг 2: число
участников); tournament-create-format (шаг 3: формат из
трёх — playoff/groups/swiss — с инлайн-настройкой);
tournament-create-params (шаг 4: дата (один день),
best-of-N, призовой фонд, приём заявок; валидации: число участников ≥
минимума формата, дата в будущем, призовой фонд необязателен);
tournament-apply, tournament-entries,
tournament-doubles, tournament-draw,
tournament-bracket-single,
tournament-groups-playoff, tournament-swiss.
Создание → Tournament(status=draft/registration).
Однодневный турнир: одна дата, расписание внутри дня.
Посев для всех форматов — по внутреннему рейтингу r на
момент жеребьёвки (выше r → выше сид; для парного турнира —
рейтинг пары, см. §6.4 «Парные матчи»).
Жеребьёвка. Организатор запускает после приёма
заявок. Посев детерминирован рейтингом r (для парного —
рейтинг пары). «Пережеребьёвка» переставляет только игроков с равным
r (случайный тай-брейк). «Зафиксировать»
лочит жеребьёвку (drawLocked=true) и генерирует структуру
формата; турнир → active.
Плей-офф + утешительный. Дополнить до ближайшей
степени двойки byes (топ-сидам); расстановка
1–N, 2–(N−1), …. Проигравшие 1-го круга
формируют утешительную single-elim; победитель утешительной берёт
(N/2+1)-е место.
Группы → платина/золото. Сиды по группам «змейкой»
(размер groupSize ∈ {2,3,4}), круговой внутри группы. По
итогам: топ-2 каждой группы → платиновый плей-офф (за
чемпионство), остальные → золотой плей-офф. В обеих
ветках — single-elim с перекрёстным посевом (1-е место группы против
2-го другой).
Швейцарская система. Туров R = ⌈log₂N⌉.
Тур 1 — посев рейтингом, верхняя половина против нижней (сид i против
сида i+N/2). Туры 2+: игроки в очковых группах (победа 1, поражение 0,
бай 1), внутри группы верх×низ по рейтингу; запрет повторных встреч (при
коллизии — обмен со следующим игроком); нижний из нечётной группы
«проваливается» (float) в следующую. Нечётное N → нижний без предыдущего
бая получает бай (1 очко). Правку пар тура организатор может внести
вручную до старта тура (FR-TOUR-12).
Автоперевод (прогресс). confirmed
турнирный матч → победитель продвигается в
TournamentMatch.nextSlot основной/платиновой ветки;
проигравший 1-го круга playoff → утешительная;
проигравшие групп по правилу распределения → золотой плей-офф. Для swiss
— по завершении тура пересчитываются очки и генерируется следующий
тур.
6.8. tournament-groups-playoff.html /
tournament-swiss.html + standings и тай-брейки
- Экран (groups): таблицы групп + платиновая и золотая сетки плей-офф; подсветка «ты».
- Сортировка в группе: 1) очки (победа = 1, поражение
= 0; ничьих в теннисе нет) → 2) личная встреча (head-to-head) → 3)
соотношение сетов → 4) соотношение геймов → 5) (остаточный) выше
внутренний
r. - Распределение по плей-офф: топ-2 каждой группы →
платиновый плей-офф (за чемпионство), остальные → золотой плей-офф; для
groupSize = 2— 1-е место → платина, 2-е → золото; дляgroupSize = 3— 1–2 → платина, 3 → золото; дляgroupSize = 4— 1–2 → платина, 3–4 → золото. - Экран (swiss): таблица очков + пары по турам, подсветка «ты».
- Сортировка standings (swiss): 1) очки (победа = 1,
поражение = 0, бай = 1) → 2) тайбрейк Бухгольц (сумма
очков всех сыгранных соперников) → 3) личная встреча (head-to-head) → 4)
внутренний
r.
6.9. admin/suspend.html (drawer)
- Элементы: чек-боксы ограничений игрока (см. FR-ADM-4), длительность (бессрочно/до даты), категория причины, комментарий (required), внутренняя заметка (optional).
- Действия: Suspend (разблокируется при валидной
форме) → создаёт
Suspension, меняет состояние сущности наsuspended, пишетAuditLogEntry, toast. - Данные: пишет
Suspension,AuditLogEntry; обновляетUser.status. > Остальные экраны (matches,match-invite,match-challenge,calendar-me,tournament-calendar,tournament-overview,tournament-apply,tournament-entries,tournament-doubles, админ-таблицы и детальные) специфицируются по тому же шаблону на этапе реализации, опираясь на мокапы.
7. Доступ: видимость и права
Сквозное правило для всех модулей. Функции
canSee(user, resource) и
canDo(user, action, resource): - Гостевого доступа нет —
все действия требуют аккаунта. - Турнир: платформенный,
каталог виден всем зарегистрированным. - Действия
организатора — только в пределах своих турниров (создание,
приём заявок, назначение пар, результаты, разбор споров). -
Санкции (§5.6) накладывают
запреты поверх ролевых прав (например,
cannot_record_matches блокирует FR-MATCH-2,
cannot_create_tournaments — создание турниров).
Алгоритмы (рейтинг NTRP, генерация сеток и посев, standings и тай-брейки, жизненный цикл матча) описаны в §6 — рядом с экранами, которые их используют: рейтинг — §6.4, жизненный цикл матча — §6.6, сетки/посев/прогресс — §6.7, standings — §6.8.
8. Архитектура (high-level)
[ PWA-клиент: Next.js (App Router), React ]
│ HTTPS / cookie-сессия
▼
[ API: Next.js Route Handlers / server actions (монолит) ]
│
├─▶ [ PostgreSQL ] ── Prisma ORM
├─▶ [ Очередь задач: BullMQ + Redis ] ── пересчёт рейтинга, прогресс турнира, рассылка, .ics
├─▶ [ Объектное хранилище S3-совместимое ] ── аватары, фото матчей
├─▶ [ Telegram OAuth ] ── вход
└─▶ [ Транзакционная почта (SMTP/Resend) ] ── регистрация, сброс пароля
- Клиент: Next.js PWA, mobile-first. Дизайн и
компоненты переносятся из текущего
mockups/assets/styles.css(CSS-переменные). Server Components для чтения, Client Components для интерактива. Service worker — offline-оболочка и установка. - API: монолит на Next.js (минимум инфраструктуры для MVP); выделение в отдельный Node-сервис — по мере роста.
- Auth: Auth.js (NextAuth) — credentials (email+пароль, argon2/bcrypt) + Telegram provider; сессия в HTTP-only cookie.
- Фоновые задачи: очередь для тяжёлого/асинхронного
(полный пересчёт рейтинга, рассылка уведомлений, генерация
.ics, ростRDпо расписанию). Инкрементальный пересчёт при подтверждении матча допустим синхронно. - Админка (
/admin) — тот же стек, отдельный layout/тема (тёмная), отдельная рольsuper-admin.
9. Модель данных
Сущности и ключевые поля (без полного DDL; финальные схемы — на этапе реализации через Prisma).
| Сущность | Ключевые поля | Связи |
|---|---|---|
| User | id, email, passwordHash, telegramId?, name, avatarUrl, isOrganizer(bool), ntrpStatus(calibrating/rated), selfAssessedNtrp, r, RD, createdAt, status(active/suspended/deleted) | 1–N RatingEvent |
| RatingEvent | id, userId, matchId, rBefore, rAfter, rdBefore, rdAfter, expected, actual, weight, createdAt | N–1 User, N–1 Match |
| Match | id, type(friendly/tournament), tournamentId?, tournamentMatchId?, isDoubles(bool), playerAId, playerA2Id?, playerBId, playerB2Id?, winnerId?, status(pending/confirmed/disputed/void), format(bestOfN), venue?, playedAt, createdBy, confirmedAt?, photos[] (до 3, URL) | 1–N MatchSet, 1–N RatingEvent |
| MatchSet | id, matchId, setIndex, gamesA, gamesB, tiebreakA?, tiebreakB? | N–1 Match |
| Tournament | id, name, format(playoff/groups/swiss), isDoubles(bool), params{slots, matchFormat, prize, date}, groupSize?, swissRounds?, drawLocked(bool), status(draft/registration/active/completed), createdBy | 1–N TournamentEntry, 1–N TournamentMatch, 1–N Group, 1–N Pair |
| TournamentEntry | id, tournamentId, userId, seed?, groupId?, pairId?, status(applied/accepted/declined) | N–1 Tournament, N–1 User |
| Pair | id, tournamentId, player1Id, player2Id, seed? | N–1 Tournament |
| TournamentMatch | id, tournamentId, bracket(main/consolation/group/platinum/gold/swiss), round, slot, entryAId?, entryBId?, matchId?, nextSlot?, nextSlotLoser? | N–1 Tournament, 0–1 Match |
| Group | id, tournamentId, name | 1–N TournamentEntry |
| MatchInvite | id, fromUserId, toUserId, proposedAt, venue?, type, note?, status(pending/accepted/declined/countered), counterProposedAt? | N–1 User×2 |
| Notification | id, userId, type, payload(json), createdAt, readAt? | N–1 User |
| Suspension | id, targetType(user), targetId, restrictions[] (включая
cannot_create_tournaments), duration(until/indefinite),
endsAt?, category, comment, internalNote?, createdBy, createdAt,
liftedAt? |
N–1 User |
| AuditLogEntry | id, actorId, action, targetType, targetId, meta(json), createdAt | N–1 admin User |
| NewsPost | id, title, body, imageUrl?, kind(news/ad), externalUrl? (required при kind=ad), status(draft/published/scheduled), pinned(bool), publishAt?, createdBy, createdAt | N–1 admin User |
Производные представления:
CalendarEvent (агрегат из MatchInvite,
Match, TournamentMatch, турнирных вех —
вычисляется, не отдельная таблица).
10. Нефункциональные требования
| Категория | Требование |
|---|---|
| Производительность | P95 загрузки ключевых экранов < 2.5 c на 4G; критичные данные — server-render; lazy-load тяжёлых списков (лента, таблицы) |
| Безопасность | пароли — argon2/bcrypt; CSRF-защита; rate-limit на auth и запись
матчей; авторизация (canDo) проверяется на сервере на
каждом мутирующем запросе; валидация всего ввода |
| Приватность | персональные данные минимизированы; согласие при регистрации;
hidden_from_search уважается |
| Локализация | UI — русский; даты/время — MSK; код готов к i18n (строки вынесены), но многоязычность вне MVP |
| Адаптив | mobile-first (база 420px), корректный таблет/десктоп; админка — desktop-first (1280+) |
| Доступность (a11y) | контраст по WCAG AA, видимый фокус, клавиатурная навигация, semantic HTML, tap-target ≥ 44px |
| PWA | устанавливаемость, offline-оболочка, иконки/манифест |
| Аналитика | ключевые события: регистрация, первый матч, подтверждение, создание турнира, активация; дашборд продуктовых метрик |
| Наблюдаемость | логирование ошибок сервера, трассировка фоновых задач рейтинга |
11. Вне скоупа MVP / роадмап
- Этап 2 — сообщества и гостевой доступ.
- Push-уведомления (web push / нативные).
- Реальные платежи и выплата призового фонда (в MVP — метаданные в UI).
- Формат Double Elimination.
- Отдельный парный рейтинг (в MVP парные влияют на общий с весом ×0.5).
- Нативные приложения iOS/Android.
- Многоязычность.
- Формат турниров Round Robin.
- Многократное пересогласование вызова (поддержана 1 итерация контр-предложения).
- Free/busy-видимость календарей между игроками; разрешение конфликтов времени.
- Иерархия ролей в админке (только
super-admin), appeal-процесс по банам, email-уведомления о санкциях. - Реальная интеграция с Google/Apple Calendar (только
.ics-экспорт).
12. Открытые вопросы
- Калибровочные константы NTRP (
S,K_base, порогиRD, окно свежести) — заданы как ориентир в §6.4 («Константы»); требуют подбора на реальных данных после набора матчей. - Источник первичного рейтинга: только самооценка + калибровка, или импорт внешнего (UTR/WTN) на старте? (MVP — самооценка + калибровка.)
- Granularity уведомлений: какие события обязательны в MVP vs можно отложить за пределы in-app.
- Правила досрочного завершения турнира (снятие игрока, walkover) — детализировать при реализации турниров.
- Вес парных матчей ×0.5 — калибруется на данных.
- Пороги анти-фарма и дисконта разгромов
(
w_rep, окно повторов,T,β, капы|Δr|) — заданы как ориентир в §6.4 («Целостность»); калибруются на реальных данных вместе с остальными константами.