Досье для разбора советом¶
Самодостаточное описание проекта на 2026-08-13: что строим, что уже стоит, какие решения приняты и почему, как всё работает и что вызывает сомнения. Читается без остальных доков; ссылки даны для углубления, а не как обязательное чтение.
Предыдущий разбор и его следствия — COUNCIL-2026-08-13.md.
1. Что это¶
Личная система трекинга тренировок и здоровья. Один пользователь сейчас, но модель данных рассчитана на нескольких (ADR-0021).
Что не даёт ни один готовый продукт и ради чего всё затевается: точные границы каждого подхода — когда он начался и закончился. Проверялись FitNotes, Hevy, Strong, Jefit и интервальные таймеры: логгеры пишут время на уровне тренировки, таймеры не знают про упражнение и вес, гибрида нет (LESSONS-0001).
Из точных границ выводится: - Не зависящее от датчиков: фактический отдых между подходами, время под нагрузкой, плотность сессии, динамика усталости. - Зависящее от датчика (гипотеза): реакция организма на эпизод усилия — пульс на нагрузке и на восстановлении.
Вторая часть — непроверенная гипотеза, и это осознанно (см. §6).
2. Источники данных¶
| Источник | Что даёт | Как попадает |
|---|---|---|
| Тренировки | упражнение, вес, повторы, T1/T2 каждого подхода | Mini App → бот → wger |
| Fitbit Air | пульс, шаги, SpO₂, сон, вес по точкам во времени | Google Health API v4, OAuth через бота |
| InBody | состав тела, посегментный анализ | PDF в бота → парсер |
| Apple Health | приём таблеток и прочее | export.xml в бота → импортёр |
3. Как устроено¶
Единая точка входа — Telegram-бот (ADR-0023). Регистрация, открытие Mini App, подключение Google-аккаунта, отправка файлов, выгрузки — всё через него. Отдельного веба и ручной настройки учёток нет.
пользователь → Telegram-бот ─┬→ Mini App (свой экран логирования + интерфейс wger)
├→ wger REST (токен подставляет сервер)
├→ OAuth Google (бот принимает callback)
├→ парсеры InBody / Apple Health
└→ core-service по gRPC (/export, /stats)
wger ──(опрос по курсору)──→ wger-sync-worker ──gRPC──→ core-service
Google Health ──(опрос)────→ google-health-sync ──gRPC──→ core-service
Два контура. Ввод тренировок (wger + Mini App + бот) и склад правды (core-service + воркеры). У них разные режимы отказа: ввод обязан работать в зале без связи, склад может лежать сутки без потери данных.
core-service — единственный источник правды для анализа. Go + gRPC + своя Postgres. wger — один из источников, не центр (ADR-0001).
Инфраструктура. Всё в одной VM pet (4 ядра, 7 ГБ, Proxmox в домашней сети).
Там же GitLab-раннер: CI собирает образ тем же демоном, который его запускает, —
поэтому container registry не нужен (ADR-0018). Наружу — обратный SSH-туннель на VPS
с Caddy и валидным TLS.
Репозитории. Группа GitLab mybit, 10 отдельных репозиториев: platform (доки,
compose), proto (контракты), core-service, telegram-bot, miniapp-frontend,
wger-sync-worker, google-health-sync, inbody-parser, applehealth-import,
wger. Микросервисы — сознательный выбор, а не следствие раздельного деплоя.
4. Ключевые решения (26 ADR, здесь — несущие)¶
| ADR | Решение | Суть обоснования |
|---|---|---|
| 0013 | Таймстампы ставит клиент, сервер валидирует | В зале связь лагает: время прихода запроса ≠ время подхода. Плюс это открывает офлайн-логирование. Границы правдоподобности защищают от сбитых часов |
| 0019 | Единица анализа — эпизод усилия, не подход | Пик пульса приходится на первые 5–20 с отдыха; датчик пишет раз в ~5 с и отбрасывает точки при плохом сигнале, который падает при хвате штанги |
| 0020 | wger — хард-форк, upstream не отслеживаем | Снимает вечный ребейз. Цена: обновления безопасности переносятся вручную |
| 0021 | user_id в схему сразу |
Пока таблицы пусты — почти бесплатно; потом миграция с бэкфиллом и переделка всех натуральных ключей |
| 0022 | Брокера нет | gRPC + upsert по натуральному ключу + курсор уже дают at-least-once с дедупликацией |
| 0023 | Бот — единственная точка входа | Решение владельца: вся коммуникация через Telegram |
| 0024 | Токены пользователей — в core, не в env | Следствие регистрации через бота и мультипользовательской схемы |
| 0025 | Публичный HTTPS — предусловие | Mini App требует валидного TLS; Google требует HTTPS в redirect URI, а OOB-флоу отключён с 2023 |
| 0012/0018 | Отдельные репозитории, сборка без registry | Независимые пайплайны; один рантайм-хост |
5. Что уже сделано, а что нет¶
Стоит и проверено: 10 репозиториев с зелёными пайплайнами; VM pet с раннером;
контракты CoreService с генерацией Go/Python (генерация локальными плагинами, без
внешнего сервиса); заготовки всех сервисов собираются и проходят тесты; форк wger
импортирован; локальный стек поднимается, grpcurl отвечает SERVING, миграция
создаёт все 5 таблиц.
Реализовано по существу — три куска логики:
- проверка initData в боте (constant-time, защита от повторного использования, 15 тестов);
- валидация таймингов подхода в core;
- разбор отчёта InBody с явным отказом на незнакомой раскладке;
- клиентский таймер подхода на двух часах (Date.now() для отправки,
performance.now() для отображения).
Не реализовано: персистентность и Export в core, обходы API у воркеров,
потоковый разбор export.xml, все эндпоинты бота, регистрация, OAuth, туннель.
Домен для HTTPS не выбран.
Порядок работ — ROADMAP.md: вход → логирование → склад → источники и проверка
гипотезы. Пять спринтов, 45 задач на доске.
Первое, что предстоит: туннель pet → VPS с валидным TLS на
mybit.arvberezin.online. Белого IP дома нет и не будет — туннель обратный, pet
сам открывает исходящее соединение, поэтому NAT и серый IP не мешают.
5а. Сосуществование с VPN на VPS¶
VPS существует не ради этого проекта. Его основная работа — naiveproxy (VPN) и
SOCKS5 (danted), то есть повседневный доступ в интернет. Проект въезжает туда
гостем, и цена ошибки несимметрична: сломав конфиг Caddy, мы уроним не проект, а VPN.
Отсюда четыре жёстких правила (ADR-0026): существующий блок Caddy не редактируется
вообще, добавляется только именованный; caddy validate до применения и reload
вместо restart; ключ туннеля ограничен одним портом
(restrict,port-forwarding,permitlisten), чтобы компрометация pet не давала шелл
на сервере с личным трафиком; порт 9080 только на 127.0.0.1.
Открытый вопрос, который кодом не решается (T-00): стоит ли вообще делить один IP между VPN и публичным именем проекта. Это общая судьба при блокировке и лишняя поверхность для фингерпринтинга naiveproxy. Альтернатива — отдельный VPS за 300–400 ₽/мес.
Заодно вскрылось состояние сервера: диск был забит на 100% (32 ГБ в одном
syslog.1), потому что logrotate никогда не ротировал syslog — молча пропускал
из-за прав на /var/log. Починено 18.08, рост логов упал с ~32 ГБ/сутки до ~14 МБ.
5б. Как разделён труд¶
45 задач размечены по исполнителю (do::):
| Метка | Задач | Что это |
|---|---|---|
do::claude-code |
32 | код |
do::claude-infra |
6 | Ansible, VPS, туннели, CI |
do::user-manual |
3 | DNS, регистрация OAuth-приложения, решение по IP |
do::user-test |
2 | зал, телефон вне домашней сети |
do::claude-test |
2 | Playwright, проверка TLS |
do::user-verify |
1 | подтвердить, что VPN и прокси живы |
Смысл разделения: видно, что блокирует работу и ждёт владельца, а что Claude может делать сам. Задача может нести две метки — например, OAuth требует и ручной регистрации приложения, и кода.
6. Что сомнительно — на что смотреть в первую очередь¶
- Гипотеза про пульс не проверена. Разрешение 5 с подтверждено из независимого источника, ошибка при силовой работе 10–20 ударов. На подход в 40 с приходится максимум 8 точек, реально меньше, и выборка смещена к спокойным моментам. Критерий проверки — остаточная дисперсия сверх упражнения, веса, повторов и предыдущего отдыха, а не совпадение с нагрудным ремнём. Проверка стоит в этапе 4.
- Бот — единая точка отказа и вся поверхность безопасности. Компрометация его токена открывает доступ ко всему. Токены пользователей будут лежать в БД — шифрование надо решить до первого живого refresh-токена.
- Соотношение машинерии и логики. Около 330 строк доменной логики против 10 репозиториев, 26 ADR и 10 пайплайнов. Прошлый совет назвал это главной претензией; владелец ответил, что микросервисы — осознанный выбор. Вопрос закрыт как решение, но цена остаётся.
- Смещение часов телефона относительно часов нигде не измеряется, а 5 секунд съедают весь бюджет сигнала.
- Общая судьба с VPN. Публичное имя проекта встаёт на тот же IP, что и naiveproxy. Блокировка адреса уносит и то, и другое (см. §5а).
- Инфраструктура впереди ценности. Первый этап целиком про доступ (туннель, регистрация, форк) — видимой функции в нём нет. Это следствие ADR-0025, но риск потери темпа в личном проекте реален.
- Не названо нигде: часовые пояса (подходы в локальном времени, точки пульса в UTC); идентичность тренажёра (обещана в целях, в схеме только имя упражнения); видимость сбоя синхронизации; бэкфилл истории; порядковый номер подхода внутри упражнения.
7. Вопросы, на которые нет ответа¶
- Какое решение в понедельник меняет этот датасет? Нигде не записано.
- «Отдать выгрузку ИИ» — это анализ или отсрочка анализа? Если решение звучит как «отдыхать, пока пульс не упадёт ниже X», то нужен реалтайм на телефоне, а архитектура имеет форму пакетного экспорта.
- Стоит ли вообще держать wger, если единственный незаменимый его актив — справочник упражнений, а интерфейс всё равно открывается через Mini App?