fix(tradein): убрать TRUNCATE из seed — деплой больше не wipe-ит данные
КРИТИЧНЫЙ БАГ: 003_seed_listings.sql содержал TRUNCATE listings,deals, а deploy-tradein.yml гоняет весь data/sql/*.sql на каждом деплое → каждый деплой стирал реальные данные парсеров и подсовывал 120 синтетики. Удалён 003_seed_listings.sql. Вместо него 003_seed_deals.sql — идемпотентный (INSERT ... WHERE NOT EXISTS), сидит ТОЛЬКО сделки и ТОЛЬКО когда таблица deals пуста. listings больше не трогается seed-ом.
This commit is contained in:
parent
1d4b720991
commit
fb49c56b81
2 changed files with 54 additions and 147 deletions
54
tradein-mvp/backend/data/sql/003_seed_deals.sql
Normal file
54
tradein-mvp/backend/data/sql/003_seed_deals.sql
Normal 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);
|
||||
|
|
@ -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;
|
||||
Loading…
Add table
Reference in a new issue