---
type: data-requirements
work_item_id: ET-012
title: "Требования к данным — ET-012: Снижение minzoom публичных треков до z5"
version: 1
status: approved
created_at: 2026-06-04
authors:
  - "agent:architect"
---

# Требования к данным — ET-012

## 1. Резюме

ET-012 — **pure read pattern change**. Никаких изменений схемы БД,
никаких новых таблиц, индексов, миграций, файлов БД, ключей
localStorage, изменений конфигов источников.

Меняется только **как** существующие данные читаются и
сериализуются в MVT при `z ∈ {5, 6}`:

- `build_gps_mvt` отбирает другой набор `rows` (фильтр по `length_m`)
  и применяет более жёсткий лимит фич;
- `_simplify_coords` применяет другой `tolerance` Douglas-Peucker'а
  к существующим WKB-координатам.

**Меняется:**
- набор фич, попадающих в MVT-тайл при `z ∈ {5, 6}`;
- размер итогового protobuf MVT (за счёт меньшего числа фич и более
  агрессивного упрощения).

**Не меняется:**
- schema таблицы `tracks` (ET-008 / ADR-005);
- schema таблицы `pipeline_runs`;
- индексы `idx_tracks_geom` (R-tree), `min_lon/max_lon/min_lat/max_lat`;
- контракт API `/api/gps-tracks/*` (REQ-F-15);
- содержимое отдельных треков (geom, name, sources_json, etc.);
- dedup-алгоритм (`compute_dedup_key`);
- ACTIVITY_TYPES enum;
- маппинги `SOURCE_ATTRIBUTIONS`, `SOURCE_LABELS`;
- localStorage ключи и значения клиента (REQ-F-18);
- содержимое `config/gps_sources.yaml`, `config/gps_regions.yaml`
  (REQ-F-16).

## 2. Архитектурные границы данных

| Слой данных                       | Тип            | Расположение                    | Изменения в ET-012                                                                                                                              |
|-----------------------------------|----------------|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| OSM-vector (`trails`)             | существующий   | `/app/data/centralfederal.sqlite` | **нет**                                                                                                                                         |
| Личные GPX треки (ET-006)         | существующий   | браузер (memory)                | **нет**                                                                                                                                         |
| Публичные GPS треки (ET-008)      | существующий   | `/app/data/gps_tracks.sqlite`   | **read-only**: новые комбинации параметров `(z, x, y)` теперь принимаются (z=5/6/7); никаких INSERT/UPDATE/DELETE                              |
| OSRM-граф                         | существующий   | `/app/data/enduro.osrm.*`       | **нет**                                                                                                                                         |
| User UI state                     | существующий   | `localStorage`                  | **нет** новых ключей, нет миграции                                                                                                              |
| MVT-кэш в RAM `app`               | существующий   | `_gps_tile_cache` (Python dict) | **расширяется ключевым пространством**: теперь могут лежать тайлы с `z ∈ {5,6,7}` в дополнение к 8..11. Ёмкость 1024 — без изменений             |
| Серверный MVT-тайл (выход)        | **существующий формат, новый z** | bytes в HTTP response | формат `application/x-protobuf` (Mapbox Vector Tile spec), source-layer `gps_tracks`, properties как в ET-008 (`id, activity, source, sources, length_km, name, ext_url`) |
| Клиентский MapLibre LRU           | существующий   | браузер                         | **расширяется ключевым пространством** аналогично серверу                                                                                       |

## 3. Серверные данные — `gps_tracks.sqlite`

### 3.1 Schema

**Без изменений vs ET-008/ET-009/ET-011.** См.
`docs/work-items/ET-008/08-data-requirements.md` §3.1, §3.5. Никаких
ALTER TABLE / DROP COLUMN / CREATE INDEX.

### 3.2 Используемые поля в SELECT при сборке MVT z5-z7

