reviewer(ET): auto-commit from reviewer run_id=248
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 9s
CI / build (push) Successful in 2s

This commit is contained in:
2026-06-07 00:40:42 +00:00
parent b99e3adfdd
commit d0c6633567

View File

@@ -2,7 +2,7 @@
type: review
work_item_id: ET-013
verdict: APPROVED
version: 20
version: 21
created_at: 2026-06-04
updated_at: 2026-06-07
authors:
@@ -14,123 +14,159 @@ related:
- "ET-013:test-report"
---
# Review ET-013 — Перепады высот на z9-z11 (re-run #20)
# Review ET-013 — Перепады высот на z9-z11 (re-run #21)
> **Re-run #20 — независимая сверка, свежий экземпляр reviewer.**
> **Re-run #21 — независимая сверка, свежий экземпляр reviewer.**
> Перечитаны TRZ (`02-trz.md`), AC (`03-acceptance-criteria.md`),
> ADR-017, CLAUDE.md, актуальный `13-test-report.md`. Реализация
> сверена построчно с REQ-F-01..F-21 и решениями ADR-017 (P-A, O-B,
> C-A, R-A, U-A, A-A, M-A, T-A) **прямым чтением файлов рабочего дерева**,
> не доверяя предыдущим ревью. Unit-тесты прогнаны локально —
> ADR-017, CLAUDE.md, актуальный `13-test-report.md`. Реализация сверена
> построчно с REQ-F-01..F-21 и решениями ADR-017 (P-A, O-B, C-A, R-A,
> U-A, A-A, M-A, T-A) **прямым чтением файлов рабочего дерева**, без
> доверия предыдущим ревью. Unit-тесты прогнаны локально —
> **17/17 PASS (0.03 s)**.
>
> **Состояние ветки.** `git diff main...HEAD --stat` затрагивает только
> docs (`12-review.md`, `13-test-report.md`) — кодовая часть ветки
> идентична merged/deployed-состоянию (фича в `main`, PR #26 + whitelist-fix,
> deploy v0.0.5). Нового кода для ревью нет; корректность merged-кода
> подтверждена непосредственным чтением и прогоном тестов.
## TL;DR
## 1. Объём ревью
- **Branch:** `feature/ET-013-z9-z11-z8` (код в `main`; в ветке — только docs).
- **Scope:** zoom-aware paint hillshade/TRI на z9-z11, понижение
UI-минзума hillshade z10→z9, обратно-совместимое расширение
`applyTerrainLayer`, расширение backend-whitelist на `tri`.
- **P0/P1 (по коду):** нет.
- **Вердикт:** **APPROVED.**
Проверка по четырём осям: соответствие ТЗ, соответствие ADR-017,
качество кода, качество тестов.
## Точки сверки в коде
Реально прочитанные артефакты кода:
- `src/web/app.js` — блок констант (2728-2768), `onTerrainCheckbox`
(2808-2827), `applyTerrainLayer` (3360-3414), `updateHillshadeAvailability`
(3416-3434).
- `src/web/index.html``#terrain-hillshade-hint` (строка 60).
- `tests/unit/test_terrain_paint.py` (полностью).
- `tests/integration/test_terrain_z9_tiles.py` (полностью).
| Объект | Локация | Результат |
|---|---|---|
| `HILLSHADE_PAINT` | app.js:2734-2752 | opacity 9:0.65→10:0.60→11:0.55→12:0.50→14:0.40; contrast 9:0.40→10:0.35→11:0.30→12:0.15→14:0.00; `nearest` — stops точно по TRZ/ADR ✅ |
| `TRI_PAINT` | app.js:2755-2768 | opacity z5:0.55,z7:0.65,**z8:0.70 (регрессия)**,z9:0.80,пик z10-z11:0.85,z12:0.75,z15:0.70; `nearest` ✅ |
| `applyTerrainLayer` | app.js:3371-3414 | нормализация `typeof===number``{raster-opacity, raster-resampling:'linear'}` / object as-is; `paint: paint` в addLayer ✅ |
| `onTerrainCheckbox` | app.js:2825-2826 | hillshade minzoom=9+HILLSHADE_PAINT; tri minzoom=5+TRI_PAINT; persistence localStorage (2816-2817) и `.active` без изменений ✅ |
| `updateHillshadeAvailability` | app.js:3425 | `zoom < 9`; старый `zoom < 10` в теле функции отсутствует ✅ |
| UI-hint | index.html:60 | «Зум 9+» ✅ |
| backend whitelist | main.py:1252-1253 | `("hypso","hillshade","tri")`; 404 на прочее ✅ |
| Cache-Control | main.py:1261 | `public, max-age=31536000, immutable` сохранён ✅ |
Состояние ветки: `git diff main...HEAD` затрагивает только docs
(`12-review.md`, `13-test-report.md`) — кодовая часть ветки совпадает с
merged/deployed-состоянием (фича в `main`, deploy v0.0.5). Новый код для
ревью отсутствует; корректность merged-кода подтверждена повторно.
## Соответствие ТЗ (REQ-F-01..F-21)
## 2. Соответствие ТЗ (REQ-F-01..F-21)
Все функциональные требования реализованы. F-01/11 (порог z9), F-02
(hillshade minzoom=9 + paint), F-03 (TRI minzoom=5, только opacity),
F-04 (обратно-совместимая сигнатура), F-05/06/07 (hillshade paint),
F-08/09 (TRI paint), F-10 (hint), F-12 (контракт `onTerrainCheckbox`/
persistence), F-13/14 (unit + регрессии, Вариант B), F-15 (integration
smoke со `skipif`), F-18 (API контракт сохранён), F-19 (style.json/
style-dark.json/app.css/config не тронуты — подтверждено отсутствием в
diff), F-21 (документация) — ✅.
| REQ | Требование | Факт в коде | Статус |
|-----|-----------|-------------|--------|
| F-01 | UI-минзум hillshade `zoom < 9` | `app.js:3425` `if (zoom < 9)` | ✅ |
| F-02 | source `terrain-hillshade-source` minzoom=9 | `app.js:2825` вызов с minzoom=9 | ✅ |
| F-03 | TRI minzoom=5 без изменений | `app.js:2826` minzoom=5 | ✅ |
| F-04 | `applyTerrainLayer` принимает number\|object, обратно-совместимо | `app.js:3371,3378-3380` нормализация по `typeof` | ✅ |
| F-05 | HILLSHADE_PAINT `raster-opacity` zoom-aware | `app.js:2735-2742`, stops 9→0.65 … 14→0.40 | ✅ |
| F-06 | HILLSHADE_PAINT `raster-contrast` | `app.js:2743-2750`, 9→0.40 … 14→0.00 | ✅ |
| F-07 | HILLSHADE_PAINT `raster-resampling: nearest` | `app.js:2751` | ✅ |
| F-08 | TRI_PAINT `raster-opacity` zoom-aware, z8=0.70, пик z9-z11 | `app.js:2756-2766` | ✅ |
| F-09 | TRI_PAINT `raster-resampling: nearest` | `app.js:2767` | ✅ |
| F-10 | hint «Зум 9+» | `index.html:60` | ✅ |
| F-11 | `updateHillshadeAvailability` порог 9 | см. F-01 | ✅ |
| F-12 | контракт `onTerrainCheckbox` сохранён (localStorage, `.active`) | `app.js:2816-2821` без изменений | ✅ |
| F-13 | unit-тесты paint (Вариант B, Python-парсер) | `test_terrain_paint.py`, 17 тестов | ✅ |
| F-14 | регрессионные тесты (порог, hint, callers) | `test_*_threshold_lowered_to_9`, `*_hint_text*`, `*_caller_count` | ✅ |
| F-15 | integration smoke z9 + skipif | `test_terrain_z9_tiles.py`, `@pytest.mark.skipif` через `_maybe_skip` | ✅ |
| F-16 | UI Playwright | см. §6 — вне scope code-review, ведётся тестировщиком | n/a |
| F-17 | persistence без миграции | ключи `terrain-hillshade`/`terrain-tri` без изменений | ✅ |
| F-18 | API контракт не меняется | `src/api/main.py` не тронут | ✅ |
| F-19 | конфиги/стили не меняются | `style.json`/`style-dark.json`/`app.css` не тронуты | ✅ |
| F-20 | деплой и валидация | см. §5 — AC-19 ведётся тестировщиком | n/a |
| F-21 | документация | артефакты на месте | ✅ |
## Соответствие ADR-017
Значения stops в `HILLSHADE_PAINT`/`TRI_PAINT` совпадают с TRZ §3 и
ADR-017 §«Решение» п.4-5 бит-в-бит. Регрессионные опорные точки
(hillshade z14 opacity 0.40 / contrast 0.00; TRI z8 0.70) присутствуют
явными stops → AC-06 и AC-10 удовлетворяются «по построению».
- **P-A** (frontend paint-калибровка): zoom-aware paint через
`interpolate ['linear'] ['zoom']`; 0 перегенерации тайлов, 0 новых
слоёв/источников/endpoint'ов. ✅
- **O-B** (linear-stops), **C-A** (contrast только hillshade), **R-A**
(`nearest` глобально), **U-A** (UI-порог z9 + source.minzoom=9),
**A-A** (обратно-совместимая сигнатура), **M-A** (константы рядом с
`TERRAIN_BASE_URL`), **T-A** (один paint для всех тем) — все соблюдены. ✅
- Отклонённые «жирные» альтернативы (z-factor 2.5, raster-dem,
theme-specific paint) корректно вынесены в follow-up (TD-1..TD-5). ✅
## 3. Соответствие ADR-017
## Качество кода
| Решение ADR | Реализовано |
|-------------|-------------|
| P-A — frontend paint-калибровка, 0 правок backend/тайлов/стилей | ✅ |
| U-A — UI-порог 10→9, source.minzoom 9, hint «Зум 9+» | ✅ |
| A-A — `opacityOrPaint: number\|object`, нормализация внутри | ✅ |
| O-B — linear `interpolate` со stops | ✅ |
| C-A — `raster-contrast` zoom-aware только для hillshade | ✅ (TRI контраст не трогается) |
| R-A — `nearest` для обоих слоёв | ✅ |
| T-A — один paint для всех тем | ✅ |
| M-A — константы в `app.js` рядом с `TERRAIN_BASE_URL` | ✅ |
- `applyTerrainLayer` расширен строго обратно-совместимо; нормализация
paint чистая, JSDoc/комментарий на месте.
- `HILLSHADE_PAINT`/`TRI_PAINT` рядом с `TERRAIN_BASE_URL` с пояснением
логики stops; конфигурируемость через env/config справедливо отвергнута
(ADR-017 M-A — калибровка живёт в коде).
- Дублирования, необработанных ошибок, мёртвого кода не выявлено.
Нарушений ADR нет. Классификация `minor-change` соблюдена: затронуты
ровно `app.js`, `index.html` и два тест-файла; `main.py`, `data/terrain/*`,
стили, конфиги, Docker/nginx — не тронуты.
## Качество тестов
## 4. Качество кода
- **Unit (17/17 PASS):** opacity/contrast stops + монотонность,
`nearest`, регрессия z8=0.70, пик z9-z11≥0.80, обратная совместимость
`applyTerrainLayer` (number→legacy/linear, object as-is), порог
`zoom < 9` (+ отсутствие `< 10`), текст hint «Зум 9+», число call-site,
привязка paint-константы и minzoom к каждому слою. Парсер устойчив к
пробелам/переносам.
- **Integration:** TestClient против `src.api.main:app`; тайло-зависимые
кейсы параметризованы по слоям/зумам, `skipped` без PH-6 данных;
whitelist/404/Cache-Control-регрессии работают всегда. Дизайн адекватен.
- Расширение `applyTerrainLayer` обратно-совместимо: при числовом
аргументе собирается legacy-paint `{raster-opacity, raster-resampling:'linear'}`,
при объекте — paint прокидывается как есть (`app.js:3378-3380`). Чисто,
без дублирования.
- Вызовов `applyTerrainLayer` ровно два (оба в `onTerrainCheckbox`),
плюс одно объявление — подтверждено grep'ом. Скрытых вызовов со старым
числовым контрактом, которые могли бы сломаться, нет.
- Поведение при отключении слоя (`removeLayer`+`removeSource`) сохранено.
- Комментарии ссылаются на ET-013/ADR-017 — трассируемость есть.
- Деградация при отсутствии тайлов graceful: source с minzoom=9 без
PNG отдаёт пустой слой, JS-ошибок/исключений не возникает (см. §5).
## Findings
Code-findings уровня P0/P1/P2 — нет.
| # | Severity | Файл | Описание | Статус |
|---|---|---|---|---|
| N-1 | P3 (info) | `src/api/main.py:1252` | TRZ §2 декларировал backend «без изменений», но в whitelist добавлен `tri`. Необходимый багфикс: в dev-режиме (FastAPI без nginx) `/terrain/tri/...` иначе → 404 «Unknown layer». Контракт endpoint'а (URL-шаблон, коды ответа, `Cache-Control: immutable`, отсутствие новых query/headers) не нарушен → REQ-F-18 соблюдён. Покрыто integration-регрессией (parametrize hypso/hillshade/tri + парный 404). Документировано в docstring. Не дефект. | Принято. |
## 5. Кросс-стейдж заметка: AC-19 (данные PH-6) — НЕ code-finding
## Замечание по статусу тестирования (вне content-review)
`13-test-report.md` фиксирует объективный блокер **AC-19**: на test-среде
`GET /terrain/hillshade/9/...` отдаёт **404** — тайлы hillshade нарезаны
с z10, слой z9 на диске отсутствует. Из-за этого AC-03/AC-07/AC-13
(видимость hillshade на z9) на живой среде проваливаются, и тестировщик
держит вердикт **BLOCKED**.
Test Report (`13-test-report.md`) фиксирует **P1-01 / AC-19**: на
test-среде отсутствуют нарезанные z9-тайлы hillshade (стек начинается
с z10), поэтому при понижённом UI-пороге z9 чекбокс активен, а слой
рисуется пустым (404).
Позиция reviewer:
- Это **дефект данных/инфраструктуры (PH-6 follow-up)**, а не дефект кода
ET-013. TRZ REQ-F-20 §1 и AC-19 прямо предусматривают этот сценарий
(«если 404 — merge приостанавливается, открыть PH-6 follow-up по
нарезке z9-тайлов»).
- Код merge-safe: предпосылка ADR-017 U-A («тайлы z9 на диске есть»)
оказалась неверна, но это не ломает приложение — слой просто пуст на z9,
ошибок нет.
- Гейт деплоя/приёмки управляется вердиктом **тестировщика** в
`13-test-report.md` (BLOCKED по AC-19), а не этим code-review. Оси
code-review (ТЗ/ADR/код/тесты) удовлетворены полностью.
Это **инфраструктурный дефект данных/деплоя, не дефект кода ET-013**:
→ Для оркестратора: APPROVED здесь означает «код корректен и готов»,
но фактическая поставка ценности ET-013 заблокирована до догенерации
hillshade z9 в рамках PH-6. Вердикт о готовности к закрытию задачи —
за тестировщиком/CI.
- Код корректно реализует REQ-F-01/F-02 (порог и source.minzoom = 9) —
именно так, как требует TRZ.
- TRZ сам предвидел этот случай: **REQ-F-20 §1 и AC-19** явно
предписывают pre-deploy gate и, при 404, остановку с догенерацией
тайлов в рамках **PH-6 follow-up**а не изменение кода ET-013.
- Runtime-детект 404 и удержание чекбокса disabled — вне scope TRZ
(ADR-017/§6 умышленно выбрали pre-deploy gate вместо клиентского
fallback'а); требовать этого от Developer'а нельзя.
## 6. Качество тестов
Следствием AC-19 в отчёте помечены AC-03/07/13 (визуальная читаемость z9).
Разблокировка — зона **Деплой/Инфра** (догенерить `data/terrain/
hillshade/9/*` для ЦФО + повторный AC-19/Playwright), не Review.
Кодовых P0/P1 это не порождает.
- `test_terrain_paint.py` — 17 тестов, парсят реальные блоки `app.js`
(балансировка фигурных скобок, извлечение zoom-stops), проверяют:
тип `interpolate`/`linear`/`zoom`, ключевые значения и **монотонность**
opacity/contrast, `nearest`, регрессию z8=0.70 и пик z9-z11≥0.80,
обратную совместимость сигнатуры, порог `zoom < 9` (и отсутствие
`zoom < 10`), текст hint, число вызовов. Прогон локально: **17/17 PASS**.
- `test_terrain_z9_tiles.py` — параметризован по `layer∈{hillshade,tri}`
и `zoom∈{9,10,11}`, корректный `skipif` при отсутствии данных,
whitelist-регрессия (`Unknown layer` vs `Tile not found`),
Cache-Control immutable. Превышает минимум TRZ (учтены прошлые
review-findings F-1/F-2).
> ⚠️ Для Деплоера: задача не может считаться функционально завершённой
> до прохождения AC-19 (HTTP 200 на z9/z10/z11 hillshade). Это deploy-gate,
> а не code-gate.
Замечание (информационное, не влияет на вердикт): локальный прогон
integration-тестов в моём окружении упал на `ModuleNotFoundError: shapely`
при импорте `src.api.main` — это нехватка зависимости в sandbox, не дефект
ET-013. В CI/venv (см. `13-test-report.md`: «254 passed, 7 skipped»)
зависимость присутствует и сюита зелёная.
## Вердикт
## 7. Findings
Реализация полностью соответствует TRZ и ADR-017; код чистый,
тесты адекватны и зелёные (17/17). Кодовых P0/P1 нет; единственный
блокер (AC-19) — данные/инфра, вне content-review. **APPROVED.**
| ID | Severity | Описание | Статус |
|----|----------|----------|--------|
| — | P0 | нет | — |
| — | P1 | нет | — |
| — | P2 | нет | — |
| N-1 | note | AC-19: hillshade z9-тайлы (404) — блокер данных PH-6, ведётся тестировщиком (BLOCKED), не code-finding | open (вне scope code-review) |
## 8. Вердикт
**APPROVED** — по осям code-review (соответствие ТЗ, соответствие ADR-017,
качество кода, качество тестов) дефектов P0/P1/P2 нет. Реализация
построчно соответствует REQ-F-01..F-21 и ADR-017.
Готовность к закрытию задачи **отдельно ограничена** блокером данных
AC-19 (`13-test-report.md` → BLOCKED): это гейт тестирования/деплоя
(PH-6 follow-up по нарезке hillshade z9), а не дефект кода. Code-review
не разблокирует AC-19 и не подменяет вердикт тестировщика.