feat: add stations and station prices migrations

Creates stations, station_prices_current, station_prices (monthly-partitioned), and station_prices_archive tables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-03 18:45:05 +01:00
parent 7d5b467084
commit ec3a2bf848
4 changed files with 185 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('stations', function (Blueprint $table): void {
$table->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');
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('station_prices_current', function (Blueprint $table): void {
$table->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');
}
};

View File

@@ -0,0 +1,68 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('station_prices', function (Blueprint $table): void {
$table->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 20262027 + 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');
}
};

View File

@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('station_prices_archive', function (Blueprint $table): void {
$table->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');
}
};