| Поле                    | Использование                                                                                                                                                                                              |
|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id`                    | MVT property                                                                                                                                                                                                |
| `name`                  | MVT property                                                                                                                                                                                                |
| `activity_type`         | MVT property                                                                                                                                                                                                |
| `length_m`              | **NEW USE**: фильтр `length_m >= min_length_m` где `min_length_m=10000` (z5) или `5000` (z6) или `2000` (z7). Раньше фильтр применялся только для z≤7 с порогом 2000                                       |
| `points_count`          | не используется в MVT (только в `/download`, ET-011)                                                                                                                                                       |
| `geom` (WKB)            | парсится через `_wkb_to_coords()` → `[(lon, lat), ...]` → передаётся в `_simplify_coords(coords, z)`. **NEW**: для z=5 tolerance=0.04°, для z=6 tolerance=0.018°                                            |
| `sources_json`          | первый элемент → MVT property `source`; весь список → comma-separated в property `sources`                                                                                                                  |
| `external_urls_json`    | первый URL → MVT property `ext_url`                                                                                                                                                                         |
| `dedup_key`, `description`, `tags_json`, `user`, `inserted_at`, `updated_at`, `created_at`, `min_lon..max_lat` | не используется в MVT (часть полей нужна только в `/download` или GeoJSON-режиме z≥12)                          |

Запрос идентичен ET-008 (`get_tracks_in_bbox`):

```sql
SELECT t.* FROM tracks t WHERE t.ROWID IN (
  SELECT pkid FROM idx_tracks_geom WHERE
    xmin <= ? AND xmax >= ? AND ymin <= ? AND ymax >= ?
) ORDER BY length_m DESC
```

**Изменения SQL: нет.** Фильтр по `length_m` — на Python-стороне в
`build_gps_mvt`, чтобы не вводить новые SQL-параметры (TRZ §3 REQ-F-08).

### 3.3 Объёмы данных

| Метрика                                  | Текущее (ET-009)    | Прогноз через 12 мес. | Гейт ET-012                                                |
|------------------------------------------|---------------------|----------------------|------------------------------------------------------------|
| Число треков в `gps_tracks.sqlite`        | ~500 (test)         | ~5000                | M-6 (p95 build_gps_mvt z5 ≤ 500 мс на БД 5000)              |
| Длинных треков (≥ 10 км)                  | ~150-200 (ЦФО)      | ~1500-2000           | M-8 (размер MVT z5 ≤ 200 KB)                                |
| Точек на трек (среднее)                  | 2000-5000           | 2000-5000            | (Tolerance Douglas-Peucker отсечёт лишнее)                  |
| Размер БД (на диске)                     | ~50 MB              | ~500 MB              | Disk-impact на mva154 — пренебрежимо                        |

При БД из 5000 треков и БД-индекс по bbox:

- Один z=5 тайл накрывает ~1250×1250 км по экватору, ~700×1250 на 55° с.ш.
- В bbox z=5 над ЦФО попадает ≤ 100% длинных треков ЦФО = ~1500.
- После Python-фильтра `length_m ≥ 10000` остаётся ~1500 длинных
  треков → ограничивается `limit=1500`.
- После `_simplify_coords` (tolerance 0.04° → ~5-30 точек на трек) →
  средний размер фичи ≈ 200 байт → MVT ≈ 300 KB до gzip → ≈ 80 KB после.

### 3.4 Индексы

**Без изменений vs ET-008.** Существующий R-tree-индекс
`idx_tracks_geom` достаточен для bbox-запросов z=5. Вторичный индекс
на `length_m` **не нужен** — `ORDER BY length_m DESC` дёшев на
выборках < 5000 строк (Python sort после SQL-фильтра по bbox; SQLite
делает табличный SCAN после R-tree фильтра).

**Watch-flag (TRZ §6, R-4):** если PERF-Z5-01 покажет деградацию при
росте БД > 20k треков — рассмотреть `CREATE INDEX idx_tracks_length
ON tracks(length_m DESC)` как отдельный work-item. Не в MVP.

## 4. Клиентские данные

### 4.1 localStorage

**Без изменений vs ET-008/ET-009/ET-011.** Используются ключи:

| Ключ                       | Назначение                                  | Изменения в ET-012 |
|----------------------------|---------------------------------------------|--------------------|
| `gps-tracks-enabled`       | bool — чекбокс «Публичные треки»            | **нет**            |
| `gps-tracks-activities`    | JSON-array — выбранные активности           | **нет**            |
| `gps-tracks-sources`       | JSON-array — выбранные источники            | **нет**            |
| `gps-tracks-color-mode`    | `"source" | "activity"`                     | **нет**            |

REQ-F-18 в TRZ §3: «никакой миграции localStorage не нужно».
Существующие сессии при следующей загрузке автоматически получают
новый порог `GPS_TRACKS_MIN_ZOOM = 5` и видят слой на z5-z7.

### 4.2 MapLibre LRU (browser-side)

Браузерный MapLibre кэширует тайлы в собственном LRU. После ET-012:

- Ключевое пространство кэша: `(source_id, z, x, y)` — расширяется на
  `z ∈ {5, 6, 7}`.
- Объём — управляется MapLibre, по умолчанию ~100 МБ; пользовательский
  опыт не страдает.
- Никакой синхронизации с серверным `_gps_tile_cache` не нужно
  (independent caches; их инвалидация — через `POST /api/gps-tracks/cache/clear`,
  которая инвалидирует только серверный LRU; клиент дёрнет свежий MVT
  при следующем reload или после move-выхода-возврата за пределы LRU).

## 5. Контракты API

### 5.1 `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt`

| Аспект                | До ET-012                                              | После ET-012                                                                       |
|-----------------------|--------------------------------------------------------|-------------------------------------------------------------------------------------|
| Path-параметр `z`     | принимается `0 ≤ z ≤ 22`                                | принимается `0 ≤ z ≤ 22` (без изменений)                                            |
| Response 200          | для z=8..11 — непустой MVT; для z<8 — пустой MVT       | для z=5..11 — непустой MVT (новые z=5/6/7); для z<5 — пустой MVT                    |
| Response Content-Type | `application/x-protobuf`                               | `application/x-protobuf` (без изменений)                                            |
| Properties фич        | `id, activity, source, sources, length_km, name, ext_url` | без изменений                                                                       |
| Cache-Control         | `public, max-age=300`                                    | без изменений                                                                       |
| Размер тела (z5)      | (раньше не использовалось клиентом, был ~0-50 KB пустой) | ≤ 200 KB до gzip (M-8)                                                              |

**Старые клиенты** (старый `gps_tracks.js`, который никогда не
запрашивал z=5..7) — продолжают работать. Никакого breaking change
в контракте нет.

### 5.2 `GET /api/gps-tracks?bbox=...`

**Без изменений.** Этот endpoint обслуживает GeoJSON-режим z≥12, а
ET-012 не трогает z≥12.

### 5.3 `POST /api/gps-tracks/cache/clear`

**Без изменений.** Инвалидирует серверный `_gps_tile_cache` целиком
(все z). Pipeline `gps-collector` дёргает его после успешного прогона
(ADR-007 §7). После ET-012 этот вызов очищает и тайлы z=5..7
автоматически.

### 5.4 `GET /api/gps-tracks/{id}/download`

**Без изменений.** ET-011 endpoint, не зависит от zoom.

### 5.5 `GET /api/gps-tracks/health`

**Без изменений.** Возвращает `tracks_total`, `tracks_by_source`,
`last_pipeline_run`.

## 6. Миграции

**Нет.** Никаких миграций БД, никаких миграций localStorage,
никаких миграций конфигов.

При деплое в test:

- БД `data/gps_tracks.sqlite` — без изменений (read-only для `app`).
- `data/centralfederal.sqlite` — без изменений (другой слой).
- Серверный MVT-кэш — очищается через `POST /api/gps-tracks/cache/clear`
  для подстраховки (см. `07-infra-requirements.md` §6.2 шаг 4); это
  не миграция, а кэш-инвалидация.
- Клиентский MapLibre LRU — самоочищается при reload браузера; явной
  миграции не нужно.

## 7. Тестовые данные

### 7.1 Для unit-тестов

`tests/unit/test_gps_mvt_zoom_tiers.py` (новый, REQ-F-09):

- Использует in-memory SQLite (как существующие тесты в
  `tests/unit/test_gps_mvt.py`).
- Фикстуры: треки разной длины (например, 1 км, 3 км, 6 км, 12 км,
  25 км), геометрия — простые LineString из 5-10 точек.
- Никаких внешних зависимостей.

`tests/unit/test_gps_mvt_simplify.py` (новый или расширение, REQ-F-10):

- Чистые unit-тесты `_simplify_coords(coords, z)` — массивы coords
  захардкожены, БД не нужна.

### 7.2 Для integration-тестов

`tests/integration/test_gps_tile_z5_z7.py` (новый, REQ-F-11):

- Использует existing fixture `gps_tracks_test_db` (фикстура из
  `conftest.py` ET-008), которая заливает 50 треков по ЦФО разной
  длины с реалистичными координатами.
- При необходимости расширяется до 200 треков для IT-Z5-02.

### 7.3 Для performance-теста

`tests/performance/test_gps_mvt_z5_perf.py` (новый, REQ-F-13):

- Fixture: 500 треков по ЦФО, каждый ≥ 10 км, реалистичная геометрия.
- Маркер `@pytest.mark.perf` — не запускается в основном `make test`.
- Запускается вручную или отдельным CI-джобом.

### 7.4 Для UI-тестов

`tests/e2e/test_ui_gps_z5.spec.ts` (новый, REQ-F-14 / `04b-ui-test-cases.md`):

- Запускается на test-среде `https://openclaw.mva154.duckdns.org/enduro/`.
- Данные — реальная БД test-среды (после ET-009 — ~200 треков ЦФО).
- Скриншот-эталоны для AC-08 (визуальная читаемость) — в
  `tests/e2e/screenshots/et012/`.

