Карта в карточке игры показывала полигон района — единственную геометрию, до которой у 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.
86 lines
6.5 KiB
PL/PgSQL
86 lines
6.5 KiB
PL/PgSQL
-- 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;
|