diff --git a/database/migrations/2026_04_03_172840_create_stations_table.php b/database/migrations/2026_04_03_172840_create_stations_table.php new file mode 100644 index 0000000..fac7194 --- /dev/null +++ b/database/migrations/2026_04_03_172840_create_stations_table.php @@ -0,0 +1,48 @@ +string('node_id', 64)->primary(); + $table->string('trading_name', 128); + $table->string('brand_name', 64)->nullable(); + $table->boolean('is_same_trading_and_brand')->default(false); + $table->boolean('is_supermarket')->default(false)->comment('Set by StationTaggingService'); + $table->boolean('is_motorway_service_station')->default(false); + $table->boolean('is_supermarket_service_station')->default(false); + $table->boolean('temporary_closure')->default(false); + $table->boolean('permanent_closure')->default(false); + $table->date('permanent_closure_date')->nullable(); + $table->string('public_phone_number', 20)->nullable(); + $table->string('address_line_1', 255)->nullable(); + $table->string('address_line_2', 255)->nullable(); + $table->string('city', 100)->nullable(); + $table->string('county', 100)->nullable(); + $table->string('country', 64)->nullable(); + $table->string('postcode', 10); + $table->decimal('lat', 10, 7); + $table->decimal('lng', 10, 7); + $table->json('amenities')->nullable(); + $table->json('opening_times')->nullable(); + $table->json('fuel_types')->nullable(); + $table->dateTime('last_seen_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('stations'); + } +}; diff --git a/database/migrations/2026_04_03_172841_create_station_prices_current_table.php b/database/migrations/2026_04_03_172841_create_station_prices_current_table.php new file mode 100644 index 0000000..b29666b --- /dev/null +++ b/database/migrations/2026_04_03_172841_create_station_prices_current_table.php @@ -0,0 +1,34 @@ +string('station_id', 64); + $table->string('fuel_type', 20); + $table->unsignedSmallInteger('price_pence')->comment('Price in pence × 100, e.g. 15990 = 159.90p'); + $table->dateTime('price_effective_at')->comment('price_change_effective_timestamp from API'); + $table->dateTime('price_reported_at')->comment('price_last_updated from API'); + $table->dateTime('recorded_at')->comment('When this row was last upserted by us'); + + $table->primary(['station_id', 'fuel_type']); + $table->foreign('station_id')->references('node_id')->on('stations')->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('station_prices_current'); + } +}; diff --git a/database/migrations/2026_04_03_172841_create_station_prices_table.php b/database/migrations/2026_04_03_172841_create_station_prices_table.php new file mode 100644 index 0000000..164614a --- /dev/null +++ b/database/migrations/2026_04_03_172841_create_station_prices_table.php @@ -0,0 +1,68 @@ +bigIncrements('id'); + $table->string('station_id', 64); + $table->string('fuel_type', 20); + $table->unsignedSmallInteger('price_pence')->comment('Price in pence × 100'); + $table->dateTime('price_effective_at'); + $table->dateTime('price_reported_at'); + $table->dateTime('recorded_at'); + + // Composite PK required for MySQL range partitioning + $table->primary(['id', 'price_effective_at']); + $table->index(['station_id', 'fuel_type', 'price_effective_at']); + $table->index('price_effective_at'); + }); + + // Monthly partitions 2026–2027 + MAXVALUE catch-all + DB::statement("ALTER TABLE station_prices + PARTITION BY RANGE (YEAR(price_effective_at) * 100 + MONTH(price_effective_at)) ( + PARTITION p202601 VALUES LESS THAN (202602), + PARTITION p202602 VALUES LESS THAN (202603), + PARTITION p202603 VALUES LESS THAN (202604), + PARTITION p202604 VALUES LESS THAN (202605), + PARTITION p202605 VALUES LESS THAN (202606), + PARTITION p202606 VALUES LESS THAN (202607), + PARTITION p202607 VALUES LESS THAN (202608), + PARTITION p202608 VALUES LESS THAN (202609), + PARTITION p202609 VALUES LESS THAN (202610), + PARTITION p202610 VALUES LESS THAN (202611), + PARTITION p202611 VALUES LESS THAN (202612), + PARTITION p202612 VALUES LESS THAN (202701), + PARTITION p202701 VALUES LESS THAN (202702), + PARTITION p202702 VALUES LESS THAN (202703), + PARTITION p202703 VALUES LESS THAN (202704), + PARTITION p202704 VALUES LESS THAN (202705), + PARTITION p202705 VALUES LESS THAN (202706), + PARTITION p202706 VALUES LESS THAN (202707), + PARTITION p202707 VALUES LESS THAN (202708), + PARTITION p202708 VALUES LESS THAN (202709), + PARTITION p202709 VALUES LESS THAN (202710), + PARTITION p202710 VALUES LESS THAN (202711), + PARTITION p202711 VALUES LESS THAN (202712), + PARTITION p202712 VALUES LESS THAN (202801), + PARTITION pFuture VALUES LESS THAN MAXVALUE + )"); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('station_prices'); + } +}; diff --git a/database/migrations/2026_04_03_172842_create_station_prices_archive_table.php b/database/migrations/2026_04_03_172842_create_station_prices_archive_table.php new file mode 100644 index 0000000..4423ad2 --- /dev/null +++ b/database/migrations/2026_04_03_172842_create_station_prices_archive_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->string('station_id', 64); + $table->string('fuel_type', 20); + $table->unsignedSmallInteger('price_pence'); + $table->dateTime('price_effective_at'); + $table->dateTime('price_reported_at'); + $table->dateTime('recorded_at'); + + $table->index(['station_id', 'fuel_type', 'price_effective_at'], 'spa_station_fuel_effective_idx'); + $table->index('price_effective_at', 'spa_price_effective_at_idx'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('station_prices_archive'); + } +};