baldav/PROJECT_MAP.md
4aiman 56c2ff4ec7 GPS: safe on-device recovery, net-kcal model, poll/watchPosition dedup
- native_root(): pick app root by presence of gps_live/gps_points files.
  app_data_dir() is the root directly on this Tauri; the old .parent().parent()
  read the wrong dir, so background (screen-off) points were never imported.
- recoverFromFiles: match file->session by id/date, plus session id stamped
  into the native file name (gps_points_<ms>_s<id>.jsonl) for exact binding
  (gps_set_session_id -> Kotlin bindSessionIfPending). Proximity dedup +-2.5s,
  recalc with noDelete (never runs the destructive glitch/>24h filter that
  once wiped real rides), delete already-imported files. Progress bar UI.
- bike kcal is NET (daily balance already books BMR*activityLevel): pedaling =
  max(physics power/efficiency, MET-net by speed), coasting (power<=0, the bike
  would roll unpowered) = basal only. Elevation via cumulative hysteresis.
  recomputeAllStats + "recompute elevation/kcal" menu refresh stored tracks
  without touching points.
- live recording: the native file is the DB source of truth; watchPosition
  only drives the live marker, so a fix is not stored twice with two timestamps.
- track list pagination, SAF local-file picker, tile regions from router.
2026-07-29 15:35:18 +03:00

201 lines
15 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.

# 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
```
### Ходьба/бег — 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
```
## Сборка
```bash
./build.sh -d android # debug APK
./build.sh android # release APK
./build.sh -i android -d # debug + install
```