gendesign/tradein-mvp/backend/data/sql/017_house_placement_history.sql
lekss361 28ddf6ef17 feat(tradein): SQL migrations 009-018 — houses + listings extension + history + IMV cache
Closes Stage 1 of AvitoScraper_v2 plan.

- 009_houses.sql: CREATE TABLE houses (multi-source: avito/cian, geom trigger, 24 cols)
- 010_houses_alter.sql: + 15 cols (material_walls, parking_type, rating_distribution, raw_characteristics, ...)
- 011_listings_alter.sql: + 24 cols (house linking, kitchen_area_m2, owners_count, owners_at_least,
  metro_stations, detail_enriched_at, domoteka/Rosreestr badges, 8 partial indexes)
- 012_sellers.sql: CREATE TABLE sellers (Avito seller cache, no logos)
- 013_listings_alter_seller.sql: listings.seller_id_fk FK
- 014_house_reviews.sql: CREATE TABLE house_reviews (textSections main/pros/cons, no images)
- 015_scrape_runs.sql: CREATE TABLE scrape_runs (status CHECK enum, heartbeat, zombie partial idx)
- 016_listings_snapshots.sql: CREATE TABLE listings_snapshots (append-only, PK listing_id+date)
- 017_house_placement_history.sql: CREATE TABLE house_placement_history (removed_date from IMV only)
- 018_avito_imv_evaluations.sql: CREATE TABLE + cache_key col + imv_cache_key_idx (TTL 24h lookup)

All migrations: BEGIN/COMMIT, IF NOT EXISTS idempotent, PostGIS geom triggers reuse listings_set_geom().
Smoke tested locally: 7 new tables, 34 indexes, 14 listings columns verified.

Refs: decisions/Schema_Avito_Houses_Listings_History.md (sec 4.2, 4.3, 4.4, 6, 7, 13, 25, 29)
Refs: decisions/AvitoScraper_v2_Implementation_Plan.md Stage 1
2026-05-23 14:29:04 +03:00

70 lines
3.9 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 017_house_placement_history.sql
-- CREATE TABLE house_placement_history — архив размещений из IMV API
-- (исторические объявления в конкретном доме, уже снятые с продажи)
--
-- Dependencies: 009_houses.sql (houses table — FK house_id, nullable)
-- Apply after: 016_listings_snapshots.sql
-- Apply before: 018_avito_imv_evaluations.sql
--
-- Source: decisions/Schema_Avito_Houses_Listings_History.md sec 6 (UPDATE 2026-05-23)
-- Key notes:
-- - photo_url НЕ сохраняем (требование 2026-05-23 "без картинок")
-- - removed_date заполняется ТОЛЬКО из IMV API (в widget housePlacementHistory его НЕТ)
-- - source = 'avito_imv' для данных из IMV API, 'avito' для данных из Houses Catalog widget
--
-- Идемпотентно: безопасно запускать повторно.
BEGIN;
CREATE TABLE IF NOT EXISTS house_placement_history (
id bigserial PRIMARY KEY,
source text NOT NULL, -- 'avito_imv' / 'avito'
house_id bigint REFERENCES houses(id), -- NULL если дом ещё не в houses table
-- Старый item ID (объявление уже снято → 404 на Avito)
ext_item_id text NOT NULL, -- e.g. '7489769108'
-- Описание (raw text + parsed fields)
title text, -- "1-к. квартира, 27,1 м², 2/5 эт."
rooms int,
area_m2 numeric(8,2),
floor int,
total_floors int,
-- Эволюция цены
start_price bigint, -- начальная ask price
start_price_date date, -- из unix timestamp startPriceDate
last_price bigint, -- финальная ask price (при снятии)
last_price_date date, -- из unix timestamp lastPriceDate
-- Дата снятия (ТОЛЬКО из IMV API — в widget housePlacementHistory отсутствует)
removed_date date, -- лучший proxy для deal_date
-- Точный days_on_market (от Avito, точнее нашего вычисления)
exposure_days int,
-- photo_url НЕ сохраняем (raw_payload содержит itemImage URL для retrofit)
raw_payload jsonb, -- полный объект из API
scraped_at timestamptz NOT NULL DEFAULT NOW(),
UNIQUE (source, ext_item_id)
);
CREATE INDEX IF NOT EXISTS hph_house_idx ON house_placement_history (house_id, last_price_date DESC)
WHERE house_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS hph_source_item_idx ON house_placement_history (source, ext_item_id);
-- Для "comparable sales в заданный период"
CREATE INDEX IF NOT EXISTS hph_removed_idx ON house_placement_history (removed_date DESC)
WHERE removed_date IS NOT NULL;
COMMENT ON TABLE house_placement_history IS
'Архив снятых объявлений (proxy для сделок) из IMV API + Houses Catalog. '
'removed_date заполняется только из IMV API. photo_url не сохраняется.';
COMMENT ON COLUMN house_placement_history.removed_date IS
'Дата снятия объявления с продажи. Лучший proxy для deal_date. '
'ТОЛЬКО из IMV API (из widget housePlacementHistory это поле отсутствует).';
COMMENT ON COLUMN house_placement_history.house_id IS
'FK на houses.id. NULL если при scrape дом ещё не был в houses table. '
'Backfill через UPDATE ... WHERE ext_item_id IN (...) при первом scrape дома.';
COMMIT;