---
type: adr
work_item_id: ET-009
adr_id: ADR-013
title: "ADR-013: Активация двух новых GPS-источников (EnduroRussia + Wikiloc) — конфиг-only изменения поверх pipeline ET-008"
status: accepted
created_at: 2026-06-01
updated_at: 2026-06-01
authors:
  - "agent:architect"
supersedes: []
superseded_by: []
labels:
  - "ET-009:activation"
  - "config-only"
---

# ADR-013 — Активация EnduroRussia и Wikiloc в pipeline GPS-треков

## Статус

**Accepted.** Архитектурное решение для ET-009.

## Контекст

ET-008 построил pipeline сбора публичных GPS-треков:
- docker-compose service `gps-collector` (`profiles: [batch]`);
- per-source изоляция (ADR-007);
- licensing-guard `_check_license_adr` (ADR-007 §6);
- БД `data/gps_tracks.sqlite` (ADR-005);
- API `/api/gps-tracks/*` (ADR-008);
- парсеры `osm.py`, `enduro_russia.py`, `wikiloc.py`, `ttrails.py`.

На момент мерджа ET-008 (2026-06-01) активирован только `osm`
(ADR-009 был `accepted`). `enduro_russia` и `ttrails` остались
`enabled: false` (ADR-010 и ADR-011 в `proposed`). Парсер `wikiloc.py`
был **разработан** в ET-008, но запись в `config/gps_sources.yaml`
**не была добавлена** и ADR-012 не был создан.

ET-009 закрывает три гэпа:
1. ADR-010 — `proposed → accepted` (EnduroRussia).
2. ADR-012 — создан с `accepted` (Wikiloc).
3. Конфиг + регионы + UI-стили — приведены в соответствие с новой
   реальностью «3 активных источника».

ADR-013 фиксирует **архитектурное решение об активации** как
самостоятельное решение работ-айтема ET-009 (отдельно от licensing-ADR
ET-008, которые описывают **что** разрешено сохранять и при каких
условиях).

## Сценарий

ET-009 — **«конфиг-only активация»**: никакой новой инфраструктуры,
никаких новых сервисов, никаких новых таблиц БД, никаких новых
endpoints API. Только:

- правка `config/gps_sources.yaml` (URL fix, флаги enabled, новая запись wikiloc);
- правка `config/gps_regions.yaml` (Wikiloc подписан на ЦФО+Чувашию);
- расширение `wikiloc.py` поддержкой `max_tracks_per_run` (≤ 30 строк, см. TRZ REQ-F-03);
- расширение `src/web/style.json` / `style-dark.json` цветами по `source` (REQ-F-13);
- расширение клиента атрибуцией `enduro_russia` / `wikiloc` (REQ-F-14);
- тестовые фикстуры + unit/integration-тесты;
- ручной первый продакшн-прогон.

## Альтернативы и решения

### Решение A — Структура licensing-ADR

**Опция A1.** Положить ADR-012 в `docs/work-items/ET-009/06-adr/`.

**Опция A2 (выбрано).** Положить ADR-012 в `docs/work-items/ET-008/06-adr/`
рядом с ADR-009/010/011, обновить ADR-010 там же.

**Обоснование.** Licensing-ADR — это **per-source documentation**,
не per-work-item. ET-008 создал пакет licensing-ADR'ов (ADR-009 для
OSM, ADR-010 для EnduroRussia, ADR-011 для ttrails); ADR-012 для
Wikiloc логически принадлежит тому же пакету. ET-009 — **активатор**,
не **законодатель источников**. Из ET-009 ADR-013 ссылается на ADR-010
и ADR-012 как на «приняли вот эти условия».

Также `config/gps_sources.yaml::license_adr` указывает на конкретный
файл; для Wikiloc TRZ ET-009 явно прописывает путь
`docs/work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md`. Хранение
в ET-008 устраняет необходимость cross-work-item ссылок в runtime
конфиге pipeline.

### Решение B — Фикс URL `enduro-russia.ru` → `endurorussia.ru`

