Exclude real GPS fixes coarser than the "Макс. погрешность GPS" setting (minAccuracyM) from a track's stats and its drawn line. Non-destructive: the points stay in the DB, so raising the threshold brings them back. - GPS.filterByAccuracy(points, threshold?): drops points with a measured accuracy above the threshold; always keeps points without accuracy (imported GPX/FTB, older recordings) and estimated dead-reckoning fills (sentinel >=999). - Wired into GPS.computeStats (top) so every recompute path funnels through it — the "Пересчитать" button, "Пересчитать всё", and the post-de-jitter recompute. Also into GPS.filterNoise so the detail-screen line matches the stats. - Removes mild jitter detours that the spike filter misses (verified: 342 -> 334m when a poor-accuracy nudge is dropped). Lower the threshold + Пересчитать to apply; raise it to undo.
19 KiB
19 KiB
PROJECT MAP — BalDav
Трекер здоровья и активности. Pure JS + Tauri 2 + SQLite. Без фреймворков и бандлеров.
Статистика
| Метрика | Значение |
|---|---|
| Строк кода | ~22000 |
| Файлов исходников | 50+ |
| Язык | JS 70%, Rust 6%, Kotlin 12%, CSS 12% |
| Таблиц в БД | 21 |
| Экранов | 7 основных + 15 модалок |
| IPC команд (Tauri) | 14 |
| Библиотеки | Leaflet, Chart.js, Hammer.js, JSZip |
| Платформы | Android (Kotlin), Desktop (Tauri) |
Структура файлов
Frontend (frontend/)
| Файл | Строк | Назначение |
|---|---|---|
js/main.js |
~5900 | Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги. appConfirm/appPrompt (in-app диалоги вместо нативных, в стеке окон). Страница «Сохранённые маршруты», оверлей маршрута на записи и просмотре (whenMapReady — await готовности карты, не таймаут). Панель де-джиттера на детали трека (ползунки окно/итерации, живое превью, сохранить/откат, reopenDetail — переоткрытие детали без наложения оверлеев). showRouteDetail — предпросмотр маршрута (карта+профиль высоты+тап-высоты+фуллскрин), showRouteEditor — создание/правка маршрута (тап=точка, тащить=двигать, тап точки=удалить). drawRoutePolyline/routeSlopeColor (полосы уклона по 1%)/routeElevColor — топо/уклон-обводка в панели routePane (маршрут ПОД треком; на rotate-карте панель вешается внутрь _rotatePane, иначе маршрут не виден при записи). fetchElevations — цепочка GPXZ→open-elevation→open-meteo→OpenTopoData. Диагностические console.log("[тег]...") глушатся в release по get_app_info().debug (cfg!(debug_assertions)) |
js/db.js |
~1550 | SQLite через Tauri IPC. Миграции, CRUD, 41 поле добавок, water_pct, reserves, sync. Хелперы маршрутов (routes CRUD, updateRoutePoints — сохранить высоты, getRoutes.hasEle). saveSmoothedTrack — чанковый CASE-апдейт lat/lon_smoothed (18 UPDATE на 4366 точек вместо 4366), setSessionSmoothed — откат де-джиттера |
js/gps.js |
~1650 | GPS запись, ккал (нетто-модель + MET-нетто floor, накат=базовый), altitude filter + гистерезис высоты, BLE HR polling, dead reckoning. Источник истины БД при живой записи — нативный файл (_processIncomingPoint(p, fromNative): watchPosition только рисует, поллинг пишет). recoverFromFiles — восстановление точек с устройства (матч по id/дате + _s<id> в имени, дедуп ±2.5с, recalculateSession(noDelete), чистка импортированных файлов). recomputeAllStats — пересчёт статы всех треков без изменения точек. filterByAccuracy — отбор по точности (minAccuracyM) внутри computeStats+filterNoise: неточные фиксы исключаются из расчёта/линии (обратимо, non-destructive; null/≥999 keep), bearing_deg. smoothTrackSG(pts,W,it) — де-джиттер Савицкого-Голея (квадратичный МНК по окну, итеративно, концы закреплены; lat/lon→метры→обратно, все поля точки сохраняются). parseRouteGPX(xml) — парсер маршрутов (rtept/trkpt/wpt, читает <ele>, DOMParser). routeElevStats(pts) — профиль маршрута (накопл. дистанция, грейд к след. точке, набор/сброс с гистерезисом, макс-уклон; высоты сглаживаются). config.gpxzKey — ключ GPXZ (localStorage) |
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, smoothed, smooth_window, smooth_iters (де-джиттер) |
gps_points |
Точки трека | session_id, ts, lat, lon, altitude_gps_m, altitude_srtm_m, speed_ms, seq, bearing_deg, accuracy_m, lat_smoothed, lon_smoothed (обратимый де-джиттер), heart_rate, spo2, bp_sys, bp_dia, accel_rms/max, gyro_pitch/yaw |
routes |
Сохранённые маршруты (оверлей навигации) | name, color, points_json ({lat,lon,ele?}), created_at |
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м, цвета по вело/пешие/бег), сохранённые маршруты (оверлей-навигация, кнопка-тоггл «М», предпросмотр с профилем высоты, топо/уклон-обводка, тоггл «∡/⛰», высоты по цепочке GPXZ→open-elevation→open-meteo→OpenTopoData), де-джиттер трека (Савицкий-Голей, кнопка «≈» на детали) - Добавки — 41 нутриент (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10)
- Настройки — профиль, образ жизни, нормы ЧСС/давления, BLE пульсометр, велосипеды, GPS конфиг, OTA обновления, импорт тайлов, карта дорог
Модалки
- Еда: showFoodModal, showEditFoodModal, showFoodFromRecipeModal
- Рецепты: showRecipeDetailModal, showRecipeEditorModal, showAddIngredientModal
- Тренировки: showSessionDetail (карта, графики скорости/высоты, пересчёт, оверлей маршрута, панель де-джиттера SG), showRoutesScreen (сохранённые маршруты)
- Прочее: 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, дата сборки, debug (cfg!(debug_assertions) — для глушения диагностики в release) |
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