-- 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;