architect(ET): auto-commit from architect run_id=79
This commit is contained in:
@@ -20,3 +20,4 @@
|
||||
| ADR-014 | GPX-download эндпоинт публичного трека: `xml.etree.ElementTree`-builder + fetch+Blob на клиенте | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-014-gpx-download-endpoint.md) |
|
||||
| ADR-015 | Политика реэкспорта публичных треков: per-source `download_allowed` в `gps_sources.yaml`, default-deny (whitelist `osm` для MVP) | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-015-source-redistribution-policy.md) |
|
||||
| ADR-016 | Снижение minzoom публичных GPS-треков до z5: калибровка существующих tier-таблиц `build_gps_mvt`/`_simplify_coords`, on-demand MVT остаётся, без heat-map/clustering | accepted | 2026-06-04 | [ET-012](../../work-items/ET-012/06-adr/ADR-016-z5-tiling-policy.md) |
|
||||
| ADR-017 | Zoom-aware paint для hillshade/TRI на z9-z11: `interpolate`-выражения по `raster-opacity` и `raster-contrast`, `raster-resampling: 'nearest'`, понижение UI-минзума hillshade с 10 до 9; без перегенерации растровых тайлов | accepted | 2026-06-04 | [ET-013](../../work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md) |
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
---
|
||||
type: adr
|
||||
work_item_id: ET-013
|
||||
adr_id: ADR-017
|
||||
title: "ADR-017: Zoom-aware paint для hillshade/TRI — калибровка клиентских raster-слоёв вместо перегенерации тайлов"
|
||||
status: accepted
|
||||
created_at: 2026-06-04
|
||||
updated_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:architect"
|
||||
supersedes: []
|
||||
superseded_by: []
|
||||
labels:
|
||||
- "ET-013:terrain-paint"
|
||||
- "minor-change"
|
||||
---
|
||||
|
||||
# ADR-017 — Zoom-aware paint для hillshade/TRI на z9-z11
|
||||
|
||||
## Статус
|
||||
|
||||
**Accepted.** Архитектурное решение для ET-013.
|
||||
|
||||
Это **калибровка клиентского рендера** растровых terrain-слоёв
|
||||
(а не пересмотр архитектуры рельефа из PH-6). BRD §3 F-14 допускает
|
||||
отсутствие отдельного ADR. ADR оформляется по прецеденту ADR-016
|
||||
(ET-012) — ради единого индекса архитектурных решений и чтобы
|
||||
зафиксировать **причины отклонения** более «жирных» альтернатив
|
||||
(перегенерация hillshade с z-factor 2.5, переход на raster-dem,
|
||||
multidirectional hillshade, theme-specific paint-таблицы), иначе
|
||||
они вернутся в обсуждение в следующем work-item.
|
||||
|
||||
## Контекст
|
||||
|
||||
### Текущее состояние (после PH-6 / ET-007)
|
||||
|
||||
- Растровые тайлы рельефа нарезаны **z8-z14** (PNG 256×256) из
|
||||
SRTM 30 м: hillshade (azimuth 315°, altitude 45°, z-factor 1.5),
|
||||
TRI (5-уровневая классификация), hypso (в UI не подключён).
|
||||
- Раздача — `GET /terrain/{layer}/{z}/{x}/{y}.png` через FastAPI
|
||||
(`src/api/main.py:1240`), `Cache-Control: immutable`.
|
||||
- Клиент (`src/web/app.js`) создаёт MapLibre raster source/layer
|
||||
динамически в `applyTerrainLayer(id, tileUrl, enabled, opacity,
|
||||
minzoom, maxzoom)`. **Сигнатура хардкодит paint:**
|
||||
`{ 'raster-opacity': opacity_number, 'raster-resampling': 'linear' }`.
|
||||
- Вызовы (`src/web/app.js:2782-2783`):
|
||||
- hillshade: `opacity=0.40, minzoom=10, maxzoom=15`.
|
||||
- TRI: `opacity=0.70, minzoom=5, maxzoom=15`.
|
||||
- UI-минзум hillshade в `updateHillshadeAvailability` (строка 3368):
|
||||
`if (zoom < 10) cb.disabled = true`.
|
||||
- В стилях `style.json` / `style-dark.json` растровых terrain-слоёв
|
||||
**нет** — они добавляются динамически из JS.
|
||||
|
||||
### Проблема
|
||||
|
||||
При зумах z9-z11 (ключевой масштаб для выбора эндуро-маршрута между
|
||||
двумя точками) рельеф визуально «теряется»:
|
||||
|
||||
- z9: hillshade выключен UI-гейтом, TRI с opacity 0.70 виден, но
|
||||
пятна мельче чем на z8.
|
||||
- z10-z11: hillshade включается, но opacity 0.40 + отсутствие
|
||||
усиления контраста + linear-resampling делают тени «бледной
|
||||
плёнкой»; TRI на тех же opacity не компенсирует.
|
||||
|
||||
Архитектурный вопрос: **как восстановить выразительность z9-z11
|
||||
без перегенерации растровых тайлов, без новых endpoint'ов, без
|
||||
новых данных и без смены paint-pipeline'а у MapLibre.**
|
||||
|
||||
## Рассмотренные варианты
|
||||
|
||||
### Вариант P (Pipeline) — где править
|
||||
|
||||
- **P-A — Frontend paint-калибровка** (выбран):
|
||||
- paint hillshade/TRI становится zoom-aware через MapLibre
|
||||
`interpolate`-выражение по `['zoom']`.
|
||||
- Меняются параметры существующих paint-properties:
|
||||
`raster-opacity`, `raster-contrast`, `raster-resampling`.
|
||||
- 0 изменений в backend, 0 в тайлах на диске.
|
||||
|
||||
- **P-B — Перегенерация hillshade с z-factor 2.5-3.0 для z9-z14.**
|
||||
Отклонён в этой задаче:
|
||||
- Требует доступа к infra-pipeline SRTM, пересборки и редеплоя
|
||||
растровых тайлов (без CI-автоматизации сейчас).
|
||||
- Долгий feedback-loop (часы регенерации на регион); калибровка
|
||||
paint даёт результат за минуты.
|
||||
- Затрагивает все zoom-уровни сразу, в т.ч. z8 (регрессия BRD F-11).
|
||||
- **Открыт как follow-up** «hillshade-rerender-z9-z14», если P-A
|
||||
окажется недостаточным.
|
||||
|
||||
- **P-C — Переход на MapLibre `hillshade` (raster-dem) layer.**
|
||||
Отклонён:
|
||||
- Требует поднять DEM в формате Terrarium/Mapbox-RGB (новый
|
||||
pipeline, новые тайлы, новый source-type, новые URL).
|
||||
- Это смена архитектуры рельефа, не калибровка. Большой скачок
|
||||
рисков и времени реализации.
|
||||
- Не решает поставленную проблему быстрее, чем P-A.
|
||||
|
||||
- **P-D — Векторные горизонтали (contours).**
|
||||
Отклонён:
|
||||
- Контуров в стэке нет. Это новая фича уровня PH-6.5, требует
|
||||
pipeline на отдельных vector tiles (планировщик стилей,
|
||||
атрибуты высот, симплификация).
|
||||
- Не заменяет hillshade/TRI, а дополняет — другая фича.
|
||||
|
||||
- **P-E — Multidirectional hillshade (4 азимута, blend).**
|
||||
Отклонён:
|
||||
- Требует пересборки тайлов и комбинирующего layer.
|
||||
- Дороже P-A на порядок при том же визуальном эффекте на z9-z11.
|
||||
|
||||
### Вариант O (Opacity scaling) — как именно скалировать opacity
|
||||
|
||||
- **O-A — Step-функция через `case [zoom_in [9,10,11]]`.** Отклонён —
|
||||
ступенчатые скачки видны как «вспышки» при плавном зуме.
|
||||
|
||||
- **O-B — Linear `interpolate` со stops для z9-z14** (выбран):
|
||||
- Hillshade `raster-opacity`: `9→0.65, 10→0.60, 11→0.55, 12→0.50, 14→0.40`.
|
||||
- Поведение на z<9 не определено (но не нужно — UI-гейт отключает слой).
|
||||
- На z14-z15 значение «закреплено» на исходных 0.40 (clamping
|
||||
у MapLibre на верхнем стопе) → регрессия z14 (BRD F-12, AC-10)
|
||||
выполняется автоматически.
|
||||
- TRI `raster-opacity`: `5→0.55, 7→0.65, 8→0.70, 9→0.80, 10→0.85,
|
||||
11→0.85, 12→0.75, 15→0.70`.
|
||||
- Точка `8→0.70` явная → регрессия z8 (BRD F-11, AC-06) выполняется
|
||||
автоматически.
|
||||
|
||||
- **O-C — Exponential `interpolate ['exponential', 2]`.** Отклонён:
|
||||
- Перерасход контраста на z11-z12 → темно/«пересвет» (R-1).
|
||||
- Linear проще и достаточен для 5 stops в узком диапазоне.
|
||||
|
||||
### Вариант C (Contrast) — добавлять ли raster-contrast
|
||||
|
||||
- **C-A — Добавить `raster-contrast` zoom-aware для hillshade**
|
||||
(выбран):
|
||||
- Stops: `9→0.40, 10→0.35, 11→0.30, 12→0.15, 14→0.00`.
|
||||
- На z14 значение 0 → регрессия (AC-10) выполняется автоматически.
|
||||
- Только для hillshade. На TRI контраст не имеет смысла
|
||||
(категориальная палитра), его не трогаем.
|
||||
|
||||
- **C-B — Не трогать контраст, поднять только opacity.** Отклонён:
|
||||
- Opacity 0.65 без контраста на z9 — это просто «более тёмная
|
||||
плёнка», а не «более выразительный рельеф». Качественный тест
|
||||
(TC-UI-04-Z10-Q) на этом варианте не пройдёт.
|
||||
|
||||
- **C-C — Уменьшать `raster-brightness-min/max` вместо contrast.**
|
||||
Отклонён:
|
||||
- Более сложная двухпараметрическая настройка для того же эффекта.
|
||||
- `raster-contrast` — стандартный для подобных случаев property.
|
||||
|
||||
### Вариант R (Resampling) — nearest vs linear
|
||||
|
||||
- **R-A — `'nearest'` на hillshade и TRI** (выбран):
|
||||
- hillshade на nearest сохраняет «жёсткие края» теней SRTM — рельеф
|
||||
читается резче.
|
||||
- TRI — категориальная палитра; linear-resampling размывает границы
|
||||
между уровнями шероховатости → пятна «текут». `'nearest'`
|
||||
сохраняет границы.
|
||||
- MapLibre **не поддерживает** `interpolate` для `raster-resampling`
|
||||
→ выбираем глобально `'nearest'` для обоих слоёв. На z12-z14
|
||||
компромисс приемлем (текстура остаётся читаемой при overzoom;
|
||||
см. R-T-3).
|
||||
|
||||
- **R-B — Глобально `'linear'`.** Отклонён:
|
||||
- Сохраняет текущую «размытую» картинку, проблема не решается.
|
||||
|
||||
- **R-C — Динамическое переключение `nearest`↔`linear` через
|
||||
отдельный layer.** Отклонён:
|
||||
- Удваивает количество raster-layers (2 hillshade + 2 TRI), плюс
|
||||
логика «когда какой layer показывать» по `getZoom()` →
|
||||
сложность не оправдана.
|
||||
|
||||
### Вариант U (UI gate) — минзум hillshade
|
||||
|
||||
- **U-A — Понизить UI-порог с 10 до 9** (выбран):
|
||||
- Тайлы z9 на диске **есть** (нарезка z8-z14 по PH-6 BRD; pre-deploy
|
||||
smoke в `07-infra-requirements.md` §6.2 шаг 1 это подтверждает).
|
||||
- Аналогично понижается `source.minzoom` с 10 до 9 (BRD F-02,
|
||||
REQ-F-02).
|
||||
- HTML hint обновляется с «Зум 10+» на «Зум 9+» (REQ-F-10).
|
||||
|
||||
- **U-B — Понизить дальше до z8.** Отклонён:
|
||||
- На z8 hillshade-тайлы 256 px покрывают ~150 км по широте — крупные
|
||||
тени становятся неразборчивым «шумом». TRI работает лучше.
|
||||
- Если будущий BRD захочет — отдельная задача.
|
||||
|
||||
- **U-C — Не менять UI-порог, оставить 10.** Отклонён:
|
||||
- Тогда на z9 пользователь не видит hillshade вообще — основная
|
||||
жалоба BRD не решается.
|
||||
|
||||
### Вариант T (Theme-specific paint) — отдельные таблицы для dark/satellite
|
||||
|
||||
- **T-A — Один paint для всех тем** (выбран в MVP):
|
||||
- Простой код, одна правда о stops.
|
||||
- AC-11 (dark) и AC-12 (satellite) — качественные проверки. Если
|
||||
оператор подтвердит читаемость на dark и satellite — конец истории.
|
||||
- Соглашение: если AC-11/AC-12 проваливаются — открывается **ADR-018
|
||||
"theme-specific terrain paint"** как follow-up; в нём вводится
|
||||
подписка на `theme-change` и переключение paint через
|
||||
`setPaintProperty` (BRD R-2, R-3).
|
||||
|
||||
- **T-B — Сразу theme-specific paint в ET-013.** Отклонён:
|
||||
- Преждевременная сложность; неизвестно, действительно ли нужны
|
||||
разные stops (вероятность по риск-таблице: средне-низкая).
|
||||
- Расширяет scope: понадобится подписка на смену темы, отдельные
|
||||
константы, новые тесты на каждый theme×layer×zoom.
|
||||
|
||||
### Вариант A (API-расширение `applyTerrainLayer`) — как передавать paint
|
||||
|
||||
- **A-A — Обратно-совместимое расширение: `opacityOrPaint: number |
|
||||
object`** (выбран):
|
||||
- Внутри функции — нормализация: если число → старый paint-объект
|
||||
с `linear` resampling; если объект → используется как есть.
|
||||
- Сохраняет старый контракт для возможных будущих вызовов
|
||||
(сейчас вызовов только два, оба в `onTerrainCheckbox`).
|
||||
- Unit-тестируется через AC-22, UT-COMPAT-01.
|
||||
|
||||
- **A-B — Сменить сигнатуру на `applyTerrainLayer(id, tileUrl,
|
||||
enabled, paint, minzoom, maxzoom)` без обратной совместимости.**
|
||||
Отклонён:
|
||||
- Если в будущем кто-то скопирует функцию для других raster-слоёв
|
||||
(POI tiles, scenic) с числом — придётся переписывать вызовы.
|
||||
- Стоимость обратной совместимости — 3 строки кода.
|
||||
|
||||
- **A-C — Завести новые функции `applyHillshadeLayer` /
|
||||
`applyTRILayer`.** Отклонён:
|
||||
- Дубликация. `applyTerrainLayer` уже обобщённая, она и есть точка
|
||||
расширения.
|
||||
|
||||
### Вариант M (Module split) — выносить ли константы в отдельный файл
|
||||
|
||||
- **M-A — `HILLSHADE_PAINT` / `TRI_PAINT` живут в `app.js` рядом с
|
||||
`TERRAIN_BASE_URL`** (выбран):
|
||||
- В стэке нет JS-bundler'а, нет ES-import-graph'а (vanilla JS,
|
||||
скрипты грузятся `<script src=...>`).
|
||||
- Выделять отдельный модуль `terrain-paint.js` ради двух констант
|
||||
— преждевременная фрагментация.
|
||||
- Unit-тестируются Python-парсером по grep (REQ-F-13 Вариант B);
|
||||
JS-test-раннера в проекте нет.
|
||||
|
||||
- **M-B — Отдельный модуль `src/web/terrain-paint.js`.** Отклонён в MVP:
|
||||
- Требует либо ставить vitest/jest (превышение scope ET-013), либо
|
||||
подключать через `<script>` с глобальными переменными — не
|
||||
эстетично.
|
||||
- Если в будущем потребуется JS-test-инфраструктура (PWA, сложная
|
||||
логика) — модуль выделяется тогда же.
|
||||
|
||||
## Решение
|
||||
|
||||
1. **Frontend paint-калибровка (P-A)**. Никаких изменений в backend
|
||||
`src/api/main.py`, в нарезке растровых тайлов на диске, в `style.json` /
|
||||
`style-dark.json`, в nginx, в Docker.
|
||||
|
||||
2. **UI-минзум hillshade понижается с 10 до 9 (U-A)** в
|
||||
`updateHillshadeAvailability` (порог `zoom < 9`), HTML hint
|
||||
`«Зум 9+»`, `source.minzoom = 9` через параметр в `applyTerrainLayer`.
|
||||
|
||||
3. **Контракт `applyTerrainLayer` расширяется (A-A)**: четвёртый
|
||||
параметр принимает либо `number` (старый контракт → `raster-opacity` +
|
||||
`linear`-resampling), либо `object` paint-properties. Внутри
|
||||
функции — нормализация.
|
||||
|
||||
4. **Hillshade paint (O-B + C-A + R-A)** — константа `HILLSHADE_PAINT`
|
||||
в `app.js`:
|
||||
- `raster-opacity`: `interpolate linear zoom [9→0.65, 10→0.60,
|
||||
11→0.55, 12→0.50, 14→0.40]`.
|
||||
- `raster-contrast`: `interpolate linear zoom [9→0.40, 10→0.35,
|
||||
11→0.30, 12→0.15, 14→0.00]`.
|
||||
- `raster-resampling`: `'nearest'`.
|
||||
|
||||
5. **TRI paint (O-B + R-A)** — константа `TRI_PAINT`:
|
||||
- `raster-opacity`: `interpolate linear zoom [5→0.55, 7→0.65,
|
||||
8→0.70, 9→0.80, 10→0.85, 11→0.85, 12→0.75, 15→0.70]`.
|
||||
- `raster-resampling`: `'nearest'`.
|
||||
|
||||
6. **Один paint для всех тем (T-A)** — без специальных таблиц для
|
||||
`theme-dark` и для спутниковой подложки в MVP. Если AC-11/AC-12
|
||||
проваливаются — открывается ADR-018 как follow-up.
|
||||
|
||||
7. **Константы живут в `app.js` (M-A)** рядом с `TERRAIN_BASE_URL`.
|
||||
|
||||
## Классификация изменения
|
||||
|
||||
**minor-change.**
|
||||
|
||||
Меняются 3 файла:
|
||||
- `src/web/app.js` (расширение `applyTerrainLayer`, добавление двух
|
||||
констант, обновление двух вызовов, изменение одного порога).
|
||||
- `src/web/index.html` (текст одного `<span>`).
|
||||
- `tests/unit/test_terrain_paint.py` + `tests/integration/test_terrain_z9_tiles.py`
|
||||
(новые).
|
||||
|
||||
Не меняются:
|
||||
- `src/api/main.py`.
|
||||
- `data/terrain/*` (тайлы на диске).
|
||||
- `style.json`, `style-dark.json`.
|
||||
- `config/*.yaml`.
|
||||
- `Dockerfile`, `docker-compose.yml`, nginx.
|
||||
|
||||
Эскалация: **не arch:major-change.** Не требует расширенного approve.
|
||||
|
||||
## Последствия
|
||||
|
||||
### Положительные
|
||||
|
||||
- Перепады на z9-z11 читаются сопоставимо с z8 (BRD §1, BRD M-9,
|
||||
AC-07..AC-09) без перегенерации тайлов.
|
||||
- Hillshade становится доступен на z9 (BRD F-01, AC-01, AC-03) —
|
||||
пользователь видит тени на «обзорном» зуме планирования маршрута.
|
||||
- Регрессия z8 (BRD F-11, AC-06) и z14 (BRD F-12, AC-10) выполняется
|
||||
автоматически за счёт явных stops в `interpolate`.
|
||||
- Backend, тайлы, конфиги не трогаются → 0 риск регрессии
|
||||
серверной/инфраструктурной части.
|
||||
- `applyTerrainLayer` остаётся обратно-совместимым → если позже
|
||||
появится ещё один raster-слой (например, hypso в UI) — функция
|
||||
переиспользуется.
|
||||
|
||||
### Отрицательные / Принимаем
|
||||
|
||||
- На z12-z14 `'nearest'`-resampling даёт лёгкую «пикселизацию»
|
||||
hillshade при overzoom (R-T-3 в `10-tech-risks.md`). Принимаем:
|
||||
на z12+ пользователь обычно отключает hillshade в пользу подложки,
|
||||
альтернатива (два layer'а с разным resampling) — overkill.
|
||||
- Сетевой трафик PNG-тайлов рельефа может вырасти до +35% на
|
||||
типичной сессии активного зумирования (BRD M-10, NFR-03).
|
||||
Принимаем: `Cache-Control: immutable` + браузерный кэш + nginx-кэш
|
||||
поглощают это после первого визита.
|
||||
- Один paint для всех тем может оказаться неоптимальным для
|
||||
`theme-dark` или спутника. Принимаем риск; митигация через
|
||||
follow-up ADR-018 если AC-11/AC-12 проваливаются.
|
||||
|
||||
### Технический долг
|
||||
|
||||
- **TD-1: Перегенерация hillshade с z-factor 2.5-3.0 для z9-z14.**
|
||||
Открыт как follow-up «hillshade-rerender-z9-z14» при недостаточности
|
||||
ET-013. Вероятность по риск-таблице — низкая.
|
||||
- **TD-2: Theme-specific paint (ADR-018).** Открывается при провале
|
||||
AC-11 или AC-12.
|
||||
- **TD-3: Подключение гипсометрии (hypso) в UI.** Тайлы есть, чекбокса
|
||||
нет. Отдельная задача (не зависит от ET-013).
|
||||
- **TD-4: Возможное вынесение `HILLSHADE_PAINT` / `TRI_PAINT` в
|
||||
отдельный модуль `src/web/terrain-paint.js`** — когда в проекте
|
||||
появится JS-test-инфраструктура.
|
||||
- **TD-5: Multidirectional hillshade** — отдельный work-item, если
|
||||
ET-013 окажется недостаточным и пользователи продолжат жаловаться
|
||||
на «плоскость» рельефа на крупных зумах.
|
||||
|
||||
## Альтернативы для будущего
|
||||
|
||||
| # | Идея | Когда возвращаться |
|
||||
|---|------|---------------------|
|
||||
| F-1 | Перегенерация hillshade с z-factor 2.5 | Если AC-07..AC-09 не выполняются после калибровки stops |
|
||||
| F-2 | Theme-specific paint (ADR-018) | Если AC-11 или AC-12 проваливаются |
|
||||
| F-3 | Подключение hypso в UI | По бизнес-запросу |
|
||||
| F-4 | Переход на raster-dem (Mapbox Terrain RGB) | При смене стратегии рельефа целиком |
|
||||
| F-5 | Векторные горизонтали (contours) | Отдельная фича PH-6.5 |
|
||||
| F-6 | Multidirectional hillshade | При жалобах на плоскость на z12+ |
|
||||
|
||||
## Связанные документы
|
||||
|
||||
- BRD: `docs/work-items/ET-013/01-brd.md` §3 (F-01..F-14), §5 (R-1..R-11), §2.4 (out of scope reasoning)
|
||||
- TRZ: `docs/work-items/ET-013/02-trz.md` §3 (REQ-F-01..REQ-F-21)
|
||||
- AC: `docs/work-items/ET-013/03-acceptance-criteria.md` (AC-01..AC-22)
|
||||
- Инфра: `docs/work-items/ET-013/07-infra-requirements.md`
|
||||
- Данные: `docs/work-items/ET-013/08-data-requirements.md`
|
||||
- Риски: `docs/work-items/ET-013/10-tech-risks.md`
|
||||
- Глобальный ADR-индекс: `docs/architecture/adr/README.md`
|
||||
- Архитектура рельефа PH-6: `docs/phases/PH-6.terrain/` (наследие)
|
||||
- Прецедент ADR-016 (ET-012) — формат «калибровочного» ADR
|
||||
249
docs/work-items/ET-013/07-infra-requirements.md
Normal file
249
docs/work-items/ET-013/07-infra-requirements.md
Normal file
@@ -0,0 +1,249 @@
|
||||
---
|
||||
type: infra-requirements
|
||||
work_item_id: ET-013
|
||||
title: "Инфраструктурные требования — ET-013: Zoom-aware paint для terrain-слоёв на z9-z11"
|
||||
version: 1
|
||||
status: approved
|
||||
created_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:architect"
|
||||
---
|
||||
|
||||
# Инфраструктурные требования — ET-013
|
||||
|
||||
## 1. Резюме
|
||||
|
||||
ET-013 — **frontend paint-калибровка**. Меняются два файла исходного
|
||||
кода (`src/web/app.js`, `src/web/index.html`) + добавляются тесты.
|
||||
Инфраструктура **не меняется**:
|
||||
|
||||
- 0 новых docker-сервисов;
|
||||
- 0 изменений в `Dockerfile`;
|
||||
- 0 изменений в `docker-compose.yml`;
|
||||
- 0 новых файлов БД, миграций, индексов;
|
||||
- 0 новых cron-записей;
|
||||
- 0 новых env / секретов / API-ключей;
|
||||
- 0 новых исходящих HTTPS-соединений;
|
||||
- 0 новых портов;
|
||||
- 0 изменений в nginx (тайлы рельефа отдаются с тех же путей
|
||||
`/enduro/terrain/{layer}/{z}/{x}/{y}.png`);
|
||||
- 0 изменений в backend (`src/api/main.py:terrain_tile` без правок).
|
||||
|
||||
Эскалация: **minor change** (см. ADR-017 §«Классификация изменения»).
|
||||
|
||||
## 2. Контейнеры и сервисы
|
||||
|
||||
| Аспект | Требование |
|
||||
|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Новый сервис | **Нет** |
|
||||
| Изменения `Dockerfile` | **Нет** |
|
||||
| Изменения `docker-compose.yml` | **Нет** |
|
||||
| Перезапуск `app` после деплоя | Нужен — `docker compose up -d --no-deps app` (≈ 5 сек простоя). Подхватывает обновлённые `src/web/app.js` и `src/web/index.html` (отдаются как статика из контейнера) |
|
||||
| Перезапуск `gps-collector` | Не нужен (не затронут) |
|
||||
| Очистка серверных кэшей | Не требуется (backend не меняется; `/terrain/*` endpoint и `Cache-Control: max-age=31536000, immutable` без изменений) |
|
||||
| Очистка клиентских кэшей | Не требуется как часть деплоя, но пользователю при первой загрузке после деплоя браузер дёрнет свежий `app.js` (cache-busting через nginx if-modified-since) |
|
||||
|
||||
### 2.1 Зависимости между сервисами
|
||||
|
||||
Без изменений vs PH-6 / ET-007:
|
||||
|
||||
- `app` → файлы `/app/data/terrain/{hillshade,tri,hypso}/{z}/{x}/{y}.png`
|
||||
(read-only при отдаче клиенту).
|
||||
- `nginx (host)` → `app:8000` через docker-network bridge.
|
||||
|
||||
## 3. Сеть
|
||||
|
||||
| Аспект | Требование |
|
||||
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Новые входящие порты | **Нет** |
|
||||
| Изменения nginx | **Нет** (`location /enduro/terrain/` без правок; новые комбинации `(z, x, y)` для z=9 — просто другие значения существующего path-параметра) |
|
||||
| nginx gzip для PNG | Не применяется (PNG уже сжат). Без изменений vs PH-6 |
|
||||
| Кэш-заголовки на `/terrain/*` | Без изменений: `Cache-Control: public, max-age=31536000, immutable` (см. `src/api/main.py:1252`). Браузерный кэш + nginx-кэш агрессивно поглощают повторы |
|
||||
| Новые исходящие соединения | **Нет** — никаких внешних API не дёргается, всё локально |
|
||||
| CORS | Без изменений; `/terrain/*` отдаётся в том же origin, что и `index.html` |
|
||||
| HTTPS / TLS | Без изменений — nginx с Let's Encrypt сертификатом DuckDNS |
|
||||
|
||||
### 3.1 Ingress / Egress — оценка дельты
|
||||
|
||||
Изменения сетевого паттерна (BRD M-10, NFR-03):
|
||||
|
||||
- **Hillshade**: UI-минзум понижается с 10 до 9 → пользователь видит
|
||||
слой на одной zoom-ступени раньше. Один тайл z9 == 4 тайла z10 по
|
||||
покрытию территории, поэтому при «активной zoom-сессии» z=8→z=12
|
||||
с включённым hillshade добавляется ≤ 1 zoom-ступень тайлов.
|
||||
- **TRI**: minzoom источника не меняется (5), opacity меняется только
|
||||
для уже-запрашиваемых тайлов. Дельта запросов **0**.
|
||||
- Итого: при типичной сессии «10 зумов между z8 и z12 с обоими слоями»
|
||||
объём PNG растёт **≤ 35%** (BRD M-10, AC-21).
|
||||
|
||||
Размер одного PNG-тайла рельефа (terrain) ≈ 8-30 KB (без gzip — PNG
|
||||
уже сжат). На сессию: было ~60 тайлов × 20 KB = 1.2 MB, станет
|
||||
~80 тайлов × 20 KB = 1.6 MB. Дельта на пользователя: ~0.4 MB.
|
||||
|
||||
При 10 одновременных пользователях на mva154 — пик ≈ 4 MB/сек
|
||||
дополнительного uplink, мизер по сравнению с uplink сервера
|
||||
(≥ 100 Mbps по DuckDNS).
|
||||
|
||||
Кэш браузера (`immutable, max-age=31536000`) поглощает 2-й и
|
||||
последующие визиты целиком.
|
||||
|
||||
### 3.2 Rate-limit на `/terrain/*`
|
||||
|
||||
**Не вводим в этой итерации.** PNG-тайлы — статика с агрессивным
|
||||
кэшем; DDoS-стоимость низкая (sendfile из ФС без вычислений). Если в
|
||||
проде обнаружится скан z=9-z=14 grid'а — добавляется отдельным
|
||||
DevOps-task'ом, не в ET-013.
|
||||
|
||||
## 4. Серверные ресурсы
|
||||
|
||||
| Аспект | Требование |
|
||||
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| CPU `app` | Без изменений по архитектуре. Раздача PNG — `FileResponse` (sendfile, zero-copy через ядро), CPU-cost пренебрежимый. Рост запросов до +35% даёт +0.5% CPU на сервере при пике сессий |
|
||||
| RAM `app` | Без изменений. PNG не буферизуются в памяти; sendfile из файловой системы |
|
||||
| Disk `app` | Без изменений. Тайлы рельефа лежат в `/home/slin/enduro-trails/data/terrain/{hillshade,tri,hypso}/{z}/{x}/{y}.png` (объём по PH-6 baseline). Никаких новых файлов / volume |
|
||||
| CPU `gps-collector` | Без изменений (не затронут) |
|
||||
| RAM `gps-collector` | Без изменений |
|
||||
| Disk `gps-collector` | Без изменений |
|
||||
|
||||
### 4.1 Размер тайлов рельефа на диске
|
||||
|
||||
**Не меняется.** ET-013 не перегенерирует тайлы; используются
|
||||
существующие нарезки z8-z14 из PH-6. Если pre-deploy smoke
|
||||
(см. §6.2 шаг 1) обнаружит отсутствие тайлов z9-z11 — задача
|
||||
останавливается, открывается PH-6 follow-up на догенерацию
|
||||
(BRD R-11, AC-19).
|
||||
|
||||
## 5. Конфигурация и секреты
|
||||
|
||||
| Аспект | Требование |
|
||||
|-------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Новые env-переменные | **Нет** |
|
||||
| Новые секреты | **Нет** |
|
||||
| Новые API-ключи | **Нет** |
|
||||
| Изменения `config/*.yaml` | **Нет** |
|
||||
| Изменения runtime config | **Нет** — `HILLSHADE_PAINT` и `TRI_PAINT` — JS-константы, живут в коде и меняются коммитом (BRD §6 q&a, ADR-017 §M) |
|
||||
| Изменения `style.json` / `style-dark.json` | **Нет** — растровые terrain-слои добавляются динамически из JS, в стилях не описаны |
|
||||
|
||||
## 6. Деплой
|
||||
|
||||
### 6.1 Среды
|
||||
|
||||
- **dev (локально)**: `make dev` (docker compose up `app`). Достаточно
|
||||
`git pull && make dev` для смены поведения.
|
||||
- **test (mva154)**: `https://openclaw.mva154.duckdns.org/enduro/`.
|
||||
CI/CD — Gitea Actions; деплой через `make deploy-test` или ручной
|
||||
SSH + `docker compose up -d --no-deps --build app` (см. §6.2).
|
||||
- **prod** — пока не задействован; ET-013 деплоится только в test.
|
||||
|
||||
### 6.2 Процедура деплоя в test
|
||||
|
||||
Последовательность шагов (REQ-F-20 в TRZ §3):
|
||||
|
||||
1. **Pre-deploy smoke**: проверить наличие тайлов z9-z11 на test-среде:
|
||||
```bash
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png' | head -1
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png' | head -1
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/11/1234/635.png' | head -1
|
||||
```
|
||||
Ожидается `HTTP/1.1 200 OK` на все три. Если хотя бы один 404 —
|
||||
merge приостанавливается (AC-19), открывается PH-6 follow-up на
|
||||
догенерацию тайлов.
|
||||
2. **Сборка образа**: `docker compose build app` на mva154 (после `git pull`).
|
||||
3. **Перезапуск `app`**: `docker compose up -d --no-deps app`.
|
||||
4. **Post-deploy smoke**:
|
||||
```bash
|
||||
# Проверка статики app.js обновился
|
||||
curl -s 'https://openclaw.mva154.duckdns.org/enduro/app.js' | grep -c 'HILLSHADE_PAINT'
|
||||
# Ожидается ≥ 1
|
||||
```
|
||||
5. **Ручная валидация AC-03..AC-12** через DevTools:
|
||||
- открыть карту, центр над Окой/югом Москвы (`[37.6, 54.5]`);
|
||||
- `window._map.setZoom(9)` — кнопка «Тени рельефа» активна, hint скрыт;
|
||||
- включить «Тени рельефа» и «Перепады»;
|
||||
- скриншоты на z9/z10/z11/z14 → визуальная приёмка AC-07..AC-10;
|
||||
- переключить тему `theme-dark` → проверить AC-11;
|
||||
- переключить подложку `#base-btn-satellite` → проверить AC-12.
|
||||
6. **Запись результатов в `13-test-report.md` и `14-deploy-log.md`**.
|
||||
|
||||
### 6.3 Rollback
|
||||
|
||||
В случае проблем (например, AC-11 «hillshade сливается с dark-темой»,
|
||||
без возможности быстрой donastройки stops):
|
||||
|
||||
1. **Frontend rollback**: `git revert <commit>` + `docker compose up -d --no-deps --build app`.
|
||||
2. **Cache invalidation**: не требуется (backend не меняется, browser
|
||||
cache на статике `app.js` инвалидируется по if-modified-since
|
||||
автоматически).
|
||||
|
||||
RTO: ≤ 5 минут (один `docker compose up -d --no-deps app`).
|
||||
RPO: 0 — никаких изменений в БД, никаких данных не теряется.
|
||||
|
||||
### 6.4 CI/CD-гейты
|
||||
|
||||
- `make lint` (ruff + eslint) — должен быть зелёным (AC-18).
|
||||
- `make test` (pytest unit + integration) — зелёный (AC-15..AC-17).
|
||||
- `pytest tests/integration/test_terrain_z9_tiles.py` — c
|
||||
`@pytest.mark.skipif` для CI без данных (AC-16), не блокирует
|
||||
merge.
|
||||
|
||||
## 7. Observability / Логирование
|
||||
|
||||
| Аспект | Требование |
|
||||
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Новые лог-сообщения | **Нет** (NFR-06 в TRZ §4) |
|
||||
| Существующие лог-сообщения | `uvicorn.access` логирует все запросы к `/terrain/*` с длиной ответа — этого достаточно для мониторинга дельты трафика после деплоя |
|
||||
| Метрики / Prometheus | Не вводим в MVP |
|
||||
| Health-endpoint | `GET /api/health` (если есть) — без изменений |
|
||||
|
||||
### 7.1 Что мониторить после деплоя
|
||||
|
||||
В `nginx access.log` на mva154 (вручную, без алёртов) — первая неделя:
|
||||
|
||||
- **Запросы к `/terrain/hillshade/9/*/*.png`**: должны появиться
|
||||
(раньше клиент их не дёргал). Если 404 — `data/terrain/hillshade/9/`
|
||||
отсутствует, инцидент (BRD R-11).
|
||||
- **Объём ответов**: ≤ +35% к baseline на терминальную пользовательскую
|
||||
сессию (BRD M-10, AC-21).
|
||||
- **Status codes**: только 200/304 (304 от if-modified-since). Никаких
|
||||
500/502 быть не должно.
|
||||
|
||||
## 8. Резервное копирование / Disaster recovery
|
||||
|
||||
| Аспект | Требование |
|
||||
|------------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| Backup БД | Без изменений vs ET-008/PH-6 (ET-013 не трогает БД) |
|
||||
| Backup тайлов рельефа | Без изменений vs PH-6. Регенерируемы из SRTM при необходимости |
|
||||
| Время восстановления (RTO) | ≤ 5 минут (rollback контейнера, см. §6.3) |
|
||||
| Точка восстановления (RPO) | 0 — никаких данных не теряется |
|
||||
|
||||
## 9. Безопасность
|
||||
|
||||
| Аспект | Требование |
|
||||
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| Auth / Authorization | Без изменений (NFR-05 в TRZ §4). `/terrain/*` — публичный (как и был) |
|
||||
| Валидация входных данных | Без изменений; existing валидация `(z, x, y)` в `terrain_tile` уже корректно принимает любые валидные z |
|
||||
| CSP | Без изменений |
|
||||
| Rate-limit | Не вводим в MVP (см. §3.2) |
|
||||
| TLS | Без изменений — nginx с Let's Encrypt сертификатом DuckDNS |
|
||||
|
||||
## 10. Совместимость
|
||||
|
||||
| Аспект | Требование |
|
||||
|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| API контракт `/terrain/*` | Не меняется (REQ-F-18). Любые клиенты (старые tab'ы со старым `app.js`) продолжают работать; они просто не дёргают z=9 hillshade |
|
||||
| MapLibre GL JS совместимость | MapLibre 4.7.0 (`index.html:10`) поддерживает `interpolate` для `raster-opacity` и `raster-contrast`. `raster-resampling` не поддерживает `interpolate` — поэтому глобально `'nearest'` (см. ADR-017 §R) |
|
||||
| Совместимость с PH-6 stack | Никаких изменений; калибровка идёт поверх существующих PH-6 тайлов |
|
||||
| Совместимость с ET-007 (Спутник) | AC-12 проверяет визуально. В случае проблем — открывается ADR-018 (theme-specific paint) |
|
||||
| Совместимость с ET-005 (units), ET-006 (GPX), ET-008 (public tracks) | Без изменений; ET-013 трогает только terrain-слои |
|
||||
| Совместимость с OSRM | Не затронуто (роутинг работает с OSRM-графом независимо) |
|
||||
| localStorage migration | Не нужно (REQ-F-17). Существующие ключи `terrain-hillshade`, `terrain-tri` — без изменений. Пользователи с включённым hillshade автоматически увидят слой на z9 при следующей загрузке |
|
||||
|
||||
## 11. Связанные документы
|
||||
|
||||
- `01-brd.md` §3 (F-01..F-14), §6 (Зависимости, инфра), AC §AC-19 (pre-deploy check)
|
||||
- `02-trz.md` §3 REQ-F-20 Деплой и валидация, §4 NFR
|
||||
- `06-adr/ADR-017-zoom-aware-terrain-paint.md` §«Классификация изменения», §«Последствия»
|
||||
- `08-data-requirements.md` (этот пакет)
|
||||
- `10-tech-risks.md` (этот пакет)
|
||||
- `docs/work-items/ET-012/07-infra-requirements.md` — образец «zero-infra» work-item (наследие)
|
||||
- `docs/work-items/ET-011/07-infra-requirements.md` — образец «zero-infra» work-item (наследие)
|
||||
289
docs/work-items/ET-013/08-data-requirements.md
Normal file
289
docs/work-items/ET-013/08-data-requirements.md
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
type: data-requirements
|
||||
work_item_id: ET-013
|
||||
title: "Требования к данным — ET-013: Zoom-aware paint для terrain-слоёв на z9-z11"
|
||||
version: 1
|
||||
status: approved
|
||||
created_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:architect"
|
||||
---
|
||||
|
||||
# Требования к данным — ET-013
|
||||
|
||||
## 1. Резюме
|
||||
|
||||
ET-013 — **pure client render change**. Никаких изменений схемы БД,
|
||||
никаких новых таблиц/индексов/миграций, никаких изменений тайлов на
|
||||
диске, никаких новых ключей `localStorage`, никаких изменений
|
||||
конфигов источников.
|
||||
|
||||
Меняется **только то, как уже существующие PNG-тайлы рельефа
|
||||
отрисовываются MapLibre на клиенте**:
|
||||
|
||||
- `raster-opacity` становится `interpolate`-выражением по `['zoom']`
|
||||
(вместо константы).
|
||||
- Для hillshade добавляется `raster-contrast` (тоже `interpolate`).
|
||||
- `raster-resampling` для обоих terrain-слоёв переключается с
|
||||
`'linear'` на `'nearest'`.
|
||||
|
||||
**Меняется:**
|
||||
|
||||
- набор `raster paint properties` у двух MapLibre-слоёв
|
||||
(`terrain-hillshade`, `terrain-tri`);
|
||||
- визуальная читаемость рельефа на z9-z11 (целевое улучшение).
|
||||
|
||||
**Не меняется:**
|
||||
|
||||
- содержимое и формат PNG-тайлов в `data/terrain/{hillshade,tri,hypso}/`
|
||||
(PH-6 наследие);
|
||||
- schema БД `centralfederal.sqlite` и `gps_tracks.sqlite`;
|
||||
- контракт API `/terrain/{layer}/{z}/{x}/{y}.png` (REQ-F-18);
|
||||
- содержимое тайлов hypso (в UI не подключён, OOS);
|
||||
- параметры генератора hillshade на сервере (azimuth, altitude,
|
||||
z-factor — PH-6, OOS);
|
||||
- параметры классификации TRI (5-уровневая палитра — PH-6, OOS);
|
||||
- ключи `localStorage` (`terrain-hillshade`, `terrain-tri` — REQ-F-17);
|
||||
- содержимое `config/*.yaml`;
|
||||
- стили `style.json`, `style-dark.json` (растровые terrain-слои в
|
||||
них не описаны — добавляются динамически из JS).
|
||||
|
||||
## 2. Архитектурные границы данных
|
||||
|
||||
| Слой данных | Тип | Расположение | Изменения в ET-013 |
|
||||
|-----------------------------------|----------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| OSM-vector (`trails`) | существующий | `/app/data/centralfederal.sqlite` | **нет** |
|
||||
| Личные GPX треки (ET-006) | существующий | браузер (memory) | **нет** |
|
||||
| Публичные GPS треки (ET-008) | существующий | `/app/data/gps_tracks.sqlite` | **нет** |
|
||||
| OSRM-граф | существующий | `/app/data/enduro.osrm.*` | **нет** |
|
||||
| Terrain hillshade PNG | существующий | `data/terrain/hillshade/{z}/{x}/{y}.png` (z=8..14) | **read-only**: добавляется новая комбинация `(z=9, x, y)`, которая клиент раньше не запрашивал. Тайлы на диске уже есть (PH-6 нарезка) |
|
||||
| Terrain TRI PNG | существующий | `data/terrain/tri/{z}/{x}/{y}.png` (z=8..14) | **read-only**: те же тайлы, что и раньше; меняется только paint |
|
||||
| Terrain hypso PNG | существующий | `data/terrain/hypso/{z}/{x}/{y}.png` | **не используется** в ET-013 (OOS) |
|
||||
| User UI state | существующий | `localStorage` | **нет** новых ключей, нет миграции |
|
||||
| MapLibre client tile cache | существующий | браузер (LRU MapLibre, ~100 MB) | **расширяется ключевым пространством**: теперь могут лежать тайлы hillshade с `z = 9` (раньше не запрашивались) |
|
||||
| Серверный кэш `/terrain/*` | не предусмотрен | n/a (FileResponse + Cache-Control immutable) | **нет** |
|
||||
|
||||
## 3. Серверные данные
|
||||
|
||||
### 3.1 Структура `data/terrain/`
|
||||
|
||||
**Без изменений vs PH-6.** Структура каталога:
|
||||
|
||||
```
|
||||
data/terrain/
|
||||
├── hillshade/
|
||||
│ ├── 8/{x}/{y}.png # baseline
|
||||
│ ├── 9/{x}/{y}.png # используется ET-013 впервые на клиенте
|
||||
│ ├── 10/{x}/{y}.png # baseline (10+ уже использовался)
|
||||
│ ├── 11/{x}/{y}.png
|
||||
│ ├── 12/{x}/{y}.png
|
||||
│ ├── 13/{x}/{y}.png
|
||||
│ └── 14/{x}/{y}.png
|
||||
├── tri/ # та же структура, z=8..14
|
||||
└── hypso/ # та же структура, в UI не подключён
|
||||
```
|
||||
|
||||
Никаких ALTER/CREATE/INSERT/UPDATE/DELETE на стороне данных. Никакой
|
||||
догенерации тайлов. Никакого преобразования формата (PNG остаётся
|
||||
PNG 256×256).
|
||||
|
||||
### 3.2 Объёмы данных
|
||||
|
||||
| Метрика | Текущее (PH-6) | После ET-013 | Гейт |
|
||||
|------------------------------------------|---------------------|-------------------------------|------------------------------------------------------|
|
||||
| Объём PNG hillshade на диске | ~ X MB (PH-6 baseline) | без изменений | n/a |
|
||||
| Объём PNG TRI на диске | ~ Y MB | без изменений | n/a |
|
||||
| Запросы hillshade за сессию | N (только z≥10) | ~ 1.25-1.35 × N (добавился z=9) | BRD M-10: ≤ +35% |
|
||||
| Запросы TRI за сессию | M (z=5..14) | без изменений | n/a |
|
||||
|
||||
### 3.3 Pre-deploy validation тайлов z9-z11
|
||||
|
||||
**Обязательная проверка перед merge** (BRD R-11, AC-19):
|
||||
|
||||
```bash
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png' | head -1
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png' | head -1
|
||||
curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/11/1234/635.png' | head -1
|
||||
```
|
||||
|
||||
Ожидается `HTTP/1.1 200 OK` на все три. Если 404 — задача
|
||||
останавливается, открывается PH-6 follow-up «hillshade-z9-z14
|
||||
backfill». См. `07-infra-requirements.md` §6.2 шаг 1.
|
||||
|
||||
### 3.4 API endpoint `terrain_tile`
|
||||
|
||||
**Без изменений** (`src/api/main.py:1240`):
|
||||
|
||||
- URL: `GET /terrain/{layer}/{z}/{x}/{y}.png`, `layer ∈ {hillshade, tri, hypso}`.
|
||||
- Возвращает: PNG из файловой системы (sendfile через `FileResponse`).
|
||||
- Заголовки: `Cache-Control: public, max-age=31536000, immutable` —
|
||||
без изменений. Браузерный кэш и nginx-кэш агрессивно поглощают
|
||||
повторы.
|
||||
- Контракт OpenAPI — без изменений (REQ-F-18, NFR-04).
|
||||
|
||||
## 4. Клиентские данные
|
||||
|
||||
### 4.1 localStorage
|
||||
|
||||
**Без изменений vs PH-6 / ET-007.** Используются ключи:
|
||||
|
||||
| Ключ | Назначение | Изменения в ET-013 |
|
||||
|----------------------------|---------------------------------------------|--------------------|
|
||||
| `terrain-hillshade` | `'1' | '0'` — чекбокс «Тени рельефа» | **нет** |
|
||||
| `terrain-tri` | `'1' | '0'` — чекбокс «Перепады» | **нет** |
|
||||
|
||||
REQ-F-17 в TRZ §3: «никакой миграции localStorage не нужно».
|
||||
Существующие сессии при следующей загрузке автоматически получают
|
||||
новый UI-порог 9 (вместо 10) и новые `HILLSHADE_PAINT` / `TRI_PAINT`
|
||||
константы. Если у пользователя `terrain-hillshade === '1'` и текущий
|
||||
zoom ≥ 9 — hillshade покажется автоматически (раньше показался бы
|
||||
только на z ≥ 10).
|
||||
|
||||
### 4.2 MapLibre LRU (browser-side)
|
||||
|
||||
Браузерный MapLibre кэширует растровые тайлы в собственном LRU
|
||||
(~100 MB по умолчанию). После ET-013:
|
||||
|
||||
- Ключевое пространство кэша: `(source_id, z, x, y)` — расширяется
|
||||
для `terrain-hillshade-source` на `z = 9` (раньше source имел
|
||||
`minzoom: 10` → запросов z=9 не было).
|
||||
- Объём — управляется MapLibre, ~100 MB. Дельта мизерная (тайл
|
||||
hillshade ≈ 8-30 KB).
|
||||
- Никакой синхронизации/инвалидации не нужно (тайлы на сервере
|
||||
не меняются; `Cache-Control: immutable` гарантирует консистентность).
|
||||
|
||||
### 4.3 In-memory paint constants
|
||||
|
||||
Новые константы в `src/web/app.js` после `TERRAIN_BASE_URL`:
|
||||
|
||||
```js
|
||||
const HILLSHADE_PAINT = {
|
||||
'raster-opacity': ['interpolate', ['linear'], ['zoom'],
|
||||
9, 0.65, 10, 0.60, 11, 0.55, 12, 0.50, 14, 0.40],
|
||||
'raster-contrast': ['interpolate', ['linear'], ['zoom'],
|
||||
9, 0.40, 10, 0.35, 11, 0.30, 12, 0.15, 14, 0.00],
|
||||
'raster-resampling': 'nearest'
|
||||
};
|
||||
|
||||
const TRI_PAINT = {
|
||||
'raster-opacity': ['interpolate', ['linear'], ['zoom'],
|
||||
5, 0.55, 7, 0.65, 8, 0.70,
|
||||
9, 0.80, 10, 0.85, 11, 0.85,
|
||||
12, 0.75, 15, 0.70],
|
||||
'raster-resampling': 'nearest'
|
||||
};
|
||||
```
|
||||
|
||||
- Это **компилируемые MapLibre `interpolate`-выражения**, не «данные»
|
||||
в архитектурном смысле. Живут в коде, изменяются коммитом
|
||||
(BRD §6 q&a «Делать ли paint-таблицы переменными окружения /
|
||||
config'ом? Нет — преждевременная абстракция»).
|
||||
- Память: < 1 KB суммарно. Производительность: MapLibre кэширует
|
||||
скомпилированные выражения (NFR-01).
|
||||
|
||||
## 5. Контракты API
|
||||
|
||||
### 5.1 `GET /terrain/{layer}/{z}/{x}/{y}.png`
|
||||
|
||||
| Аспект | До ET-013 | После ET-013 |
|
||||
|-----------------------|--------------------------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| Поддерживаемые `layer`| `hillshade`, `tri`, `hypso` | без изменений |
|
||||
| Path-параметр `z` | принимается любой валидный z, тайлы на диске z=8..14 | без изменений |
|
||||
| Response 200 | для существующих `(z, x, y)` PNG | без изменений |
|
||||
| Response 404 | для несуществующих `(z, x, y)` | без изменений |
|
||||
| Response Content-Type | `image/png` | без изменений |
|
||||
| Cache-Control | `public, max-age=31536000, immutable` | без изменений |
|
||||
|
||||
**Старые клиенты** (старый `app.js` со старым `minzoom = 10` для
|
||||
hillshade) — продолжают работать. Никакого breaking change в
|
||||
контракте нет (NFR-04).
|
||||
|
||||
### 5.2 Прочие endpoint'ы
|
||||
|
||||
ET-013 не трогает: `/api/gps-tracks/*`, `/api/trails/*`, `/api/route/*`,
|
||||
`/api/health`. Их контракты — без изменений.
|
||||
|
||||
## 6. Миграции
|
||||
|
||||
**Нет.** Никаких миграций БД, миграций localStorage, миграций
|
||||
конфигов, миграций тайлов.
|
||||
|
||||
При деплое в test:
|
||||
|
||||
- `data/terrain/*` — без изменений (read-only для `app`).
|
||||
- БД `centralfederal.sqlite`, `gps_tracks.sqlite` — без изменений.
|
||||
- Серверный кэш — отсутствует у `/terrain/*` (статическая раздача
|
||||
с `Cache-Control: immutable`).
|
||||
- Клиентский MapLibre LRU — самоочищается при reload браузера;
|
||||
явной миграции не нужно.
|
||||
- localStorage — старые ключи интерпретируются как раньше;
|
||||
включённый ранее hillshade автоматически появится на z9 (REQ-F-17,
|
||||
AC-14).
|
||||
|
||||
## 7. Тестовые данные
|
||||
|
||||
### 7.1 Для unit-тестов
|
||||
|
||||
`tests/unit/test_terrain_paint.py` (новый, REQ-F-13 / REQ-F-14):
|
||||
|
||||
- Python-парсер исходного `src/web/app.js` через `re`.
|
||||
- Никаких внешних зависимостей.
|
||||
- Никаких фикстур данных.
|
||||
- Проверяет наличие `HILLSHADE_PAINT` / `TRI_PAINT`, наличие
|
||||
ключевых stops (`9, 0.65`, `11, 0.55`, `14, 0.40`, `8, 0.70`,
|
||||
`10, 0.85`), наличие `'raster-resampling': 'nearest'`, порог
|
||||
`zoom < 9` в `updateHillshadeAvailability`.
|
||||
|
||||
### 7.2 Для integration-тестов
|
||||
|
||||
`tests/integration/test_terrain_z9_tiles.py` (новый, REQ-F-15):
|
||||
|
||||
- Использует FastAPI `TestClient` для `src/api/main.py:app`.
|
||||
- Опирается на наличие файла `data/terrain/hillshade/9/<x>/<y>.png` —
|
||||
если каталога нет, тест `skipped` с reason (CI без данных).
|
||||
- На test-среде mva154 (где данные есть) — выполняется как
|
||||
smoke-проверка endpoint'а.
|
||||
- Дополнительно: `test_hillshade_invalid_zoom_404` — sanity на
|
||||
невалидном zoom.
|
||||
|
||||
### 7.3 Для UI-тестов (Playwright)
|
||||
|
||||
`04b-ui-test-cases.md` — список тест-кейсов TC-UI-01..TC-UI-10:
|
||||
|
||||
- Запускается на test-среде `https://openclaw.mva154.duckdns.org/enduro/`.
|
||||
- Данные — реальные PNG-тайлы рельефа на mva154 (PH-6 нарезка).
|
||||
- Скриншот-эталоны для AC-06..AC-12 (визуальная читаемость) — в
|
||||
`tests/e2e/screenshots/et013/`.
|
||||
- Скриншоты сравниваются оператором (качественная приёмка), не
|
||||
пиксельный diff (BRD M-9, R-1..R-3).
|
||||
|
||||
## 8. Резервные копии и DR
|
||||
|
||||
Без изменений vs PH-6.
|
||||
|
||||
- БД `centralfederal.sqlite`, `gps_tracks.sqlite` — бэкап тем же
|
||||
crontab-скриптом, что и раньше; ET-013 не трогает.
|
||||
- PNG-тайлы `data/terrain/*` — регенерируются из SRTM при необходимости
|
||||
(PH-6 pipeline). RPO для тайлов = время регенерации (часы),
|
||||
но они read-only и не теряются при деплое ET-013.
|
||||
|
||||
RPO для ET-013: 0 (никаких данных не пишется/не теряется).
|
||||
|
||||
## 9. Privacy / Compliance
|
||||
|
||||
| Аспект | Требование |
|
||||
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| PII | **Нет.** PNG-тайлы рельефа — derivative из SRTM 30 м (NASA, public domain). Никаких персональных данных нигде в data-flow ET-013 |
|
||||
| Licensing | **Без изменений** (PH-6 наследие: SRTM 30 m — public domain; derivative PNG распространяется свободно). ET-013 не меняет источник данных |
|
||||
| Attribution | MapLibre attribution control отображает атрибуцию активных источников (OSM, Esri). Атрибуция SRTM/NASA не выводится в UI (PH-6 решение); ET-013 это не меняет |
|
||||
| GDPR / 152-ФЗ | Не применимо (нет PII) |
|
||||
|
||||
## 10. Связанные документы
|
||||
|
||||
- `01-brd.md` §2.1 (текущая реализация), §3 (F-01..F-14), §6 (Зависимости.Данные)
|
||||
- `02-trz.md` §3 REQ-F-04..REQ-F-09 (paint constants), REQ-F-13..REQ-F-15 (тесты), REQ-F-17 (localStorage), REQ-F-18 (API), REQ-F-19 (configs/styles)
|
||||
- `06-adr/ADR-017-zoom-aware-terrain-paint.md` §«Решение», §«Последствия»
|
||||
- `07-infra-requirements.md` §3 (network), §6 (deploy procedure), §3.1 (ingress estimate)
|
||||
- `10-tech-risks.md` (этот пакет)
|
||||
- `docs/work-items/ET-012/08-data-requirements.md` — образец «read-pattern change» документа (наследие)
|
||||
- `docs/phases/PH-6.terrain/` — наследие нарезки тайлов
|
||||
357
docs/work-items/ET-013/10-tech-risks.md
Normal file
357
docs/work-items/ET-013/10-tech-risks.md
Normal file
@@ -0,0 +1,357 @@
|
||||
---
|
||||
type: tech-risks
|
||||
work_item_id: ET-013
|
||||
title: "Технические риски — ET-013: Zoom-aware paint для terrain-слоёв на z9-z11"
|
||||
version: 1
|
||||
status: approved
|
||||
created_at: 2026-06-04
|
||||
authors:
|
||||
- "agent:architect"
|
||||
---
|
||||
|
||||
# Технические риски — ET-013
|
||||
|
||||
Технические риски этапа калибровки клиентского paint для растровых
|
||||
terrain-слоёв. Бизнес-риски — в BRD §5 (R-1..R-11). Шкала:
|
||||
вероятность (Н/С/В) × влияние (Н/С/В).
|
||||
|
||||
## R-T-1 — Тайлы hillshade z9-z11 отсутствуют на test-среде
|
||||
|
||||
- **Описание:** BRD §2.1 утверждает, что PH-6 нарезала hillshade
|
||||
z8-z14. Если реальная нарезка на mva154 отличается (например,
|
||||
z10-z14), при включении hillshade на z9 пользователь увидит
|
||||
404-шахматную доску, а в DevTools — череду failed requests.
|
||||
- **Вероятность / Влияние:** Н / В.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §U-A):** pre-deploy smoke
|
||||
`curl -I` на 3 разных тайла (z9/z10/z11) над ЦФО — обязателен
|
||||
перед merge (`07-infra-requirements.md` §6.2 шаг 1, AC-19).
|
||||
- **Эскалация:** при 404 — задача останавливается, открывается
|
||||
PH-6 follow-up «hillshade-z9-z14 backfill». ET-013 не мержится.
|
||||
- **Acceptance гейт:** AC-19 в `03-acceptance-criteria.md`.
|
||||
|
||||
## R-T-2 — `raster-contrast` 0.40 даёт «пересвет» / черноту на тёмных тайлах
|
||||
|
||||
- **Описание:** На z9-z11 hillshade-тайлы из тёмных лесных зон
|
||||
(низкая средняя яркость PNG) при `raster-contrast: 0.40` могут
|
||||
«провалиться в черноту» — пиксели clipping'уются к 0, тени
|
||||
превращаются в чёрные кляксы, теряя информацию.
|
||||
- **Вероятность / Влияние:** С / С.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §C-A):** stops контраста
|
||||
подобраны консервативно (0.40 на z9 → быстрый спад к 0 на z14);
|
||||
значения калибруются по результатам визуальной приёмки.
|
||||
- **Acceptance гейт:** TC-UI-04-Z10-Q (BRD R-1, AC-07..AC-09)
|
||||
— оператор смотрит скриншоты на холмистом районе. При
|
||||
«пересвете» — снижаем contrast в stops до 0.25-0.30 итеративно.
|
||||
- **Принцип:** stops живут в коде, правка — одна строка, не ADR.
|
||||
|
||||
## R-T-3 — `'nearest'`-resampling на overzoom z12-z14 даёт пикселизацию
|
||||
|
||||
- **Описание:** При overzoom (когда MapLibre тянет тайл z14 для
|
||||
z15-z18) `'nearest'`-resampling показывает крупные квадраты вместо
|
||||
плавных теней. Это особенно заметно на hillshade.
|
||||
- **Вероятность / Влияние:** С / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §R-A):** MapLibre не
|
||||
поддерживает `interpolate` для `raster-resampling`, поэтому
|
||||
глобальное `'nearest'` — единственный простой путь. Альтернатива
|
||||
(два layer'а) отклонена как overkill.
|
||||
- **Контекст использования:** на z12+ пользователь обычно
|
||||
отключает hillshade в пользу подложки (для города нужны улицы,
|
||||
а не тени). Это вторичный сценарий.
|
||||
- **Acceptance гейт:** AC-10 (TC-UI-06-Z14-Q) — оператор
|
||||
подтверждает «не темнее и не контрастнее, чем до ET-013» (т.к.
|
||||
opacity и contrast уже вернулись к baseline). Пикселизация
|
||||
допустима, если не нарушает читаемость.
|
||||
- **Fallback:** если визуально неприемлемо — отдельным минорным
|
||||
патчем вводится второй layer hillshade с `'linear'` для z12+,
|
||||
переключаемый по `getZoom()`. Это **не часть ET-013**.
|
||||
|
||||
## R-T-4 — Сетевой трафик растёт > +35% при активной zoom-сессии
|
||||
|
||||
- **Описание:** Снижение UI-минзума hillshade с 10 до 9 добавляет
|
||||
+1 zoom-уровень. На активной сессии (пользователь крутит зум
|
||||
z8→z11→z8→z11 много раз) первая загрузка z9 тайлов даёт
|
||||
заметную дельту трафика. BRD M-10 = ≤ +35%.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** `Cache-Control: public,
|
||||
max-age=31536000, immutable` (`src/api/main.py:1252`) +
|
||||
браузерный кэш + nginx-кэш. После первого визита повторные
|
||||
запросы дают 304 If-Modified-Since (или вовсе не доходят до
|
||||
сервера — browser hits memory cache).
|
||||
- **Acceptance гейт:** AC-21 в `03-acceptance-criteria.md` —
|
||||
network-traffic ≤ 135% от baseline на сценарии zoom-петли
|
||||
z=8→9→10→11→10→9→8.
|
||||
- **Мониторинг:** см. `07-infra-requirements.md` §7.1 — первая
|
||||
неделя оператор смотрит `nginx access.log` на аномалии.
|
||||
|
||||
## R-T-5 — На тёмной теме (ET-007 `theme-dark`) hillshade с opacity 0.65 + contrast 0.40 сливается в кашу
|
||||
|
||||
- **Описание:** Тёмная подложка + полупрозрачный тёмный hillshade
|
||||
с усиленным контрастом → визуально неразличимая «грязь». BRD R-2.
|
||||
- **Вероятность / Влияние:** С / С.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §T-A):** в MVP — один paint
|
||||
для всех тем. Если AC-11 проваливается — открывается ADR-018
|
||||
«theme-specific terrain paint» с отдельной таблицей stops для
|
||||
`theme-dark` (через подписку на `theme-change` event и
|
||||
`setPaintProperty`).
|
||||
- **Acceptance гейт:** AC-11 (TC-UI-09-Z10-DARK-Q) — оператор
|
||||
проверяет на dark + holmistom районе. Если провал — фиксируется
|
||||
в `13-test-report.md` и открывается follow-up.
|
||||
- **Принцип:** не плодим сложность пока не доказана необходимость.
|
||||
|
||||
## R-T-6 — На спутниковой подложке (ET-007) hillshade «глушит» снимок
|
||||
|
||||
- **Описание:** Esri World Imagery уже содержит визуальный рельеф
|
||||
(тени снимков). Поверх него полупрозрачный hillshade с opacity
|
||||
0.65 → снимок превращается в «серую плёнку», пользователь теряет
|
||||
цвета поверхности. BRD R-3.
|
||||
- **Вероятность / Влияние:** Н / С.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §T-A):** UX-нота: на спутнике
|
||||
пользователь обычно отключает hillshade — снимок и так
|
||||
«показывает» рельеф. Если AC-12 проваливается — open ADR-018
|
||||
с правилом «на satellite layer'е opacity hillshade = старые
|
||||
0.40» (через подписку на `applyBaseLayer`).
|
||||
- **Acceptance гейт:** AC-12 (TC-UI-08-Z10-SAT-Q).
|
||||
- **Принцип:** не плодим сложность пока не доказана необходимость.
|
||||
|
||||
## R-T-7 — TRI с opacity 0.85 на z9-z11 перекрывает грунтовки/тропы
|
||||
|
||||
- **Описание:** Слой `trails-*` (грунтовки, тропы) рисуется тонкими
|
||||
линиями. Если TRI поднять до opacity 0.85, цветные пятна
|
||||
категориальной палитры могут визуально «убить» линии трасс.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** существующая логика в
|
||||
`applyTerrainLayer` (`src/web/app.js:3337-3339`) вставляет
|
||||
terrain-слои **перед** первым `trails-*` или `poi-*` слоем —
|
||||
z-order корректный. TRI рисуется ПОД линиями трасс, не НАД.
|
||||
- **Тесты:** AC-07..AC-09 (визуальная приёмка на холмистом
|
||||
районе с грунтовками).
|
||||
|
||||
## R-T-8 — MapLibre 4.7.0 не поддерживает `interpolate` для `raster-contrast`
|
||||
|
||||
- **Описание:** Если документация MapLibre врёт или версия 4.7.0
|
||||
имеет regression на `raster-contrast` с zoom-выражением, paint
|
||||
не применится, в DevTools будет warning, hillshade покажется с
|
||||
default contrast = 0.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (NFR-04 в TRZ §4):** MapLibre 4.7.0
|
||||
официально поддерживает `interpolate` для всех raster paint
|
||||
properties, кроме `raster-resampling`. Проверка — публичная
|
||||
документация maplibre.org.
|
||||
- **Smoke-проверка после деплоя:** DevTools
|
||||
`window._map.getPaintProperty('terrain-hillshade', 'raster-contrast')`
|
||||
должен вернуть массив `['interpolate', ...]` (AC-04).
|
||||
- **Fallback:** если фактически не работает — заменить на
|
||||
`case`-step выражение (грубое stepwise) или просто оставить
|
||||
числовую константу `0.30` для z9-z11 (одно значение, без
|
||||
zoom-плавности).
|
||||
|
||||
## R-T-9 — Регрессия z8: после правки TRI_PAINT на z8 перепады выглядят иначе
|
||||
|
||||
- **Описание:** В новой `TRI_PAINT` для z=8 стоит `0.70` — точно
|
||||
как было. Но если при правке нечаянно поставить `8, 0.75` (или
|
||||
пропустить стоп для z8 — тогда `interpolate` между `7→0.65` и
|
||||
`9→0.80` даст на z8 значение ~0.72), регрессия z8 нарушится.
|
||||
- **Вероятность / Влияние:** С / С.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §O-B):** в `TRI_PAINT` явно
|
||||
указан стоп `8, 0.70` (не полагаемся на интерполяцию между
|
||||
соседними стопами).
|
||||
- **Acceptance гейт:** AC-06 (TC-UI-02-Z8-REGR) — скриншот
|
||||
сравнивается с до-ET-013 baseline.
|
||||
- **Unit-тест:** REQ-F-13 проверяет наличие `8, 0.70` в исходнике
|
||||
`TRI_PAINT` через regex.
|
||||
|
||||
## R-T-10 — Регрессия z14: hillshade «не возвращается» к baseline
|
||||
|
||||
- **Описание:** Если stops `HILLSHADE_PAINT` не закрываются явным
|
||||
стопом на z14 (например, `14, 0.40, 14, 0.00`), MapLibre
|
||||
экстраполирует за пределами последнего стопа, и на z14-z15
|
||||
hillshade может остаться «перегретым» (opacity 0.55, contrast
|
||||
0.20).
|
||||
- **Вероятность / Влияние:** Н / С.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §O-B / §C-A):** `interpolate`
|
||||
у MapLibre clamp'ит значения за пределами крайних stops
|
||||
(clamping behavior). Явные стопы `14, 0.40` для opacity и
|
||||
`14, 0.00` для contrast обеспечивают регрессию z14.
|
||||
- **Acceptance гейт:** AC-10 (TC-UI-06-Z14-Q) — скриншот
|
||||
сравнивается с до-ET-013 baseline.
|
||||
- **Unit-тест:** REQ-F-13 проверяет наличие `14, 0.40` и `14, 0`
|
||||
в исходнике `HILLSHADE_PAINT`.
|
||||
|
||||
## R-T-11 — `applyTerrainLayer` ломает обратную совместимость
|
||||
|
||||
- **Описание:** При расширении сигнатуры
|
||||
`opacity → opacityOrPaint: number | object` существующая логика
|
||||
(если есть где-то ещё в `src/web/`) может сломаться при передаче
|
||||
числа.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §A-A):** внутри функции —
|
||||
нормализация `(typeof opacityOrPaint === 'number') ? {…linear…} :
|
||||
opacityOrPaint`. Старый контракт работает без изменений.
|
||||
- **Acceptance гейт:** AC-22, UT-COMPAT-01 (REQ-F-14) — статический
|
||||
grep по `src/web/*.js`: подтверждает, что вызовов
|
||||
`applyTerrainLayer` только два (оба в `onTerrainCheckbox`), оба
|
||||
переведены на новые константы.
|
||||
- **Принцип:** unit-тест на нормализацию + явный комментарий
|
||||
`// ET-013: backwards-compat shim` в коде.
|
||||
|
||||
## R-T-12 — Старый клиент (закэшированный в браузере) не подхватывает новый `app.js`
|
||||
|
||||
- **Описание:** Пользователь с открытой вкладкой неделю назад имеет
|
||||
закэшированный старый `app.js` со старым `applyTerrainLayer` без
|
||||
paint-нормализации. При reload браузер должен дёрнуть свежий
|
||||
`app.js`. Service worker — не настроен в MVP (PH-9 не реализована).
|
||||
- **Вероятность / Влияние:** С / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** `src/web/index.html` загружает
|
||||
`app.js` напрямую (без SW). nginx + `Cache-Control` на `*.js`
|
||||
— стандартные (не immutable; If-Modified-Since работает).
|
||||
При reload браузер делает conditional GET → 200 (если файл
|
||||
изменился) или 304.
|
||||
- **Backwards compat:** старый клиент с `minzoom=10` для hillshade
|
||||
продолжает работать; он просто не запрашивает hillshade z=9.
|
||||
Никаких 4xx-ответов нет (REQ-F-18 — контракт неизменен).
|
||||
- **Митигация в долгую:** PWA / SW (PH-9) введёт правильную
|
||||
inval-стратегию.
|
||||
|
||||
## R-T-13 — Hint «Зум 10+» забыт в HTML → расхождение с фактическим порогом
|
||||
|
||||
- **Описание:** В `src/web/index.html` строка
|
||||
`<span id="terrain-hillshade-hint">Зум 10+</span>`. Если правка
|
||||
REQ-F-10 потеряется (например, мердж-конфликт), у пользователя
|
||||
на z<9 будет hint «Зум 10+», который противоречит фактическому
|
||||
порогу 9.
|
||||
- **Вероятность / Влияние:** С / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (REQ-F-10):** в HTML текст явно
|
||||
меняется на «Зум 9+». Это атомарная правка, проверяется
|
||||
grep'ом.
|
||||
- **Acceptance гейт:** AC-01 — проверяет `«Зум 9+»` в исходнике
|
||||
`index.html`. AC-03 — проверяет `hint.style.display === 'none'`
|
||||
на z=9.
|
||||
- **Unit-тест:** REQ-F-14 (UT-REG-02) — grep по строке `zoom < 9`
|
||||
в `app.js` и `«Зум 9+»` в `index.html`.
|
||||
|
||||
## R-T-14 — `nearest`-resampling на TRI делает «зернистую» картинку, пользователю не нравится
|
||||
|
||||
- **Описание:** TRI — категориальная палитра (5 уровней). На
|
||||
`'nearest'` ясно видны 30-метровые SRTM-клетки, картинка
|
||||
выглядит «зернистой». BRD R-10 классифицирует это как «желаемое
|
||||
поведение» (показ «реальных» границ перепадов), но возможен
|
||||
субъективный негативный отзыв.
|
||||
- **Вероятность / Влияние:** С / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (ADR-017 §R-A):** на TRI «зернистость»
|
||||
— спецификация. Категориальные данные требуют резких границ,
|
||||
`'linear'` их размывает.
|
||||
- **Fallback:** если AC-07..AC-09 проваливаются с пометкой
|
||||
«зернисто» — откатывается F-09 (TRI → `'linear'`), hillshade
|
||||
остаётся на `'nearest'`. Это одна строка кода в `TRI_PAINT`.
|
||||
- **Acceptance гейт:** AC-07..AC-09 — оператор подтверждает
|
||||
качественную приёмку.
|
||||
|
||||
## R-T-15 — Performance деградация из-за `interpolate` в paint
|
||||
|
||||
- **Описание:** Если MapLibre на каждом zoom-tick пересчитывает
|
||||
`interpolate`-выражение без кэширования, на слабых устройствах
|
||||
(mobile, low-end) может появиться jank при зуме.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение (NFR-01 в TRZ §4):** MapLibre кэширует
|
||||
скомпилированные `interpolate`-выражения; вычисление при
|
||||
смене zoom — < 1 мс на frame.
|
||||
- **Эмпирически:** существующие слои `gps_tracks.js`,
|
||||
`trails-*` уже используют `interpolate` по zoom без жалоб.
|
||||
- **Тест:** AC-13 (TC-UI-07-Z9-MOBILE) — Playwright mobile
|
||||
viewport, проверяет работоспособность; не measure'ит FPS, но
|
||||
регрессия проявится визуально.
|
||||
|
||||
## R-T-16 — Pre-deploy smoke не покрывает все регионы (тайлы z9 могут отсутствовать вне ЦФО)
|
||||
|
||||
- **Описание:** Pre-deploy `curl` проверяет 3 тайла над ЦФО. Если
|
||||
нарезка z9 ограничена только ЦФО, пользователь над Уралом /
|
||||
Алтаем увидит 404. По BRD §6 это OOS (MVP покрывает только
|
||||
ЦФО), но риск стоит явно зафиксировать.
|
||||
- **Вероятность / Влияние:** С / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** в MVP test-среда обслуживает ЦФО
|
||||
(`centralfederal.sqlite`). Тайлы вне ЦФО — out of scope.
|
||||
- **Принцип:** если пользователь панорамирует за пределы ЦФО,
|
||||
на z9-z14 он увидит «шахматку» из 404 и для terrain, и для
|
||||
trails — это известная граница MVP, не баг ET-013.
|
||||
- **Документация:** зафиксировать в `14-deploy-log.md` как
|
||||
«known limitation».
|
||||
|
||||
## R-T-17 — `eslint` падает на новых `interpolate`-массивах
|
||||
|
||||
- **Описание:** Если в проекте настроен `eslint` с правилами
|
||||
`no-magic-numbers` или жёстким `max-len`, длинные массивы
|
||||
`['interpolate', ['linear'], ['zoom'], 9, 0.65, …]` могут
|
||||
завалить линтер.
|
||||
- **Вероятность / Влияние:** Н / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** существующие JS-файлы
|
||||
(`gps_tracks.js`) уже используют похожие массивы — значит,
|
||||
eslint их пропускает.
|
||||
- **Acceptance гейт:** AC-18 (`make lint` зелёный). При проблеме
|
||||
— добавить `// eslint-disable-next-line` точечно.
|
||||
|
||||
## R-T-18 — Калибровка stops «не угадывает» желаемую читаемость с первого раза
|
||||
|
||||
- **Описание:** Значения `9→0.65, 10→0.60, 11→0.55` для hillshade
|
||||
выбраны архитектором по эстимейту из BRD. На реальных данных
|
||||
оператор может сказать «на z9 ещё мало, на z10 уже слишком
|
||||
темно». Это **итеративный процесс**, не «упало».
|
||||
- **Вероятность / Влияние:** В / Н.
|
||||
- **Митигация:**
|
||||
- **Архитектурное решение:** stops живут в JS-константах
|
||||
`HILLSHADE_PAINT` / `TRI_PAINT`. Правка одной цифры — одна
|
||||
строка кода + новый коммит. Не требует архитектурного
|
||||
re-decide (ADR-017 §«Технический долг» TD-1).
|
||||
- **Процесс:** после первого деплоя — фикс stops по фидбеку
|
||||
оператора без новой задачи. Учитывать в bandwidth-плане до
|
||||
закрытия ET-013.
|
||||
- **Гейт:** AC-07..AC-09 — качественные, оператор-driven.
|
||||
Они и есть «точка калибровки».
|
||||
|
||||
## Сводная таблица
|
||||
|
||||
| # | Риск | Вер | Влиян | Митигация (тип) |
|
||||
|-------|--------------------------------------------------------------------|-----|-------|--------------------------------------------------|
|
||||
| R-T-1 | Тайлы z9-z11 отсутствуют | Н | В | Pre-deploy smoke + AC-19; STOP на 404 |
|
||||
| R-T-2 | `raster-contrast` 0.40 — пересвет/чернота | С | С | Итеративная калибровка stops; AC-07..AC-09 |
|
||||
| R-T-3 | `'nearest'` пикселизация на z12+ | С | Н | Принимается; fallback — двойной layer |
|
||||
| R-T-4 | Трафик +35% превышает гейт M-10 | Н | Н | `immutable` кэш; AC-21 |
|
||||
| R-T-5 | Hillshade на тёмной теме — каша | С | С | AC-11; follow-up ADR-018 при провале |
|
||||
| R-T-6 | Hillshade «глушит» спутник | Н | С | AC-12; follow-up ADR-018 при провале |
|
||||
| R-T-7 | TRI 0.85 перекрывает trails | Н | Н | Existing z-order (terrain ПОД trails) |
|
||||
| R-T-8 | MapLibre 4.7.0 не поддерживает interpolate для raster-contrast | Н | Н | Документация подтверждает; fallback — case-step |
|
||||
| R-T-9 | Регрессия z8 TRI | С | С | Явный стоп `8, 0.70`; AC-06; unit-тест |
|
||||
| R-T-10| Регрессия z14 hillshade | Н | С | Явные стопы `14, 0.40` и `14, 0`; AC-10 |
|
||||
| R-T-11| `applyTerrainLayer` обратная совместимость | Н | Н | Нормализация внутри функции; UT-COMPAT-01 |
|
||||
| R-T-12| Старый клиент в кэше браузера | С | Н | Backwards-compat контракта |
|
||||
| R-T-13| Hint «Зум 10+» забыт | С | Н | grep-проверка + AC-01 |
|
||||
| R-T-14| TRI `'nearest'` — зернисто | С | Н | Specified behavior; fallback — откат F-09 |
|
||||
| R-T-15| `interpolate` deg performance | Н | Н | MapLibre кэширует expr; NFR-01 |
|
||||
| R-T-16| Pre-deploy smoke ≠ покрытие региона | С | Н | Known MVP limitation; deploy-log |
|
||||
| R-T-17| eslint падает на длинных массивах | Н | Н | Существующий код уже использует такие массивы |
|
||||
| R-T-18| Stops не угадывают с первого раза | В | Н | Итеративная калибровка; AC-07..AC-09 — qualitative |
|
||||
|
||||
## Связанные документы
|
||||
|
||||
- `01-brd.md` §5 Бизнес-риски R-1..R-11 (часть пересекается)
|
||||
- `02-trz.md` §3 REQ-F-04..REQ-F-15 (paint, тесты), §4 NFR-01..NFR-07
|
||||
- `06-adr/ADR-017-zoom-aware-terrain-paint.md` §«Решение», §«Последствия», §«Технический долг»
|
||||
- `07-infra-requirements.md` §3 (network), §6 (deploy procedure), §7 (мониторинг)
|
||||
- `08-data-requirements.md` §3.3 (pre-deploy validation), §5 (API contracts)
|
||||
- `03-acceptance-criteria.md` AC-01..AC-22 (все гейты)
|
||||
Reference in New Issue
Block a user