File size: 5,034 Bytes
8b7419b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
-- 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;