**Опция B1.** Считать это bug-fix'ом без отдельного ADR.

**Опция B2 (выбрано).** Документировать в этом ADR §3.

**Обоснование.** Парсер по умолчанию использует `endurorussia.ru`
(см. `enduro_russia.py:45`). YAML-конфиг же содержит
`enduro-russia.ru`. На момент `enabled: false` это работало бы
криво (парсер брал бы default URL); при `enabled: true` мы получили
бы баг R-4 (тогда же — баг в `external_url` сохранённых треков, см.
BRD R-9). Фиксация решения «правильный URL — без дефиса» в ADR
полезна как точка истории.

### Решение C — `max_tracks_per_run` в Wikiloc

**Опция C1.** Жёстко зашить cap = 50 в коде парсера.

**Опция C2 (выбрано).** Параметр в `gps_sources.yaml`, парсер читает
через `self.config.get("max_tracks_per_run")`. Если не указан — без cap.

**Обоснование.** Cap в конфиге → cap легко менять без релиза кода.
После первой стабильной серии прогонов оператор может поднять до 200
или снять полностью.

Реализация — 8 строк в `wikiloc.py::collect()`:
```python
max_tracks = self.config.get("max_tracks_per_run")
yielded = 0
# ...
if max_tracks is not None and yielded >= max_tracks:
    logger.info("Wikiloc: reached max_tracks_per_run=%d, stopping", max_tracks)
    return
yielded += 1
```

### Решение D — Динамический UI-фильтр источников

**Опция D1.** Захардкодить список источников в HTML (`#gps-source-grid`).

**Опция D2 (выбрано).** Клиент строит фильтр из ответа
`/api/gps-tracks/health.tracks_by_source` (источники, у которых > 0
треков в БД). Маппинг `source_id → label` — JS-константа.

**Обоснование.** На момент первого открытия страницы (`tracks_by_source`
содержит только `osm`), UI показывает только OSM-чекбокс. После первого
прогона ET-009 — все 3 чекбокса. Активация четвёртого источника
(`ttrails` в будущем) не требует изменений в UI-коде.

### Решение E — Source priorities

| Source | source_priority | Смысл |
|---|---|---|
| `osm` | 100 | Самый авторитетный; первая ссылка в `external_urls` |
| `enduro_russia` | 80 | Тематическая платформа эндуро в РФ |
| `wikiloc` | 70 | Глобальная платформа, ниже из-за HTML-парсинга |
| `ttrails` | 60 (потенциально) | Будет настроен при активации |

Применение: при dedup-merge метаданные с большим `source_priority`
перекрывают (ADR-006 ET-008). `sources_json` упорядочен по убыванию
priority.

**Решение:** оставить как в ET-008 (без изменений в этой части кода).

## Решение

### 1. ADR licensing — обновить и создать

| ADR | Действие | Файл |
|---|---|---|
| ADR-010 | `proposed → accepted` | `docs/work-items/ET-008/06-adr/ADR-010-enduro-russia-licensing.md` |
| ADR-012 | новый, `accepted` | `docs/work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md` |
| ADR-011 | без изменений (`proposed`) | `docs/work-items/ET-008/06-adr/ADR-011-ttrails-licensing.md` |
| ADR-009 | без изменений (`accepted`) | `docs/work-items/ET-008/06-adr/ADR-009-osm-licensing.md` |
| ADR-013 (этот) | новый, `accepted` | `docs/work-items/ET-009/06-adr/ADR-013-source-activation.md` |

### 2. Конфиг — финальное состояние `config/gps_sources.yaml`

