---
type: tech-risks
work_item_id: ET-009
title: "Технические риски — ET-009: Активация EnduroRussia + Wikiloc"
version: 1
status: approved
created_at: 2026-06-01
authors:
  - "agent:architect"
---

# Технические риски — ET-009

Технические риски этапа активации двух новых GPS-источников. Бизнес-риски —
в BRD §6 ET-009. Многие риски наследуются от ET-008 (R-1, R-5, R-9 из
`docs/work-items/ET-008/10-tech-risks.md`); здесь — специфика ET-009.
Шкала: вероятность (Н/С/В) × влияние (Н/С/В).

## R-1 — Wikiloc меняет HTML → парсер возвращает 0 треков

- **Описание:** Парсер Wikiloc опирается на regex-извлечение
  `<a href="/trails/…/<id>">` и `<h1>` для названия. Wikiloc может в
  любой момент изменить разметку (новый шаблон, JS-rendering) → парсер
  вернёт 0 треков.
- **Вероятность / Влияние:** В / С.
- **Митигация:**
  - Парсер уже спроектирован **graceful**: `return` без `raise` при
    отсутствии match'ей regex (см. `wikiloc.py::_extract_track_paths`).
  - Health-эндпоинт показывает `tracks_by_source.wikiloc = 0` после
    прогона → видимый сигнал оператору.
  - Unit-тест UT-WL-01 на снимке `wikiloc-search-page1.html` — при
    смене разметки CI зелёным быть не сможет, разработчик обновит
    фикстуру + парсер за 1 итерацию.
  - `gps_sources.yaml::wikiloc.enabled: false` — мгновенное отключение
    без deploy при критической поломке.
- **Наследник от:** ET-008 R-1 (general).

## R-2 — Wikiloc банит IP mva154

- **Описание:** Скрейпер с фиксированного IP может попасть в чёрный
  список Wikiloc'а (особенно при ошибках rate-limit или
  накоплении 1000+ запросов в сутки). Pipeline начнёт получать 403/429
  на все запросы → новых треков не будет.
- **Вероятность / Влияние:** С / В.
- **Митигация:**
  - `rate_limit_sec: 10` — самый консервативный rate в проекте.
  - `max_tracks_per_run: 50` — soft-cap на первом прогоне; ≤ 150
    запросов на одну активацию.
  - `User-Agent` с контактным URL — платформа может связаться
    через email до бана.
  - **Graceful-stop** на 403/429 — не агрессивный retry, не вызывает
    дополнительных запросов.
  - **Мониторинг первых 3 прогонов** оператором; при систематических
    403 → `enabled: false` + новый ADR-update «Wikiloc deprecated».
  - Запрет использования прокси через сторонний IP (нарушает дух
    прозрачности; см. ET-008 R-5).

## R-3 — EnduroRussia API меняет схему ответа

- **Описание:** `enduro_russia.py::_parse_gpx` ожидает поля
  `id`, `name`, `difficulty`, `created_at` в JSON-items. Платформа
  может добавить/переименовать поля.
- **Вероятность / Влияние:** Н / С.
- **Митигация:**
  - Парсер использует `.get()` с дефолтами — отсутствие необязательных
    полей не валит.
  - Отсутствие `id` → запись пропускается (`continue`), не валит весь
    прогон.
  - Контрактный smoke-тест `tests/contract/test_endurorussia_api_smoke.py`
    с маркером `@pytest.mark.network` — запускается nightly или вручную,
    сигнализирует о поломке внешнего API до пропущенного cron-прогона.
  - Pipeline-error не лоадит всю БД: `errors_json` фиксирует, оператор
    видит через `/health`.

## R-4 — Расхождение конфига `enduro-russia.ru` (с дефисом) vs
реального `endurorussia.ru` (без дефиса)

- **Описание:** До ET-009 `gps_sources.yaml::enduro_russia.base_url`
  содержит `https://enduro-russia.ru` (с дефисом), но реальный
  домен — `https://endurorussia.ru` (без дефиса; парсер по default
  использует именно его). При активации `enabled: true` без фикса URL
  парсер использовал бы default из кода, но `external_url` сохранённых
  треков опирался бы на `base_url` из конфига → fragmentation
  external_url'ов между «корректным» и «дефис-вариантом».
- **Вероятность / Влияние:** Случилось (известный bug в конфиге) /
  В (при активации).
- **Митигация:**
  - **F-01 в BRD/TRZ** — фикс URL в одно изменение.
  - **Регрессионный тест UT-ER-05** — проверяет, что парсер
    сохраняет URL без дефиса при передаче `base_url` без дефиса.
  - One-shot UPDATE для существующих треков (опционально, см.
    `07-infra-requirements.md` §4.1).

