Files
enduro-trails/docs/work-items/ET-008/01-brd.md
claude-bot 0840818c9a
All checks were successful
CI / lint (push) Successful in 5s
CI / test (push) Successful in 5s
CI / build (push) Successful in 1s
analyst(ET-008): BRD, TRZ, AC, TestPlan, UI tests v2
2026-06-01 11:44:40 +00:00

229 lines
23 KiB
Markdown
Raw Permalink 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.
---
type: brd
work_item_id: ET-008
title: "BRD: GPS-треки с публичных платформ на карте"
version: 2
status: draft
created_at: 2026-06-01
updated_at: 2026-06-01
changelog:
- "v2 (2026-06-01): полная переработка под реальный business request — серверная агрегация из ≥3 источников по региону, дедупликация, фильтры по активности и источнику, расширяемость на регионы. Предыдущая v1 трактовала задачу как URL-импорт + OSM live-поиск, что не соответствовало бизнес-цели."
authors:
- "agent:analyst"
---
# BRD — ET-008: GPS-треки с публичных платформ на карте
## 1. Цель
Показать пользователю Enduro Trails реальные GPS-треки, **заранее
собранные с публичных платформ** (Wikiloc, Offmaps.ru, Тропинки.ру,
EnduroRussia.ru, OSM Public GPS Traces, Nakarte.me, Komoot и т.п.) и
сохранённые на сервере. Цель — три практические задачи мотоэндуриста:
1. **Видеть реальные дороги/тропы, которых нет в OSM.** Vector-тайлы
`trails` показывают только OSM-данные; реальные грунтовки/тропы из
GPS-логов дают информацию, которой в OSM никогда не было.
2. **Понимать, где реально ездят.** Плотность публичных треков на
участке — прямая прокси-метрика популярности и проходимости.
3. **Выявлять «мёртвые» дороги.** OSM-грунтовка, не покрытая ни одним
публичным треком за последние N лет — кандидат на «давно никто не
ездит, может быть заросла».
ET-008 даёт **новый отдельный слой** (поверх `trails`, ниже маршрута
OSRM) с отдельными линиями (не heatmap), цветом по источнику или типу
активности, с UI-фильтрами.
## 2. Контекст
- Vector-тайлы из OSM (`/api/tiles/{z}/{x}/{y}.mvt`) уже отдают
грунтовки/тропы/POI. ET-008 их **не заменяет** — добавляет
параллельный слой публичных GPS-треков.
- ET-006 реализовал клиентский импорт GPX-файлов пользователем
(`window.gpxTracks`, `#sheet-gpx`). Это **другой сценарий**: ET-006 —
«мой трек в памяти браузера», ET-008 — «треки сообщества с сервера».
Модели данных не пересекаются.
- Стек БД: SQLite + Spatialite. Для ET-008 заводится **отдельная** БД
`data/gps_tracks.sqlite` — чтобы не смешивать данные с основной
`centralfederal.sqlite` и иметь независимый цикл обновления / бэкапа.
- Pipeline сбора**офлайн-скрипт на cron**, не runtime. На запрос
пользователя сервер отдаёт уже собранные данные.
- Регион MVP: **ЦФО + Чувашия** (18 субъектов ЦФО + Чувашская
Республика, площадь ≈ 670 тыс. км²). Расширение на другие регионы —
через конфиг-файл.
## 3. Scope
### In scope
| # | Функция |
| ----- | ----------------------------------------------------------------------------- |
| F-01 | Pipeline сбора GPX-треков с ≥ 3 публичных источников |
| F-02 | Хранение треков в SQLite + Spatialite: геометрия + метаданные |
| F-03 | Дедупликация: один реальный трек = одна запись, даже если найден в N источниках |
| F-04 | Метаданные трека: источник, URL, тип активности, дата, длина, кол-во точек, автор (если публичен) |
| F-05 | API endpoint `GET /api/gps-tracks?bbox=…&activity=…&source=…` для отдачи треков клиенту |
| F-06 | Векторные тайлы публичных треков `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` для эффективной отдачи на низких зумах |
| F-07 | Визуализация **отдельными линиями** (не heatmap) на карте |
| F-08 | Цветовая дифференциация: палитра по источнику (default) с возможностью переключения на палитру по типу активности |
| F-09 | UI-чекбокс «Публичные треки» в `#terrain-popup`: включить/выключить весь слой |
| F-10 | UI-фильтр по типу активности (enduro / moto / offroad / bicycle / hike / other), multi-select |
| F-11 | UI-фильтр по источнику, multi-select |
| F-12 | Конфиг-файл регионов: bbox + название + список активных источников |
| F-13 | MVP-датасет: ЦФО + Чувашия, ≥ 5000 треков |
| F-14 | Совместимость со сменой стиля карты (через `rebuildMapOverlays()` по аналогии с ET-006 REQ-F-13 и ET-007 REQ-F-06) |
| F-15 | Совместимость со спутниковой подложкой (ET-007): треки видны на спутнике с halo для контраста |
| F-16 | Клик по треку → popup с метаданными: имя/тип активности/источник/дата/длина и ссылка на оригинал |
| F-17 | Health-эндпоинт `/api/gps-tracks/health`: дата последнего сбора, кол-во треков по источникам, ошибки последнего прогона |
### Out of scope
- **Real-time сбор**: только периодический офлайн (cron, 12 раза в неделю).
- **Wikiloc Premium / Komoot Premium / любые платные API**: используем
только бесплатные публичные endpoints и публичные HTML-страницы там,
где это разрешено ToS источника.
- **Strava Metro как источник линий**: это heatmap, не отдельные треки —
не соответствует бизнес-требованию «отдельные линии». Опционально в
будущем — как метрика популярности для валидации, не для MVP.
- **OAuth-интеграции** (вход пользователя в Strava/Komoot со своим
аккаунтом): отдельный work item.
- **Загрузка пользователем своих треков в общую базу**: отдельный work item.
- **Редактирование/обрезка треков на стороне сервера**.
- **Конвертация из KML/FIT/TCX**: pipeline принимает только GPX.
- **Snap-to-road** для треков (выравнивание под дороги OSM).
- **Учёт сложности (drag-level) внутри трека**: фильтр только по типу
активности; сложность — отдельная задача (требует анализа геометрии и
скорости).
## 4. Источники (с оценкой реализуемости в MVP)
Анализ каждого источника из business request с честной оценкой
доступности и юридических условий:
| # | Источник | Тип доступа | MVP | Комментарий |
| - | ------------------------- | ------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| 1 | OSM Public GPS Traces | Документированный API | **да** | `/api/0.6/trackpoints?bbox=…&page=…`. Лицензия ODbL, атрибуция OSM. Объём для ЦФО оценочно ≈ 50100K точек, тыс. треков. |
| 2 | EnduroRussia.ru | Web (HTML + GPX-ссылки) | **да** | По регионам, есть прямые GPX-ссылки. Лицензия и условия скрейпинга — фиксируются в ADR `06-adr/source-licensing.md` до начала разработки. |
| 3 | Тропинки.ру / ttrails.ru | Web (GPX/KML) | **да** | Эндуро-категория, GPX доступен без авторизации. Условия скрейпинга — то же ADR. |
| 4 | Offmaps.ru | Web | пилот | Требует ревью формата выдачи и лицензии. Подключаем в пилот-режим если ADR разрешает. |
| 5 | Nakarte.me | Public layers + JSON | пилот | Агрегатор: содержит ссылки на треки внешних источников. Может быть «бесплатным» путём к Wikiloc/Strava-treki косвенно. Требует ревью лицензии. |
| 6 | Wikiloc | API (премиум) | нет | Бесплатный публичный API не отдаёт GPX. Без премиума — невозможно. **Откладываем.** |
| 7 | Komoot | API (партнёрский) | нет | Публичный API ограничен, нет публичной выдачи GPX по bbox. **Откладываем.** |
| 8 | Strava Metro | API (исследовательский) | нет | Heatmap, не отдельные треки → не соответствует бизнес-требованию. **Out of scope.** |
**MVP-минимум: 3 источника живут в продакшне** — обязательно OSM
(гарантированно доступен), плюс минимум 2 из (2)(5) по результатам
ADR-ревью лицензий.
### Юридический минимум
Перед началом разработки каждого источника (2)(5) — **обязательный
ADR** `docs/work-items/ET-008/06-adr/<source>-licensing.md`:
1. Что говорит ToS источника о скрейпинге / массовой загрузке GPX.
2. Что говорит robots.txt.
3. На каких условиях разрешена публикация чужих треков
(имя/анонимизация/атрибуция).
4. Rate-limit, который мы будем соблюдать (default: 1 req / 5 sec, с
корректным `User-Agent: enduro-trails/<v> (+contact)`).
5. Список метаданных, которые **нельзя** сохранять/публиковать (личные
адреса, имена при отсутствии явного согласия).
Источник без явного зелёного света в ADR — **не включается** в pipeline.
## 5. Метрики успеха
| Метрика | Критерий MVP |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| Покрытие региона | ≥ 5000 уникальных треков для ЦФО + Чувашии после первого полного прогона pipeline |
| Источники в продакшне | ≥ 3 источника, отдающих данные в БД |
| Дедупликация | < 5% дублей (один реальный трек — одна запись). Метрика: руками отсэмплировать 100 треков, посчитать дубли. |
| Производительность отдачи bbox | `GET /api/gps-tracks?bbox=…` ≤ 300 мс p95 на z ≥ 10 (≤ 500 треков в видимой области) |
| Производительность отдачи тайлов | `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` ≤ 200 мс p95 на z = 811 |
| Производительность отрисовки | При включённом слое pan/zoom без видимых фризов на десктопе и мобильных с 4 ГБ RAM |
| Расширяемость на регион | Добавить новый регион (bbox + название + список источников) — ≤ 30 строк YAML-конфига, без правки кода |
| Скорость UI-фильтров | Переключение фильтра по активности/источнику меняет видимую выборку за ≤ 200 мс (фильтрация на клиенте) |
| Сохранение слоя при `setStyle()` | Слой не теряется при переключении тёмной темы / спутника / hillshade — восстанавливается через `rebuildMapOverlays()` |
| Pipeline стабильность | Падение парсера одного источника не валит остальных; лог + алерт в `/api/gps-tracks/health` |
| Атрибуция | На карте видна атрибуция каждого активного источника; в popup трека — ссылка на оригинал |
## 6. Риски
| Риск | Вероятность | Влияние | Митигация |
| ----------------------------------------------------------------------------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Источник меняет HTML → парсер ломается | Высокая | Среднее | Каждый источник в отдельном модуле, изолированная ошибка. Pipeline пишет статус по источнику в health-эндпоинт. Алерт при 2 неудачных прогонах подряд. |
| ToS источника запрещает скрейпинг | Средняя | Высокое | Обязательный ADR с фиксацией лицензии до подключения источника. Источник без явного разрешения — не включается. |
| Дубли треков из разных источников (один и тот же трек выкладывают на 2 платформах) | Высокая | Среднее | Spatial+temporal hash: bbox округлённый до 0.01° + длина ± 5% + дата ± 1 день → одна запись. Алгоритм в TRZ §6. |
| Перегрузка карты на низких зумах (10K+ треков в видимой области) | Высокая | Высокое | На клиенте: на z < 10 — отдача через MVT-тайлы с упрощением геометрии (как `simplify_coords` для `trails`). На z ≥ 10 — JSON с лимитом 500 треков. |
| Размер БД растёт неконтролируемо (миллионы треков при расширении на РФ) | Низкая | Среднее | Отдельная `gps_tracks.sqlite`. Ротация: треки старше N лет (по конфигу, default 5) удаляются. Метрика размера БД в health. |
| Скрейпер банится по IP | Средняя | Среднее | Rate-limit + backoff + `User-Agent` с контактом. Сбор по cron 12 раза в неделю, не чаще. Per-source конфигурируемый delay. |
| Персональные данные в треках (точки «дом», имена) | Низкая | Высокое | Не сохраняем waypoint без явного публичного флага. Не сохраняем `author` если ToS требует анонимизации. Список запрещённых полей — в `08-data-requirements.md`. |
| Лицензия источника обязывает менять/удалять данные по требованию автора | Средняя | Среднее | Сохраняем `external_id` и `external_url` — можем удалить точечно по запросу. Pipeline уважает «удалённое на источнике» → удалять и у нас. |
| Pipeline ест слишком много трафика mva154 | Средняя | Низкое | Per-source лимит на прогон (например, max 1000 новых треков за прогон). Метрики в health. |
| Отдача больших MVT тайлов медленная | Средняя | Среднее | Серверный кэш тайлов (LRU 1024 записи, как уже сделано для `trails`). Упрощение геометрии по зуму. |
## 7. Зависимости
### Backend
- Новый пакет `src/api/gps_tracks/` с подмодулями:
- `models.py` — Pydantic + SQL schema
- `sources/<source>.py` — модули per-source (OSM, EnduroRussia, ttrails, …)
- `dedup.py` — алгоритм дедупликации
- `db.py` — обвязка SQLite + Spatialite
- `endpoint.py` — FastAPI routes
- `mvt.py` — генерация MVT-тайлов
- Зависимости Python: `httpx` (есть), `lxml` или `defusedxml` (новая —
для безопасного парсинга XML на сервере), `shapely` (есть).
### Pipeline
- Скрипт `scripts/gps_collect.py` — точка входа.
- Конфиг `config/gps_sources.yaml` — список источников и параметры.
- Конфиг `config/gps_regions.yaml` — список регионов (bbox + список
активных источников per-region).
- Cron на mva154: `0 3 * * 1,4 /usr/local/bin/python
/opt/enduro-trails/scripts/gps_collect.py` (Mon + Thu, 03:00 UTC).
- Логи: `/var/log/enduro-trails/gps-collect.log`.
### Frontend
- Новый модуль `src/web/gps_tracks.js` — слой, фильтры, popup, по
аналогии с `gpx.js`.
- Расширение `index.html`:
- Чекбокс «Публичные треки» и кнопка «Фильтры» в `#terrain-popup`.
- Bottom sheet `#sheet-gps-filters` с фильтрами по активности и
источнику.
- Расширение `style.json` / `style-dark.json`: layer + halo-layer для
спутника (по аналогии с `trails-track-halo-satellite` из ET-007).
- Интеграция с `rebuildMapOverlays()` в `app.js`.
### Инфра
- Файловая: `data/gps_tracks.sqlite` на mva154, права чтения для FastAPI,
права записи только для pipeline. Бэкап в общий backup-стек проекта.
- Сетевая: исходящие HTTPS к источникам с mva154 (уже разрешено).
### Документация
- `06-adr/source-licensing.md` — лицензии всех источников.
- `06-adr/dedup-algorithm.md` — обоснование выбора алгоритма
дедупликации.
- `06-adr/storage-schema.md` — обоснование отдельной БД vs единой.
- `07-infra-requirements.md` — cron, бэкапы, ротация, мониторинг.
- `08-data-requirements.md` — схема БД, поля, ограничения, политика
персональных данных.
- `10-tech-risks.md` — расширенный риск-реестр (расширяет §6 BRD).
### Связи с другими work items
- **ET-006** — модель `window.gpxTracks` живёт параллельно. ET-008 не
трогает её, использует свою модель `window.gpsTracksLayer`.
- **ET-007** — спутниковая подложка. ET-008 добавляет halo-слой для
публичных треков в режиме «Спутник» по тому же паттерну.
- **PH-3 Smart Route** — публичные треки в будущем могут стать входом
для построения «реально-езженого» маршрута. Не в scope ET-008.
- **PH-9 PWA** — слой публичных треков должен корректно работать в
офлайне (через cached MVT). Учитывается в TRZ, но реализация офлайна
— задача PH-9.