```yaml
sources:
  - id: osm
    name: "OSM Public GPS Traces"
    enabled: true
    license_adr: "docs/work-items/ET-008/06-adr/ADR-009-osm-licensing.md"
    base_url: "https://api.openstreetmap.org/api/0.6"
    rate_limit_sec: 1
    user_agent: "enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)"
    attribution: "© OpenStreetMap contributors (ODbL)"
    parser_module: "src.api.gps_tracks.sources.osm"
    save_user_field: true
    external_url_template: "https://www.openstreetmap.org/user/{user}/traces/{external_id_numeric}"

  - id: enduro_russia
    name: "EnduroRussia.ru"
    enabled: true                                                # FIX: было false
    license_adr: "docs/work-items/ET-008/06-adr/ADR-010-enduro-russia-licensing.md"
    base_url: "https://endurorussia.ru"                          # FIX: было https://enduro-russia.ru (с дефисом)
    rate_limit_sec: 5
    user_agent: "enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)"
    attribution: "EnduroRussia.ru"
    parser_module: "src.api.gps_tracks.sources.enduro_russia"
    save_user_field: false
    source_priority: 80

  - id: wikiloc                                                  # NEW
    name: "Wikiloc"
    enabled: true
    license_adr: "docs/work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md"
    base_url: "https://www.wikiloc.com"
    rate_limit_sec: 10
    user_agent: "enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)"
    attribution: "© Wikiloc contributors"
    parser_module: "src.api.gps_tracks.sources.wikiloc"
    save_user_field: false
    source_priority: 70
    activity_filter: [motorcycle, enduro]
    max_tracks_per_run: 50

  - id: ttrails
    name: "Тропинки.ру"
    enabled: false                                               # NOT CHANGED in ET-009
    license_adr: "docs/work-items/ET-008/06-adr/ADR-011-ttrails-licensing.md"
    base_url: "https://ttrails.ru"
    rate_limit_sec: 5
    user_agent: "enduro-trails/1.0 (+https://openclaw.mva154.duckdns.org/enduro/)"
    attribution: "ttrails.ru"
    parser_module: "src.api.gps_tracks.sources.ttrails"
    save_user_field: false
```

### 3. Регионы — финальное состояние `config/gps_regions.yaml`

```yaml
regions:
  - id: tsfo_plus_chuvashia
    name: "ЦФО + Чувашия"
    bbox: [29.0, 49.5, 47.5, 60.0]
    enabled: true
    sources: [osm, enduro_russia, wikiloc, ttrails]              # +wikiloc

  - id: north_caucasus
    name: "Северный Кавказ"
    bbox: [37.0, 41.5, 49.0, 47.0]
    enabled: false                                                # NOT CHANGED
    sources: [osm, enduro_russia]
```

Замечание: `ttrails` остаётся в списке `sources`, но pipeline-guard
автоматически пропустит его (`enabled: false` в sources.yaml + ADR-011
в `proposed`).

### 4. Парсер Wikiloc — расширение `max_tracks_per_run`

В `src/api/gps_tracks/sources/wikiloc.py::WikilocParser.collect()`
добавляется счётчик и проверка cap. Изменение локализованное (≤ 8
строк), не затрагивает API парсера или сигнатуру методов.

### 5. UI-стили — цвета по источнику

В `src/web/style.json` и `src/web/style-dark.json` слой `gps-tracks-layer`
получает match-expression:
```json
["match", ["get", "source"],
  "osm",            "#3cb44b",
  "enduro_russia",  "#e6194b",
  "wikiloc",        "#4363d8",
  "#808080"]
```

Halo-слой `gps-tracks-halo-satellite` остаётся белым полупрозрачным
(unchanged).

### 6. UI-атрибуция

В `src/web/gps_tracks.js` (или клиентский модуль ET-008) маппинг
`SOURCE_ATTRIBUTIONS` расширяется значениями для `enduro_russia` и
`wikiloc`. MapLibre Attribution control обновляется при изменении
`/api/gps-tracks/health.tracks_by_source`.

### 7. Тесты

Полный список — TRZ ET-009 §3 (REQ-F-06..F-12). Новые файлы:
- `tests/unit/test_gps_tracks_enduro_russia.py` (UT-ER-01..08);
- `tests/unit/test_gps_tracks_wikiloc.py` (UT-WL-01..10);
- `tests/integration/test_pipeline_et009.py` (IT-ER-01, IT-WL-01,
  IT-WL-02, IT-DEDUP-01, IT-LIC-01);
