baldav/README.md
4aiman 9f8ab0eeb1 GPS model: accel-based Crr scale, auto walk-the-bike split; document climb/elevation limits
Acted on docs/gps-model-findings.md (independent analysis of 156 rides).

Implemented:
- crrFromAccel extended past the old 0.008 ceiling: 7-11 -> 0.008,
  11-15 -> 0.012 (packed dirt), >15 -> 0.020 (grass/rut/gravel). Rough
  surfaces were undercounted ~3x. Applied to session median and per-point Crr.
- Auto walk-the-bike detection (single-speed only, gear_count 1/unset):
  a normal-fix segment slower than the 30-rpm-cadence speed is the rider
  pushing, not riding. Excluded from ride distance/speed/elevation (elevRef
  reset so a pushed climb doesn't merge with the ridden one); effort counted
  as walking, not bike physics. Every sub-threshold point excluded regardless
  of duration; only runs >=30s reported as walked (walkBikeSec/walkBikeMeters).
  gearCount threaded through every computeStats/finish caller.

Documented as known limitations (left as-is by decision, need calibration/DEM):
- kcal max(physics, MET) never sums the biological overhead, so climbs are
  slightly undercounted (comment + README + PROJECT_MAP).
- absolute elevation gain is bounded by the raw GPS altimeter (tens of
  percent; needs a terrain model / wired DEM preload).
2026-09-07 15:50:19 +03:00

95 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BalDav
Персональный трекер здоровья и физической активности.
## Возможности
- **Дневник питания** — ~13000 продуктов с меткой источника (Магнит, 5ка, ИОН, KDV, Гриднев, свои). Поиск порядко-независимый (слова в любом порядке). Каталог чистится от неиспользованного импорта без метки (health-diet и пр.) кнопкой в настройках — съеденные и в рецептах сохраняются. 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50).
- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию с id сессии в имени `gps_points_<ms>_s<id>.jsonl` для однозначной привязки, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). При живой записи нативный файл — источник истины для БД, watchPosition только рисует маркер (без дублей одного фикса с разными ts). Восстановление точек из файлов на устройстве — матч файла к сессии по id/дате, дозаполнение разрывов с дедупом по близости ±2.5с, безопасный пересчёт без удаления точек, чистка уже импортированных файлов. Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация) и course-up по GPS-курсу (в ландшафте карта доворачивается вместе с экраном — курс к физическому верху). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Настраиваемый цвет линии трека (чтобы отличать от цветных дорог). Навигационный маркер позиции с треугольником-направленцем (по компасу или GPS-курсу); порядок слоёв тайлы<дороги<маршрут<трек<маркер. Импорт тайлов по регионам (SQLite, роутер LAN).
- **BLE пульсометр** — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека.
- **Расчёт ккал** — велосипед: НЕТТО-модель (суточный баланс уже учитывает BMR, поэтому активность считается сверх покоя). Кручение = max(физика мощности из gradient+Crr+CdA / КПД, эмпирический MET-нетто по скорости); накат (freewheel, `power≤0` — «велик катился бы сам») = только базовый обмен. Crr на грунтах уточняется по вибрации акселерометра (до 0.020 на траве/колее). Ходьба с велосипедом в горку (только односкоростной) отделяется автоматически по скорости ниже каденса 30 об/мин и не засчитывается как езда. MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Набор высоты — кумулятивный гистерезис (устойчив на плотных треках, где per-point разница <1м); абсолютная точность набора ограничена сырым GPS-альтиметром (порядка десятков процентов, без модели рельефа не разрешается). Известное ограничение: `max(физика, MET)` не складывает накладные расходы, поэтому подъёмы по ккал слегка недосчитываются. Пункт меню «Пересчитать высоту/калории» пересчитывает статистику всех треков без изменения точек. Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности.
- **Дистанция на разрывах GPS** — на «слепых» участках (интервал между фиксами > `gapThresholdSec`) путь считается по скорости (speed×dt, с ограничением по макс. скорости), а не прямой хордой — иначе разрывы занижают/искажают длину. Телепорт-скачки без временного разрыва отбрасываются. Расчёт зависит только от времени и скорости, поэтому live-счётчик, «Стоп» и пересчёт дают одинаковую длину. Порог `gapThresholdSec` настраивается; при изменении и пересчёте применяется сразу.
- **Тренировки** — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу.
- **Нутриенты** — 37 микронутриентов (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10). Нормы РСП/АУП (РФ/ЕС/США). Фармакокинетика — запасы в организме по полураспаду.
- **Трекинг жидкости** — water_pct в продуктах, прогресс-бар, настраиваемая норма.
- **Графики** — скорость, высота, ЧСС тренировки с зумом и pan. Тренд веса. Донат макронутриентов. Баланс ккал (приход/расход).
- **OTA обновления** — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер.
- **Ручные маршруты** — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API.
- **Сохранённые маршруты** — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат `<rtept>`), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега.
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM).
- **Редактор маршрутов** — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, тап по линии — вставить точку в это место, тащить точку — двигать, тап по точке — удалить. Высоты подтягиваются онлайн после отрисовки (из предпросмотра).
- **Де-джиттер трека** — сглаживание дрожащего GPS-трека методом Савицкого-Голея (настраиваемое окно/число итераций, живое превью на карте детали трека). Хранится обратимо: оригинал и сглаженный вариант лежат раздельно, калории пересчитываются по сглаженным точкам, откат к оригиналу в один тап. Точность каждого фикса (`accuracy_m`) пишется в БД: при пересчёте трека точки хуже порога «Макс. погрешность GPS» исключаются из расчёта и линии (обратимо — точки в БД остаются; поднять порог = вернуть). Работает во всех пересчётах (кнопка «Пересчитать», «Пересчитать всё», после де-джиттера); импорт/старьё без точности и dead-reckoning заполнения не трогает.
- **Экспорт/импорт** — свои продукты, рецепты и добавки одним файлом (JSON).
- **Вес, сон, добавки** — логирование с историей.
## Стек
- **Frontend**: чистый JavaScript, HTML, CSS. Без React/Vue/bundler.
- **Backend**: Rust (Tauri 2) — SQLite, IPC, seed import, BLE (btleplug на десктопе).
- **Android**: Kotlin — foreground GPS service, BLE HR service, WakeLock, SAF.
- **Библиотеки**: Leaflet 1.9.4 (карты), leaflet-rotate (компас), Chart.js (графики), Hammer.js (жесты), JSZip.
## Сборка
```bash
# Зависимости: Rust, Node.js, Android SDK + NDK
./build.sh -d android # debug APK
./build.sh android # release APK
./build.sh -i android -d # debug + установка на устройство
./build.sh -u android # release + upload на роутер (LAN) и VPS (OTA)
./build.sh -d -i -u android # debug + install + upload
```
## Версия и трассируемость сборки
Чтобы по розданному APK однозначно находить, где регресс/косяк:
- **`versionName 0.1.NNN`** — монотонный `BUILD_NUMBER` (инкремент на каждой сборке в `build.sh`), первичный уникальный идентификатор билда. Виден в Настройках и в свойствах пакета Android.
- **git-хэш коммита исходников** зашивается в бинарник при компиляции (`src-tauri/build.rs` → `GIT_HASH`) и показывается в Настройках: `v0.1.NNN (<hash>) <дата>`. Хэш всегда свежий (`build.rs` перезапускается при смене коммита через `cargo:rerun-if-changed`), с суффиксом **`-dirty`**, если отслеживаемый код (`frontend/`, `src-tauri/src/`) не закоммичен.
- Хэш = HEAD на момент компиляции. `build.sh` бампает версию, но НЕ коммитит, поэтому в dev-сборке хэш = последний коммит, а код может быть впереди (тогда `-dirty`). **Для раздачи: закоммить код перед release-сборкой** — тогда хэш чистый и APK точно соответствует коммиту.
Решение о точном встроенном хэше принято на коммите `b4fdc824144` (ветка master, 2026-08-16).
## Структура
Подробно: [PROJECT_MAP.md](PROJECT_MAP.md)
```
frontend/
js/main.js — UI, навигация, модалки, компас, ландшафт (~5500 строк)
js/db.js — БД, миграции, CRUD (~1500)
js/gps.js — GPS, физика ккал, BLE HR polling (~1200)
js/bmr.js — BMR формула, РСП, полураспад нутриентов
js/sync.js — синхронизация между устройствами (LAN)
js/tilecache.js — оффлайн тайлы (IndexedDB)
css/styles.css — стили, тёмная тема, ландшафтный режим
img/gps-placeholder.png — пиксельарт заглушка до GPS
data/products_seed.db — 13480 продуктов
src-tauri/
src/lib.rs — Rust бэкенд, seed import
src/ble.rs — BLE HR (btleplug десктоп, file IPC Android)
gen/android/ — Kotlin: MainActivity, GpsRecorderService, BleHrService
tools/
5ka_scraper.user.js — Tampermonkey скрейпер 5ка
magnit_scraper.user.js — Tampermonkey скрейпер Магнит
ion_scraper.user.js — Tampermonkey скрейпер ИОН
smart/
protocol.js — FitPro BLE протокол (reverse-engineered)
hr-reader.js — Node.js прототип чтения ЧСС
```
## Данные продуктов
Каталог (seed) очищен от импорта без метки источника (health-diet, ашан, фастфуд) — остаются источники с плашкой:
| Источник | Продуктов | Витамины/минералы |
|----------|-----------|-------------------|
| Магнит (SSR парсинг) | 7690 | нет |
| 5ка (API скрейпер) | 3354 | нет |
| ИОН (web.ion.ru) | 1079 | да |
| KDV (SSR парсинг) | 667 | нет |
| Гриднев (PDF) | 217 | да |
| Свои (manual) | — | по вводу |
| **Всего (seed)** | **~13000** | |
На устройстве такая же чистка — кнопка «Очистить продукты без магазина» в настройках (удаляет только неиспользованные без метки; съеденные/в рецептах остаются).