---
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 `<h1>` или 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/<id>"]` | `["https://www.wikiloc.com/trails/<slug>/<id>"]` |
| `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) — `<trk><trkseg><trkpt>` |
| Лицензия | 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=<code>&sw=<lat,lon>&ne=<lat,lon>&page=<N>` |
| Endpoint трека | `GET https://www.wikiloc.com/trails/<slug>/<id>` |
| Endpoint GPX | `GET https://www.wikiloc.com/wikiloc/downloadTrail.do?id=<id>` |
| Формат поиска | HTML (regex-extract `<a href="/trails/…/<id>">`) |
| Формат трека | HTML (regex-extract `<h1>` для имени + ссылка на 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/<id>"]` /
  `["https://www.wikiloc.com/trails/<slug>/<id>"]`.

**Никаких новых 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 (`<trkseg></trkseg>`) | UT-ER-02 (skip-логика) |
| `enduro-russia-track-3.gpx` | GPX с одной точкой за пределами bbox | UT-ER-03 (bbox-фильтрация) |
| `wikiloc-search-page1.html` | HTML страницы поиска; ≥ 5 ссылок `/trails/…/<id>` | 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 автоматически.
