All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m15s
Ревью справедливо поймало два места, где текст после снятия предиката стал неточным: 1. Якорь в deploy/import-rosreestr.sh обещал «потребителей, фильтрующих по d.rooms, больше нет» — это верно только про предикаты РАВЕНСТВА. app/tasks/asking_to_sold_ratio.py:148,152 по-прежнему КЛЮЧУЕТСЯ этим бакетом (GROUP BY LEAST(GREATEST(rooms,0),4)) и намеренно зеркалит ту же синтетику на листинговой стороне (#2620). Прежняя формулировка сказала бы будущему редактору, что проверять некого, — а в сценарии «поменяли CASE на реальную комнатность» вернулся бы именно #2620. 2. Докстринг GET /sales-vs-listings обещал listing «с такими же rooms». После снятия предиката это верно для пары запрос↔объявление, но не для пары сделка↔объявление: deal_rooms может не совпадать с запрошенным rooms. Кода правка не касается. Полный сьют: 5946 passed, 35 skipped; ruff чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
9.1 KiB
Bash
Executable file
134 lines
9.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Импорт реальных сделок Росреестра из gendesign-БД в tradein deals.
|
||
#
|
||
# Источник: gendesign-postgres-1 / rosreestr_deals (6.8М строк, партиц.).
|
||
# Берём квартиры (realestate_type_code=002001003000) региона REGION_CODE — по умолчанию
|
||
# 66 = вся Свердловская область, все города (не только ЕКБ: city-фильтр снят давно,
|
||
# шапка про «ЕКБ квартиры» была неправдой).
|
||
# rooms выводим из площади (Росреестр не отдаёт кол-во комнат).
|
||
# Координаты — NULL, проставляются отдельно (geocode по street).
|
||
#
|
||
# DOC_TYPE по умолчанию ДКП (вторичка): ДДУ идут по ценам котлована и скёюят median,
|
||
# отделены сознательно (см. PR-A). С #3051 это параметр, а не литерал — для Москвы
|
||
# (REGION_CODE=77) типы разделяются колонкой deals.doc_type (миграция 288), которую
|
||
# скрипт теперь заполняет.
|
||
#
|
||
# #3051 п.3: REGION_CODE параметризован (default 66 — Свердловская обл., byte-for-byte
|
||
# прежнее поведение). Этот bash-путь — НЕ region-generic: для region_code=77 (Москва) он
|
||
# НЕ подставляет canonical_city вместо city источника (Росреестр по Москве отдаёт
|
||
# муниципальный округ/поселение, не сам город) и не пишет raw_payload с
|
||
# okato/quarter_cad_number/district — эту логику несёт только Python-путь
|
||
# (app/services/scheduler.py::import_rosreestr_dkp, боевой планировщик). Если этот
|
||
# скрипт когда-нибудь запустят вручную с REGION_CODE=77 — city/address будут
|
||
# "муниципальный округ Раменки, ..." как есть из источника, НЕ "Москва, ...". Держать
|
||
# паритет фильтров (area/price/doc_type) обязательно, паритет city-override — нет.
|
||
#
|
||
# Запуск на прод-хосте: ./import-rosreestr.sh
|
||
# REGION_CODE=77 DOC_TYPE='ДДУ' ./import-rosreestr.sh # Москва, первичка
|
||
# Повторяемо: дедуп по dedup_hash, новый запуск подтянет свежие кварталы.
|
||
|
||
set -euo pipefail
|
||
|
||
SRC_PG="${SRC_PG:-gendesign-postgres-1}"
|
||
DST_PG="${DST_PG:-tradein-postgres}"
|
||
SRC_DB="${SRC_DB:-gendesign}"
|
||
SRC_USER="${SRC_USER:-gendesign}"
|
||
SINCE="${SINCE:-2024-01-01}"
|
||
# #3051: регион и тип документа — параметры со старыми дефолтами (поведение не меняется).
|
||
REGION_CODE="${REGION_CODE:-66}"
|
||
DOC_TYPE="${DOC_TYPE:-ДКП}"
|
||
# Оба значения интерполируются в SQL текстом (не bind-параметром) — валидация здесь
|
||
# и есть единственная защита от SQL-инъекции. Паттерн — в переменной (не инлайн в
|
||
# [[ =~ ]]): голая кавычка в regex-операнде ломает bash-парсинг команды.
|
||
DOC_TYPE_RE="^[^;']+\$"
|
||
[[ "$REGION_CODE" =~ ^[0-9]+$ ]] || { echo "REGION_CODE должен быть целым числом, получено: '$REGION_CODE'" >&2; exit 1; }
|
||
[[ "$DOC_TYPE" =~ $DOC_TYPE_RE ]] || { echo "DOC_TYPE не должен содержать ' или ;, получено: '$DOC_TYPE'" >&2; exit 1; }
|
||
|
||
echo "[$(date -u +%H:%M:%S)] import-rosreestr: region=$REGION_CODE doc_type=$DOC_TYPE с $SINCE"
|
||
|
||
# 1. Staging-таблица в tradein.
|
||
docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||
-- DROP перед CREATE: страхует от устаревшей формы staging-таблицы, если прошлый
|
||
-- запуск упал между CREATE и финальным DROP под старым кодом (без source_id).
|
||
DROP TABLE IF EXISTS deals_ros_staging;
|
||
CREATE TABLE deals_ros_staging (
|
||
dedup_hash text PRIMARY KEY, source_id text, address text, region_code int, city text,
|
||
rooms int, area_m2 numeric,
|
||
floor int, year_built int, price_rub bigint, price_per_m2 int, deal_date date,
|
||
doc_type text
|
||
);
|
||
"
|
||
|
||
# 2. Трансформируем на gendesign-стороне, пайпим CSV в staging.
|
||
docker exec "$SRC_PG" psql -U "$SRC_USER" -d "$SRC_DB" -v ON_ERROR_STOP=on -c "
|
||
COPY (
|
||
SELECT
|
||
'ros:dkp:' || id::text AS dedup_hash,
|
||
id::text AS source_id,
|
||
trim(city) || ', ' || trim(street) AS address,
|
||
region_code AS region_code,
|
||
trim(city) AS city,
|
||
-- ЯКОРЬ #3256: это НЕ комнатность, а бакет площади. Границы 30/44/62/85
|
||
-- обязаны совпадать с asking_to_sold_ratio.area_bucket() — совпадение
|
||
-- проверяет tests/test_3256_deals_rooms_key.py, который ПАРСИТ этот CASE.
|
||
-- ПОМЕНЯЕШЬ НА РЕАЛЬНУЮ КОМНАТНОСТЬ — вернись в #3256: потребители
|
||
-- (estimator._fetch_dkp_corridor, _fetch_deals, API /street-deals) сейчас
|
||
-- НЕ фильтруют по deals.rooms ИМЕННО потому, что здесь синтетика; с
|
||
-- настоящей комнатностью предикат нужно вернуть, иначе все сайты молча
|
||
-- продолжат ключеваться площадью.
|
||
-- Потребителей, фильтрующих РАВЕНСТВОМ по d.rooms, больше нет: последний —
|
||
-- TVF street_sales_vs_listings — расчищен миграцией 300 (#3451). Ключ там
|
||
-- асимметричный: d.rooms снят, l.rooms ОСТАВЛЕН (у объявлений комнатность
|
||
-- настоящая). Появится новый потребитель — сверься с этим якорем.
|
||
-- НО: app/tasks/asking_to_sold_ratio.py:148,152 по-прежнему КЛЮЧУЕТСЯ этим
|
||
-- бакетом (GROUP BY LEAST(GREATEST(rooms,0),4)) и намеренно зеркалит ту же
|
||
-- синтетику на листинговой стороне (#2620). Поменяешь CASE на реальную
|
||
-- комнатность — вернётся именно #2620 (миграция 23-55% объявлений между
|
||
-- бакетами, ratio>1 в «4+»), а не только вопрос предикатов.
|
||
CASE
|
||
WHEN area < 30 THEN 0 WHEN area < 44 THEN 1
|
||
WHEN area < 62 THEN 2 WHEN area < 85 THEN 3
|
||
ELSE 4 END AS rooms,
|
||
round(area, 2) AS area_m2,
|
||
LEAST(NULLIF(substring(floor from '[0-9]+'), '')::int, 100) AS floor,
|
||
year_build AS year_built,
|
||
round(deal_price)::bigint AS price_rub,
|
||
round(price_per_sqm)::int AS price_per_m2,
|
||
period_start_date AS deal_date,
|
||
doc_type AS doc_type
|
||
FROM rosreestr_deals
|
||
WHERE region_code = $REGION_CODE
|
||
AND city IS NOT NULL AND trim(city) <> ''
|
||
AND realestate_type_code = '002001003000'
|
||
AND area BETWEEN 18 AND 200
|
||
AND deal_price BETWEEN 1000000 AND 100000000
|
||
AND street IS NOT NULL AND trim(street) <> ''
|
||
AND doc_type = '$DOC_TYPE'
|
||
AND period_start_date >= '$SINCE'
|
||
) TO STDOUT WITH CSV
|
||
" | docker exec -i "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||
COPY deals_ros_staging FROM STDIN WITH CSV
|
||
"
|
||
|
||
STAGED=$(docker exec "$DST_PG" psql -U tradein -d tradein -tAc "SELECT count(*) FROM deals_ros_staging")
|
||
echo "[$(date -u +%H:%M:%S)] staged: $STAGED строк"
|
||
|
||
# 3. Удаляем старые синтетические сделки, вставляем реальные (дедуп).
|
||
docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||
DELETE FROM deals WHERE address = 'Екатеринбург, реальная сделка';
|
||
|
||
INSERT INTO deals (
|
||
source, dedup_hash, source_id, address, region_code, city, rooms, area_m2, floor,
|
||
year_built, price_rub, price_per_m2, deal_date, doc_type
|
||
)
|
||
SELECT 'rosreestr', dedup_hash, source_id, address, region_code, city, rooms, area_m2, floor,
|
||
year_built, price_rub, price_per_m2, deal_date, doc_type
|
||
FROM deals_ros_staging
|
||
ON CONFLICT (dedup_hash) DO NOTHING;
|
||
|
||
DROP TABLE deals_ros_staging;
|
||
"
|
||
|
||
TOTAL=$(docker exec "$DST_PG" psql -U tradein -d tradein -tAc "SELECT count(*) FROM deals")
|
||
echo "[$(date -u +%H:%M:%S)] deals всего: $TOTAL"
|
||
echo "Координаты проставит geocode-deals (отдельный шаг)."
|