Files
enduro-trails/docs/work-items/ET-011/08-data-requirements.md
claude-bot 6fe2ecf12b
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Successful in 6s
CI / build (push) Has been skipped
architect(ET): auto-commit from architect run_id=64
2026-06-03 20:44:55 +00:00

18 KiB
Raw Blame History

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
agent:architect

Требования к данным — 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: bool per-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 МБ 80150 мс
200 000 (cap) ~ 26 МБ 400500 мс
> 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-8
Content-Disposition: attachment; filename="…"; filename*=UTF-8''…
Cache-Control: private, max-age=3600
Content-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. Контракты, которые нельзя ломать

  1. Schema tracks, pipeline_runs — не меняются (read-only эндпоинт).
  2. Структура GeoJSON и MVT на других эндпоинтах — не меняется.
  3. GPX 1.1 формат выходного файла — соответствует topografix XSD; изменение структуры (например, добавление <extensions>) — breaking change для пользователей, которые уже импортировали в свои навигаторы; требует minor-bump в creator="Enduro Trails" или отдельной фичи.
  4. download_allowed поле в gps_sources.yaml — optional, default false; никогда не делать его required (поломает все существующие конфиги). Pipeline не должен начать читать это поле в будущем — разделение confidently distinct concerns.
  5. Ответ 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 не затронут.