Garmin записывает почти всё: сон, HRV, стресс, Body Battery, пульс на тренировке, каждый подход в зале. ChatGPT ничего этого не видит. Чтобы спросить у него, как прошла неделя, нужно было сначала открыть Garmin Connect, переписать цифры руками и вставить их в чат.
Один вечер, один скрипт. Забрать сон и HRV за сегодня, сложить в файл. Никакой архитектуры, просто раздражение от того, что данные есть, а спросить их некому.
Сейчас это открытый пайплайн: локальный кэш, агентный Telegram-бот, локальная модель на Ollama, веб-мастер настройки, Windows-дистрибутив и заявка на акселератор Sber500. По пути пришлось несколько раз пересобрать саму задачу.
Первое расширение
Один скрипт быстро перестал устраивать. Сначала появился дневной отчёт: сон, HRV, стресс, Body Battery, RHR, шаги. Потом недельный, со сравнением к прошлой неделе. Потом стало понятно, что без файловой библиотеки на диске всё равно получается свалка. Захотел разобрать тренировку за прошлый вторник - а файла для неё просто нет.
Библиотека сейчас выглядит так. Реальная структура на сегодня, без вымышленных примеров:
data/library/ ├── _index.md ├── context.md ├── sleep_2026-07-18_2026-08-01.md ├── daily/ │ ├── 2026-07-13.md │ ├── 2026-07-15.md │ └── 2026-08-14.md ├── weekly/ │ ├── 2026-W29.md │ ├── 2026-W30.md │ ├── 2026-W32.md │ └── 2026-W33.md ├── range/ │ └── 2026-07-18_2026-07-31.md └── activities/ ├── 2026-07-13_кунгурский_район_велосипед.md ├── 2026-07-13_кунгурский_район_велосипед.csv ├── 2026-07-13_медитация.md ├── 2026-07-13_медитация.csv ├── 2026-08-10_adliswil_велотренажер.md └── 2026-08-10_adliswil_велотренажер.csv
Дальше пошли детали, из которых на самом деле и собирается доверие к цифрам. Продолжительность тренировки сначала печаталась как "58м". Читалось это как метры, не минуты. Поправил на "58:57". Мелочь, но если в отчёте путаются единицы измерения, дальше не веришь и остальным числам.
Отдельно пришлось повозиться со сплитами для велосипеда. У бега автоматические километровые отметки почти всегда есть, у велосипеда часто нет ни одной. Один сплит на 20 километров ничего не говорит: где я разгонялся, а где полз в горку - непонятно. Пришлось считать сплиты самому: брать точки трека с переменным интервалом записи и пересчитывать их в километровые отрезки.
Вот кусок настоящего отчёта, без правок, за сегодня:
--- date: 2026-08-14 sleep_h: 7.52 hrv_ms: 47.0 rhr: 51 stress_avg: 29 activities: 4 --- ## Дайджест — 2026-08-14 Сон: 7ч31м (глубокий 1ч10м), качество: 83/100 HRV: 47 мс (UNBALANCED) Стресс за день (средний): 29 RHR: 51 уд/мин Body Battery: 83 -> 10 Шаги: 5915 Калории: 2547 ккал (активные: 614 ккал) ### Тренировка: Кор и ягодицы strength_training, 0.00км, 22:20, темп н/д, avg HR 93, TE н/д, 90 ккал Силовые сеты: 12 подходов (актив 15:39, отдых 6:41): - Hip Stability (Dead Bug): 2 подх., 40 повт. - Banded Exercises (Reverse Crunch): 2 подх., 40 повт. - Banded Exercises (Glute Bridge): 2 подх., 40 повт., вес до 5.0 кг - Hyperextension (Kneeling Superman): 2 подх., 42 повт. - Plank (Side Plank): 2 подх., 0 повт. - Push Up: 2 подх., 41 повт. Мышечные группы: Кор/пресс (6 подх.), Ягодицы (4 подх.), Всё тело (4 подх.), Спина (2 подх.), Грудь (2 подх.), Трицепс (2 подх.)
Развилка: закрытый продукт или открытый
В какой-то момент стало интересно, можно ли на этом заработать. Рынок AI-фитнес-коучей в России уже не пустой: закрытые сервисы, подписки, чужие серверы, на которых лежат данные о твоём сне и пульсе. Стандартная схема простая - сделать закрытый продукт, продавать доступ.
Решил наоборот. Открытый код, self-hosted, каждый ставит себе сам. Данные уходят только в Garmin Connect и в тот LLM-провайдер, которого сам укажешь. Если указать локальную модель через Ollama, данные вообще не покидают компьютер. Дело не в том, что бизнес на этом плохая идея. Дело в теме: для "моего сна и моего пульса" закрытая коробка с чужим сервером внутри доверия не вызывает. Код, который можно прочитать и запустить у себя - вызывает.
Момент, где сломался собственный аргумент
К этому моменту уже работал отчёт за произвольный период: указываешь даты, получаешь markdown с итогами по шагам, сну, тренировкам. Работало нормально. Но каждый повторный запрос снова шёл в Garmin API по каждому дню периода. Попросил отчёт за две недели, через час попросил его снова - опять 14 обращений к API, хотя ни один день за это время не изменился.
Задал себе простой вопрос. Почему это не работает как графики в самом Garmin Connect? Там история не пересчитывается заново при каждом открытии, она просто открывается.
Оказалось, что до этого момента кэш вообще не был архитектурой. Просто побочный эффект того, как быстро писалась первая версия. Пришлось разложить это по-другому. Локальный SQLite хранит сырые ответы API, а не только готовые посчитанные цифры. Это важно: новое поле в отчёте можно достать из уже сохранённых сырых данных, не долбя Garmin заново. Отчёт за период сначала проверяет, какие дни уже в кэше, и в API летит только недостающий кусок. Фоновая синхронизация через Windows Task Scheduler держит кэш тёплым сама, без моего участия.