- `tests/contract/test_endurorussia_api_smoke.py` (CT-ER-01, CT-ER-02,
  маркер `@pytest.mark.network`);
- 7 файлов фикстур в `tests/fixtures/gps-tracks/`.

### 8. Деплой

Без изменений в `docker-compose.yml`, `Dockerfile`, `nginx`, cron.
После merge — стандартный `docker compose up -d --no-deps app`. Pipeline
запускается **вручную** оператором по runbook'у в `14-deploy-log.md`.
Автоматический cron включается отдельным DevOps-task'ом после двух
успешных ручных прогонов подряд (out of ET-009 scope, BRD §3).

## Последствия

### Положительные

- **Минимальная инфра-нагрузка.** Никаких новых контейнеров, БД, env,
  секретов, портов, nginx-правил.
- **Высокая обратимость.** Откат активации одного источника = `enabled:
  false` без редеплоя.
- **Источник истины** для конфигов — в репозитории; деплой
  воспроизводим.
- **Покрытие тестами** новых источников + интеграционный тест
  licensing-guard'а через mock-ADR с `proposed`-статусом.

### Отрицательные / ограничения

- **Wikiloc HTML-парсер** — потенциально хрупок (R-1 из ET-008
  tech-risks). Митигация — фикстуры + health-эндпоинт + быстрое
  отключение через конфиг.
- **IP mva154 банится Wikiloc'ом** — средняя вероятность; митигация —
  graceful-stop + `max_tracks_per_run` cap + ручной мониторинг
  первых 3 прогонов (см. tech-risks ET-009 R-2).
- **Удаление дефиса в `enduro-russia.ru` URL** — для **новых** треков
  работает «из коробки»; для **существующих** треков в БД (если есть
  snapshot до фикса) могут остаться `external_urls` с дефисом. Это
  опциональный one-shot fix (BRD R-9), не блокирующий ET-009.
- **Размер БД** вырастет с ~5 MB (только OSM) до ~10–50 MB после
  первого прогона. Хорошо в пределах REQ-NF-03 ≤ 2 GB.
- **Cron автоматизация** отложена до отдельного DevOps-task'а. Это
  **сознательное замедление** — даём оператору проверить три прогона
  вручную перед автоматизацией.

## Классификация изменения

**Minor change** на уровне инфраструктуры (никаких новых компонентов).
**Minor change** на уровне ADR (status-flip + новый licensing-ADR с
identical-pattern).

Лейбл `arch:major-change` **не выставляется** — изменение не вводит
новых архитектурных компонентов, только активирует существующие.

## Невыполнимость / эскалация

ETC-009 не требует архитектурной эскалации. Если на момент работы:
1. ADR-010 или ADR-012 оказались бы в `proposed`/`rejected` →
   разработка останавливается (`back-to:analysis`).
2. Wikiloc систематически возвращает 403 на mva154 в первые три прогона →
   `enabled: false` + новый ADR-update «Wikiloc deprecated».
3. EnduroRussia API возвращает 5xx в первые три прогона → диагностика
   через `pipeline_runs.errors_json`; при подтверждении сторонних
   проблем — wait-and-see, source остаётся `enabled: true`.

## Связанные документы

- `docs/work-items/ET-009/01-brd.md` §2, §3, §5
- `docs/work-items/ET-009/02-trz.md` REQ-F-01..F-20
- `docs/work-items/ET-009/03-acceptance-criteria.md` AC-01..AC-20
- `docs/work-items/ET-009/07-infra-requirements.md` (этот work item)
- `docs/work-items/ET-009/08-data-requirements.md` (этот work item)
- `docs/work-items/ET-009/10-tech-risks.md` (этот work item)
- `docs/work-items/ET-008/06-adr/ADR-007-pipeline-architecture.md` §6
- `docs/work-items/ET-008/06-adr/ADR-010-enduro-russia-licensing.md`
- `docs/work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md`
- `docs/architecture/README.md` (обновлён в ET-009)
- `docs/architecture/adr/README.md` (обновлён в ET-009)
