gendesign/tradein-mvp/backend/data/sql/281_street_scheme.sql
bot-backend ca3f073b0e feat(mera/b2c): улица сделки доезжает до витрины готовой схемой, а не геометрией
Карта в карточке игры показывала полигон района — единственную геометрию, до
которой у tradein был доступ. Улицы живут в базе gendesign, foreign table и
гранта на них не было.

Мост по образцу соседей (v_tradein_cad_buildings / v_tradein_osm_poi_ekb):
gendesign 195 — вьюха v_tradein_osm_roads_ekb (highway + water) с GRANT В ТОМ
ЖЕ ФАЙЛЕ (после #3227 грант отдельной миграцией теряется при пересоздании);
tradein 281 — foreign table gendesign_osm_roads_ekb плюс колонки street_name /
street_scheme в landing_showcase_deals.

Пересчёт витрины кладёт в строку УЖЕ СПРОЕЦИРОВАННЫЕ SVG-пути окна 840x840 м
вокруг центра улицы. Проекция — та же равнопромежуточная с cos(широты), что в
export_ekb_districts_svg.py; второй в проекте нет. Замер 2026-08-29: GeoJSON
того же окна 7-8 КБ на строку, схема — 2.8-2.9 КБ.

ЧТО ДАННЫЕ ВЫДЕРЖИВАЮТ, И НИ СЛОВОМ БОЛЬШЕ
  · Это УЛИЦА, а не дом: deals.address уровня улицы, номер дома у 2.7% сделок.
    В схеме намеренно НЕТ координат окна и констант проекции — точку дома по
    ней нельзя поставить даже случайно. Это замок, а не забывчивость.
  · Зданий нет: cad_buildings — 18 307 контуров на город, в плотном центре 51
    здание на радиус 450 м, где их в разы больше. Нарисованная застройка
    заявляла бы полноту, которой в данных нет.
  · Улицы — фильтрованная выгрузка «источников шума»: именованные покрыты
    хорошо, дворовые и служебные проезды отсутствуют.
  · Улица сматчилась у 550 названий из 654 — 31 410 сделок из 34 021 (92.3%).
    Остальным street_scheme = NULL, и это штатно: фронт показывает район,
    который для этого и оставлен.
  · Перекрёсток («Челюскинцев/Шейнкмана») берём первой улицей: таких адресов
    три на 34 021 сделку, и обе улицы одинаково верны на уровне улицы.
  · Наличие схемы НА ОТБОР СТРОК НЕ ВЛИЯЕТ — то же правило, что запрещает
    отбор по величине ошибки: иначе витрина показывала бы не работу оценщика,
    а те 92% адресов, что удобно легли на OSM.

Тесты (каждый сломан вручную и покраснел): нормализация на реальных адресах
включая ё/е и «8 Марта»; недоступная вьюха и упавший запрос дают None, а не
исключение; схема не раздувается — потолок в байтах на реальной плотности плюс
прямая проверка округления до 0.1.
2026-08-29 23:25:44 +05:00

86 lines
6.5 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 281_street_scheme.sql
-- Улицы ЕКБ на витрине сделок: сторона tradein (PR 2 из 2).
--
-- ДВА ДЕЙСТВИЯ В ОДНОМ ФАЙЛЕ, И ЭТО ОСОЗНАННО:
-- 1) foreign table над `v_tradein_osm_roads_ekb` (миграция gendesign 195);
-- 2) колонки `street_name` / `street_scheme` в `landing_showcase_deals`.
-- Порознь у них разные зависимости, но смысл один: без геометрии колонки некому
-- заполнить, без колонок геометрию некуда положить. Один файл = один
-- транзакционный исход: если 195 на той стороне ещё не задеплоена, падает
-- ВСЁ и не остаётся полусостояния «колонка есть, данных не будет никогда».
-- Файл идемпотентен, поэтому повтор после деплоя 195 доводит дело до конца.
--
-- ПОЧЕМУ КОЛОНКА В `landing_showcase_deals`, А НЕ ОТДЕЛЬНАЯ ТАБЛИЦА. Схема
-- живёт ровно столько же, сколько строка витрины: пересчёт делает
-- DELETE+INSERT всей витрины, и схема обязана умереть вместе со своей строкой.
-- Отдельная таблица дала бы собственный жизненный цикл (то есть возможность
-- пережить свой батч и уехать на фронт рядом с ЧУЖИМИ числами), join в
-- публичной ручке и вторую точку отказа — в обмен ни на что: связь строго 1:1.
--
-- ЧТО ЛЕЖИТ В `street_scheme` — ГОТОВЫЕ SVG-ПУТИ, А НЕ ГЕОМЕТРИЯ. Окно
-- 840×840 м в GeoJSON — это 7-8 КБ на сделку плюс проекция в рантайме у
-- посетителя. Уже спроецированная схема того же окна — 2.8-2.9 КБ (замер
-- 2026-08-29 на «улица Краснолесья» и «улица 8 Марта»).
--
-- ГРАНИЦА ЧЕСТНОСТИ, ЗАПИСАННАЯ ЗДЕСЬ, А НЕ ТОЛЬКО В ГОЛОВЕ АВТОРА:
-- * ЭТО УЛИЦА, А НЕ ДОМ. `deals.address` — уровня улицы («Екатеринбург,
-- Краснолесья»), номер дома известен у 2.7% сделок. В схеме намеренно НЕТ
-- ни координат окна, ни констант проекции: по ней физически нельзя
-- положить (lon, lat) в её систему координат, то есть нарисовать точку
-- дома нельзя даже случайно.
-- * `street_name` NULLABLE, и NULL — ШТАТНЫЙ СЛУЧАЙ. Нормализация названия
-- даёт совпадение с OSM у 550 названий из 654, это 31 410 сделок из
-- 34 021 — 92.3% (замер 2026-08-29 по той же выборке, из которой
-- набирается витрина). Оставшиеся 8% схемы не получают, и фронт
-- показывает район — запасной вариант, который для этого и оставлен.
--
-- Зависимости:
-- - 060_postgres_fdw_extension.sql (SERVER gendesign_remote + USER MAPPING,
-- пароль подставляется на старте бэкенда). Сервер объявлен с
-- OPTIONS (extensions 'postgis') — именно поэтому `&&` с конвертом уходит
-- на ту сторону в GiST-индекс, а не тянет сюда десять тысяч линий.
-- - gendesign: вьюха public.v_tradein_osm_roads_ekb + GRANT SELECT роли
-- tradein_fdw_reader (миграция 195, деплоится ПЕРВОЙ).
-- - 276_landing_showcase_deals.sql (сама витрина).
--
-- Идемпотентна: DROP FOREIGN TABLE IF EXISTS + ADD COLUMN IF NOT EXISTS.
BEGIN;
-- Конвенция проекта (#2752): ALTER TABLE берёт ACCESS EXCLUSIVE и, ожидая его
-- за чужой сессией, встаёт в очередь ПЕРЕД запросами приложения.
SET LOCAL lock_timeout = '5s';
ALTER TABLE landing_showcase_deals
ADD COLUMN IF NOT EXISTS street_name text,
ADD COLUMN IF NOT EXISTS street_scheme jsonb;
COMMENT ON COLUMN landing_showcase_deals.street_name IS
'Улица сделки как она называется в OSM. NULL — штатно: адрес не сматчился '
'(8% сделок), схемы нет, фронт показывает район. Это УЛИЦА, а не дом: '
'номер дома известен у 2.7% сделок.';
COMMENT ON COLUMN landing_showcase_deals.street_scheme IS
'Готовые SVG-пути окна 840x840 м вокруг центра улицы: {street, w, h, '
'target[], roads[{c,d}], water[], labels[{t,x,y}]}. Координат окна и '
'констант проекции в ней НЕТ намеренно — по схеме нельзя положить точку '
'дома, которого в данных нет. Зданий нет тоже (cad_buildings покрывает '
'город на 18 307 контуров и в центре показывает пустые кварталы).';
DROP FOREIGN TABLE IF EXISTS gendesign_osm_roads_ekb;
CREATE FOREIGN TABLE gendesign_osm_roads_ekb (
name text,
road_class text,
geom geometry(Geometry, 4326)
)
SERVER gendesign_remote
OPTIONS (schema_name 'public', table_name 'v_tradein_osm_roads_ekb');
COMMENT ON FOREIGN TABLE gendesign_osm_roads_ekb IS
'Живой срез gendesign.osm_noise_sources_ekb (улицы и вода ЕКБ). Выгрузка '
'фильтрованная («источники шума»): именованные улицы покрыты хорошо, '
'дворовые и служебные проезды отсутствуют. Читает '
'app/services/street_scheme.py при пересчёте витрины лэндинга; в публичной '
'ручке НЕ используется — там читаются готовые схемы из landing_showcase_deals.';
COMMIT;