grid_geometry.py:59-61 — sanity-check сравнивает искажённые единицы EPSG:3857 с порогами, заданными как наземные метры → валидные крупные/вытянутые квартал… #1629

Closed
opened 2026-06-16 18:44:21 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: logic/projection · Файл: backend/app/services/cadastre/grid_geometry.py:59-69

Файл: backend/app/services/cadastre/grid_geometry.py:59-69 (квартал-фолбэк через bulk_harvest.py:252 и :336-343).

Проблема: width/height (стр. 59-60) вычисляются из bbox = ST_Extent(ST_Transform(geom, 3857)) (стр. 36, 49-54). geom хранится в EPSG:4326 (geometry(MultiPolygon,4326), data/sql/58_schema_cad_quarters.sql:14), а EPSG:3857 (Web Mercator) — НЕ равнопротяжённая проекция: на широте ЕКБ линейный масштаб sec(lat)≈1.8-2.0 (для тестового healthy-bbox test_cadastre_bulk.py:258 широта ~59.68°N, фактор ≈1.98). Значит 1 наземный метр ≈ 1.8-2.0 единиц 3857. Но пороги 100..10000 (стр. 61) и весь docstring/лог ('expected 100-10000m', 'width 200-4000м', '~750м × ~750м', стр. 55-58, 64, 82) описаны как НАЗЕМНЫЕ МЕТРЫ.

Импакт: верхняя граница 10000 единиц 3857 = всего ~5050 м реальной земли. Реальные вытянутые кварталы ЕКБ вдоль магистралей/рек/ж-д (≈6000 м наземной длины = ~11886 единиц) дают width|height > 10000 → quarter_bbox_3857 возвращает None. В результате bulk_harvest.py:252 (фаза territorial_zones) и bulk_harvest.py:336-343 (_grid_walk_category) пропускаются — квартал получает только snapshot-фазу (≤20 объектов/категория), теряя десятки-сотни ЗУ/зданий при overflow. Лог при этом ложно пишет 'broken geom — ... (expected 100-10000m)', хотя геометрия корректна (тихая потеря данных, harvest_meta не отражает реальную причину). Дополнительно: per-axis искажение делает порог анизотропным относительно намерения. Нижняя граница (<100) безопасна.

Фикс: измерять размеры в наземных метрах, а не в единицах 3857. Варианты: (1) ST_Length по геодезии/географии — ST_XMax/XMin/YMax/YMin на ST_Extent(geom::geography) либо ST_Transform в равнопротяжённую локальную проекцию (МСК-66 / UTM zone 41N), затем сравнивать с 100..10000 м; (2) либо оставить 3857, но скорректировать верхний порог делением на текущий cos(lat) (≈ умножить на ~2 → ~20000 единиц) и обновить docstring/лог, что единицы — Web Mercator, а не метры. Предпочтителен вариант (1) как точный. Обновить тесты test_cadastre_bulk.py:256-280 под наземные метры.

Почему баг: Из-за искажения per-axis верхняя граница (width > 10000 or height > 10000) отбраковывает реальные кварталы, у которых хотя бы одна сторона > ~5.5 км наземной длины. В ЕКБ распространены вытянутые кварталы вдоль магистралей/рек/ж-д (например ~200 м × ~6000 м реальных = ~366 × ~10970 единиц 3857) — для них quarter_bbox_3857 возвращает None, _grid_walk_category (bulk_harvest.py:336-343) и фаза territorial_zones (bulk_harvest.py:252-253) полностью пропускаются. Квартал получает только snapshot-фазу (≤20 объектов на категорию), теряя десятки-сотни ЗУ/зданий при наличии overflow. Это тихая потеря данных в дискавери: harvest_meta не помечает квартал как 'broken geom' в смысле реальной поломки — лог говорит «broken geom», хотя геометрия корректна. Нижняя граница (<100) безопасна (реально broken geom <0.01 м), верхняя — нет.

Как чинить: Считать ширину/высоту в истинных метрах, а не в единицах Web Mercator: либо ST_Area(geom::geography)/ST_Length по geography, либо вычислять extent в локальной метрической проекции МСК-66 / UTM, либо просто расширить верхний порог с учётом sec(lat)≈1.83 (т.е. ~18000-20000 в единицах 3857) и перепроверить, что вытянутые кварталы проходят. Минимум — привести комментарии/докстринг и тестовые значения в соответствие с тем, что пороги в искажённых единицах 3857, а не в наземных метрах, чтобы граница не настраивалась по ложному предположению.


deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.85 · unit B17

**Severity:** medium · **Категория:** logic/projection · **Файл:** `backend/app/services/cadastre/grid_geometry.py:59-69` Файл: backend/app/services/cadastre/grid_geometry.py:59-69 (квартал-фолбэк через bulk_harvest.py:252 и :336-343). Проблема: width/height (стр. 59-60) вычисляются из bbox = ST_Extent(ST_Transform(geom, 3857)) (стр. 36, 49-54). geom хранится в EPSG:4326 (geometry(MultiPolygon,4326), data/sql/58_schema_cad_quarters.sql:14), а EPSG:3857 (Web Mercator) — НЕ равнопротяжённая проекция: на широте ЕКБ линейный масштаб sec(lat)≈1.8-2.0 (для тестового healthy-bbox test_cadastre_bulk.py:258 широта ~59.68°N, фактор ≈1.98). Значит 1 наземный метр ≈ 1.8-2.0 единиц 3857. Но пороги 100..10000 (стр. 61) и весь docstring/лог ('expected 100-10000m', 'width 200-4000м', '~750м × ~750м', стр. 55-58, 64, 82) описаны как НАЗЕМНЫЕ МЕТРЫ. Импакт: верхняя граница 10000 единиц 3857 = всего ~5050 м реальной земли. Реальные вытянутые кварталы ЕКБ вдоль магистралей/рек/ж-д (≈6000 м наземной длины = ~11886 единиц) дают width|height > 10000 → quarter_bbox_3857 возвращает None. В результате bulk_harvest.py:252 (фаза territorial_zones) и bulk_harvest.py:336-343 (_grid_walk_category) пропускаются — квартал получает только snapshot-фазу (≤20 объектов/категория), теряя десятки-сотни ЗУ/зданий при overflow. Лог при этом ложно пишет 'broken geom — ... (expected 100-10000m)', хотя геометрия корректна (тихая потеря данных, harvest_meta не отражает реальную причину). Дополнительно: per-axis искажение делает порог анизотропным относительно намерения. Нижняя граница (<100) безопасна. Фикс: измерять размеры в наземных метрах, а не в единицах 3857. Варианты: (1) ST_Length по геодезии/географии — ST_XMax/XMin/YMax/YMin на ST_Extent(geom::geography) либо ST_Transform в равнопротяжённую локальную проекцию (МСК-66 / UTM zone 41N), затем сравнивать с 100..10000 м; (2) либо оставить 3857, но скорректировать верхний порог делением на текущий cos(lat) (≈ умножить на ~2 → ~20000 единиц) и обновить docstring/лог, что единицы — Web Mercator, а не метры. Предпочтителен вариант (1) как точный. Обновить тесты test_cadastre_bulk.py:256-280 под наземные метры. **Почему баг:** Из-за искажения per-axis верхняя граница (width > 10000 or height > 10000) отбраковывает реальные кварталы, у которых хотя бы одна сторона > ~5.5 км наземной длины. В ЕКБ распространены вытянутые кварталы вдоль магистралей/рек/ж-д (например ~200 м × ~6000 м реальных = ~366 × ~10970 единиц 3857) — для них quarter_bbox_3857 возвращает None, _grid_walk_category (bulk_harvest.py:336-343) и фаза territorial_zones (bulk_harvest.py:252-253) полностью пропускаются. Квартал получает только snapshot-фазу (≤20 объектов на категорию), теряя десятки-сотни ЗУ/зданий при наличии overflow. Это тихая потеря данных в дискавери: harvest_meta не помечает квартал как 'broken geom' в смысле реальной поломки — лог говорит «broken geom», хотя геометрия корректна. Нижняя граница (<100) безопасна (реально broken geom <0.01 м), верхняя — нет. **Как чинить:** Считать ширину/высоту в истинных метрах, а не в единицах Web Mercator: либо ST_Area(geom::geography)/ST_Length по geography, либо вычислять extent в локальной метрической проекции МСК-66 / UTM, либо просто расширить верхний порог с учётом sec(lat)≈1.83 (т.е. ~18000-20000 в единицах 3857) и перепроверить, что вытянутые кварталы проходят. Минимум — привести комментарии/докстринг и тестовые значения в соответствие с тем, что пороги в искажённых единицах 3857, а не в наземных метрах, чтобы граница не настраивалась по ложному предположению. --- <sub>deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.85 · unit B17</sub>
bot-backend added the
week ревью 1
label 2026-06-16 18:44:21 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1629
No description provided.