--- type: data-requirements work_item_id: ET-009 title: "Требования к данным — ET-009: Активация EnduroRussia + Wikiloc" version: 1 status: approved created_at: 2026-06-01 authors: - "agent:architect" --- # Требования к данным — ET-009 ## 1. Резюме ET-009 — **активация** двух уже разработанных source-парсеров. Никаких изменений в схеме БД, контрактах API, формате localStorage или dedup-алгоритме. **Меняются:** - Содержимое существующей таблицы `tracks` (новые записи с `source_id ∈ {enduro_russia, wikiloc}`); - Содержимое существующей таблицы `pipeline_runs` (новые записи с `source_id ∈ {enduro_russia, wikiloc}`); - Содержимое `config/gps_sources.yaml`, `config/gps_regions.yaml`; - Содержимое `src/web/style.json`, `style-dark.json` (match-expressions по `source`). **Не меняются:** - Schema `tracks`, `pipeline_runs`; - API контракты `/api/gps-tracks*`; - localStorage ключи и значения; - Dedup-алгоритм (`compute_dedup_key`); - ACTIVITY_TYPES enum. ## 2. Архитектурные границы данных | Слой данных | Тип | Расположение | Изменения в ET-009 | |---|---|---|---| | OSM-vector (`trails`, `centralfederal.sqlite`) | существующий | `/app/data/centralfederal.sqlite` | **нет** | | Личные GPX треки (ET-006) | существующий | браузер (memory) | **нет** | | Публичные GPS треки (ET-008) | существующий | `/app/data/gps_tracks.sqlite` | **+новые записи** из новых источников | | OSRM-граф | существующий | `/app/data/enduro.osrm.*` | **нет** | | User UI state | существующий | `localStorage` | **нет** новых ключей | ## 3. Серверные данные — `gps_tracks.sqlite` ### 3.1 Schema **Без изменений vs ET-008.** См. `docs/work-items/ET-008/08-data-requirements.md` §3.1, §3.5. Никаких ALTER TABLE / DROP COLUMN / INDEX CREATE не делается. ### 3.2 Новые записи в `tracks` | Поле | Значение для `source_id='enduro_russia'` | Значение для `source_id='wikiloc'` | |---|---|---| | `dedup_key` | вычислено `compute_dedup_key` | вычислено `compute_dedup_key` | | `name` | из JSON `meta.name` | из HTML `

