baldav/PROJECT_MAP.md
4aiman cd975b5640 GPS distance: estimate blind-GPS gaps by speed*time; savable gap setting
On stretches where the fix rate drops (dt > gapThresholdSec, incl. dead-
reckoning) the straight chord mis-measures the path, so computeStats now
estimates gap distance by avg(speed)*dt (capped at max speed; a tiny chord
over a long gap means the rider was stopped and the reported speed is stale
-> trust the position). A fast jump WITHOUT a time gap is a teleport glitch
and is dropped. Distance now depends only on ts + speed, not on the
`estimated` flag (which live points carry but DB rows don't), so the live
counter, Stop and recompute finally agree. Verified on a real ride: at
gapThreshold=30 -> 35.7 km / 1024 kcal (map-measured ~35 km); clean tracks
without gaps unchanged.

Settings: read GPS config on input+change (reliable on mobile), add a
"Save GPS settings" button and a "saved" toast so it's clear the value
persisted; changing gapThreshold + recompute now visibly applies.
2026-07-31 12:58:34 +03:00

16 KiB
Raw Blame History

PROJECT MAP — BalDav

Трекер здоровья и активности. Pure JS + Tauri 2 + SQLite. Без фреймворков и бандлеров.

Статистика

Метрика Значение
Строк кода ~22000
Файлов исходников 50+
Язык JS 70%, Rust 6%, Kotlin 12%, CSS 12%
Таблиц в БД 20
Экранов 7 основных + 15 модалок
IPC команд (Tauri) 14
Библиотеки Leaflet, Chart.js, Hammer.js, JSZip
Платформы Android (Kotlin), Desktop (Tauri)

Структура файлов

Frontend (frontend/)

Файл Строк Назначение
js/main.js ~5800 Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги
js/db.js ~1500 SQLite через Tauri IPC. Миграции, CRUD, 41 поле добавок, water_pct, reserves, sync
js/gps.js ~1600 GPS запись, ккал (нетто-модель + MET-нетто floor, накат=базовый), altitude filter + гистерезис высоты, BLE HR polling, dead reckoning. Источник истины БД при живой записи — нативный файл (_processIncomingPoint(p, fromNative): watchPosition только рисует, поллинг пишет). recoverFromFiles — восстановление точек с устройства (матч по id/дате + _s<id> в имени, дедуп ±2.5с, recalculateSession(noDelete), чистка импортированных файлов). recomputeAllStats — пересчёт статы всех треков без изменения точек. Отбор по точности (minAccuracyM), bearing_deg
js/sync.js ~300 Синхронизация между устройствами (LAN, PHP сервер на роутере)
js/tilecache.js 178 Оффлайн кэш тайлов карты (IndexedDB)
js/bmr.js ~70 BMR (Mifflin-St Jeor), РСП из БД (33 нутриента), полураспад
css/styles.css ~2100 Темная тема, мобильный дизайн, HR зоны, ландшафтный режим
index.html 28 Точка входа, порядок загрузки скриптов
img/gps-placeholder.png 63KB Пиксельарт заглушка при поиске GPS
data/products_seed.db ~12MB 24443 продукта (HD 8090, magnit 7690, 5ka 3354, ashan 2000, ion 1079, kdv 667, mcdonalds 619, kfc 361, dodo 258, gridnev 217)

Библиотеки (lib/): leaflet.js (1.9.4), leaflet-rotate.js (компас), chart.min.js, chartjs-plugin-zoom.min.js, hammer.min.js, jszip.min.js

Бэкенд Rust (src-tauri/)

Файл Строк Назначение
src/lib.rs ~750 Миграции v1, IPC команды, seed import, update check, tile import, road map rebuild. native_root() — выбор корня приложения по наличию gps_live_points.jsonl/gps_points_* (на этом Tauri app_data_dir() = корень напрямую; старый .parent().parent() читал мимо, отсюда потеря фоновых точек)
src/ble.rs ~470 BLE HR: btleplug (desktop), file IPC (Android), FitPro + Standard HR
src/main.rs 5 Точка входа
Cargo.toml — tauri, tauri-plugin-sql, rusqlite, serde, btleplug, tokio, uuid
tauri.conf.json — Конфиг приложения, плагины, ресурсы

Android Kotlin (src-tauri/gen/android/.../dev/baldav/app/)

Файл Строк Назначение
MainActivity.kt ~300 FileObserver IPC, seed DB, safe insets, SAF, BLE commands, OTA check
GpsRecorderService.kt ~530 Foreground service GPS, WakeLock, JSONL (один файл на сессию до Стоп, идемпотентный onStartCommand при START_STICKY-перезапуске ОС; bindSessionIfPending() дописывает id сессии в имя файла _s<id> по gps_session.txt), 50Hz акселерометр/гироскоп, dead reckoning, raw sensor files
BleHrService.kt ~340 Foreground service BLE HR, FitPro protocol, GATT queue, scan/connect
generated/*.kt ~1100 Tauri-сгенерированные: WebView, IPC, permissions

Инструменты (tools/)

Скрипт Назначение
5ka_scraper.user.js Tampermonkey скрейпер 5ka.ru (BFS категорий, Load JSON)
magnit_scraper.user.js Tampermonkey скрейпер magnit.ru (NUXT_DATA парсинг)
ion_scraper.user.js Tampermonkey скрейпер web.ion.ru (fetch, без перезагрузок)
import_ion.js Импорт ION продуктов в seed DB
import_with_dedup.js Импорт с дедупликацией по имени
enrich_nutrients.js Обогащение микронутриентами из ION
parse_magnit.js Парсер HTML каталога Магнит
parse_kdv.js Парсер HTML каталога KDV
download_tiles.js Скачивание тайлов карт для оффлайна
pack_tiles.js Упаковка PNG тайлов региона в SQLite для импорта
healthdiet_scraper.user.js Tampermonkey скрейпер health-diet.ru (КБЖУ + 37 нутриентов)
import_healthdiet.js Импорт health-diet.ru JSON в seed DB (авто-бамп версии)
fetch_openfoodfacts.js OpenFoodFacts API
fuzzy_dedup.js Нечёткая дедупликация

Корень

Файл Назначение
build.sh Сборка: android/debug/release, sync frontend, версионирование, -u upload на VPS
BUILD_NUMBER Автоинкремент номера сборки
ion_products.json 1111 продуктов ION (сырые данные скрейпера)
5ka_products_fresh.json 3103 продукта 5ka (сырые данные)

База данных

Таблицы (Rust v1 миграция + JS миграции)

Таблица Назначение Ключевые поля
profile Key-value настройки key, value
products Продукты (13480) name, kcal/protein/fat/carbs per 100g, витамины, минералы, nutrients_guessed
food_log Дневник еды date, time, product_id/recipe_id, amount_g, unit_id, unit_amount, ккал/БЖУ
recipes Рецепты name, cooking_method_id, servings, notes
recipe_ingredients Ингредиенты рецептов recipe_id, product_id, custom_name, amount_g, cooking_method_id
cooking_methods Способы готовки (6 шт) name, потери витаминов/БЖУ в %
serving_units Единицы измерения (5 шт) name (шт/ст.л./ч.л./стакан/мл), grams_equivalent
product_units Привязка единиц к продуктам product_id, unit_id, grams_per_unit
weight_log Лог веса date, time, weight_kg
sleep_log Лог сна date, bed/asleep/wake time, nap_minutes
activity_log Лог активности date, type, minutes, kcal, activity_type (squats/jumprope/stretch/swim/manual), sets, reps, distance_m
supplements Добавки (БАД) name, daily_dose_g, витамины/минералы
supplement_log Приём добавок date, supplement_id, dose_g
gps_sessions GPS тренировки date, activity_type, distance, kcal, speed, elevation, bike_id
gps_points Точки трека session_id, ts, lat, lon, altitude_gps_m, altitude_srtm_m, speed_ms, seq, bearing_deg, heart_rate, spo2, bp_sys, bp_dia, accel_rms/max, gyro_pitch/yaw
bikes Велосипеды name, weight_kg, wheel, звёзды, surface_type, is_active
rdi_standards Стандарты РСП (RU/EU/US) code, name
rdi_values Нормы нутриентов standard_id, nutrient_key, value, norm_type (rdi/aup)
nutrient_halflife Полураспад нутриентов nutrient_key, halflife_avg_days
nutrient_reserves Запасы нутриентов по дням date, reserves (JSON)
road_points Дедуплицированные точки всех треков lat, lon, atype (bike/walk)

Seed DB (products_seed.db)

Источник Продуктов Микронутриенты
magnit 8627 нет
5ka 3103 нет
ION 1078 витамины + минералы
kdv 672 нет
Всего 13480 1398 обогащены (nutrients_guessed)

Экраны и навигация

Нижний навбар (5 табов)

[Сегодня] [Продукты] [Трек] [Добавки] [Настройки]

Экраны

  • Сегодня — баланс ккал, еда (продукты + блюда), GPS тренировки, активность, вес, сон, добавки, прогресс-бар жидкости, нутриенты (приход + запасы)
  • Продукты — подтабы [Продукты | Рецепты], поиск, карточки с КБЖУ, добавление в дневник
  • Трек — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate), course-up по GPS-курсу (карта и панель доворачиваются в ландшафт вместе, bearing = h), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, вело/пешие)
  • Добавки — 41 нутриент (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10)
  • Настройки — профиль, образ жизни, нормы ЧСС/давления, BLE пульсометр, велосипеды, GPS конфиг, OTA обновления, импорт тайлов, карта дорог

Модалки

  • Еда: showFoodModal, showEditFoodModal, showFoodFromRecipeModal
  • Рецепты: showRecipeDetailModal, showRecipeEditorModal, showAddIngredientModal
  • Тренировки: showSessionDetail (карта, графики скорости/высоты, пересчёт)
  • Прочее: showWeightModal, showSleepModal, showActivityModal, showProfileModal, showBikeModal, showMapDownloadModal, showLogSupplementModal

IPC команды (Rust → JS)

Команда Назначение
import_seed_products Импорт продуктов из seed DB, прогресс-события
get_safe_insets Отступы Android status/nav bar
gps_service_command start/stop GPS + BLE команды (ble_scan/connect/disconnect)
gps_service_read_points Чтение активного JSONL файла точек (через native_root)
gps_set_session_id Передать id сессии сервису (пишет gps_session.txt → сервис дописывает _s<id> в имя файла)
list_points_files / read_points_file / delete_points_file Список/чтение/удаление нативных gps_points_* (восстановление + чистка)
get_points_filename Имя активного файла точек (по указателю gps_live_points.jsonl)
debug_native_paths Диагностика путей нативных файлов
save_file SAF диалог сохранения (GPX экспорт)
get_app_info Версия, git hash, дата сборки
get_update_info Проверка OTA обновления (BUILD_NUMBER с VPS)
ble_scan Сканирование BLE устройств
ble_scan_results Результаты скана (Android)
ble_connect Подключение к BLE устройству
ble_disconnect Отключение BLE
ble_get_hr Последние данные HR/SpO2/BP
ble_is_connected Статус подключения
ble_check_bluetooth Состояние Bluetooth адаптера
ble_save_device Сохранение привязанного устройства
import_tiles Чтение тайлового SQLite батчами (base64)
download_file Скачивание бинарного файла по URL (без лимита размера)
rebuild_roads Перестройка road_points из всех треков (2м дедуп)
get_road_points Чтение road_points батчами (обход лимита SQL plugin)

Формула ккал (GPS)

Велосипед — НЕТТО-модель (суточный баланс = intake − BMR*activityLevel − activity, поэтому активность считается СВЕРХ покоя)

Power = v * (m*g*sin(gradient) + Crr*m*g*cos(gradient) + 0.5*CdA*rho*v^2)
  m = rider_kg + bike_kg;  gradient из СГЛАЖЕННОЙ высоты
  Crr = 0.004 (асфальт) / 0.006 (смешанный) / 0.008 (грунт)
  CdA = 0.6, rho = 1.225, efficiency = 0.25

Педалирование (power>0): kcal = max(физика, MET-нетто), физика = power*dt/(0.25*4184)
  MET по скорости: <16км/ч=6.0, <19=6.8, <22=8.0, <25=10.0, <28=12.0, иначе 14.0
  MET-нетто = (dt/60)*((MET-1)*3.5*weight)/200   (−1 MET покоя, он уже в BMR)
  Floor только при v≥5км/ч (ниже — дрожание GPS, не езда)
Накат / freewheel (power≤0, «велик катился бы сам»): kcal = базальный метаболизм
  (детектор кручу/накат по IMU-каденсу проверен на 50Hz — сигнал слишком слаб, не используется; хук cadence_rpm — под BLE-датчик шатуна)

Набор высоты: кумулятивный гистерезис (elevRef, порог 1м) — на плотных треках per-point разница <1м даёт 0

Дистанция по сегментам (computeStats, gps.js ~189-235)

segMaxMs = maxSpeedKmh/3.6 (вело) или walkMaxSpeedKmh/3.6 (пеш); gapThr = config.gapThresholdSec (30)
шип   (dt<=gapThr И v>segMaxMs)  → сегмент не считать (телепорт без разрыва по времени)
разрыв(dt>gapThr)               → segDist = min(avg(speed)*dt, segMaxMs*dt); если хорда<20м → берём хорду (стоял, скорость залипла)
норма (иначе)                   → segDist = хорда (haversine)
distance += segDist; высота/maxSpeed — только на «норме»; ккал в разрыве/estimated = walk-MET по времени
Зависит только от ts+speed (не от флага estimated) → live == «Стоп» == пересчёт. gapThreshold настраивается, применяется при пересчёте.

Ходьба/бег — MET-таблица

<4 км/ч = 2.5 MET, 4-5.5 = 3.5, 5.5-7 = 5.0, 7-9 = 8.0, 9-12 = 10.0, 12-16 = 12.5, 16+ = 15.0
kcal = (dt/60) * (MET * 3.5 * weight_kg) / 200

Сборка

./build.sh -d android    # debug APK
./build.sh android       # release APK
./build.sh -i android -d # debug + install