## R-5 — EnduroRussia и Wikiloc — двойник одного и того же трека → массовые дубли

- **Описание:** Авторы часто публикуют одну и ту же поездку и на
  Wikiloc, и на EnduroRussia (Wikiloc даже сохраняет `creator=Wikiloc`
  в GPX мета-теге, что подтверждается на практике). Без правильно
  работающего dedup'а в БД появятся два трека с одинаковой геометрией.
- **Вероятность / Влияние:** В / С.
- **Митигация:**
  - `compute_dedup_key` (ADR-006) основан на `bbox+length+date`, который
    при достаточно похожих координатах и одной дате попадает в один
    bucket → upsert ON CONFLICT мержит.
  - **Интеграционный тест IT-DEDUP-01** — задаёт фикстуру `wikiloc-track.gpx`
    с координатами, совпадающими с одним из EnduroRussia-треков; проверяет
    итоговое объединение `sources_json=['enduro_russia','wikiloc']`.
  - Метаданные при merge — берутся от source с большим `source_priority`
    (`enduro_russia=80 > wikiloc=70`); `external_urls` — оба сохраняются.
  - Если на практике dedup пропускает (например, точное время / точный
    bbox slightly off): план отступления ADR-006 §8 (сузить
    length-bucket, добавить activity).

## R-6 — Cron первого прогона превышает окно из-за rate-limit Wikiloc

- **Описание:** При больших cap'ах `max_tracks_per_run` и rate-limit
  10 сек × 3 запроса/трек первый прогон Wikiloc может занять часы.
- **Вероятность / Влияние:** С / Н.
- **Митигация:**
  - `max_tracks_per_run: 50` — soft-cap → ≤ 25 минут на прогон Wikiloc.
  - EnduroRussia при rate-limit 5 сек × 305 треков ≈ 25 минут — окей.
  - Cron автоматизация **отложена** до отдельного DevOps-task'а
    после двух успешных ручных прогонов; оператор контролирует
    длительность.
  - Опционально: `timeout 21600 docker compose ...` в cron (ET-008
    R-11 уже фиксирует).

## R-7 — UI-фильтр «Источник» не подхватывает новые ID

- **Описание:** Если в ET-008 UI-фильтр (`#gps-source-grid`) построен
  с захардкоженным списком `[osm]`, новые источники не появятся как
  чекбоксы.
- **Вероятность / Влияние:** Н / С.
- **Митигация:**
  - **Дизайн ET-008**: UI строит фильтр **динамически** из ответа
    `/api/gps-tracks/health.tracks_by_source` (источники с count > 0).
    После первого прогона ET-009 — фильтр сам покажет 3 чекбокса.
  - UI-тест TC-UI-04 (в `04b-ui-test-cases.md` ET-008) расширен для
    ET-009: проверяет наличие 3 чекбоксов после двух прогонов.
  - Маппинг `SOURCE_LABELS` (в `gps_tracks.js`) расширяется явно
    в ET-009 — даёт корректные читаемые названия.

## R-8 — Цветовая палитра в `style.json` / `style-dark.json` не содержит новых ID → линии серые

- **Описание:** В ET-008 match-expression `line-color` может содержать
  только `osm`; новые источники получат fallback-серый.
- **Вероятность / Влияние:** В / Н.
- **Митигация:**
  - **REQ-F-13** явно требует обновить match-expression с тремя
    источниками + fallback.
  - Code-review-чеклист: проверить наличие `enduro_russia`, `wikiloc`
    в `paint.line-color` обоих стилей.
  - При пропуске: визуальный регресс легко заметен в smoke-тесте
    (TC-UI-05).

## R-9 — Дамп БД (резервная копия с старым URL) — orphan записи

- **Описание:** Если на test-сервере есть резервная копия БД, в которой
  `external_urls_json` содержит `enduro-russia.ru` (с дефисом),
  то после фикса URL новые treki будут иметь `endurorussia.ru` (без
  дефиса), а старые — `enduro-russia.ru`. Это не криминал, но
  фрагментация атрибуции.
- **Вероятность / Влияние:** Н / Н.
- **Митигация:**
  - На практике `enduro_russia` до ET-009 был `enabled: false` →
    таких записей **нет**. Риск гипотетический.
  - Опциональный one-shot `UPDATE tracks SET external_urls_json = REPLACE(...)`
    — фиксируется в `14-deploy-log.md` если применяется.

## R-10 — ADR-010 / ADR-012 регрессировали в `proposed`

- **Описание:** Между моментом написания BRD/TRZ ET-009 и моментом
  активации (merge → deploy) кто-то откатил статус ADR в `proposed`.
  Pipeline-guard заблокирует source с `skipped_license`.
