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