Если не можешь найти дорогу, иди туда, где её нет, и оставь след.

Ральф Уолдо Эмерсон

Попользовавшись множеством приложений вида «калькулятор калорий» и «трекер тренировок», пришел к выводу, что функционал подобных приложений не так широк, как этого бы хотелось, а доступ к более‑менее продвинутому функционалу стоит несоразмерно много для российского кошелька. Философия популярных приложений часто такова: вот, отслеживай съеденные калории, но чтобы контролировать соотношение БЖУ, отслеживать потребление воды и т. д. — плати деньгу. С вас 20 баксов в месяц, но только сегодня всего за 199$ можешь получить доступ на год. Ну что, пробиваем? (*утрированно*)

Касательно скудного функционала: есть прекрасные калькуляторы калорий (дневники питания), которые хоть и за дорого, но справляются со своей основной задачей — калькулировать эти самые калории + БЖУ. Про микроэлементы или хотя бы клетчатку как-нибудь промолчим. Но что касается физической активности, то она в подобных приложениях идет, как кажется, в небольшой довесок к основному функционалу, без какой-либо глубокой проработанности. Еще один пример: есть список видов физической активности — можешь их добавить, указав продолжительность, мы примерно рассчитаем, сколько калорий ты потратил и запишем в дневник тренировок. (Хотя такие расчеты очень примерны и мало отражают действительность, в принципе, как и расчет калорий, который потребил организм, но сейчас не об этом). Хочешь программу тренировок? Отслеживать каждый подход? Получить подробную статистику и рекомендации? — сори, у вас документов нету, такого функционала не подвезли.

Еще один неудобный момент — отсутствие кроссплатформенности. Лично мне не всегда удобно пользоваться приложением на телефоне. Если я взял какую-то еду и сел за компьютер, мне не очень комфортно лезть в телефон и заносить ее в приложение. Куда удобнее было бы работать, не отрываясь, на том же ПК, открыв, например, сайт того же самого сервиса. Однако, популярные калькуляторы калорий в основном базируются лишь на одном устройстве. Хотя, если вы стоите на кухне и что-то готовите, то заносить продукты на телефоне — самый удобный вариант.

Почему так, Обэме?

— Почему чат‑бот?

Я не замечал чат-ботов схожего функционала, поэтому пришла идея сделать свой (может быть, плохо искал). Встречал боты со всякими калькуляторами для расчета КБЖУ и всего такого, однако такой скудный функционал нам неинтересен. Мне было нужно что-то единое, где в одной «пачке» — дневники питания и тренировок.

Также здесь мы сразу обрубаем на корню проблему с кроссплатформенностью, так как приложение Telegram можно открыть на телефоне, компьютере или же использовать веб-версию в браузере.

Единственный весомый минус при таком подходе заключается в том, что мы ограничены в плане интерфейса. Какой функционал нам дал ТГ, тем и пользуемся: 2 вида кнопок, команды, сообщения и т. д. Однако, несмотря на это, даже в таком случае возможно сделать что-то максимально удобное для пользователя.

А вот весомый плюс — легкость и быстрота разработки. Есть готовый интерфейс, а тебе остается только определить кнопки, команды и сообщения, который будет слать и принимать бот. Быстро, дешево и сердито.

— Почему Telegram?

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

Цели и мечты

Перед тем как начать писать код, давайте ответим на вопрос: «А что же мы собираемся сделать?»

GPT-3.5

Начальной задачей, как это и должно быть в любом деле, было необходимо сделать минимально жизнеспособный продукт — чат-бот, который содержит лишь самый основной функционал дневника питания и дневника тренировок, а также минимально необходимую статистику по каждому из них.

