Repeat/interval analysis: auto-find repeated segments in one ride, table + last/first verdict

This commit is contained in:
4aiman 2026-09-17 22:11:10 +03:00
commit 7717a73129
6 changed files with 203 additions and 0 deletions

View file

@ -0,0 +1,48 @@
# Сессия 2026-09-17 - автоматический разбор повторов внутри поездки (интервалы)
## Контекст
Заказ из `docs/gps-model-findings.md` (раздел «БЭКЛОГ: автоматический разбор повторов»).
Пользователь катает интервалы: один подъём 5 раз за поездку, один трек, без стоп/старт.
Нужно: повторы находятся сами по самоподобию, таблица заездов и число «последний/первый».
Действовали по утверждённому плану `~/.claude/plans/merry-discovering-dream.md`
(в docs/plans не копировал - план короткий и одноразовый; здесь ссылка).
## Сделано
- `GPS.detectRepeats(points, opts)` (gps.js) - порт `tools/track/pers.py`, но эталонный
проход находится сам:
1. сетка ~10м (формула из `wind_by_passes`/`heightmap.py`);
2. горячие ячейки = >= minPasses (3) входов (заход = смена ключа ячейки);
3. эталон = ОДИН чистый проход: от первой горячей ячейки, добавляя новые, до первого
возврата к более ранней (склейка кругов давала абсурд - см. баги);
4. проходы = порт `passes()`: позиция в seq растёт от старта (допуск назад 3, гэп 25),
покрытие >= 80%; старт-гвард `pos<=15%` не даёт спуску открыть заезд;
5. нормировка: сравнение по `L_seq / T_прохода` (снимает 5% разброса длины GPS).
Направление без явной проверки bearing: seq хранит первую (подъёмную) позицию ячейки,
спуск роняет позицию и закрывает заезд.
- `avgPower` в возврат `GPS.computeStats` (накопление `power*dt` в вело-ветке / время
педалирования) - для колонки «Вт».
- UI: секция-аккордеон «Разбор повторов» в `showSessionDetail` (только вело), таблица
(№/время/км-ч норм/каденс/Вт), вердикт последний/первый (зелёный >=100%, жёлтый >=90%,
красный <90%), подсветка участка на карте (бирюза `#00e5ff`). CSS в styles.css.
## Баги по пути (и фиксы)
- Эталон как «самый длинный горячий ран» склеивал 5 кругов -> segLength 4406м и заезды по
9с (норм. скорость 1762 км/ч). Фикс: эталон = один проход до первого возврата.
- Следующая точка в ТОЙ ЖЕ ячейке считалась «возвратом» и сразу обрывала seq (seq=1).
Фикс: `c===seq[last]` -> продолжить, не обрывать.
## Проверка
- Node-харнесс на `tools/track/baldav.db` (реальные данные):
- 11 сен (3214 тчк): участок 1577м, **5 заездов**, норм. скорости 15.0/17.1/17.8/17.9/17.5,
последний/первый 117%.
- 17 сен (3079 тчк): участок 1630м, **5 заездов**, 16.9/15.8/17.3/16.4/17.7, 105%.
Совпадает с находками («11 и 17 сентября, по 5 повторов»).
- Контроль: 5 обычных вело-поездок (8-23 км) + 2 короткие -> «повторов не найдено» (0 ложных).
- Десктоп (Xvfb): временно подсажена сессия 17 сен в дев-БД (бэкап + точечное удаление
после, БД байт-в-байт восстановлена). Секция рисуется, аккордеон работает, таблица 5 строк
совпадает с харнессом, вердикт зелёный 105%, участок подсвечен бирюзой на карте. Скрин.
- Далее: сборка APK + проверка на телефоне (реальные интервалы там же).
## Не в этой задаче
Из того же файла НЕ делалось: поправка модели п.2 (аддитивная мощность вместо max(физика,MET);
п.1 про Crr уже был сделан ранее), массовое уточнение высот, ветер (Часть 2 - там вопросы).