Разница на практике простая: 14 обращений к API на первый запрос отчёта и 0 обращений на повторный запрос за тот же период. Для того, кто просто пишет боту "как прошли последние две недели", вся эта возня с кэшем не видна вообще - отчёт открывается мгновенно, как в самом приложении Garmin Connect, а не пересчитывается заново на каждый вопрос. Сам отчёт после этого выглядит так:

Создание тренировок в Garmin
Сам я тренировки в приложении Garmin руками почти никогда не собираю. Открыть конструктор, расставить шаги, выставить зоны пульса - отдельная лишняя задача поверх основной. Но как только у агента появился доступ не только на чтение, но и на запись, стало ясно, что это работает и для того, кто вообще никогда этим не занимался: не нужно разбираться в интерфейсе конструктора, достаточно попросить словами. Пайплайн перестал быть только читалкой. Появилась возможность создавать тренировки прямо в Garmin: бег, велосипед, силовые, с интервалами и алертами по пульсовым зонам.
Один конкретный кейс. Взял шаблон интервальной тренировки на велосипеде и подогнал разминку и заминку под длительность последней реальной поездки, сами интервалы оставил без изменений. На спринтах добавил алерт при превышении пульса выше зоны 3. Собрал, отправил в Garmin, а на часах алерт не сработал. Разобрался: поле zoneNumber в шаге тренировки осталось пустым, потому что степ собирался по чуть другой логике, чем разминка. Удалил тренировку, пересобрал с явным указанием зоны на каждом интервале - и вот тогда алерт наконец сработал:

Для силовых пришлось разобрать API отдельно: подходы, повторы, вес, категория упражнения. А заодно собрать свою таблицу соответствия категорий Garmin группам мышц, потому что "мышечная карта" в приложении Garmin Connect считается на устройстве пользователя и наружу не отдаётся.
Агентный бот и локальная модель
Телеграм-бот поверх такого пайплайна не может быть просто чатом, который пересказывает готовый отчёт. Если он умеет читать данные, он должен уметь запрашивать их под конкретный вопрос собеседника. Показывать то, что уже посчитано заранее - для этого мало.
Поэтому бот вызывает те же функции, что и CLI: получить метрики за период, найти тренировку, создать тренировку, удалить тренировку, загрузить файл активности. Но для любого изменяющего действия перед реальным выполнением бот показывает кнопки "Подтвердить" и "Отменить":

Забавно спотыкнулся на этом же месте. Попросил бота удалить тестовую тренировку, и вместо кнопок он написал текстом: "Точно удалить? Да/нет". Формально всё понял правильно, но обошёл весь смысл подтверждения - просто изобразил его словами вместо вызова инструмента. Полез в системный промпт и увидел причину: там было написано что-то вроде "спроси подтверждение перед изменяющим действием", и модель восприняла это буквально, вместо того чтобы сразу вызвать функцию и дать внешней системе показать кнопки. Переписал промпт так, чтобы модель сразу вызывала инструмент, а подтверждение показывала уже сама система. После этого кнопки заработали. Причём одинаково и на облачной модели, и на локальной.
Локальная модель тут не для галочки. Часть людей не хочет, чтобы данные о сне и здоровье вообще уходили к внешнему провайдеру. Поэтому добавил Ollama прямо в веб-мастер настройки: статус, кнопка установки, кнопка скачивания рекомендованной модели с прогресс-баром. Для агентных задач по function calling на обычном пользовательском железе лучше всего показала себя qwen3:4b - не топовая модель, но она реально вызывает нужные инструменты вместо того, чтобы просто красиво рассуждать вслух.
На чтение агент реагирует так же самостоятельно, как и на изменяющие команды: сам решает, за какой период и по какой тренировке запросить данные, и оформляет ответ обычным текстом, а не сырым markdown с решётками и звёздочками:

