From d6139a99f7527166c816cbe6c12326aa1b44dc41 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Mon, 21 Sep 2026 12:42:57 +0300 Subject: [PATCH] Docs: repeat-detection findings and interval backlog notes --- docs/gps-model-findings.md | 296 +++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/docs/gps-model-findings.md b/docs/gps-model-findings.md index bf9f629..fce3197 100644 --- a/docs/gps-model-findings.md +++ b/docs/gps-model-findings.md @@ -351,3 +351,299 @@ Aeropod). Трубка, направленная вперёд, плюс датч Готовый код обоих методов варианта 1 -- в `tools/track/track.py` (функция `wind_by_passes` и секция «ветер» в `report`), выше описан алгоритмически. + +--- + +# БЭКЛОГ: автоматический разбор повторов внутри одной поездки + +Заказано пользователем 17 сентября 2026. + +## Задача + +Пользователь катает интервалы: приезжает к подъёму, проезжает его 5 раз, +скатываясь вниз между заездами, потом уезжает домой. Сейчас, чтобы разобрать +такую тренировку, приходится либо останавливать и заново стартовать трек +на каждом повторе (пользователь называет это бредом), либо разбирать +вручную задним числом. + +Нужно: **один трек на всю тренировку, повторы находятся сами.** Пользователь +готов на отдельный режим «велотренировка», если так проще. + +## Что должно получиться на выходе + +Таблица повторов: номер, время, средняя скорость, каденс, мощность. +И главное число, по которому принимается решение о завтрашнем дне: + + отношение последнего повтора к первому + +Порог, выведенный из данных (11 и 17 сентября, по 5 повторов): +- последний быстрее или равен первому -- не на пределе, можно повторить завтра +- последний медленнее на 5-10% -- нормальная тяжёлая тренировка +- последний медленнее больше чем на 10% -- два дня спокойно + +## Алгоритм, проверен вручную на этих данных + +Повторы ищутся по самоподобию трека, без опорных точек от пользователя. + +1. **Разложить точки поездки по ячейкам** ~10 м (сетка как в `heightmap.py`). +2. **Найти ячейки, посещённые больше одного раза**, и отбросить те, где + направление движения (`bearing_deg`) при посещениях различается больше + чем на 60 градусов: проезд вниз по той же дороге повтором не считается. + **Без проверки направления половина повторов теряется или считается + задом наперёд** -- проверено, это главная ловушка. +3. **Собрать самую длинную цепочку** таких ячеек, идущую подряд по ходу + движения. Это и есть повторяемый участок. Требовать длину не меньше + 300 м и не меньше 3 проходов, иначе это перекрёсток у дома, а не подъём. +4. **Разметить проходы:** для каждого прохода записать вход и выход + по цепочке, время, длину. +5. **Нормировать время на общую длину:** у пользователя GPS даёт от 1138 + до 1199 м на одном и том же подъёме, то есть 5% разброса. Без нормировки + сравнение повторов между собой врёт. + +## Чего делать НЕ надо + +**Не просить пользователя ставить метки.** Он специально сказал, что +останавливать и перезапускать трек не будет. + +**Не опираться на высоту отдельного проезда.** Профиль повторяемого участка +брать из карты высот по многим проездам (см. соседний раздел): у одиночного +проезда GPS даёт ступеньки вида 0, 0, 0, 0, 8.5 м на ровном месте. + +**Не считать мощность усреднением по точкам.** Усреднять по времени. +При усреднении по точкам отрезки чуть разной длины дают разный вес +медленным кускам, и на одинаковой скорости выходят разные ватты +(проверено: 17.9 км/ч -> 173 Вт и 18.1 км/ч -> 209 Вт на одном подъёме). + +## Готовый код + +Ручная версия детектора по двум опорным точкам -- в истории этой сессии, +в `tools/track/`. Она находит проходы правильно, но опорные точки задаются +руками. Автоматизировать надо шаги 1-3. + +--- + +# БЭКЛОГ: массовое уточнение высот по всем трекам + +Заказано пользователем 17 сентября 2026. Приоритет свой, не срочный. + +## Что нужно + +Пункт меню, который уточняет высоты **пакетно**: идёт от последнего трека +назад по времени и обрабатывает те, где `altitude_srtm_m` ещё не заполнена. +Пользователь запускает его вручную, когда сидит на проводном интернете. + +Требования к исполнению, из общих правил проекта: +- показывать прогресс числами: сколько треков обработано, сколько осталось +- прерываемость: в поле связь рвут РЭБ и РКН, процесс должен переживать + обрыв и продолжаться с того места, где встал, а не начинать заново +- уже уточнённые треки не трогать повторно + +## Зачем, с измерениями + +Аудит всех 83 велопоездок показал, что **высота за май и июнь непригодна**: + + май-июнь: набор в БД 0-23 м, расчётный по сглаженной высоте 50-207 м + июль-сентябрь: набор в БД и расчётный сходятся (168/138, 251/275, 227/235) + +До 17 мая запись шла раз в 3-4 секунды при шуме высотомера 0.8 м между +соседними точками. Пример 10 мая (session_id 3): сырая высота скачет +`36 20 23 20 20 36 36 35 35 35 34 34 33 20 23 20 19 32 32 32 31`, то есть +приёмник бегает между двумя уровнями с размахом 16 м. Медианный фильтр +и скользящее среднее это приглаживают, но остаток даёт уклоны до 12.8% +на треке, где реального набора 14 м на 13 км. + +Практическое следствие: **любой анализ по уклонам за май и июнь даёт +фальшивые результаты**, причём правдоподобные на вид. Построенная по этим +данным кривая «лучший удержанный момент на шатунах» показала май лучшим +месяцем сезона, хотя по мощности за 20 минут рост с мая по сентябрь +составил 1.12 -> 1.75 Вт/кг. + +Отдельно найдены отказы высотомера внутри годных треков: 2 сентября +(session_id 1788361994788) после точки 4194 значение падает на 10 м +и **замерзает** -- 26.8, 26.9, 26.9, 26.9 шестьдесят точек подряд, +а после перерыва записи прыгает на 64.8 м с точностью 22 м. Уклоны +на этом участке выдуманы целиком. + +## Признаки негодной высоты, годятся как автотест + +Все три считаются по уже записанным данным, без сети: + +1. `elevation_gain_m` в сессии и набор, пересчитанный по точкам, расходятся + больше чем вдвое в любую сторону +2. медиана модуля разности высоты между соседними точками выше 1.5 м +3. доля точек с шагом записи больше 8 секунд выше 15% + +По этим признакам из 83 поездок отсеиваются 31. После уточнения высот +через SRTM проверку надо прогнать повторно: она же покажет, помогло ли. + +## Объём работы, посчитан по базе + + всего точек 418 465 + уже уточнено 47 420 (11.3%, 12 сессий из 170) + осталось 371 045 + запросов по 100 3 711 + +Размер пачки 100 задан в `frontend/js/main.js` около строки 3085 и совпадает +с потолком OpenTopoData. + +## Что надо поправить ДО массового прогона + +**Откат по цепочке источников менять не надо.** Он сделан намеренно: +лучше получить хоть какую-то высоту, чем никакой. Проблема не в откате, +а в том, что после него не остаётся следа, кто именно ответил. + +**1. Нет колонки источника.** В `gps_points` есть `altitude_srtm_m`, но нет +поля, откуда это значение пришло. При работающем откате база неизбежно +становится смесью источников, и без этой колонки отличить их потом нельзя +никак. Нужна колонка (ALTER TABLE, без пересоздания), заполняемая при записи. +Это единственное обязательное изменение. + +**2. Откат должен быть виден пользователю.** Сейчас он сообщается только +строкой в консоли (`[gpxz] http 403`). Достаточно показать в конце итог +вида «уточнено 2314 точек: GPXZ 0, open-elevation 0, open-meteo 2314». + +**3. Обработка 429 и 402 отдельно от сетевой ошибки.** По лимиту +останавливаться до завтра, по обрыву повторять. Иначе при массовом прогоне +упёршийся лимит молча отправит весь остаток базы к запасному источнику. + +## Чего ждать и чего не ждать + +Copernicus GLO-30 это сетка 30 м. На подъём длиной 200-400 м приходится +7-13 отсчётов, этого хватает. На короткий рамп 60-100 м приходится 2-3 +отсчёта, форму по ним не восстановить. То есть массовое уточнение закроет +затяжные подъёмы и общий набор, но НЕ закроет короткие рампы, на которых +пользователь срывается. Для них нужен либо барометр, либо разовый замер +профиля конкретного подъёма угломером. + +## Лимиты GPXZ против объёма + +Тарифы с их сайта: Evaluation бесплатно и 100 запросов в сутки (помечен +«testing only»), Small 99 USD/мес и 2500 в сутки, Large 249 USD/мес и 7500. + + 3711 запросов при 100/сутки = 37 дней + 3711 запросов при 2500/сутки = 1.5 дня + +Отдельно: одна поездка это медиана 19 запросов, но максимум 133. То есть +**длинная поездка не влезает в бесплатный суточный лимит даже одна.** +Если массовый прогон делать на бесплатном ключе, он обязан быть +возобновляемым и считать свой суточный расход сам. + +## Вариант без лимитов: свой Open Topo Data + +GPXZ на своём сайте прямо пишет, что открытый форк их API-сервера +называется Open Topo Data. Размер пачки 100, уже зашитый в коде, +взят как раз из его потолка, то есть **протокол совместим и менять +в клиенте нужен только базовый URL** (сейчас `https://api.gpxz.io/v1/...` +захардкожен в `frontend/js/main.js` около строки 3091). + +Считано по базе: все треки пользователя укрываются **двумя градусными +тайлами 1x1**. Для GLO-30 это порядка сотни мегабайт на диске, разово +скачивается по проводу. + +Что это даёт против платного тарифа: +- лимита нет вообще, весь бэклог прогоняется за один вечер +- в поле сеть не нужна: у пользователя её глушат, и это постоянная + проблема всех сетевых зависимостей приложения +- тот же софт и тот же набор данных, значит результаты сопоставимы + с уже уточнёнными 12 сессиями, если там был GPXZ + +Минус: нужен хост, на котором это крутится, и разовая возня с датасетом. + +## Но своего хоста может и не понадобиться + +Автор Open Topo Data держит бесплатный публичный инстанс +`api.opentopodata.org`. Его ограничения: + + 100 точек на запрос -- ровно тот размер пачки, что уже в коде + 1 запрос в секунду -- нужен троттлинг, сейчас пауз между пачками нет + 1000 запросов в сутки -- в десять раз больше бесплатного GPXZ + + весь бэклог 3711 запросов -> 3.7 суток, 62 минуты чистого времени, + то есть ~15 минут работы в день + +Сравнение по срокам на тот же объём: + + GPXZ Evaluation (бесплатно) 37.1 дней + Open Topo Data публичный 3.7 дней + GPXZ Small (99 USD/мес) 1.5 дня + +**Ключ не нужен вообще**, значит отпадает и вопрос, укладывается ли +массовый прогон личного архива в пометку «testing only» бесплатного +тарифа GPXZ. + +### Выбор датасета, и почему это не мелочь + +На публичном инстансе **нет Copernicus GLO-30**, на котором работает GPXZ. +Из глобальных 30-метровых доступны `mapzen`, `aster30m`, `srtm30m`. +`eudem25m` (25 м, лучшая точность) покрывает только страны EEA, регион +пользователя в его extent не входит. + +Отсюда два следствия: + +1. Датасет будет **не тот**, которым уточнены нынешние 12 сессий. + Смешивать нельзя, и колонка источника из пункта 2 выше становится + не пожеланием, а обязательной: без неё база превратится в винегрет + из трёх рельефов. +2. Разумный выбор по умолчанию `srtm30m` (USGS): у `aster30m` известны + шумы и артефакты, `mapzen` это композит из нескольких источников, + то есть внутри одного датасета точность неоднородна. + +Хорошая проверка после прогона: 12 уже уточнённых сессий уточнить заново +новым датасетом в отдельную колонку и сравнить набор высоты. Разойдутся +сильно - значит выбор датасета важнее, чем кажется, и это надо знать +до того, как на этих высотах строить разбор поездок. + +## ПРОВЕРЕНО: в базе лежит не то, что все думали + +Проверка сделана, и она важнее всего остального в этом разделе. + +**Ключ GPXZ не работает и, судя по всему, не работал никогда.** Запрос +к `api.gpxz.io` возвращает HTTP 403 с телом `error code: 1010` +и заголовком `Server: cloudflare`. Ответ **одинаковый с ключом и без +ключа**, то есть запрос режет Cloudflare по подписи клиента, до всякой +проверки ключа. Ключ при этом валидный на вид: 28 символов, префикс `ak_`. + +**Те 12 «уточнённых» сессий уточнены open-meteo.** Это третий, последний +запасной источник в цепочке `GPXZ -> open-elevation -> open-meteo`. +Доказательство: опорные целые значения в `altitude_srtm_m` совпадают +с ответом open-meteo точка в точку и расходятся с `srtm30m` на 2-3 м. + +**Дробные значения в базе создаёт приложение, а не источник.** Записанная +последовательность `39.000, 38.857, 38.714, 38.571, 38.429, 38.286, +38.143, 38.000` это `39 - k/7`, линейная интерполяция между двумя целыми +отсчётами. Все внешние DEM (srtm30m, aster30m, mapzen, srtm90m, +open-elevation, open-meteo) отдают **целые метры**. То есть дроби +в колонке выглядят как высокая точность, а на деле это прямая, натянутая +между узлами сетки в 30-90 м. + +Практическое следствие для любого будущего разбора: **нельзя считать +уклон по `altitude_srtm_m` на коротких кусках.** Интерполяция даёт +гладкий правдоподобный профиль там, где данных нет вообще. + +### Что из этого следует для задачи + +1. Тихий откат (пункт 1 выше) не гипотетическая опасность, а уже + случившийся факт: база заполнена последним источником в цепочке, + и об этом никто не знал. +2. Колонка источника (пункт 2) обязательна. +3. Отказ надо показывать пользователю, а не писать в консоль. + +### Покупать ли GPXZ: нет + +С карты покрытия на их сайте: лидар 0.5-2 м у GPXZ есть только в США, +западной и северной Европе и Новой Зеландии. Регион пользователя +в жёлтой зоне, где по их же легенде 30 м, а по их тексту база +из Copernicus 30m. То есть платный GPXZ дал бы Copernicus 30m вместо +бесплатного SRTM 30m: другая вертикальная точность, **та же +горизонтальная сетка 30 м**. + +А упирается всё именно в горизонтальную: подъём длиной 60-100 м, на +котором пользователь срывается, это 2-3 отсчёта на любой тридцатиметровой +сетке. Ни один DEM его не разрешит, ни платный, ни бесплатный. + +Измерено на одном подъёме (1153 м, 229 точек, первый заезд 16 сентября): +источники расходятся между собой до 2-3% уклона на сотню метров, что +того же порядка, что шум GPS. `mapzen` при этом отдаёт числа, побитово +совпадающие с `srtm30m`. + +Вывод: брать бесплатный `srtm30m` с публичного Open Topo Data.