377 lines
19 KiB
Markdown
377 lines
19 KiB
Markdown
---
|
||
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 автоматически.
|