addSql('CREATE EXTENSION IF NOT EXISTS postgis'); // SargassumObservation $this->addSql(' CREATE TABLE sargassum_observation ( id UUID NOT NULL DEFAULT gen_random_uuid(), geometry geometry(MULTIPOLYGON, 4326) NOT NULL, bbox geometry(POLYGON, 4326), detected_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, source VARCHAR(50) NOT NULL, tile_id VARCHAR(100) NOT NULL, cloud_coverage DOUBLE PRECISION NOT NULL, confidence DOUBLE PRECISION NOT NULL, afai_mean DOUBLE PRECISION NOT NULL, afai_std DOUBLE PRECISION NOT NULL, coverage_area DOUBLE PRECISION, processing_version VARCHAR(50), created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id) ) '); $this->addSql('CREATE INDEX CONCURRENTLY idx_observation_detected_at ON sargassum_observation (detected_at)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_observation_source ON sargassum_observation (source)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_observation_geometry ON sargassum_observation USING GIST (geometry)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_observation_bbox ON sargassum_observation USING GIST (bbox)'); // SargassumForecast $this->addSql(' CREATE TABLE sargassum_forecast ( id UUID NOT NULL DEFAULT gen_random_uuid(), source_observation_id UUID NOT NULL, geometry geometry(MULTIPOLYGON, 4326) NOT NULL, valid_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, computed_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, model_version VARCHAR(50) NOT NULL, time_horizon INT NOT NULL, confidence DOUBLE PRECISION NOT NULL, drift_vector_avg geometry(POINT, 4326), created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id) ) '); $this->addSql('ALTER TABLE sargassum_forecast ADD CONSTRAINT fk_forecast_observation FOREIGN KEY (source_observation_id) REFERENCES sargassum_observation (id) ON DELETE CASCADE'); $this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_valid_at ON sargassum_forecast (valid_at)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_horizon ON sargassum_forecast (time_horizon)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_forecast_geometry ON sargassum_forecast USING GIST (geometry)'); // DataIngestionJob $this->addSql(' CREATE TABLE data_ingestion_job ( id UUID NOT NULL DEFAULT gen_random_uuid(), source VARCHAR(50) NOT NULL, tile_id VARCHAR(100) NOT NULL, requested_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, processed_at TIMESTAMP(0) WITHOUT TIME ZONE, status VARCHAR(20) NOT NULL DEFAULT \'pending\', retry_count INT NOT NULL DEFAULT 0, error_message TEXT, PRIMARY KEY(id) ) '); $this->addSql('CREATE INDEX CONCURRENTLY idx_job_status ON data_ingestion_job (status)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_job_requested_at ON data_ingestion_job (requested_at)'); // CoastalPoint $this->addSql(' CREATE TABLE coastal_point ( id UUID NOT NULL DEFAULT gen_random_uuid(), name VARCHAR(150) NOT NULL, geometry geometry(POINT, 4326) NOT NULL, type VARCHAR(30) NOT NULL, region VARCHAR(100) NOT NULL, PRIMARY KEY(id) ) '); $this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_type ON coastal_point (type)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_region ON coastal_point (region)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_coastal_geometry ON coastal_point USING GIST (geometry)'); // ImpactScore $this->addSql(' CREATE TABLE impact_score ( id UUID NOT NULL DEFAULT gen_random_uuid(), coastal_point_id UUID NOT NULL, timestamp TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, score INT NOT NULL, level VARCHAR(20) NOT NULL, distance_to_nearest_sargassum DOUBLE PRECISION, density_estimate DOUBLE PRECISION, trend VARCHAR(20), PRIMARY KEY(id) ) '); $this->addSql('ALTER TABLE impact_score ADD CONSTRAINT fk_score_coastal_point FOREIGN KEY (coastal_point_id) REFERENCES coastal_point (id) ON DELETE CASCADE'); $this->addSql('CREATE INDEX CONCURRENTLY idx_score_timestamp ON impact_score (timestamp)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_score_level ON impact_score (level)'); // UserFeedback $this->addSql(' CREATE TABLE user_feedback ( id UUID NOT NULL DEFAULT gen_random_uuid(), coastal_point_id UUID, location geometry(POINT, 4326) NOT NULL, timestamp TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, has_seaweed BOOLEAN NOT NULL, density VARCHAR(20), source VARCHAR(100), PRIMARY KEY(id) ) '); $this->addSql('ALTER TABLE user_feedback ADD CONSTRAINT fk_feedback_coastal_point FOREIGN KEY (coastal_point_id) REFERENCES coastal_point (id) ON DELETE SET NULL'); $this->addSql('CREATE INDEX CONCURRENTLY idx_feedback_timestamp ON user_feedback (timestamp)'); $this->addSql('CREATE INDEX CONCURRENTLY idx_feedback_location ON user_feedback USING GIST (location)'); } public function down(Schema $schema): void { $this->addSql('ALTER TABLE sargassum_forecast DROP CONSTRAINT fk_forecast_observation'); $this->addSql('ALTER TABLE impact_score DROP CONSTRAINT fk_score_coastal_point'); $this->addSql('ALTER TABLE user_feedback DROP CONSTRAINT fk_feedback_coastal_point'); $this->addSql('DROP TABLE IF EXISTS user_feedback'); $this->addSql('DROP TABLE IF EXISTS impact_score'); $this->addSql('DROP TABLE IF EXISTS sargassum_forecast'); $this->addSql('DROP TABLE IF EXISTS sargassum_observation'); $this->addSql('DROP TABLE IF EXISTS coastal_point'); $this->addSql('DROP TABLE IF EXISTS data_ingestion_job'); } }