- **Вероятность / Влияние:** Н / В.
- **Митигация:**
  - **F-03 / REQ-F-05** — pre-check перед активацией:
    ```bash
    grep -E "^status:" docs/work-items/ET-008/06-adr/ADR-010-enduro-russia-licensing.md
    grep -E "^status:" docs/work-items/ET-008/06-adr/ADR-012-wikiloc-licensing.md
    ```
    Оба должны вернуть `accepted`. Иначе — STOP и эскалация архитектору.
  - Интеграционный тест IT-LIC-01 проверяет работу pipeline-guard'а:
    подменяет `accepted → proposed` в копии ADR-010 и убеждается, что
    pipeline скипает source с `status='skipped_license'`.
- **Наследник от:** ET-008 R-9.

## R-11 — Пользовательский opt-in для новых источников

- **Описание:** Пользователи с уже сохранённым `localStorage['gps-tracks-sources']
  = ["osm"]` после ET-009 **не увидят** треки EnduroRussia/Wikiloc на
  своих устройствах — клиент сохраняет старое значение, новые источники
  по умолчанию не enabled в UI.
- **Вероятность / Влияние:** В / Н.
- **Митигация:**
  - Это **сознательное решение** UX (см. `08-data-requirements.md` §4.2):
    добавление источников без согласия пользователя — нарушение
    принципа без сюрпризов.
  - Чекбоксы новых источников появятся в `#sheet-gps-filters`
    automatically (через health-endpoint), пользователь может включить
    их вручную.
  - В release-notes (если они есть в проекте) — фиксируем «появились
    два новых источника, активация в фильтре».

## R-12 — Wikiloc-парсер сохраняет описание / автора несмотря на ADR-012

- **Описание:** ADR-012 §3 явно запрещает сохранять `description` и
  `user` для Wikiloc. Если реализация парсера не уважает этот запрет
  (например, `TrackInsert.description` заполняется), нарушение
  licensing-условий.
- **Вероятность / Влияние:** Н / В.
- **Митигация:**
  - **Текущая реализация:** `wikiloc.py::_parse_gpx` возвращает
    `TrackInsert(description=None, user=None)` (зашито в коде).
  - Unit-тест UT-WL-05 проверяет, что `description=None` и `user=None`
    в возвращаемом `TrackInsert`.
  - Code-review checklist в `12-review.md`: при любом изменении
    парсера Wikiloc убедиться, что эти поля остаются null.

## R-13 — Тестовые фикстуры устаревают

- **Описание:** Снимки HTML/JSON, использованные в unit-тестах,
  отражают состояние API/HTML **на момент снятия**. Через 6-12
  месяцев платформа может изменить разметку, и фикстуры станут
  неактуальны. Тесты пройдут (фикстура соответствует тесту), но
  парсер **не будет работать** в production.
- **Вероятность / Влияние:** С / С.
- **Митигация:**
  - Контрактный smoke-тест `test_endurorussia_api_smoke.py`
    (`@pytest.mark.network`, nightly) — проверяет реальную схему
    API, ловит расхождение.
  - Аналогичный smoke для Wikiloc **не** делаем (риск бана IP при
    регулярных запросах; ETC-009 §«REQ-F-16»).
  - Health-эндпоинт показывает `tracks_by_source.wikiloc` после
    каждого продакшн-прогона; устойчивое 0 — сигнал.
  - При устаревании фикстуры — снимаем заново (1 час работы), парсер
    обновляем (1-3 часа).

## R-14 — Производительность endpoint деградирует при росте кол-ва треков

- **Описание:** REQ-NF-02 ET-008 фиксирует p95 ≤ 300 мс на bbox с
  ≤ 500 треков. После ET-009 в БД появятся ещё ≤ 250 треков —
  пренебрежимо относительно 5000 OSM.
- **Вероятность / Влияние:** Н / Н.
- **Митигация:**
  - R-tree индекс по `geom` (ADR-005) → O(log n) bbox-prefetch.
  - AC-20 — нагрузочный тест 100 запросов после первого прогона.
  - При деградации — анализ EXPLAIN QUERY PLAN; добавление индекса
    `idx_tracks_source` опционально (out of scope ET-009).

## R-15 — Конфликт MAPPING-таблиц для одной активности

- **Описание:** EnduroRussia маппит `motorcycle → moto`, Wikiloc
  тоже `motorcycle → moto` — корректно. Но: EnduroRussia при
  отсутствии match'а в MAPPING возвращает `enduro` (fallback),
  Wikiloc — `moto`. Для одного и того же трека (попавшего в оба
  источника) при merge получим `activity_type` от source с большим
  `source_priority` = `enduro_russia` → `enduro`. Это **OK**: priority
  делает выбор детерминированным.
