#!/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 (отдельный шаг)."