-- Esquema Neon/Postgres para Idealista HF + Telegram v2 -- Incluye medición de desplazamiento como desaparición rápida del anuncio -- después de haber sido observado en distritos efectivamente consultados. CREATE TABLE IF NOT EXISTS idealista_runs ( run_id BIGSERIAL PRIMARY KEY, started_at TIMESTAMPTZ NOT NULL DEFAULT now(), finished_at TIMESTAMPTZ, source_filename TEXT, entries_count INTEGER DEFAULT 0, scraped_count INTEGER DEFAULT 0, inserted_count INTEGER DEFAULT 0, updated_count INTEGER DEFAULT 0, unchanged_count INTEGER DEFAULT 0, reactivated_count INTEGER DEFAULT 0, missing_updated_count INTEGER DEFAULT 0, probably_rented_count INTEGER DEFAULT 0, snapshot_count INTEGER DEFAULT 0, status TEXT NOT NULL DEFAULT 'running', error_message TEXT, districts_queried JSONB DEFAULT '[]'::jsonb, metadata JSONB DEFAULT '{}'::jsonb ); CREATE TABLE IF NOT EXISTS idealista_listings ( listing_key TEXT PRIMARY KEY, listing_id TEXT, district_slug TEXT, source_input TEXT, page_hint INTEGER, position_in_page INTEGER, global_position INTEGER, url TEXT, title TEXT, address_text TEXT, location_full TEXT, price_eur NUMERIC, price_text TEXT, price_period TEXT, tipologia INTEGER, tipologia_text TEXT, area_m2 NUMERIC, area_text TEXT, floor_info TEXT, listed_when TEXT, estimated_published_at DATE, tag TEXT, agency_name TEXT, agency_url TEXT, image_main_url TEXT, image_main_webp TEXT, image_count INTEGER, online_booking BOOLEAN, has_map_button BOOLEAN, first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_run_id BIGINT REFERENCES idealista_runs(run_id) ON DELETE SET NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, status TEXT NOT NULL DEFAULT 'active', missing_runs INTEGER NOT NULL DEFAULT 0, visible_runs INTEGER NOT NULL DEFAULT 1, days_to_displacement INTEGER, displacement_detection_lag_days INTEGER, rental_velocity_category TEXT NOT NULL DEFAULT 'unknown', rental_velocity_basis TEXT NOT NULL DEFAULT 'visible_runs_3_day_cadence', rental_velocity_classified_at TIMESTAMPTZ, deactivated_at TIMESTAMPTZ, reactivated_at TIMESTAMPTZ, content_hash TEXT, payload JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_idealista_listings_listing_id ON idealista_listings(listing_id); CREATE INDEX IF NOT EXISTS idx_idealista_listings_district ON idealista_listings(district_slug); CREATE INDEX IF NOT EXISTS idx_idealista_listings_price ON idealista_listings(price_eur); CREATE INDEX IF NOT EXISTS idx_idealista_listings_last_seen ON idealista_listings(last_seen_at); CREATE INDEX IF NOT EXISTS idx_idealista_listings_status ON idealista_listings(status); CREATE INDEX IF NOT EXISTS idx_idealista_listings_missing ON idealista_listings(missing_runs); CREATE INDEX IF NOT EXISTS idx_idealista_listings_velocity ON idealista_listings(rental_velocity_category); CREATE INDEX IF NOT EXISTS idx_idealista_listings_visible_runs ON idealista_listings(visible_runs); CREATE TABLE IF NOT EXISTS idealista_listing_snapshots ( snapshot_id BIGSERIAL PRIMARY KEY, run_id BIGINT REFERENCES idealista_runs(run_id) ON DELETE CASCADE, listing_key TEXT NOT NULL, listing_id TEXT, district_slug TEXT, price_eur NUMERIC, area_m2 NUMERIC, title TEXT, url TEXT, page_hint INTEGER, position_in_page INTEGER, global_position INTEGER, listed_when TEXT, estimated_published_at DATE, content_hash TEXT, observed_at TIMESTAMPTZ NOT NULL DEFAULT now(), payload JSONB DEFAULT '{}'::jsonb, UNIQUE(run_id, listing_key) ); CREATE INDEX IF NOT EXISTS idx_idealista_snapshots_listing_key ON idealista_listing_snapshots(listing_key); CREATE INDEX IF NOT EXISTS idx_idealista_snapshots_run ON idealista_listing_snapshots(run_id); CREATE INDEX IF NOT EXISTS idx_idealista_snapshots_observed ON idealista_listing_snapshots(observed_at); -- Consultas útiles: -- Mayor desplazamiento: anuncios que desaparecieron más rápido. -- Para corrida cada tercer día se clasifica principalmente por visible_runs: -- 1 = very_fast, 2-3 = fast, 4-6 = normal, 7-10 = slow, 11+ = very_slow. -- SELECT listing_key, district_slug, status, rental_velocity_category, visible_runs, -- missing_runs, first_seen_at, last_seen_at, deactivated_at, -- days_to_displacement, displacement_detection_lag_days, price_eur, area_m2, title, url -- FROM idealista_listings -- WHERE status = 'probably_rented' -- ORDER BY visible_runs ASC, days_to_displacement ASC NULLS LAST, deactivated_at DESC NULLS LAST; -- Mayor antigüedad en base: -- SELECT * FROM idealista_listings ORDER BY first_seen_at ASC; -- Menor antigüedad en base: -- SELECT * FROM idealista_listings ORDER BY first_seen_at DESC;