EXE, веб-мастер настройки, все каналы использования
Всё, что описано выше - кэш, запись тренировок, агентный бот - не имеет смысла, если для запуска нужно уметь программировать. Один и тот же пайплайн должен работать и для программиста, который живёт в терминале, и для человека, который никогда не открывал консоль и просто хочет написать боту "как прошла неделя?" и получить ответ.
Для второго человека путь выглядит так: скачал zip, распаковал, дважды кликнул по иконке .exe. Никакого Python, никакой установки зависимостей. Автоматически открывается браузер на локальной странице настройки. Вписываешь почту и пароль от Garmin, вставляешь готовый ключ от LLM-провайдера (или, если не хочется, чтобы данные вообще куда-то уходили, выбираешь пресет локальной модели - Ollama сама предложит установить себя и скачать модель прямо из этой же формы) и токен бота от @BotFather. Сохранил - и дальше весь пайплайн живёт в трее, а разговор с ним происходит в Telegram с телефона, лёжа на диване. Ни строчки кода, ни одного файла руками не трогаешь.
Для первого человека - программиста - под тем же движком есть входы попроще: CLI-команды напрямую, Skill-файлы для Cursor и Claude Code (агент сам понимает, что нужно вызвать daily или activity export, когда его просто просят словами), AGENTS.md для Codex, MCP-сервер для внешних LLM-клиентов вроде Claude Desktop. Код один и тот же в обоих случаях, просто разная упаковка вокруг одного ядра, под разный уровень готовности иметь дело с терминалом.

После настройки - дашборд со списком собранных отчётов и кнопками "собрать снапшот/сегодня/неделю" без похода в консоль:

Честный и смешной баг
Уже после публикации репозитория, во время последней проверки перед релизом, нашёл в собственном закоммиченном скрипте кое-что странное. Скрипт регистрации ежедневной синхронизации в Task Scheduler несколько коммитов подряд был битым: прямо в тело PowerShell-скрипта попали номера строк как обычный текст.
- 10|.EXAMPLE +.EXAMPLE # Другое время/глубина синка .\scripts\register_daily_sync_task.ps1 -Time "07:00" -Days 5 - 20|$ProjectRoot = Split-Path -Parent $PSScriptRoot +$ProjectRoot = Split-Path -Parent $PSScriptRoot $VenvPython = Join-Path $ProjectRoot ".venv\Scripts\python.exe"
При копировании куда-то затесались служебные пометки номеров строк, и никто их не убрал. Скрипт запускается раз в день фоново, ошибку никто не видел, пока я не решил вручную его прогнать и проверить. PowerShell на такое просто падает с ошибкой синтаксиса. Починил. Заодно добавил обеим версиям скрипта BOM в начало файла, потому что без него Windows PowerShell 5.1 иногда неправильно читает кириллицу внутри строк и тоже ломается - отдельно и по-своему.
Sber500 x DISRUPT
Когда репозиторий уже был публичным и рабочим, подал заявку на трек "разработаю продукт с нуля" в акселераторе Sber500 x DISRUPT. Заявку подавал не ради строчки в резюме. Хотелось понять, можно ли на этом построить что-то дальше: партнёров, аудиторию, деньги на развитие без превращения проекта в закрытую подписку.
Что дальше
Репозиторий открыт: github.com/TolmachevKirill/garmin_ai. Собранный Windows-дистрибутив лежит в релизах, актуальная версия - v1.0.2.

Сейчас пайплайн заточен под Garmin, потому что именно с него началась задача. Но разрыв не только у владельцев Garmin. У кого-то другие часы, другой велостанок, другой источник данных о сне и тренировках - и тот же запрос: цифры есть, а спросить у ИИ их некому.
Если у тебя такое устройство и такой запрос, пиши. Готов разбирать, как подтянуть твои данные в тот же контур: кэш, отчёты, бот. Не обещаю универсальный каталог всех брендов заранее. Обещаю разбирать конкретный кейс, если он реален.
Ограничения сейчас честные. Локальные модели тянут не всё так же уверенно, как облачные. Официального API для мышечной карты Garmin не даёт, поэтому она считается через собственную таблицу категорий. Часть сценариев проверена только на моих тренировках.
Это не финальная версия продукта. Это рабочий пайплайн, который уже закрывает мой собственный разрыв между часами и ИИ - и который можно переделывать под чужой, если запрос конкретный.

