Теперь настало время поговорить о второй составляющей чат-бота — дневник питания (он же калькулятор калорий).
Что по БД?
В отличие от БД с физическими упражнениями здесь есть из чего выбрать. Существует куча баз продуктов питания с доступом по API, к примеру:
- Open Food Facts
- Fatsecret
- FoodData Central от Министерства сельского хозяйства США + можно скачать саму БД
- Nutritionix
Остановиться было решено на… Nutritionix, так как она обладает одной интересной фишкой — распознавание всех продуктов из одного запроса. То есть, мы можем просто послать на сервис строку вида «3 вареных яйца и банка пива», а сервис выудит все перечисленные продукты их количество/вес/объем и отправит в ответе информацию по каждой позиции. Например:
При этом если мы не укажем конкретный вес/объем продукта, то сервис просто возьмет стандартное значение: 1 вареное яйцо — 50 грамм или 1 кусочек хлеба — 29 грамм. Кому интересно — можете затестить данный функционал сервиса по ссылке ниже:
Кстати, подобный функционал у Nutritionix есть и для тренировок — ввел объем выполненного упражнения (например, пробежал 30 минут) и получил количество потраченных калорий, но, сейчас не об этом.
Помимо калорий, белков, жиров у углеводов сервис предоставляет и другие составляющие продукта — минералы, витамины, алкоголь, вода, соль, сахар и другие — всего 161 позиция.
Но, как вы заметили, сервис принимает запросы только на английском языке. Как это нас остановит? Никак. Что же тогда делать? Переводить…
Трудности (нет) перевода
Перевести простейшее предложение вида «2 помидора, ложка оливкового масла и зубчик чеснока» не будет сложной задачей для любого популярного переводчика, поэтому давайте воспользуемся функционалом одного из них. Для этого давайте выберем какой-нибудь онлайн-переводчик и Python-библиотеку под него, например googletrans для Google Переводчика.
Шаг 1. Устанавливаем библиотеку
Шаг 2. Переводим
Ключи к успеху
Итак, пользователь что-то ввел, мы это перевели, а теперь настало время отправить запрос к Nutritionix. Однако, для начала нам нужно получить парочку ключей для взаимодействия с сервисом. Для этого переходим по данной ссылке, регистрируемся и копируем Application ID и Application Key.
Код крутится — бот мутится
У нас есть два пакетика травы, семьдесят пять ампул мескалина, 5 пакетиков диэтиламида лизергиновой кислоты или ЛСД, солонка, наполовину наполненная кокаином, и целое море разноцветных амфетаминов, барбитуратов и транквилизаторов, а так же литр текилы, литр рома, ящик «Бадвайзера», пинта чистого эфира, и 12 пузырьков амилнитрита 2 ключа для API и переведенный запрос, так что — давайте кодить.
Все запросы (POST) будем посылать на следующий URL, сохранив его в переменную:
1. В начале подключаем библиотеку requests и собираем заголовки из Content-Type, Application ID и Application Key:
2. Собираем тело запроса, который включает переведенный запрос и параметра timezone (оставим по дефолту US/Eastern, пока это неважно):
3. Отсылаем POST-запрос на сервер Nutritionix, куда включаем URL, заголовки и тело запроса, а его ответ сохраняем в переменную response:
4. Проверяем что запрос удался и вернул код 200 (OK), переводим его в JSON и получаем значение по ключу «foods», где как раз и лежит список словарей с информацией по каждому продукту:
Для большего удобного я создал класс, который представляет каждый продукт, полученный из запроса. В его конструктор мы просто передаем словарь из списка словарей и заполняем атрибуты:
5. Осталось только воспользоваться генератором списка и передать каждый словарь из списка словарей в конструктор класса NutritionixFood. В итоге мы получим список объектов данного класса.
Готово! Весь код можете просмотреть на гитхаб:
Теперь осталось только объединить это с библиотекой telebot:
- Запросить ввод текста с перечислением съеденного
- Перевести текст на английский
- Отправить текст через API Nutritionix
- Получить ответ сервера
- «Конвертировать» ответ сервера в список объектов класса NutritionixFood
- Вывести список продуктов пользователю, переведя названия продуктов с английского на русский:
Дьявол кроется в деталях
Как я говорил выше, кроме основных составляющих пищи, которые в основном нас и интересуют (КБЖУ), данный сервис предоставляет еще кучу других. Некоторые из них, например сахар или калий, хранятся в атрибутах класса в готовом виде (nf_sugars и nf_potassium соответственно), но основная часть содержится в атрибуте full_nutrients со списком словарей, каждый из которых имеет следующие ключи:
- ID нутриента
- Его количество
Например, для запроса «3 boiled eggs» мы получим следующее:
Чтобы сопоставить attr_id с реальным «веществом» необходимо обратиться к справочной таблице:
Здесь нас интересуют лишь самые основные колонки: A (ID), D (Название) и E (единица измерения). Для простоты взаимодействия можно скопировать данную таблицу в Excel, а уже из него спарсить все это дело в таблицу БД. На всякий случай оставил тег USDA (может когда-то пригодится) и добавил колонку ru_name, в которую потом можно будет «запихнуть» русское название нутриента, прогнав колонку name, к примеру, через тот же самый googletrans.
Кстати, в этой же справочной таблице на 2 листе есть ссылка на документ от FDA (Агентство Министерства здравоохранения и социальных служб США), где прописаны нормы потребления нутриентов. Здесь в основном нас интересует колонка 3 (взрослые и дети >= 4 лет), ну а кого-то 6 (беременные и кормящие женщины).
Вернемся к коду и создадим класс, представляющий каждый нутриент:
И начинаем проходиться по тому самому списку словарей
Подобным образом:
В итоге получим список объектов класса NutritionixNutrient, каждый из которых содержит информацию о конкретном нутриенте из продукта. А имея информацию обо всех нутриентах, потребленных в течение определенного периода уже можно сделать выводы о его диете (много соли, мало витамина B и т. д.) и дать соответствующие рекомендации, опираясь на вышеописанные нормы FDA или национальные.
Затестить функционал по добавлению продуктов питания можете в данном чат-боте совершенно бесплатно.
Кстати, а почему бы не облегчить жизнь пользователю и дать ему возможность просто записать голосовое сообщение со всем съеденным? Поговорим об это в части № 3…