Анкетирование и подбор тренировок — ME AI Club

Анкетирование и логика подбора тренировок

Детальный анализ всех опросов и алгоритма рекомендации тренировок в проекте.

1. Общая схема

В боте используются три типа анкетирования:

Тип Когда Куда сохраняется Для чего используется
Предварительная анкета При первом входе (/start → «Заполнить анкету») user_surveys + обновление users Профиль пользователя: пол, возраст, цели, ограничения, уровень. Пробный период 7 дней.
Опрос перед тренировкой «Мои тренировки» → опросник pre_workout_checkins Подбор конкретного видео: блок + видео внутри блока (правила + ChatGPT).
Опрос после тренировки «Завершить тренировку» check_ins Оценка нагрузки и самочувствия; опционально — соответствие пожеланиям.
┌─────────────────────────────────────────────────────────────────────────────┐
│                         ЖИЗНЕННЫЙ ЦИКЛ ПОЛЬЗОВАТЕЛЯ                         │
└─────────────────────────────────────────────────────────────────────────────┘

  /start (новый)          «Мои тренировки»           «Завершить тренировку»
       │                         │                            │
       ▼                         ▼                            ▼
  ┌─────────────┐         ┌─────────────┐             ┌─────────────┐
  │ Предваритель-│         │ Pre-workout │             │ Post-workout│
  │ ная анкета   │         │ опрос       │             │ опрос       │
  │ (имя, пол,   │         │ (самочувст- │   Тренировка │ (нагрузка,  │
  │ возраст,     │         │ вие, энергия│ ◄──────────►│ до/после)   │
  │ цели,        │         │ тело,       │             │             │
  │ здоровье)    │         │ пожелания,  │             │             │
  └──────┬───────┘         │ длительность│             └──────┬──────┘
         │                 └──────┬──────┘                    │
         ▼                        ▼                           ▼
  user_surveys +            pre_workout_                 check_ins
  users (level,             checkins                         │
  restrictions,                                            (load_rating,
  goals)                                                    wellbeing_before,
         │                        │                         wellbeing_after)
         └────────────────────────┼─────────────────────────┘
                                  │
                                  ▼
                    RecommendationService + AIService
                    → блок → видео (с учётом противопоказаний)

2. Предварительная анкета (онбординг)

Точка входа: /start → кнопка «Заполнить анкету» (start_questionnaire).

Файлы: handlers/start.py, состояния PreliminarySurveyStates, services/survey_service.pysave_preliminary_survey(), update_user_from_survey().

2.1. Последовательность шагов

Шаг Состояние FSM Вопрос / действие Сохранение в state / БД
1block1_nameКак к тебе обращаться? (имя из TG или своё)preferred_name; при первом ответе создаётся запись в users.
2block1_genderПол (Мужской / Женский / Другое)gender
3block1_ageВозраст (число 10–120)age
4block1_countryСтрана проживанияcountry
5Подтверждение профиля, переход к блоку 2
6block2_difficultiesСложности с внедрением спорта (множественный выбор)difficulties
7block3_experienceОпыт в спортеexperience_level
8block4_healthОграничения по здоровью (в т.ч. «Ничего из вышеперечисленного»)health_limitations
9block5_equipmentОборудование / инвентарьequipment
10block7_completionЗавершение: сохранение анкеты, обновление пользователя, активация триалаUserSurvey + обновление User

После завершения вызываются:

  • survey_service.save_preliminary_survey(...) — запись в user_surveys;
  • survey_service.update_user_from_survey(user, survey) — перенос в users: gender, age, country, equipment, restrictions (из health_limitations), goals (из difficulties + experience_level);
  • активация пробного периода (7 дней).

2.2. Связь с подбором тренировок

  • Уровень (user.level): по умолчанию 1; может меняться в настройках или по результатам пост-опроса (корректировка уровня).
  • Ограничения (user.restrictions): используются в RecommendationService.check_contraindications() — при несовместимости с блоком выполняется fallback на блок 2 (мобильность).
  • Цели (user.goals): передаются в ChatGPT при выборе конкретного видео из блока (ai_service.recommend_video_from_block()).

3. Опрос перед тренировкой (pre-workout)

Точка входа: «Мои тренировки» (my_trainings) или «Мои тренировки» из меню → start_pre_workout / _send_pre_greeting. После приветствия пользователь нажимает «Продолжить» → pre_block1_continue.

Файлы: handlers/main_menu.py (состояния PreWorkoutStates), services/survey_service.pysave_pre_workout_checkin().

Есть два сценария опроса перед тренировкой (оба ведут к подбору видео):

  • Основной (используется сейчас): блоки 1 → 2 → 3 → 4 → 4.5 → 5 → 5.5 → 6.
  • Сокращённый (legacy): «Готов к тренировке» → 4 коротких вопроса (настроение, энергия, сон, ограничения) → подбор.

3.1. Основной опрос (блоки 1–6)

Блок Состояние FSM Вопрос Варианты / поле Коэффициент Куда пишется в state
1block1_greetingПриветствие, готовность к короткому чекуПродолжить / ОтменаПри «Продолжить» — переход к блоку 2. В Redis: pre_workout_started (TTL 60 мин).
2block2_wellbeingКак себя чувствуешь?Полон(а) энергии, Нормально, Устал(а), Плохо, Свой вариантwellbeing_coefficient: 1.5 / 1.0 / 0.7 / 0.5pre_wellbeing, wellbeing_coefficient
3block3_energy_moodУровень энергии и настроение?На пике, Средний, Нужна зарядка, Настроение не оченьenergy_coefficient: 1.5 / 1.0 / 0.7 / 0.5pre_energy_level, pre_mood, energy_coefficient
4block4_bodyЧто стоит учесть перед тренировкой?Всё в порядке, Боль в мышцах, Дискомфорт, Что-то болит, Другоеbody_coefficient: 1.5 / 1.0 / 0.7 / 0.5pre_body_sensations, body_coefficient
4.5block4_5_wishesПожелания по тренировке?Нет, Спина/шея, Растяжка, Пресс, Ягодицы, После сидения, Свой вариантpre_wishes
5block5_durationПродолжительность и тип15–20 мин, ~30 мин, 30+ мин, Растяжка/разминкаpre_workout_duration, pre_workout_type
5.5block5_5_wellbeing_ratingСамочувствие сейчас (1–5)1–5pre_wellbeing_rating
6block6_completionВсё готово, начинаем?Да, поехали! / ПодождиПри «Да» — сохранение чек-ина и подбор тренировки.

При завершении вызывается survey_service.save_pre_workout_checkin(...) с полями: wellbeing, energy_level, mood, body_sensations, workout_duration, workout_type, wishes, wellbeing_rating.

3.2. Коэффициент интенсивности (intensity_coefficient)

Считается один раз перед вызовом рекомендации:

intensity_coefficient = (wellbeing_coef + energy_coef + body_coef) / 3
  • wellbeing_coef — из блока 2 (1.5 / 1.0 / 0.7 / 0.5).
  • energy_coef — из блока 3 (1.5 / 1.0 / 0.7 / 0.5).
  • body_coef — из блока 4 (1.5 / 1.0 / 0.7 / 0.5).

Используется в RecommendationService.suggest_block_by_state():

  • < 0.8 — восстановительные блоки (1, 2);
  • 0.8–1.2 — стандартные (3, 4, 6);
  • ≥ 1.2 — интенсивные (5, 7, 8) с учётом уровня.

3.3. Преобразование в формат RecommendationService

Данные из опроса Поле для рекомендации Правило
pre_wellbeingmood (mapped_mood)Словарь: «Полна/полон энергии» → отлично, «Нормально…» → нормально, «Немного устал(а)» → устал, «Утомлён(а)…» → плохо.
pre_energy_levelenergy (mapped_energy)Высокая → Высокий, Средняя → Средний, Низкая → Низкий.
sleepВ основном сценарии не спрашивается: всегда «Нормально».
pre_body_sensationslimitsПо ключевым словам: «болит»/«боль» → «Плохо себя чувствую», «дискомфорт»/«неприятные» → «Лёгкое недомогание», иначе «Нет».
pre_wishesuser_goalsДобавляются к существующим целям пользователя (user.goals) при вызове AI.

3.4. Legacy-опрос (4 шага)

Если пользователь попадает в сценарий с кнопкой «Готов(а) к тренировке» (pre_ready_yes), запускается короткий опрос:

  • pre_q1: настроение → pre_mood.
  • pre_q2: уровень энергии → pre_energy.
  • pre_q3: сон → pre_sleep.
  • pre_q4: ограничения → pre_limits.

Далее вызывается тот же get_recommended_video(mood, energy, sleep, limits, ...).

4. Опрос после тренировки (post-workout)

Точка входа: нажатие «Завершить тренировку» (finish_training_<video_id>).

Файлы: handlers/main_menu.py (состояния CheckInStates), services/user_service.pycreate_check_in().

4.1. Последовательность

Шаг Состояние FSM Вопрос Шкала Поле в state / БД
1waiting_for_load_ratingПодошла ли нагрузка?1 (совсем тяжело) – 5 (идеально)checkin_load_rating → CheckIn.load_rating
2waiting_for_wellbeing_beforeСамочувствие до тренировки?1 (никак) – 5 (полна/полон ресурса)checkin_wellbeing_before → CheckIn.wellbeing_before
3waiting_for_wellbeing_afterСамочувствие после?1 (разбит/разбита) – 5 (в теле и в моменте)checkin_wellbeing_after → CheckIn.wellbeing_after
4 (опц.)waiting_for_wishes_feedbackЕсли перед тренировкой был «свой вариант» пожеланий — насколько тренировка соответствовала?1–5Сохраняется в логи/детали.

После шага 3 (или 4) создаётся запись в check_ins: load_rating, wellbeing_before, wellbeing_after, mood="completed" и заметка о видео. Показывается случайное поддерживающее сообщение и кнопки «Хочу ещё одну тренировку» и «Главное меню».

Самочувствие до тренировки для пост-опроса: берётся из последнего pre-workout чек-ина (survey_service.get_user_last_checkin(user.id).wellbeing_rating) и подставляется в вопрос 2; при сохранении в CheckIn пишется переданный пользователем ответ (wellbeing_before).

5. Логика подбора тренировок

Цепочка: правила по состоянию → выбор блока → проверка противопоказаний → фильтр по уровню → выбор видео внутри блока (ChatGPT или случайный fallback).

5.1. Выбор блока (suggest_block_by_state)

Вход: mood, energy, sleep, limits, user_level (1–7), intensity_coefficient.

Порядок правил:

  1. Ограничения или плохой сон: limits in («Лёгкое недомогание», «Плохо себя чувствую») или sleep == «Очень плохо» → блок 2 (мобильность).
  2. Низкий коэффициент интенсивности (< 0.8): Восстановление: уровень ≤ 2 → блок 1, иначе блок 2.
  3. Высокий коэффициент (≥ 1.2): user_level ≥ 5 → блок 5 (Табата); user_level ≥ 3 → блок 7 (функциональные); иначе блок 3 (пилатес).
  4. Средний коэффициент (0.8–1.2): Низкая энергия или «устав» в mood → блок 1 (если level ≤ 2) иначе блок 2. Высокая энергия и отличное настроение → блок 5 (если level ≥ 4) иначе блок 3. Средняя энергия → блок 3. По умолчанию → блок 2.

Смысл блоков:

  • Блок 1: короткие узконаправленные (МФР, нейро-фитнес), восстановление.
  • Блок 2: мобильность, растяжка, дыхание.
  • Блок 3: пилатес. Блок 4: пресс. Блок 5: Табата (интервальные). Блок 6: с резиной. Блок 7: функциональные. Блок 8: интенсивные.

5.2. Противопоказания и fallback блока

get_recommended_block_with_fallback() сначала получает блок из suggest_block_by_state(), затем вызывает check_contraindications(block_number, user_restrictions_text, db). Проверяются ключевые слова: грыжи, операции, беременность, диастаз. Если блок противопоказан → возвращается блок 2 как безопасный.

5.3. Выбор конкретного видео (get_recommended_video)

  1. Рекомендованный блок (с учётом противопоказаний): get_recommended_block_with_fallback(...).
  2. Список видео блока: video_service.get_videos_by_block(recommended_block).
  3. Если видео нет — пробуют блок 2; если и там пусто — возвращают None.
  4. Фильтр по уровню: оставляют видео с abs(v.level - user_level) <= 1. Если таких нет — берут все видео блока.
  5. Выбор одного видео: если передан ai_service — вызывается ai_service.recommend_video_from_block(...); ответ GPT — JSON { "video_id", "video_title" }; при ошибке — случайный выбор из filtered_videos.

5.4. Роль ChatGPT

В промпт передаются: описание блока, данные анкеты (настроение, энергия, сон, ограничения), уровень пользователя, цели, противопоказания, список доступных видео. Задача модели: выбрать одну тренировку с учётом состояния, уровня, целей и безопасности. Ответ парсится; проверяется, что video_id входит в переданный список.

5.5. Fallback при отсутствии рекомендации

Если get_recommended_video() вернул None, вызывается video_service.get_next_video_no_repeat(user_id, user_level) — случайное видео по уровню (или уровень 1).

5.6. Программа тренировок (selected_program)

Если у пользователя задана программа (user.selected_program), в legacy-ветке pre-workout сначала вызывается video_service.get_next_program_video(...). Если видео по программе найдено — оно и отдаётся; иначе используется интеллектуальная рекомендация через get_recommended_video(). В основном сценарии (блоки 1–6) программа не используется — всегда подбор по состоянию.

6. Схемы потоков данных

6.1. Pre-workout → рекомендация (основной сценарий)

[Блок 2] Самочувствие     → pre_wellbeing, wellbeing_coefficient (1.5/1.0/0.7/0.5)
[Блок 3] Энергия/настроение → pre_energy_level, pre_mood, energy_coefficient
[Блок 4] Тело/ощущения    → pre_body_sensations, body_coefficient
[Блок 4.5] Пожелания      → pre_wishes
[Блок 5] Длительность    → pre_workout_duration, pre_workout_type
[Блок 5.5] Оценка 1–5    → pre_wellbeing_rating

                    │
                    ▼
    intensity_coefficient = (wellbeing + energy + body) / 3
                    │
                    ▼
    mood = map(pre_wellbeing)   energy = map(pre_energy_level)
    sleep = "Нормально"         limits = from pre_body_sensations
    user_goals += pre_wishes
                    │
                    ▼
    RecommendationService.get_recommended_block_with_fallback(...)
                    │
                    ▼
    VideoService.get_videos_by_block(block) → фильтр по level ± 1
                    │
                    ▼
    AIService.recommend_video_from_block(...)  или random
                    │
                    ▼
    Одно видео → WebApp / кнопка «Завершить тренировку»

6.2. Сохранение и использование чек-инов

Pre-workout завершён          Post-workout завершён
        │                              │
        ▼                              ▼
save_pre_workout_checkin()     create_check_in(
  → pre_workout_checkins         load_rating,
  (wellbeing, energy_level,     wellbeing_before,
   mood, body_sensations,        wellbeing_after)
   workout_duration,             → check_ins
   workout_type, wishes,
   wellbeing_rating)

wellbeing_before в пост-опросе может подставляться из последнего PreWorkoutCheckin.wellbeing_rating (для отображения вопроса), но в CheckIn сохраняется ответ пользователя после тренировки.

7. Таблицы БД

user_surveys (предварительная анкета)

ПолеТипОписание
user_idFK usersПользователь
gender, age, countrystringБлок 1
difficultiesJSON (array)Сложности
experience_levelstringОпыт
health_limitationsJSON (array)Здоровье
equipmentJSON (array)Оборудование
planning_preference, workout_durationstringОпционально

pre_workout_checkins (опрос перед тренировкой)

ПолеТипОписание
user_idFK usersПользователь
wellbeingstringСамочувствие (блок 2)
energy_levelstringЭнергия (блок 3)
moodstringНастроение (блок 3)
body_sensationsstringТело/ощущения (блок 4)
workout_durationstringДлительность (блок 5)
workout_typestringТип (workout/stretch)
wishesstringПожелания (блок 4.5)
wellbeing_ratingintОценка 1–5 (блок 5.5)
created_atdatetimeВремя чека

check_ins (опрос после тренировки)

ПолеТипОписание
user_idFK usersПользователь
datedatetimeВремя чека
moodstringЧасто "completed"
notestextЗаметка (например, id видео)
level_adjustmentintКорректировка уровня
load_ratingintНагрузка 1–5
wellbeing_beforeintСамочувствие до 1–5
wellbeing_afterintСамочувствие после 1–5

users (поля, влияющие на рекомендации)

  • level (1–7) — фильтр видео и выбор блока.
  • goals (JSON) — передаются в ChatGPT при выборе видео.
  • restrictions (JSON) — проверка противопоказаний блоков; при совпадении — переход на блок 2.
  • selected_program — при использовании программы: следующее видео по программе (в legacy pre-workout).

Иллюстрация: от ответов к блоку

Ответы пользователя                    Коэффициент              Блок
─────────────────────────────────────────────────────────────────────────
«Полна энергии» + «На пике» +           (1.5+1.5+1.5)/3 = 1.5    5 или 7
«Всё в порядке»                         ≥1.2, level учтён         (Табата/функционал)

«Нормально» + «Средний» +               (1.0+1.0+1.0)/3 = 1.0     3
«Всё в порядке»                         0.8–1.2, средняя энергия  (Пилатес)

«Устала» + «Нужна зарядка» +            (0.7+0.7+1.0)/3 ≈ 0.8     2 или 1
«Лёгкая боль в мышцах»                  граница 0.8               (Мобильность)

«Плохо себя чувствую» + любое           limits = «Плохо…»        2
                                         → всегда блок 2         (Безопасный)
Made on
Tilda