Merge pull request 'fix(tradein): убрать TRUNCATE из seed — деплой больше не wipe-ит данные' (#380) from fix/tradein-seed-truncate into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 4s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 26s
Deploy Trade-In / deploy (push) Successful in 15s

This commit is contained in:
lekss361 2026-05-21 10:19:40 +00:00
commit dbe0bbebff
2 changed files with 54 additions and 147 deletions

View file

@ -0,0 +1,54 @@
-- Trade-In MVP — идемпотентный сид СДЕЛОК (deals).
--
-- ВАЖНО: этот файл применяется на КАЖДОМ деплое (deploy-tradein.yml гоняет
-- весь data/sql/*.sql). Поэтому:
-- 1. НЕ трогает listings — реальные данные парсеров не должны затираться.
-- 2. Вставляет сделки ТОЛЬКО если таблица deals пуста (WHERE NOT EXISTS).
-- На втором и последующих деплоях SELECT вернёт 0 строк → no-op.
--
-- 80 синтетических сделок (Росреестр/ДомКлик) в центре ЕКБ — для страницы 3
-- PDF-отчёта. Реальные сделки появятся когда подключим дамп rosreestr_deals.
INSERT INTO deals (
source, dedup_hash, address, lat, lon, geom,
rooms, area_m2, floor, total_floors, year_built,
price_rub, price_per_m2, deal_date, days_on_market
)
SELECT
CASE WHEN random() < 0.6 THEN 'rosreestr' ELSE 'domklik' END,
md5('seed-deal:' || i::text || ':' || rooms_pick::text || ':' || area_pick::text),
'Екатеринбург, реальная сделка',
lat, lon,
ST_SetSRID(ST_MakePoint(lon, lat), 4326),
rooms_pick,
area_pick,
(1 + (random() * 15)::int),
(3 + (random() * 15)::int),
(1965 + (random() * 60)::int),
(ppm2 * area_pick)::int,
ppm2,
NOW() - (random() * 365 || ' days')::interval,
(15 + (random() * 90)::int)
FROM (
SELECT
i, lat, lon, rooms_pick,
CASE rooms_pick
WHEN 1 THEN (28 + random() * 17)::numeric(10,2)
WHEN 2 THEN (40 + random() * 25)::numeric(10,2)
WHEN 3 THEN (58 + random() * 30)::numeric(10,2)
END AS area_pick,
(110000 + random() * 70000)::int AS ppm2
FROM (
SELECT
gs AS i,
56.8354 + (random() - 0.5) * 0.026 AS lat,
60.6172 + (random() - 0.5) * 0.050 AS lon,
CASE
WHEN random() < 0.3 THEN 1
WHEN random() < 0.8 THEN 2
ELSE 3
END AS rooms_pick
FROM generate_series(1, 80) gs
) random_lots
) generated
WHERE NOT EXISTS (SELECT 1 FROM deals LIMIT 1);

View file

@ -1,147 +0,0 @@
-- Trade-In MVP — Seed данные для разработки (v2, фикс распределения).
-- 120 синтетических объявлений + 60 фактических сделок в центре ЕКБ.
-- Использовать пока парсеры не подключены к продакшну.
BEGIN;
TRUNCATE listings, deals RESTART IDENTITY CASCADE;
-- ── Helper: 10 улиц ЕКБ с координатами ─────────────────────────────────────
WITH streets(idx, name, lat, lon) AS (VALUES
(0, 'ул. Малышева', 56.8332::float, 60.5944::float),
(1, 'ул. Куйбышева', 56.8282, 60.6114),
(2, 'ул. 8 Марта', 56.8240, 60.6000),
(3, 'ул. Белинского', 56.8160, 60.6080),
(4, 'пр. Ленина', 56.8400, 60.6100),
(5, 'ул. Толмачёва', 56.8390, 60.6020),
(6, 'ул. Радищева', 56.8300, 60.5890),
(7, 'ул. Мамина-Сибиряка', 56.8420, 60.6190),
(8, 'ул. Луначарского', 56.8350, 60.6280),
(9, 'ул. Первомайская', 56.8440, 60.6240)
),
-- 5 типов квартир (rooms, базовая площадь, базовая цена в руб 2026)
flat_types(idx, rooms, base_area, base_price) AS (VALUES
(0, 0, 28.0::float, 6500000::bigint),
(1, 1, 40.0, 9000000),
(2, 2, 60.0, 12500000),
(3, 3, 80.0, 17000000),
(4, 4, 110.0, 22000000)
),
sources(idx, name) AS (VALUES
(0, 'avito'),
(1, 'cian'),
(2, 'domklik')
),
counts(rooms, n) AS (VALUES
(0, 12), -- студии
(1, 30), -- 1к
(2, 45), -- 2к
(3, 25), -- 3к
(4, 8) -- 4+к
)
INSERT INTO listings (
source, source_url, dedup_hash, address, lat, lon, region_code,
rooms, area_m2, floor, total_floors, year_built, house_type,
price_rub, price_per_m2, listing_date, days_on_market, scraped_at
)
SELECT
src.name AS source,
'https://' || src.name || '.example/lot/' || row_num AS source_url,
md5(src.name || row_num::text || street.name || ft.rooms::text) AS dedup_hash,
street.name || ', ' || (10 + (row_num * 7) % 150)::text AS address,
street.lat + ((random() - 0.5) * 0.012) AS lat, -- ≈±650м jitter
street.lon + ((random() - 0.5) * 0.012) AS lon,
66 AS region_code,
ft.rooms,
ft.base_area + (random() * 6 - 3) AS area_m2, -- ±3м² jitter
1 + (row_num * 3) % 18 AS floor,
9 + (row_num * 5) % 14 AS total_floors,
1960 + (row_num * 11) % 64 AS year_built,
(ARRAY['panel', 'brick', 'monolith', 'monolith_brick'])[1 + (row_num * 7) % 4] AS house_type,
(ft.base_price * (0.85 + random() * 0.30))::bigint AS price_rub,
((ft.base_price * (0.85 + random() * 0.30)) / (ft.base_area + (random() * 6 - 3)))::int AS price_per_m2,
CURRENT_DATE - ((row_num % 90)::text || ' days')::interval AS listing_date,
row_num % 90 AS days_on_market,
NOW() - ((row_num % 14)::text || ' days')::interval AS scraped_at
FROM (
-- Раскрываем counts в rows: для rooms=0 12 строк, для rooms=1 30 строк, ...
SELECT
c.rooms,
row_number() OVER (PARTITION BY c.rooms) AS rooms_row,
row_number() OVER () AS row_num
FROM counts c
CROSS JOIN LATERAL generate_series(1, c.n) gs
) AS expanded
JOIN flat_types ft ON ft.rooms = expanded.rooms
JOIN streets street ON street.idx = expanded.row_num % 10
JOIN sources src ON src.idx = expanded.row_num % 3;
-- ── DEALS (60 шт., цены на ~7% ниже листингов) ────────────────────────────
WITH streets(idx, name, lat, lon) AS (VALUES
(0, 'ул. Малышева', 56.8332::float, 60.5944::float),
(1, 'ул. Куйбышева', 56.8282, 60.6114),
(2, 'ул. 8 Марта', 56.8240, 60.6000),
(3, 'ул. Белинского', 56.8160, 60.6080),
(4, 'пр. Ленина', 56.8400, 60.6100),
(5, 'ул. Толмачёва', 56.8390, 60.6020),
(6, 'ул. Радищева', 56.8300, 60.5890),
(7, 'ул. Мамина-Сибиряка', 56.8420, 60.6190)
),
flat_types(idx, rooms, base_area, base_price) AS (VALUES
(1, 1, 40.0::float, 9000000::bigint),
(2, 2, 60.0, 12500000),
(3, 3, 80.0, 17000000)
),
deal_counts(rooms, n) AS (VALUES
(1, 18),
(2, 28),
(3, 14)
)
INSERT INTO deals (
source, dedup_hash, address, lat, lon, region_code,
rooms, area_m2, floor, total_floors, year_built, house_type,
deal_date, price_rub, price_per_m2, days_on_market, scraped_at
)
SELECT
'rosreestr' AS source,
md5('deal:' || row_num::text || street.name || ft.rooms::text) AS dedup_hash,
street.name || ', ' || (10 + (row_num * 5) % 140)::text AS address,
street.lat + ((random() - 0.5) * 0.012) AS lat,
street.lon + ((random() - 0.5) * 0.012) AS lon,
66,
ft.rooms,
ft.base_area + (random() * 6 - 3) AS area_m2,
1 + (row_num * 3) % 18 AS floor,
9 + (row_num * 5) % 14 AS total_floors,
1960 + (row_num * 11) % 64 AS year_built,
(ARRAY['panel', 'brick', 'monolith'])[1 + (row_num * 7) % 3] AS house_type,
CURRENT_DATE - ((row_num * 6)::text || ' days')::interval AS deal_date,
-- Сделки на 5-12% ниже листингов (вывод PDF Брусники)
(ft.base_price * 0.92 * (0.85 + random() * 0.20))::bigint AS price_rub,
((ft.base_price * 0.92 * (0.85 + random() * 0.20)) / (ft.base_area + (random() * 6 - 3)))::int AS price_per_m2,
20 + (row_num % 100) AS days_on_market,
NOW() - ((row_num * 6)::text || ' days')::interval AS scraped_at
FROM (
SELECT
c.rooms,
row_number() OVER () AS row_num
FROM deal_counts c
CROSS JOIN LATERAL generate_series(1, c.n) gs
) AS expanded
JOIN flat_types ft ON ft.rooms = expanded.rooms
JOIN streets street ON street.idx = expanded.row_num % 8;
-- ── Sanity ─────────────────────────────────────────────────────────────────
DO $$
DECLARE
n_listings int;
n_deals int;
BEGIN
SELECT COUNT(*) INTO n_listings FROM listings;
SELECT COUNT(*) INTO n_deals FROM deals;
RAISE NOTICE 'Seed: % listings, % deals', n_listings, n_deals;
END $$;
COMMIT;