- **Вероятность / Влияние:** Н / Н.
- **Митигация:**
  - Принято as is. Уточнение в `08-data-requirements.md` §3.4.
  - Unit-тесты UT-ER-04 и UT-WL-06 проверяют отдельные MAPPING'и.

## R-16 — Регрессия e2e-тестов ET-008

- **Описание:** Расширение `style.json` / `gps_tracks.js`
  атрибуцией и цветами может случайно сломать существующие
  selectors / визуальные тесты ET-008.
- **Вероятность / Влияние:** Н / С.
- **Митигация:**
  - **AC-19** — все e2e ET-008 (E-01..E-41) должны пройти после
    мерджа ET-009.
  - Регрессионный прогон `pytest tests/e2e/ -v` — обязательный
    шаг CI.

## R-17 — Pipeline скипает source из-за неправильного `license_adr` path

- **Описание:** Pipeline-guard `_check_license_adr` читает YAML
  front-matter файла по пути из `license_adr`. Если путь опечатан
  (например, `ADR-12-...` вместо `ADR-012-...`), guard вернёт false →
  `status='skipped_license'`.
- **Вероятность / Влияние:** Н / В.
- **Митигация:**
  - Pre-deploy check: убедиться, что `license_adr` указывает на
    реально существующий файл с `status: accepted`.
  - При первом запуске pipeline в test-среде оператор смотрит
    `pipeline_runs[-1].status`; если `skipped_license` —
    диагностирует и исправляет до merge в main.
  - Pydantic-валидация `gps_sources.yaml` в pipeline ET-008 уже
    требует обязательное `license_adr` поле; отсутствие — exception
    при старте.
- **Наследник от:** ET-008 R-9.

## Сводная таблица

| ID | Риск | Вер. | Влияние | Класс | Статус |
|---|---|---|---|---|---|
| R-1 | Wikiloc меняет HTML | В | С | Высокий | принят + graceful + быстрое отключение |
| R-2 | Wikiloc банит IP mva154 | С | В | Высокий | rate-limit 10s + cap 50 + UA + monitor |
| R-3 | EnduroRussia API меняет схему | Н | С | Низкий | smoke-тест + graceful + health |
| R-4 | Расхождение URL `enduro-russia` vs `endurorussia` | Случилось | В | Высокий | F-01 фикс + UT-ER-05 |
| R-5 | Дубли EnduroRussia/Wikiloc | В | С | Средний | dedup-key + IT-DEDUP-01 |
| R-6 | Cron первого прогона долго | С | Н | Низкий | `max_tracks_per_run=50` + ручной прогон |
| R-7 | UI-фильтр не подхватит | Н | С | Низкий | динамика из health + SOURCE_LABELS |
| R-8 | Стили без новых цветов | В | Н | Низкий | REQ-F-13 + review + smoke |
| R-9 | Orphan записи с старым URL | Н | Н | Низкий | гипотетический (БД чистая); опц UPDATE |
| R-10 | ADR-010/012 регрессировали в proposed | Н | В | Высокий | pre-check + IT-LIC-01 |
| R-11 | Пользовательский opt-in для новых источников | В | Н | Низкий | сознательный UX-compromise |
| R-12 | Wikiloc сохраняет description/user | Н | В | Высокий | parser-design + UT-WL-05 + review |
| R-13 | Фикстуры устаревают | С | С | Средний | smoke-test + health + ручной refresh |
| R-14 | Деградация endpoint | Н | Н | Низкий | R-tree + AC-20 |
| R-15 | Конфликт MAPPING | Н | Н | Низкий | source_priority детерминирует |
| R-16 | Регрессия ET-008 e2e | Н | С | Низкий | AC-19 + pytest e2e |
| R-17 | Неправильный `license_adr` path | Н | В | Высокий | pre-deploy check + Pydantic |

**Высокие классы:**
- R-1, R-2 — операционные, ожидаемые для скрейп-источника Wikiloc;
  митигация — multi-layer (graceful + monitor + конфиг-kill-switch).
- R-4 — known bug в конфиге, прямо адресован REQ-F-01.
- R-10, R-17 — критичны для legal compliance; митигация многослойная
  (pre-check + integration-тест + Pydantic).
- R-12 — критичен для соблюдения ADR-012; митигация через design +
  UT-WL-05 + review.

**Блокирующих рисков нет.** Высокие классы требуют внимания на этапе
разработки и code review.

## Эскалация

- **arch:major-change** — **не выставляется** (см. ADR-013
  §«Классификация»). Изменение не вводит новых архитектурных компонентов.
- **back-to:analysis** — не требуется. ТЗ полное, BRD ясный,
  open-questions в `TRZ §6` закрыты дефолтными решениями.
- Эскалация архитектору требуется **только** при срабатывании R-10
  (ADR в `proposed` на момент активации). Тогда задача останавливается
  до повторного апрува ADR.
