feat: add postcode resolution to /api/stations and Filament SearchResource
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

Extends NearbyStationsRequest to accept `postcode` (full or outcode) as an alternative to lat/lng. PostcodeService resolves it via postcodes.io and falls through to coordinates. Also adds SearchResource to the Filament admin panel for viewing logged search activity with fuel type filter and price/distance stats columns. Includes SQLite GREATEST/LEAST function polyfills in AppServiceProvider for test compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-05 19:10:25 +01:00
parent 3ccdc28763
commit 7101ed3550
15 changed files with 392 additions and 45 deletions

View File

@@ -6,6 +6,7 @@ use App\Enums\PredictionSource;
use App\Enums\TrendDirection;
use Database\Factories\PricePredictionFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
@@ -18,6 +19,8 @@ use Illuminate\Support\Carbon;
* @property int $confidence
* @property string|null $reasoning
* @property Carbon $generated_at
*
* @method static Builder<PricePrediction> bestFirst()
*/
#[Fillable(['predicted_for', 'source', 'direction', 'confidence', 'reasoning', 'generated_at'])]
class PricePrediction extends Model
@@ -37,4 +40,21 @@ class PricePrediction extends Model
'generated_at' => 'datetime',
];
}
/**
* Order by source quality: llm_with_context llm ewma.
* Use this whenever reading the "best" prediction for a given date.
*
* @param Builder<PricePrediction> $query
* @return Builder<PricePrediction>
*/
public function scopeBestFirst(Builder $query): Builder
{
$priority = implode(', ', array_map(
fn (string $v) => "'$v'",
[PredictionSource::LlmWithContext->value, PredictionSource::Llm->value, PredictionSource::Ewma->value],
));
return $query->orderByRaw("FIELD(source, $priority)");
}
}

View File

@@ -13,13 +13,14 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
#[Fillable(['name', 'email', 'password', 'is_admin', 'postcode'])]
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
class User extends Authenticatable implements FilamentUser
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable, TwoFactorAuthenticatable;
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
/**
* Get the attributes that should be cast.