Что касается монетизации, то было решено сделать классическую систему подписок по периодам: неделя, месяц и т. д. Но перед тем как её купить, юзер может получить бесплатные 7 дней, а потом уже решать — надо это ему или нет. Цены решено было выбрать достаточно демократичные, чтобы стоимость подписки была не более той, которую пользователь привык тратить на другие сервисы для просмотра фильмов или прослушивания музыки. То есть должна получиться эдакая «бытовая» трата, которую человеку не жалко отдать за полезный продукт — 200-300 рублей в месяц.

Для реализации данного функционала решено было выбрать API ЮMoney (бывшие Яндекс. Деньги), взаимодействие с которым велось посредством библиотеки yoomoney от Алексея Коршука.

БД нет, но вы держитесь

В начале было решено сконцентрироваться на дневнике тренировок, а уже после переходить к дневнику питания (он же калькулятор калорий), так как реализация последнего виделась более трудной (оказалось, не так чтобы очень сильно) .

Чтобы юзер смог занести какую-либо физическую активность, нужно где-то достать список ее видов, а это оказалось не так просто, так как нормальных готовых баз на русском языке, по всей видимости, просто нет. Поэтому пришлось строить ее самостоятельно, а так как делать это очень долго, нужно процесс автоматизировать. При этом в сети куча сайтов с каталогами физических упражнений, а это значит — заводим свой Selenium и начинаем парсить. В качестве цели был выбран не топовый, но более-менее популярный сервис DailyFit, где страницы упражнений сжатые, без воды и лишь с основной информацией, которая нам нужна на этапе создания МЖП:

  • Название

  • Целевая группа мышц

  • Дополнительные группы мышц

  • Вид упражнения (силовое, кардио, растяжка или плиометрическое)

  • Тип упражнения (базовое, изолирующее) — только для силовых упражнений

  • Необходимое оборудование (штанга, гиря и т. д.)

  • Уровень сложности

Также на каждой странице есть симпотишные фото выполнения каждого упражнения, при этом в 2 вариантах: мужское исполнение и женское. Все фото мы парсим тоже, и оба вида — потом будем показывать фото соответственно полу пользователя. При этом нам не нужно никуда сохранять каждое фото, достаточно только ссылки на него.

Пример страницы с физическим упражнением
Пример страницы с физическим упражнением

Всего на момент парсинга каталог упражнений содержал 594 позиции, что довольно прилично и охватывает, наверное, почти все возможные виды, которые может искать пользователь. Теперь наша БД (в моем случае на SQLite ) заполнена кучей упражнений, да еще и по несколько фото к каждому (целых 2582 штуки). Все ссылки на фото хранятся в отдельной таблице и каждое из них имеет колонку с идентификатором пола (male или female) и ID упражнения, к которому оно относится. Ниже можете просмотреть, какие у нас получились таблицы:

Таблица доступных физических упражнений
Таблица доступных физических упражнений
Таблица с фото для каждого из них
Таблица с фото для каждого из них

Если вдруг кому понадобится парсер, можете найти его ниже. Хотя это сделанный быстро и на коленке код, но вполне рабочий.

https://github.com/Molot999/dailyfit_parser

Не ручаюсь за точность характеристик каждого упражнения на данном сервисе, но для начала этого будет достаточно.

Теперь юзеру будет из чего выбрать, осталось только ему их правильно предложить. Об этом и поговорим далее…

Чем больше выбор, тем сложнее попасть в цель

База данных готова, осталось определить, как пользователь будет искать то самое упражнение из общей кучи. Самое простое, что можно сделать — это реализовать механизм поиска по названию. Ввел пользователь запрос «жим», а мы ему:

И еще 70 видов физических упражнений из нашей БД, в которой есть слово «жим». Сделать такую выборку очень просто, используя простейший функционал нашей СУБД SQLite3, а именно при помощи оператора LIKE:

 SELECT * FROM available_exercises WHERE title LIKE '%жим%';