## 8. Резервные копии и DR

Без изменений vs ET-008. БД `gps_tracks.sqlite` бэкапится тем же
crontab-скриптом, что и раньше. RPO = 0 (ET-012 не трогает данные).

## 9. Privacy / Compliance

| Аспект                  | Требование                                                                                                                                                                                                                                                  |
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| PII в новых MVT         | **Нет нового PII.** На z=5..7 в MVT-фичу попадают те же поля, что и на z=8..11: `id, activity, source, sources, length_km, name, ext_url`. Поле `user` (потенциальный PII) в MVT не попадает на любых z. Поле `name` может содержать имя автора — но это уже было разрешено ET-008/ADR-005 для всех z ≥ 8. |
| Licensing               | **Без изменений** (ADR-009 OSM ODbL, ADR-010 EnduroRussia accepted, ADR-012 Wikiloc accepted с обезличиванием). Снижение minzoom не меняет, какие источники exposed клиенту — все треки в БД уже прошли licensing-guard pipeline'а                            |
| Attribution             | `MapLibre attribution control` отображает атрибуцию всех активных источников; это работает независимо от zoom — на z=5 пользователь видит те же бейджи «© OSM | EnduroRussia | © Wikiloc», что и на z=10                                                       |

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

- `01-brd.md` §6 Зависимости.Backend, §6 Зависимости.Тесты
- `02-trz.md` §3 REQ-F-09..F-14 (тесты), REQ-F-16..F-18 (не меняем конфиги/стили/localStorage)
- `06-adr/ADR-016-z5-tiling-policy.md` §«Решение», §«Последствия»
- `07-infra-requirements.md` §4 (LRU, RAM), §6 (cache clear at deploy)
- `10-tech-risks.md` (этот пакет)
- `docs/work-items/ET-008/08-data-requirements.md` §3 (schema, индексы) — наследие
- `docs/work-items/ET-009/08-data-requirements.md` (если есть) — наследие
