From ea462ad9c41d812a4137a1a2b664da3f0b1af1bb Mon Sep 17 00:00:00 2001 From: 4aiman Date: Mon, 21 Sep 2026 11:27:24 +0300 Subject: [PATCH] Repeat analysis: detect a segment ridden twice (minPasses 3 -> 2); count no longer blocks 2-rep sessions --- PROJECT_MAP.md | 2 +- docs/session-logs/2026-09-17_repeat-intervals.md | 14 ++++++++++++++ frontend/js/gps.js | 6 +++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index e9de6fd..c567abf 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -152,7 +152,7 @@ - Тренировки: showSessionDetail (карта, графики скорости/высоты, секция «Сегменты» - сворачиваемый `
`-аккордеон, свёрнут по умолчанию, чтобы кнопки внизу были доступны; пересчёт, оверлей маршрута, панель де-джиттера SG), showRoutesScreen (сохранённые маршруты) - Похвастаться: showShareModal / buildShareImage (сторис-PNG: форма трека + статы; `opts.fields` галочками в модалке, выбор в `localStorage.baldav_share_fields`; прозрачный/фото; в стеке окон - `winPush("share")`), exportShareImage (нативный share/save) - Объединение поездок: в `renderGpsScreen` долгий тап (500мс) по `.gps-session-item` -> режим выделения (`.selected`, плавающая `#gps-sel-bar`), 2+ -> `DB.mergeGpsSessions(idStrs)` (db.js): точки в самую раннюю сессию, `orig_session_id` per-point (реверс), `seq=NULL` (иначе совпадающие seq перемешивают порядок), лишние сессии удаляются; затем `GPS.recalculateSession(target,{noDelete})`. Колонка `gps_points.orig_session_id` (миграция) -- Разбор повторов: `GPS.detectRepeats(points)` (gps.js) - самоподобие: сетка ~10м, горячие ячейки (>=3 входов), самый длинный эталонный проход -> опорная линия; каждая точка ПРОЕЦИРУЕТСЯ на неё (cell-индекс, допуск вбок 30м) -> прогресс; проход = прогресс растёт от низа до >=80% (порт идеи `tools/track/pers.py`). Мост через короткие остановки/дрожь; время заезда `movingSec` = только движение (стопы вырезаны). Возвращает `{found,segLength,passCount,passes:[{startIdx,endIdx,movingSec}]}`. Секция «Разбор повторов» в `showSessionDetail` (только вело): таблица + вердикт последний/первый + подсветка участка на карте. Мощность на заезд - `avgPower` (новое поле возврата `GPS.computeStats`) +- Разбор повторов: `GPS.detectRepeats(points)` (gps.js) - самоподобие: сетка ~10м, горячие ячейки (>=2 входов = minPasses), самый длинный эталонный проход -> опорная линия; каждая точка ПРОЕЦИРУЕТСЯ на неё (cell-индекс, допуск вбок 30м) -> прогресс; проход = прогресс растёт от низа до >=80% (порт идеи `tools/track/pers.py`). Мост через короткие остановки/дрожь; время заезда `movingSec` = только движение (стопы вырезаны). Возвращает `{found,segLength,passCount,passes:[{startIdx,endIdx,movingSec}]}`. Секция «Разбор повторов» в `showSessionDetail` (только вело): таблица + вердикт последний/первый + подсветка участка на карте. Мощность на заезд - `avgPower` (новое поле возврата `GPS.computeStats`) - Роутинг по проезженному: loadRideGraph (кэш графа из `get_ride_graph`, grid-индекс) / rideRoute (Dijkstra между концами ноги, снап ≤60м, отсев кривого) - в `showRouteEditor` пробуется до BRouter - Прочее: showWeightModal, showSleepModal, showActivityModal, showProfileModal, showBikeModal, showMapDownloadModal, showLogSupplementModal diff --git a/docs/session-logs/2026-09-17_repeat-intervals.md b/docs/session-logs/2026-09-17_repeat-intervals.md index fd822a4..70dc302 100644 --- a/docs/session-logs/2026-09-17_repeat-intervals.md +++ b/docs/session-logs/2026-09-17_repeat-intervals.md @@ -88,6 +88,20 @@ вырезан из первого), норм.скор [20.1,21.1,20.9,22.5], посл/перв 112%. 11 сен 5× (16.6..18.8, 113%), 17 сен 5× (101%), контроль 5 обычных - 0 ложных, короткие - нет. +## Фикс 4: детекция при 2 заездах (порог 3 -> 2) +- Симптом (пользователь + разбор другого агента): при двух проездах туда-обратно детекции + нет по построению. +- Проверка по коду + харнесс: `minPasses` (по умолч. 3) работал И как порог «горячей» ячейки + (>=3 входов), И как финальный порог числа проходов. Урезанный до 2 заездов трек 16 сен: + порог 3 -> НЕ НАЙДЕНО, порог 2 -> найдено (2 заезда, 1555м). Подтверждено. +- Фикс: `minPasses` по умолчанию 2. Перекрёстки отсекаются длиной (`minLenM` 300м), не + счётчиком; обычная поездка «туда-обратно» (коммьют) не ловится, т.к. проходы считаются + монотонно в ОДНУ сторону (спуск/возврат start-guard отсекает). +- Проверка: 2-заездный -> 2×; 16 сен 4×, 11/17 по 5× (без изменений); 5 обычных поездок + (включая туда-обратно) - 0 ложных. Признаки из разбора агента (возвратный участок, время, + высота) не понадобились - длина+направление уже отсекают мусор. Снос вбок и дырки в цепочке + уже покрыты (проекция 30м в разметке проходов; gap>5 в эталонном проходе). + ## Не в этой задаче Из того же файла НЕ делалось: поправка модели п.2 (аддитивная мощность вместо max(физика,MET); п.1 про Crr уже был сделан ранее), массовое уточнение высот, ветер (Часть 2 - там вопросы). diff --git a/frontend/js/gps.js b/frontend/js/gps.js index 1725d30..60e97be 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -105,7 +105,11 @@ GPS.detectRepeats = function(points, opts) { opts = opts || {}; let cell = opts.cellM ?? 10; // grid cell ~10 m (as in tools/track heightmap) let minLenM = opts.minLenM ?? 300; // repeated segment must be at least this long - let minPasses = opts.minPasses ?? 3; // and ridden at least this many times + let minPasses = opts.minPasses ?? 2; // ridden at least this many times (2: a segment ridden + // twice is already a repeat; 3 missed the normal "there and + // back twice" case. Crossroads are rejected by minLenM, not + // by the count; same-direction monotonic passes reject a plain + // out-and-back commute - verified 0 false positives on rides) let minFrac = opts.minFrac ?? 0.8; // a pass must cover this fraction of the segment (projection // handles lateral drift, so a full rep reads as full coverage // and this stays strict, as in tools/track pers.py)