Этот оператор осуществляет поиск по подстроке, а это значит, что к списку выше прибавятся различные отжимания. Также данного вида поиск чувствителен к окончаниям слова, то есть если мы введем «разгибания», то мы не получим в результате упражнений наподобие «Разгибание на трицепс на верхнем блоке».

Простыми словами, подстрока — это небольшая часть строки, которая содержится внутри этой строки. Когда мы говорим о строках, подстрока представляет собой набор символов, которые находятся внутри другой, более длинной строки.

Это простейший вариант поиска, который несложен в исполнении, но неудобен для пользователя, так как приходится долго искать нужное упражнение в огромном списке. Также стоит учитывать ограничение Телеграма на длину сообщения, ведь если по запросу придет большой список упражнений, и вы попытаетесь его отправить, это вызовет ошибку.

Нужно было как-то улучшить релевантность поиска, чтобы сверху списка оказались те упражнения, которые, скорее всего, и нужны пользователю. Для этого пришлось по-быстрому разработать несложный алгоритм из нескольких шагов, который показал себя вполне годно:

  • Перед тем как что-то искать, необходимо удостовериться в том, что юзер написал запрос корректно и без ошибок («жим», а не «жым»). Для этого воспользуемся сервисом Яндекс.Спеллер, который исправляет в тексте орфографические ошибки. Для взаимодействия с API данного сервиса нам поможет библиотека pyaspeller.

  • Теперь решаем проблему с окончаниями, о которой говорилось выше. Что для этого нужно сделать? Правильно — попросту убрать окончания, получив основу слова (или же нескольких слов), то есть провести стемминг. В этом нам поможет библиотека nltk. Теперь, если юзер ввел «разгибание гантелей», то в запросе будет «разгибан гантел».

  • Теперь можно начать сам поиск по БД. Здесь без изменений — используем тот же оператор LIKE. Если в запросе несколько слов, используем оператор AND, тогда в выдаче получим только те упражнения, где в названии есть сразу обе подстроки (и «разгибан» и «гантел» сразу).

  • Теперь в ход идут метрики, по которым мы поднимаем наверх те упражнения, которые с большей вероятностью интересны пользователю. Первый из них — это коэффициент Жаккара (насколько название упражнения приближено к запросу пользователя). К примеру, юзер ввел «жим штанги лежа» и благодаря использованию этого коэффициента список упражнений будет таким:

    1. Жим штанги лежа

    2. Жим штанги лежа узким хватом

    3. Жим штанги лежа широким хватом

  • Следующая метрика — «популярность» упражнения, которая складывается из количества просмотров и количества фактических выполнений упражнения другими пользователями. Как можно полагать, количество выполнений является более приоритетной цифрой, поэтому число просмотров и число выполнений должны иметь разный вес. В качестве теста было распределить его так: просмотры — 0.3, а выполнения — 0.7.

  • Теперь осталось провести сортировку списка упражнений по 2 метрикам:

search_results.sort(key=lambda ex: (ex.popularity, ex.similarity), reverse=True)
  • На заключительном этапе нужно проверить, что длина сообщения с упражнением не будет выходить за допустимые пределы. В моем случае каждая строка с упражнением состоит из id упражнения в БД с косой чертой (чтобы можно было нажать на него и выбрать упражнение, а не вводить вручную) и его названия. Соответственно, нам нужно получить длину каждой строки упражнения по следующей формуле: длина id + длина названия + 2 (косая черта и пробел между ID и названием). Далее складываем длину всех строк и проверяем, будет ли длина сообщения выше допустимого предела (4 096 символов) и если больше — убираем последнюю строку и далее по кругу, пока длина не впишется в лимит. Если в сообщении помимо списка есть еще текст — учитываем и его длину.

    На этом все, осталось только послать сообщение со списком упражнений пользователю. Например, такое:

Косые черты рядом с ID упражнения в данном случае помогают сделать команду, которая при нажатии автоматически отправляется боту. Далее, получив ID упражнения в БД, мы можем вывести его подробную информацию. К примеру, такую:

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

