From effad0a4e25aed20ba86b3808db612919886613b Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Sun, 5 Jul 2026 09:28:52 +0000 Subject: [PATCH] Fix schema: add missing tables and correct forecast_snapshots structure - Add booking_com_hotels and booking_com_rates (competitor rate scraper tables) - Fix forecast_snapshots: was using old pivoted schema (snapshot_date, prophet_value etc.); code expects normalised rows (perception_date, model, forecast_value, actual_value). Add DO block migration to drop+recreate if old schema is detected. Co-Authored-By: Claude Sonnet 4.6 --- backend/schema.sql | 78 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/backend/schema.sql b/backend/schema.sql index 751990b..50b00e6 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -273,6 +273,45 @@ CREATE TABLE IF NOT EXISTS booking_scrape_log ( CREATE INDEX IF NOT EXISTS idx_booking_scrape_log_started ON booking_scrape_log(started_at DESC); +-- ============================================ +-- COMPETITOR RATE SCRAPER (booking.com hotels and rates) +-- ============================================ + +CREATE TABLE IF NOT EXISTS booking_com_hotels ( + id SERIAL PRIMARY KEY, + booking_com_id VARCHAR(50) UNIQUE, + name VARCHAR(255) NOT NULL, + booking_com_url TEXT, + star_rating DECIMAL(2,1), + review_score DECIMAL(3,1), + review_count INTEGER DEFAULT 0, + tier VARCHAR(20) DEFAULT 'market', + display_order INTEGER DEFAULT 999, + notes TEXT, + is_active BOOLEAN DEFAULT TRUE, + first_seen_at TIMESTAMPTZ DEFAULT NOW(), + last_seen_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS booking_com_rates ( + id SERIAL PRIMARY KEY, + hotel_id INTEGER NOT NULL REFERENCES booking_com_hotels(id) ON DELETE CASCADE, + rate_date DATE NOT NULL, + availability_status VARCHAR(30), + rate_gross DECIMAL(10,2), + currency VARCHAR(10) DEFAULT 'GBP', + room_type VARCHAR(100), + breakfast_included BOOLEAN DEFAULT FALSE, + free_cancellation BOOLEAN DEFAULT FALSE, + no_prepayment BOOLEAN DEFAULT FALSE, + rooms_left INTEGER, + scrape_batch_id UUID REFERENCES booking_scrape_log(batch_id), + scraped_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_booking_com_rates_hotel_date ON booking_com_rates(hotel_id, rate_date); +CREATE INDEX IF NOT EXISTS idx_booking_com_rates_date ON booking_com_rates(rate_date); + -- ============================================ -- SYNC LOGGING -- ============================================ @@ -663,22 +702,45 @@ CREATE TABLE IF NOT EXISTS daily_budgets ( CREATE INDEX IF NOT EXISTS idx_daily_budgets_date ON daily_budgets(date, budget_type); --- Forecast snapshots (for tracking how forecasts evolve over time) +-- Forecast snapshots (one row per perception_date+target_date+model+metric) CREATE TABLE IF NOT EXISTS forecast_snapshots ( id SERIAL PRIMARY KEY, - snapshot_date DATE NOT NULL, + perception_date DATE NOT NULL, target_date DATE NOT NULL, + model VARCHAR(50) NOT NULL, metric_code VARCHAR(50) NOT NULL, - days_out INTEGER NOT NULL, - prophet_value DECIMAL(12,2), - xgboost_value DECIMAL(12,2), - pickup_value DECIMAL(12,2), - catboost_value DECIMAL(12,2), + days_out INTEGER, + forecast_value DECIMAL(12,2), + actual_value DECIMAL(12,2), created_at TIMESTAMP DEFAULT NOW(), - UNIQUE(snapshot_date, target_date, metric_code) + UNIQUE(perception_date, target_date, model, metric_code) ); +-- Migration: drop old pivoted-column schema and recreate if snapshot_date column exists +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'forecast_snapshots' AND column_name = 'snapshot_date' + ) THEN + DROP TABLE forecast_snapshots; + CREATE TABLE forecast_snapshots ( + id SERIAL PRIMARY KEY, + perception_date DATE NOT NULL, + target_date DATE NOT NULL, + model VARCHAR(50) NOT NULL, + metric_code VARCHAR(50) NOT NULL, + days_out INTEGER, + forecast_value DECIMAL(12,2), + actual_value DECIMAL(12,2), + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(perception_date, target_date, model, metric_code) + ); + END IF; +END $$; + CREATE INDEX IF NOT EXISTS idx_forecast_snapshots_target ON forecast_snapshots(target_date, metric_code); +CREATE INDEX IF NOT EXISTS idx_forecast_snapshots_perception ON forecast_snapshots(perception_date, model, metric_code); -- ============================================ -- USER ROLES (migration for existing users table)