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