` или GPX metadata/name | | `description` | nullable (ADR-010: сохраняем) | **null** (ADR-012: `save_description: false`) | | `activity_type` | из MAPPING (`difficulty → enduro/moto`) | из MAPPING (`motorcycle → moto`, `enduro → enduro`) | | `user` | **null** (ADR-010: `save_user_field: false`) | **null** (ADR-012) | | `created_at` | из JSON `meta.created_at` (если есть) | nullable | | `length_m`, `points_count` | вычислено из GPX | вычислено из GPX | | `min_lon..max_lat` | вычислено | вычислено | | `geom` | WKB LineString | WKB LineString | | `sources_json` | `["enduro_russia"]` или `["enduro_russia", ...]` после merge | `["wikiloc"]` или `[..., "wikiloc"]` | | `external_urls_json` | `["https://endurorussia.ru/tracks/"]` | `["https://www.wikiloc.com/trails//"]` | | `tags_json` | `[]` (источник не отдаёт tags) | `[]` | | `inserted_at`, `updated_at` | NOW() | NOW() | ### 3.3 Dedup-key — без изменений Алгоритм `compute_dedup_key` (ADR-006) не меняется. Применяется к трекам из всех источников. **Ожидаемое поведение для пары (osm-трек, enduro_russia-трек, wikiloc-трек)** из одной поездки: - Одинаковые `(bbox_quantized, length_bucket, date)` → одинаковый `dedup_key`; - Upsert ON CONFLICT → `sources_json` объединяется `["osm", "enduro_russia", "wikiloc"]` (порядок по `source_priority` descending); - `external_urls_json` синхронно объединяется. См. ET-008 ADR-006 для деталей. ### 3.4 ACTIVITY_TYPES — без изменений Enum остаётся прежним. MAPPING каждого source-парсера независимо переводит свои категории в этот enum. | Source-категория | → ACTIVITY_TYPES | |---|---| | EnduroRussia: `enduro`, `hard`, `soft` | `enduro` | | EnduroRussia: `мото`, `тур` | `moto` | | EnduroRussia: `motorcycle` | `moto` | | EnduroRussia: `offroad` | `offroad` | | EnduroRussia: остальное | `enduro` (fallback в коде) | | Wikiloc (`act=19`): `motorcycle`, `enduro` | `moto` (default из `MAPPING['motorcycle']`) | | Wikiloc (`act=3`): `mtb`, `mountain biking` | `bicycle` | | Wikiloc: `hiking`, `running`, `trail running` | `hike` | | Wikiloc: `offroad` | `offroad` | | Wikiloc: неизвестное | `moto` (parser fallback) | ### 3.5 Новые записи в `pipeline_runs` После первого прогона: ```sql SELECT id, source_id, status, tracks_new, finished_at - started_at FROM pipeline_runs ORDER BY id DESC LIMIT 5; ``` Ожидаемо ≥ 2 новые строки: - `source_id='enduro_russia'`, `status='ok'` (или `partial`), `tracks_new ≥ 200`; - `source_id='wikiloc'`, `status ∈ {ok, partial, rate_limited}`, `tracks_new ≥ 1`. `errors_json` — null или JSON-object `{HTTPError429: N, ...}` если были transient errors. ### 3.6 Размер БД — оценка после ET-009 | Источник | Треков | Средний размер записи | Итого | |---|---|---|---| | OSM (уже в БД) | ≤ 5000 | ≈ 21 КБ | ≤ 105 МБ | | EnduroRussia (новое) | ≈ 200–305 | ≈ 50 КБ (треки длиннее) | ≈ 10–15 МБ | | Wikiloc (новое) | ≈ 1–50 | ≈ 50 КБ | ≈ 0.5–2.5 МБ | | **Итого после ET-009** | ≤ 5400 | | ≤ 130 МБ | Запас до операционного лимита (2 ГБ) — больше 15×. ### 3.7 GC и retention Без изменений vs ET-008. Месячный GC через `--gc` (запускается отдельным cron'ом после двух успешных ручных прогонов). ### 3.8 Backup Без изменений (см. `07-infra-requirements.md` §4.2). ## 4. Клиентское хранилище ### 4.1 Существующие ключи (ET-008) — без изменений | Ключ | Значение | Замечания для ET-009 | |---|---|---| | `gps-tracks-enabled` | `"true"` \| `"false"` | без изменений | | `gps-tracks-activities` | JSON-array | без изменений | | `gps-tracks-sources` | JSON-array source IDs | **может содержать новые ID** после первого прогона; клиент сам подхватит. Defaults обновляются автоматически: при первом открытии после ET-009 — все 3 enabled источника попадают в default-набор | | `gps-tracks-color-mode` | `"source"` \| `"activity"` | без изменений | ### 4.2 Миграция defaults При первом открытии страницы после ET-009 клиент видит, что `gps-tracks-sources` (если есть в `localStorage` со старым значением `["osm"]`) **не содержит** `enduro_russia` и `wikiloc`. Поведение ET-008: - Существующее значение `localStorage` сохраняется (пользователь сознательно мог выключить источники); - Новые источники появляются в UI-фильтре с галкой `unchecked`; - Пользователь может включить их вручную. Это **компромисс UX**: автоматическое включение новых источников без согласия пользователя — нарушение принципа «без сюрпризов»; оставляем явный opt-in. При желании оператора (нет в scope ET-009) — добавить one-shot migration в client-side JS: «если `gps-tracks-sources` существует и не содержит `enduro_russia` или `wikiloc` — добавить и пересохранить». **Не делаем в ET-009.** ### 4.3 Не-персистентное состояние `window.gpsTracksLayer` (ET-008) — без изменений. Маппинг `SOURCE_ATTRIBUTIONS` в `gps_tracks.js` расширяется: ```js const SOURCE_ATTRIBUTIONS = { osm: "© OpenStreetMap contributors (ODbL)", enduro_russia: "EnduroRussia.ru", wikiloc: "© Wikiloc contributors", ttrails: "ttrails.ru", // для будущей активации }; ``` И маппинг `SOURCE_LABELS` для UI-чекбоксов: ```js const SOURCE_LABELS = { osm: "OSM", enduro_russia: "EnduroRussia", wikiloc: "Wikiloc", ttrails: "ttrails.ru", }; ``` ## 5. Внешние входные данные ### 5.1 OSM Public GPS Traces (ADR-009) — без изменений См. `docs/work-items/ET-008/08-data-requirements.md` §5.1. ### 5.2 EnduroRussia.ru (ADR-010 accepted) | Параметр | Значение | |---|---| | Endpoint list | `GET https://endurorussia.ru/api/tracks?page=N&limit=50` | | Endpoint GPX | `GET https://endurorussia.ru/api/tracks/{id}/gpx` | | Формат list | JSON `{items: [{id, name, difficulty, created_at}, ...], total}` | | Формат GPX | XML (GPX 1.1) — `` | | Лицензия | Public; ADR-010 §3 — обезличенно (без `user`) | | Атрибуция | `EnduroRussia.ru` | | Rate-limit | 5 sec / req | | Объём для ЦФО+Чувашии (оценка) | ≥ 200 треков | | User-Agent | `enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)` | | Authentication | Нет | ### 5.3 Wikiloc (ADR-012 accepted) | Параметр | Значение | |---|---| | Endpoint поиска | `GET https://www.wikiloc.com/wikiloc/find.do?act=&sw=&ne=&page=` | | Endpoint трека | `GET https://www.wikiloc.com/trails//` | | Endpoint GPX | `GET https://www.wikiloc.com/wikiloc/downloadTrail.do?id=` | | Формат поиска | HTML (regex-extract ``) | | Формат трека | HTML (regex-extract `

` для имени + ссылка на GPX) | | Формат GPX | XML (GPX 1.1) | | Лицензия | Proprietary (ADR-012 §3 — обезличенно, без description) | | Атрибуция | `© Wikiloc contributors` | | Rate-limit | **10 sec / req** (жёстко) | | Graceful-stop | На 403/429 — `return` без `raise` | | max_tracks_per_run | 50 (soft-cap первого прогона) | | User-Agent | `enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)` | | Authentication | Нет | ### 5.4 ttrails.ru (ADR-011 proposed) **Не используется в ET-009.** `enabled: false` в `gps_sources.yaml`, pipeline-guard пропускает. ## 6. Контракт публичного API ### 6.1 `GET /api/gps-tracks` — без изменений Endpoint остаётся как в ET-008. Новые ID источников (`enduro_russia`, `wikiloc`) появляются в значениях: - `properties.sources` — массив `["enduro_russia"]` / `["wikiloc"]` / `["osm", "enduro_russia"]` (после dedup-merge); - `properties.external_urls` — `["https://endurorussia.ru/tracks/"]` / `["https://www.wikiloc.com/trails//"]`. **Никаких новых query-параметров, response-полей или error-кодов.** Query-параметр `source=...` (фильтр по source ID) уже существует; теперь принимает новые значения `enduro_russia`, `wikiloc`. ### 6.2 `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` — без изменений `properties.source` в MVT-feature может теперь принимать значения `enduro_russia` / `wikiloc` (первый source в `sources_json`). Клиент-стиль (match-expression `line-color`) переключается на соответствующий цвет. ### 6.3 `GET /api/gps-tracks/health` — без изменений в схеме Response shape без изменений. Содержимое: - `tracks_by_source` теперь содержит ключи `enduro_russia` и `wikiloc` с числовыми значениями; - `last_pipeline_run.sources_ok` / `sources_error` / `sources_skipped_license` могут содержать новые source IDs. Клиент-side `SOURCE_ATTRIBUTIONS` маппинг превращает ключи `tracks_by_source` в строки атрибуции для MapLibre Attribution control (REQ-F-14). ### 6.4 `POST /api/gps-tracks/cache/clear` — без изменений ## 7. Персональные данные (PII) Без изменений vs ET-008 §7, с расширением табличного сводного: | Канал | PII | Условия в ET-009 | |---|---|---| | `tracks.user` для `enduro_russia` | **нет** — `save_user_field: false` (ADR-010) | сохраняется null | | `tracks.user` для `wikiloc` | **нет** — `save_user_field: false` (ADR-012) | сохраняется null | | `tracks.geom`, `tracks.created_at`, `tracks.length_m` | низкий риск, публично выложено автором | сохраняется как в ET-008 | | `tracks.description` для `enduro_russia` | возможны следы PII в свободном тексте | сохраняется в default (ADR-010 §3); может быть пере-включено `save_description: false` | | `tracks.description` для `wikiloc` | возможны следы PII | **null** — `save_description: false` (ADR-012) | | `tracks.name` для `enduro_russia` / `wikiloc` | название может содержать псевдонимы | сохраняется (видно в popup) | | IP mva154 становится известен `endurorussia.ru`, `wikiloc.com` | да | стандартное поведение скрейпера; User-Agent с контактом | ### 7.1 Право на удаление Без изменений. `external_urls_json` хранит ссылку; точечное удаление по запросу автора возможно (ET-008 §7.1). ### 7.2 GDPR / РФ ФЗ-152 Без изменений. Обрабатываются только публично выложенные данные. ## 8. Атрибуция **Расширение vs ET-008:** Источник | Атрибуция-строка | |---|---| | `osm` | `© OpenStreetMap contributors (ODbL)` | | `enduro_russia` | `EnduroRussia.ru` | | `wikiloc` | `© Wikiloc contributors` | | `ttrails` (будущее) | `ttrails.ru` | Клиент формирует список из `tracks_by_source` (где count > 0) через `SOURCE_ATTRIBUTIONS` маппинг и подмешивает в MapLibre Attribution control при включённом слое «Публичные треки». В **popup трека** (`gps_tracks.js`) — ссылки `external_urls` (как в ET-008 REQ-F-18); никаких дополнительных правок. ## 9. Backup и retention Без изменений vs ET-008 §9. Ежедневный snapshot + 14 дней retention для `data/gps_tracks.sqlite`. После ET-009 backup-размер вырастет с ~5 МБ до ~50 МБ — пренебрежимое влияние на disk budget. ## 10. Тестовые данные (фикстуры) ET-009 вводит новые фикстуры в `tests/fixtures/gps-tracks/`: | Файл | Содержимое | Использование | |---|---|---| | `enduro-russia-api-tracks-page1.json` | реальный snapshot `GET /api/tracks?page=0&limit=50`; ≥ 5 items с полями id/name/difficulty/created_at | UT-ER-01..08, IT-ER-01 | | `enduro-russia-track-1.gpx` | реальный GPX, ≥ 10 trkpt, в bbox `tsfo_plus_chuvashia` | UT-ER-01, IT-ER-01 | | `enduro-russia-track-2.gpx` | пустой GPX (``) | UT-ER-02 (skip-логика) | | `enduro-russia-track-3.gpx` | GPX с одной точкой за пределами bbox | UT-ER-03 (bbox-фильтрация) | | `wikiloc-search-page1.html` | HTML страницы поиска; ≥ 5 ссылок `/trails/…/` | UT-WL-01, IT-WL-01 | | `wikiloc-trail-page.html` | HTML страницы одного трека | UT-WL-02..04, IT-WL-01 | | `wikiloc-track.gpx` | реальный GPX, координаты совпадают с одним из EnduroRussia-треков | UT-WL-05, IT-DEDUP-01 | | `wikiloc-rate-limited.html` | пустой/тестовый HTML | UT-WL-07/08 (для mock 403/429) | **Снимки делаются разово, вручную** оператором / разработчиком через `curl` или браузер-инспектор; сохраняются в git и не зависят от состояния сайта. ### 10.1 Юридический статус фикстур Фикстуры в `tests/fixtures/gps-tracks/` — публичные snapshot'ы открытых страниц/API, размещённые исключительно для **верификации парсеров** (некоммерческое тестовое использование). Не включаются в production-БД, не отдаются через API. Внутри фикстур не сохраняются authentication-cookies, авторские контактные данные или иные PII. При запросе администратора платформы — фикстура подменяется на синтетический минимальный пример с той же структурой. ## 11. Контракты, которые нельзя ломать Без изменений vs ET-008 §10: 1. `dedup_key` формула — не меняется в ET-009. 2. `ACTIVITY_TYPES` enum — не меняется в ET-009. 3. GeoJSON response shape — не меняется. 4. MVT layer name `gps_tracks` и properties — не меняется. 5. localStorage keys — не меняется. **Новое**: маппинги `SOURCE_ATTRIBUTIONS` / `SOURCE_LABELS` в клиенте являются «soft contract»: добавление ключей — safe; удаление — сломает атрибуцию для соответствующих треков. ## 12. Вывод ET-009 — **append-only data event**: - Заполняет существующую схему БД новыми записями; - Использует существующие API-контракты без изменений; - Расширяет существующие client-side маппинги (атрибуция, цвета); - Никаких миграций, никаких ALTER, никаких новых ключей localStorage. Юридически защищён через ADR-010 (accepted) и ADR-012 (accepted). Pipeline-guard прозрачен — `proposed` ADR блокирует source автоматически.