Files
radarsargasses/backend/migrations/Version20260401000000.php
Gwadaking e5e729c487 Phase 0 : entités, migrations, API Platform, PostGIS
- API Platform + Doctrine ORM + jsor/doctrine-postgis installés
- 6 entités Symfony (SargassumObservation, SargassumForecast,
  DataIngestionJob, CoastalPoint, ImpactScore, UserFeedback)
- Migration initiale manuelle avec CREATE EXTENSION postgis,
  toutes les tables, index GIST et BTREE
- doctrine.yaml configuré pour PostgreSQL 16 + types PostGIS
- Roadmap Phase 0 complète

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 02:37:27 -04:00

148 lines
6.9 KiB
PHP

<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260401000000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Schéma initial — toutes les entités + extension PostGIS + index spatiaux';
}
public function up(Schema $schema): void
{
$this->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');
}
}