Структура дневника тренировок

Здесь все относительно просто: пользователь начинает тренировку, выбирает упражнения и вносит подходы по каждому упражнению. Соответственно, нам нужно создать 3 таблицы в нашей базе данных: Тренировки, Упражнения и Подходы. При этом упражнение хранит ID тренировки, а подход — ID упражнения в ее рамках.

В рамках упражнения имеется сообщение с всеми подходами: вес отягощения, количество повторений и время выполнения (не уверен, что оно будет многим интересно).

А в сводке по всей тренировке выводим все упражнения, количество подходов и время выполнения всего упражнения (немного поинтереснее):

По окончании тренировки можем просмотреть краткую сводку по ее длительности объему, интенсивности и количеству упражнений:

И щепотка статистики

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

На первое время было решено сделать статистику по 3 метрикам, которые как раз и есть в сводке по окончании тренировки: объем, интенсивность и длительность.

Просматривать статистику в виде текста не так интересно, куда лучше смотреть на график, где сразу понятно, что куда катится. В этом нам поможет библиотека matplotlib. Вот то, что мы получаем на выходе:

Конец

На этом первая часть материала подошла к концу. Здесь я описал лишь личный опыт, без претензий на что-либо. Не стесняйтесь и расскажите в комментариях, какие моменты и как стоит улучшить.

Можете затестить функционал чат-бота совершенно бесплатно, подписка не нужна.

В следующей статье рассмотрим организацию дневника питания и его интересные фишки. Например, такие:

Комментарии (8)


  1. temabed
    26.07.2023 20:15
    +1

    Лайк поставить еще не могу тут, но коммент напишу: спасибо за статью. А ссылка на репозиторий лишней бы не была))


    1. novikovnvn
      26.07.2023 20:15
      +1

      А как же продавать потом если ссылка на репозиторий будет?)


  1. csharpreader
    26.07.2023 20:15
    +1

    доступ к более‑менее продвинутому функционалу стоит несоразмерно много для российского кошелька

    Ну да, 38 руб/месяц – прям дыра в бюджете (XBodyBuild, например).

    А к слову о Телеграме (не в адрес автора поста камень), так я, например, наоборот рад лишний раз выйти за пределы этого монстра-комбайна, каким стал ТГ.

    А, взглянув на интерфейс подсчёта калорий и тренировок в остонадоевшей и, скажем мягко, простенькой обёртке Телеграма, так только перекрестился и ушёл обратно жевать еду в специализированное приложение (пусть несовершенное, но лучше).

    Nota bene! То, что автор реализовал своё задуманное и таки «подковал блоху» – за рамками моих реплик. Все эти мысли касаются только самой концепции Телеграм-бота на заданную тему.


  1. Rayvor
    26.07.2023 20:15

    Получил, через пару минут работы с ботом


  1. brightcow
    26.07.2023 20:15

    Вот в такой тупик попал
    И
    И

    Идея хорошая


  1. twistfire92
    26.07.2023 20:15

    Единственный весомый минус при таком подходе заключается в том, что мы ограничены в плане интерфейса. Какой функционал нам дал ТГ, тем и пользуемся: 2 вида кнопок, команды, сообщения и т. д. Однако, несмотря на это, даже в таком случае возможно сделать что-то максимально удобное для пользователя.

    Ну вообще-то телега давно умеет в WebApp, специально выпускали для этого DurgerKingBot в качестве примера. Там уже возможно реализовать что-то посложнее и красивее 2-х видов кнопок.


  1. Gardalus
    26.07.2023 20:15

    Тут микро-меню с колбэками и пагинацией было бы намного более симпатичным, раз уж сразу на монетизацию нацелены.


  1. Firsto
    26.07.2023 20:15

    популярные калькуляторы калорий в основном базируются лишь на одном устройстве

    У FatSecret и приложения есть, и сайт удобный.)