--- 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` (если есть) — наследие