Compare commits
No commits in common. "95f0b0aa6476cd693893ec103f3e8f2f3dd3cdfc" and "338ccbb78fcde881e1bc476aac5d88f77221e723" have entirely different histories.
95f0b0aa64
...
338ccbb78f
4 changed files with 14 additions and 103 deletions
|
|
@ -180,16 +180,9 @@ def import_rosreestr_dkp(
|
||||||
SERVER gendesign_remote настроен в 060_postgres_fdw_extension.sql.
|
SERVER gendesign_remote настроен в 060_postgres_fdw_extension.sql.
|
||||||
USER MAPPING создаётся при startup в core/fdw.py (tradein_fdw_reader).
|
USER MAPPING создаётся при startup в core/fdw.py (tradein_fdw_reader).
|
||||||
|
|
||||||
Область покрытия: вся Свердловская область (region_code=66), не только Екатеринбург —
|
|
||||||
прежний ILIKE-фильтр по подстроке города (ограничивавший импорт одним Екатеринбургом)
|
|
||||||
снят (Mera trade-in расширяется на весь регион, unlocks +47183 сделок вне ЕКБ уже
|
|
||||||
сидящих в source foreign table). address и deals.city строятся из реального city
|
|
||||||
источника (не хардкод "Екатеринбург"), deals.region_code заполняется из строки
|
|
||||||
источника (= 66 при текущем фильтре).
|
|
||||||
|
|
||||||
Фильтры (совпадают с import-rosreestr.sh + Fix_Rosreestr_Dkp_Filter_May24):
|
Фильтры (совпадают с import-rosreestr.sh + Fix_Rosreestr_Dkp_Filter_May24):
|
||||||
- region_code = 66 (вся Свердловская область, все города)
|
- region_code = 66 (Свердловская область)
|
||||||
- city IS NOT NULL AND trim(city) != '' (непустой город → корректный address)
|
- city ILIKE '%катеринбург%'
|
||||||
- realestate_type_code = '002001003000' (квартира)
|
- realestate_type_code = '002001003000' (квартира)
|
||||||
- area BETWEEN 18 AND 200
|
- area BETWEEN 18 AND 200
|
||||||
- deal_price BETWEEN 1000000 AND 100000000
|
- deal_price BETWEEN 1000000 AND 100000000
|
||||||
|
|
@ -280,9 +273,7 @@ def import_rosreestr_dkp(
|
||||||
id,
|
id,
|
||||||
id AS source_id_src,
|
id AS source_id_src,
|
||||||
'ros:dkp:' || CAST(id AS text) AS dedup_hash,
|
'ros:dkp:' || CAST(id AS text) AS dedup_hash,
|
||||||
trim(city) || ', ' || trim(street) AS address,
|
'Екатеринбург, ' || trim(street) AS address,
|
||||||
region_code,
|
|
||||||
trim(city) AS city,
|
|
||||||
CASE
|
CASE
|
||||||
WHEN area < 30 THEN 0
|
WHEN area < 30 THEN 0
|
||||||
WHEN area < 44 THEN 1
|
WHEN area < 44 THEN 1
|
||||||
|
|
@ -305,7 +296,7 @@ def import_rosreestr_dkp(
|
||||||
period_start_date AS deal_date
|
period_start_date AS deal_date
|
||||||
FROM gendesign_rosreestr_deals
|
FROM gendesign_rosreestr_deals
|
||||||
WHERE region_code = 66
|
WHERE region_code = 66
|
||||||
AND city IS NOT NULL AND trim(city) <> ''
|
AND city ILIKE '%катеринбург%'
|
||||||
AND realestate_type_code = '002001003000'
|
AND realestate_type_code = '002001003000'
|
||||||
AND area BETWEEN 18 AND 200
|
AND area BETWEEN 18 AND 200
|
||||||
AND deal_price BETWEEN 1000000 AND 100000000
|
AND deal_price BETWEEN 1000000 AND 100000000
|
||||||
|
|
@ -340,17 +331,14 @@ def import_rosreestr_dkp(
|
||||||
inserted = db.execute(
|
inserted = db.execute(
|
||||||
text("""
|
text("""
|
||||||
INSERT INTO deals (
|
INSERT INTO deals (
|
||||||
source, dedup_hash, source_id, address, region_code, city,
|
source, dedup_hash, source_id, address, rooms, area_m2,
|
||||||
rooms, area_m2, floor, year_built, price_rub, price_per_m2,
|
floor, year_built, price_rub, price_per_m2, deal_date
|
||||||
deal_date
|
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
'rosreestr',
|
'rosreestr',
|
||||||
CAST(:dedup_hash AS text),
|
CAST(:dedup_hash AS text),
|
||||||
CAST(:source_id AS text),
|
CAST(:source_id AS text),
|
||||||
CAST(:address AS text),
|
CAST(:address AS text),
|
||||||
CAST(:region_code AS int),
|
|
||||||
CAST(:city AS text),
|
|
||||||
CAST(:rooms AS int),
|
CAST(:rooms AS int),
|
||||||
CAST(:area_m2 AS numeric),
|
CAST(:area_m2 AS numeric),
|
||||||
CAST(:floor_num AS int),
|
CAST(:floor_num AS int),
|
||||||
|
|
@ -366,8 +354,6 @@ def import_rosreestr_dkp(
|
||||||
"dedup_hash": row["dedup_hash"],
|
"dedup_hash": row["dedup_hash"],
|
||||||
"source_id": str(row["source_id_src"]),
|
"source_id": str(row["source_id_src"]),
|
||||||
"address": row["address"],
|
"address": row["address"],
|
||||||
"region_code": row["region_code"],
|
|
||||||
"city": row["city"],
|
|
||||||
"rooms": row["rooms"],
|
"rooms": row["rooms"],
|
||||||
"area_m2": row["area_m2"],
|
"area_m2": row["area_m2"],
|
||||||
"floor_num": row["floor_num"],
|
"floor_num": row["floor_num"],
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
-- 177_deals_city_region.sql
|
|
||||||
-- deals.city + region_code backfill — расширение Mera trade-in estimator с
|
|
||||||
-- Екатеринбурга на ВСЮ Свердловскую область (region_code=66, все города).
|
|
||||||
--
|
|
||||||
-- WHY:
|
|
||||||
-- Ночной import_rosreestr_dkp (scheduler.py, source='rosreestr_dkp_import')
|
|
||||||
-- до сих пор фильтровал `city ILIKE '%катеринбург%'` и хардкодил
|
|
||||||
-- 'Екатеринбург, ' в address, а deals.region_code никогда не заполнялся
|
|
||||||
-- (NULL на всех 49791 текущих строках). Foreign table
|
|
||||||
-- gendesign_rosreestr_deals уже отдаёт чистые city + region_code из
|
|
||||||
-- source-таблицы (migration 072) — снятие фильтра открывает +47183
|
|
||||||
-- не-ЕКБ сделок, уже сидящих в источнике.
|
|
||||||
--
|
|
||||||
-- WHAT:
|
|
||||||
-- - deals.region_code уже существует (002_core_tables.sql), но deals.city
|
|
||||||
-- отсутствовал (было только текстовое поле address с хардкод-префиксом
|
|
||||||
-- 'Екатеринбург, '). Добавляем city text — импортёр (scheduler.py,
|
|
||||||
-- тот же PR) теперь заполняет его из реального source.city.
|
|
||||||
-- - Индекс (city, deal_date) — под будущие per-city аналитические запросы
|
|
||||||
-- (аналог deals_source_idx для (source, deal_date)).
|
|
||||||
-- - Backfill существующих 49791 строк: все текущие deals с source='rosreestr'
|
|
||||||
-- импортированы ДО этой миграции старым EKB-only кодом, т.е. фактически
|
|
||||||
-- все они — Екатеринбург (то же допущение, что зашито в address
|
|
||||||
-- 'Екатеринбург, ' + street). Простановка region_code=66, city='Екатеринбург'
|
|
||||||
-- задним числом делает старые строки self-describing наравне с новыми.
|
|
||||||
--
|
|
||||||
-- SCOPE: только schema (city column + index) + backfill старых rosreestr-строк.
|
|
||||||
-- Новые импорты (после деплоя scheduler.py) заполняют оба поля сами.
|
|
||||||
--
|
|
||||||
-- IDEMPOTENCY / SAFETY:
|
|
||||||
-- - ADD COLUMN IF NOT EXISTS / CREATE INDEX IF NOT EXISTS — безопасный re-run.
|
|
||||||
-- - Backfill UPDATE ограничен `region_code IS NULL OR city IS NULL` —
|
|
||||||
-- повторный прогон no-op (после первого раза условие уже false).
|
|
||||||
--
|
|
||||||
-- Dependencies: 002_core_tables.sql (deals, deals.region_code)
|
|
||||||
|
|
||||||
BEGIN;
|
|
||||||
|
|
||||||
ALTER TABLE deals ADD COLUMN IF NOT EXISTS city text;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS deals_city_deal_date_idx ON deals (city, deal_date);
|
|
||||||
|
|
||||||
-- Backfill: все существующие rosreestr-сделки импортированы старым EKB-only
|
|
||||||
-- кодом (до этой миграции) => гарантированно Екатеринбург, region_code=66.
|
|
||||||
UPDATE deals
|
|
||||||
SET region_code = 66,
|
|
||||||
city = 'Екатеринбург'
|
|
||||||
WHERE source = 'rosreestr'
|
|
||||||
AND (region_code IS NULL OR city IS NULL);
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
Чисто-юнит: ассертим SQL-текст, эмитируемый import_rosreestr_dkp (через inspect.getsource),
|
Чисто-юнит: ассертим SQL-текст, эмитируемый import_rosreestr_dkp (через inspect.getsource),
|
||||||
плюс содержимое backfill-миграции 077 — без живого FDW / DB-фикстуры.
|
плюс содержимое backfill-миграции 077 — без живого FDW / DB-фикстуры.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -90,18 +89,12 @@ def test_migration_077_converts_md5_to_plain_key() -> None:
|
||||||
assert "BEGIN;" in sql and "COMMIT;" in sql
|
assert "BEGIN;" in sql and "COMMIT;" in sql
|
||||||
|
|
||||||
|
|
||||||
def test_migration_077_shared_filter_matches_live_import() -> None:
|
def test_migration_077_filter_matches_live_import() -> None:
|
||||||
"""Дедуп-релевантные фильтры src CTE миграции 077 совпадают с живым импортом.
|
"""Фильтр src CTE байт-в-байт совпадает с живым импортом → все строки сконвертируются."""
|
||||||
|
|
||||||
Исключение — city ILIKE (см. test_live_import_dropped_ekb_city_filter ниже):
|
|
||||||
077 backfill'ил ЕКБ-строки под EKB-only scope того времени; живой импорт расширен
|
|
||||||
на всю Свердловскую область (region_code=66, все города), поэтому city-фильтр из
|
|
||||||
живого импорта СНЯТ намеренно. Остальные клозы обязаны совпадать байт-в-байт,
|
|
||||||
иначе 077 конвертировал бы не тот набор строк.
|
|
||||||
"""
|
|
||||||
sql = _MIGRATION_077.read_text("utf-8")
|
sql = _MIGRATION_077.read_text("utf-8")
|
||||||
for clause in (
|
for clause in (
|
||||||
"region_code = 66",
|
"region_code = 66",
|
||||||
|
"city ILIKE '%катеринбург%'",
|
||||||
"realestate_type_code = '002001003000'",
|
"realestate_type_code = '002001003000'",
|
||||||
"area BETWEEN 18 AND 200",
|
"area BETWEEN 18 AND 200",
|
||||||
"deal_price BETWEEN 1000000 AND 100000000",
|
"deal_price BETWEEN 1000000 AND 100000000",
|
||||||
|
|
@ -110,17 +103,3 @@ def test_migration_077_shared_filter_matches_live_import() -> None:
|
||||||
):
|
):
|
||||||
assert clause in sql, f"missing filter clause in migration: {clause!r}"
|
assert clause in sql, f"missing filter clause in migration: {clause!r}"
|
||||||
assert clause in _IMPORT_SRC, f"missing filter clause in import: {clause!r}"
|
assert clause in _IMPORT_SRC, f"missing filter clause in import: {clause!r}"
|
||||||
|
|
||||||
|
|
||||||
def test_live_import_dropped_ekb_city_filter() -> None:
|
|
||||||
"""Живой импорт БОЛЬШЕ не фильтрует по городу — Mera расширена на всю обл. 66.
|
|
||||||
|
|
||||||
Историческая миграция 077 (уже применена на прод, трогать нельзя) сохраняет свой
|
|
||||||
EKB-only city-фильтр; живой импорт — нет. Guard против случайного возврата
|
|
||||||
ЕКБ-скоупа, который снова отрезал бы +47k не-ЕКБ сделок.
|
|
||||||
"""
|
|
||||||
sql = _MIGRATION_077.read_text("utf-8")
|
|
||||||
# 077 — исторический EKB-scope, city-фильтр там остаётся неизменным.
|
|
||||||
assert "city ILIKE '%катеринбург%'" in sql
|
|
||||||
# Живой импорт city-фильтр снял намеренно (region_code=66 = вся область).
|
|
||||||
assert "ILIKE '%катеринбург%'" not in _IMPORT_SRC
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||||||
-- запуск упал между CREATE и финальным DROP под старым кодом (без source_id).
|
-- запуск упал между CREATE и финальным DROP под старым кодом (без source_id).
|
||||||
DROP TABLE IF EXISTS deals_ros_staging;
|
DROP TABLE IF EXISTS deals_ros_staging;
|
||||||
CREATE TABLE deals_ros_staging (
|
CREATE TABLE deals_ros_staging (
|
||||||
dedup_hash text PRIMARY KEY, source_id text, address text, region_code int, city text,
|
dedup_hash text PRIMARY KEY, source_id text, address text, rooms int, area_m2 numeric,
|
||||||
rooms int, area_m2 numeric,
|
|
||||||
floor int, year_built int, price_rub bigint, price_per_m2 int, deal_date date
|
floor int, year_built int, price_rub bigint, price_per_m2 int, deal_date date
|
||||||
);
|
);
|
||||||
"
|
"
|
||||||
|
|
@ -39,9 +38,7 @@ docker exec "$SRC_PG" psql -U "$SRC_USER" -d "$SRC_DB" -v ON_ERROR_STOP=on -c "
|
||||||
SELECT
|
SELECT
|
||||||
'ros:dkp:' || id::text AS dedup_hash,
|
'ros:dkp:' || id::text AS dedup_hash,
|
||||||
id::text AS source_id,
|
id::text AS source_id,
|
||||||
trim(city) || ', ' || trim(street) AS address,
|
'Екатеринбург, ' || trim(street) AS address,
|
||||||
region_code AS region_code,
|
|
||||||
trim(city) AS city,
|
|
||||||
CASE
|
CASE
|
||||||
WHEN area < 30 THEN 0 WHEN area < 44 THEN 1
|
WHEN area < 30 THEN 0 WHEN area < 44 THEN 1
|
||||||
WHEN area < 62 THEN 2 WHEN area < 85 THEN 3
|
WHEN area < 62 THEN 2 WHEN area < 85 THEN 3
|
||||||
|
|
@ -54,7 +51,7 @@ docker exec "$SRC_PG" psql -U "$SRC_USER" -d "$SRC_DB" -v ON_ERROR_STOP=on -c "
|
||||||
period_start_date AS deal_date
|
period_start_date AS deal_date
|
||||||
FROM rosreestr_deals
|
FROM rosreestr_deals
|
||||||
WHERE region_code = 66
|
WHERE region_code = 66
|
||||||
AND city IS NOT NULL AND trim(city) <> ''
|
AND city ILIKE '%катеринбург%'
|
||||||
AND realestate_type_code = '002001003000'
|
AND realestate_type_code = '002001003000'
|
||||||
AND area BETWEEN 18 AND 200
|
AND area BETWEEN 18 AND 200
|
||||||
AND deal_price BETWEEN 1000000 AND 100000000
|
AND deal_price BETWEEN 1000000 AND 100000000
|
||||||
|
|
@ -74,10 +71,10 @@ docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||||||
DELETE FROM deals WHERE address = 'Екатеринбург, реальная сделка';
|
DELETE FROM deals WHERE address = 'Екатеринбург, реальная сделка';
|
||||||
|
|
||||||
INSERT INTO deals (
|
INSERT INTO deals (
|
||||||
source, dedup_hash, source_id, address, region_code, city, rooms, area_m2, floor,
|
source, dedup_hash, source_id, address, rooms, area_m2, floor,
|
||||||
year_built, price_rub, price_per_m2, deal_date
|
year_built, price_rub, price_per_m2, deal_date
|
||||||
)
|
)
|
||||||
SELECT 'rosreestr', dedup_hash, source_id, address, region_code, city, rooms, area_m2, floor,
|
SELECT 'rosreestr', dedup_hash, source_id, address, rooms, area_m2, floor,
|
||||||
year_built, price_rub, price_per_m2, deal_date
|
year_built, price_rub, price_per_m2, deal_date
|
||||||
FROM deals_ros_staging
|
FROM deals_ros_staging
|
||||||
ON CONFLICT (dedup_hash) DO NOTHING;
|
ON CONFLICT (dedup_hash) DO NOTHING;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue