18 KiB
type, work_item_id, title, version, status, created_at, authors
| type | work_item_id | title | version | status | created_at | authors | |
|---|---|---|---|---|---|---|---|
| data-requirements | ET-011 | Требования к данным — ET-011: Скачивание трека из popup | 1 | approved | 2026-06-03 |
|
Требования к данным — ET-011
1. Резюме
ET-011 — read-only data event. Никаких изменений схемы БД,
никаких новых таблиц, индексов, миграций, localStorage-ключей. Эндпоинт
GET /api/gps-tracks/{id}/download собирает GPX-файл из существующих
полей таблицы tracks (ET-008 / ADR-005), переиспользует существующий
WKB-парсер (mvt.py::_wkb_to_coords), не пишет ни в одну таблицу.
Меняется:
- Содержимое
config/gps_sources.yaml(одно optional-полеdownload_allowed: boolper-source; см. ADR-015). - Контракт API расширяется одним новым endpoint'ом (
/download).
Не меняется:
- Schema таблиц
tracks,pipeline_runs; - Контракты существующих API
/api/gps-tracks,/tiles/...,/health,/cache/clear; - localStorage ключи и значения клиента;
- Dedup-алгоритм (
compute_dedup_key); - ACTIVITY_TYPES enum;
- Маппинги
SOURCE_ATTRIBUTIONS,SOURCE_LABELS.
2. Архитектурные границы данных
| Слой данных | Тип | Расположение | Изменения в ET-011 |
|---|---|---|---|
OSM-vector (trails) |
существующий | /app/data/centralfederal.sqlite |
нет |
| Личные GPX треки (ET-006) | существующий | браузер (memory) | нет |
| Публичные GPS треки (ET-008) | существующий | /app/data/gps_tracks.sqlite |
read-only: новый запрос на скачивание; никаких INSERT/UPDATE/DELETE |
| OSRM-граф | существующий | /app/data/enduro.osrm.* |
нет |
| User UI state | существующий | localStorage |
нет новых ключей |
| Скачанный GPX-файл | новое (выход) | downloads-папка браузера пользователя | формат GPX 1.1, см. §4 |
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 / CREATE INDEX.
3.2 Используемые поля в SELECT для /download
| Поле | Использование |
|---|---|
id |
Path-параметр запроса; PK lookup |
name |
<metadata><name> и <trk><name> в GPX; имя файла |
description |
<metadata><desc> (если не null) |
activity_type |
<trk><type> |
user |
<metadata><author><name> (если не null; для OSM по ADR-009) |
created_at |
<metadata><time> (если не null; ISO-8601 UTC) |
length_m |
информативно, в GPX не входит |
points_count |
проверка cap REQ-NF-02 (> 200000 → 413) |
geom (WKB) |
парсится через _wkb_to_coords() в [(lon, lat), ...]; каждая пара → один <trkpt> |
sources_json |
license-guard ADR-015; <link> элементы в <metadata> |
external_urls_json |
<link href=…> элементы; ответ 403 для CTA |
dedup_key, tags_json, inserted_at, updated_at, min_lon..max_lat |
не используется в /download |
3.3 SQL-запрос
SELECT id, name, description, activity_type, user, created_at,
length_m, points_count, geom, sources_json, external_urls_json
FROM tracks WHERE id = ?
Один параметр ? — integer, валидируется FastAPI. Использует
автоматический PRIMARY KEY-индекс. Стоимость: ~1 ms.
3.4 Кэширование на стороне сервера
Не вводим. Mvt-кэш ET-008 — другой механизм (по (z,x,y)). Для
скачивания одиночного трека:
- Кэш-хит редкий (пользователь обычно качает один раз).
- Размер GPX до 20 МБ × N треков — раздуло бы LRU-кэш и заняло RAM.
- Производительность сборки и так в бюджете (REQ-NF-01 = 300 ms p95).
Клиентский кэш — через заголовок Cache-Control: private, max-age=3600
(см. ADR-014 §6). Браузер сам кэширует blob.
3.5 Изменения объёма БД
Нет. Эндпоинт read-only.
3.6 Backup retention
Без изменений (см. ET-008 §9).
4. Контракт GPX-файла (выходные данные)
4.1 Структура XML
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1"
creator="Enduro Trails"
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<metadata>
<name>{tracks.name | "Без названия"}</name>
<desc>{tracks.description}</desc> <!-- если не null -->
<author>
<name>{tracks.user}</name> <!-- если не null -->
</author>
<link href="{external_urls[0]}">
<text>Источник: {sources[0]}</text>
</link>
<!-- ... по одному <link> на каждый external_url -->
<time>{tracks.created_at | ISO-8601 UTC}</time> <!-- если не null -->
<copyright author="Enduro Trails"> <!-- если "osm" ∈ sources -->
<license>https://www.openstreetmap.org/copyright</license>
</copyright>
</metadata>
<trk>
<name>{tracks.name | "Без названия"}</name>
<type>{tracks.activity_type | "other"}</type>
<trkseg>
<trkpt lat="55.123456" lon="37.654321" />
<!-- ... по одному <trkpt> на каждую координату из geom -->
</trkseg>
</trk>
</gpx>
4.2 Соответствие схеме
Валидируется по http://www.topografix.com/GPX/1/1/gpx.xsd без
ошибок и warnings (REQ-NF-03, AC-5). Тестовая фикстура
tests/fixtures/gpx-1.1/gpx.xsd (snapshot схемы).
4.3 Размер и плотность
| Кол-во точек | Типичный размер | Время сборки (4-worker uvicorn) |
|---|---|---|
| 100 | ~ 15 КБ | < 5 мс |
| 1 000 | ~ 130 КБ | < 20 мс |
| 5 000 | ~ 650 КБ | < 50 мс |
| 50 000 | ~ 6.5 МБ | 80–150 мс |
| 200 000 (cap) | ~ 26 МБ | 400–500 мс |
| > 200 000 | — | 413 Payload Too Large |
Округление координат %.6f — точность ≈ 0.11 м (более чем достаточно
для эндуро-навигации; экономит ~30% bytes vs Python-default float repr).
4.4 Кодировка
UTF-8 строго. Content-Type: application/gpx+xml; charset=utf-8.
ElementTree сам выдаёт UTF-8 при tostring(root, encoding="utf-8", xml_declaration=True).
4.5 Что НЕ попадает в GPX
| Поле | Причина |
|---|---|
<ele> (высота) |
Не хранится в БД (BRD A2 / ET-008 ограничение) |
<time> в каждом <trkpt> |
Не хранится в БД (BRD A2) |
<wpt> (waypoints) |
Не moнодим из треков |
<rte> (роуты) |
Не применимо для public GPS-tracks |
<extensions> |
Минимализм; кастомные расширения — отдельная фича |
tracks.dedup_key, tracks.length_m, tracks.points_count |
Внутренние метаданные, не часть GPX-стандарта |
tracks.tags_json |
В этой итерации не нужны; если потребуется — <keywords> в metadata |
5. Конфигурация — gps_sources.yaml
5.1 Новое поле download_allowed
| Поле | Тип | Default | Назначение |
|---|---|---|---|
download_allowed |
bool | false (если отсутствует — deny) |
Управляет ответом 403 в /download эндпоинте |
Финальные значения для ET-011 (закрытие BRD Q-1):
source.id |
download_allowed |
Юридическое основание |
|---|---|---|
osm |
true |
ODbL разрешает реэкспорт с атрибуцией (ADR-009 + ADR-015 §«Решение D») |
enduro_russia |
false |
Default-deny; ADR-010 ничего не говорит про реэкспорт |
wikiloc |
false |
ToS Wikiloc запрещает массовый ре-экспорт (ADR-012) |
ttrails |
false |
ADR-011 в proposed; не собирается и не отдаётся |
5.2 Влияние на pipeline
gps-collector игнорирует новое поле (pipeline-код не обращается к
download_allowed). Это redistribution-only флаг.
6. Контракт публичного API
6.1 GET /api/gps-tracks/{track_id}/download — новый
Параметры
| Параметр | Тип | Где | Обязательный | Default |
|---|---|---|---|---|
track_id |
int (ge=1) | path | да | — |
format |
str | query | нет | "gpx" (whitelist {"gpx"}) |
Ответы
| Статус | Body | Headers (ключевые) | Триггер |
|---|---|---|---|
| 200 | XML (GPX 1.1) | Content-Type: application/gpx+xml; charset=utf-8Content-Disposition: attachment; filename="…"; filename*=UTF-8''…Cache-Control: private, max-age=3600Content-Length: <bytes> |
happy path |
| 400 | {"detail": "unsupported_format"} |
стандартные | format не в whitelist |
| 403 | {"detail": "source_forbidden", "external_urls": [...]} |
стандартные | Ни один source трека не в download_allowed whitelist (ADR-015) |
| 404 | {"detail": "track_not_found"} |
стандартные | Трек с указанным id отсутствует в БД |
| 413 | {"detail": "track_too_large"} |
стандартные | tracks.points_count > 200000 |
| 500 | {"detail": "internal_error"} |
стандартные | необработанное исключение (db read fail, XML build fail) |
Кодирование имени файла
RFC 5987:
filename="<ascii_fallback>.gpx"— ASCII-printable санитизированное имя (см. ADR-014 §F).filename*=UTF-8''<percent_encoded>.gpx— UTF-8 имя черезurllib.parse.quote(name, safe='', encoding='utf-8').
Пример (name = "По грязи к Чёрному озеру"):
Content-Disposition: attachment; filename="track-42.gpx"; filename*=UTF-8''%D0%9F%D0%BE%20%D0%B3%D1%80%D1%8F%D0%B7%D0%B8%20%D0%BA%20%D0%A7%D1%91%D1%80%D0%BD%D0%BE%D0%BC%D1%83%20%D0%BE%D0%B7%D0%B5%D1%80%D1%83.gpx
ASCII-fallback track-42.gpx используется только если у пользователя
браузер не понимает filename* (последние 10+ лет — не встречается).
6.2 Существующие эндпоинты — без изменений
GET /api/gps-tracks, GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt,
GET /api/gps-tracks/health, POST /api/gps-tracks/cache/clear —
без изменений.
7. Клиентское хранилище
7.1 localStorage
Без изменений. Никаких новых ключей. Существующие ключи ET-008
(gps-tracks-enabled, gps-tracks-activities, gps-tracks-sources,
gps-tracks-color-mode) — без изменений.
7.2 Не-персистентное состояние
window.gpsTracksLayer — без изменений.
SOURCE_ATTRIBUTIONS, SOURCE_LABELS маппинги — без изменений.
8. Персональные данные (PII)
| Канал | PII | Обработка в ET-011 |
|---|---|---|
<author><name> в скачанном GPX |
возможно (OSM user-name) | попадает только для OSM (ADR-009 collect_user_field: true). Для EnduroRussia/Wikiloc/ttrails — null в БД, элемент опускается |
<metadata><desc> |
возможно (свободный текст автора) | только для OSM-источника при ANY-rule ADR-015 трек качается; для не-OSM — <copyright> не указывается, но <desc> может содержать merged-text. Это сознательный компромисс ADR-015 §B (см. R-3 в 10-tech-risks.md) |
<link href=…> external_urls |
URL-ы могут указывать на профиль автора | сохранены как есть в external_urls_json (паттерн ET-008) |
| IP клиента в логах скачивания | стандартный uvicorn access-log | без изменений; ротация в Docker |
8.1 Право на удаление
Без изменений. Удаление записи из tracks (ET-008 §7.1) автоматически
делает её недоступной через /download (404).
8.2 GDPR / РФ ФЗ-152
Обрабатываются только публично выложенные данные с условием
download_allowed: true. ODbL OSM покрывает реэкспорт (ADR-009).
9. Атрибуция
В скачанном GPX:
<copyright>с OSM-license URL — если"osm" ∈ sources.<link>для каждогоexternal_url— атрибуция в виде ссылок, кликабельная в любом GPX-просмотрщике (OsmAnd, Garmin BaseCamp, QGIS).creator="Enduro Trails"в корневом<gpx>— атрибуция нашего сервиса.
В UI: без изменений (MapLibre Attribution control остаётся как в ET-008).
10. Backup и retention
Не применимо к ET-011. Эндпоинт read-only, не создаёт persistent- артефактов.
11. Тестовые данные (фикстуры)
11.1 Новые фикстуры
| Файл | Содержимое | Использование |
|---|---|---|
tests/fixtures/gpx-1.1/gpx.xsd |
XSD-схема topografix 1.1 (~30 КБ), скачана один раз | UT-03, IT-07 (валидация выходного GPX) |
tests/fixtures/gps-tracks/sample-tracks-fixture.sql |
(опц.) набор INSERT для трёх кейсов: OSM-трек 5 точек, EnduroRussia-трек 50 точек, Wikiloc-трек 100 точек | IT-01..08 |
gpx.xsd коммитится один раз; не зависит от внешних сервисов в
runtime (только на момент UT-теста).
11.2 Юридический статус фикстур
gpx.xsd — открытый XML Schema от topografix.com, свободно
распространяемый (см. footer на topografix.com). Хранение в репо для
тестирования — стандартная практика.
Тестовые SQL-фикстуры с координатами — синтетические (рандомные), не содержат реальных треков от публикаторов.
12. Контракты, которые нельзя ломать
- Schema
tracks,pipeline_runs— не меняются (read-only эндпоинт). - Структура GeoJSON и MVT на других эндпоинтах — не меняется.
- GPX 1.1 формат выходного файла — соответствует topografix XSD;
изменение структуры (например, добавление
<extensions>) — breaking change для пользователей, которые уже импортировали в свои навигаторы; требует minor-bump вcreator="Enduro Trails"или отдельной фичи. download_allowedполе вgps_sources.yaml— optional, defaultfalse; никогда не делать его required (поломает все существующие конфиги). Pipeline не должен начать читать это поле в будущем — разделение confidently distinct concerns.- Ответ 403 schema —
{"detail": "source_forbidden", "external_urls": [...]}— клиент используетexternal_urls[0]для CTA. Удаление поля сломает UX.
13. Вывод
ET-011 — read-only data event:
- Не меняет схему БД, не добавляет миграции, не вводит новые таблицы;
- Использует существующие данные в
tracksчерез один SELECT; - Возвращает новый артефакт (GPX-файл) пользователю — не сохраняет на сервер;
- Расширяет один конфиг-файл одним optional-полем;
- Поддерживает default-deny для лицензионной чистоты.
Юридически защищён через ADR-009 (OSM ODbL) + ADR-015 (default-deny whitelist). Pipeline-collector не затронут.