Files
fuel-alert/app/Filament/Widgets/StatsOverviewWidget.php
Ovidiu U 203200acb9 chore: retire legacy oil prediction pipeline
Removes everything that was made redundant by the new forecasting
stack. Per docs/superpowers/specs/2026-05-01-prediction-rebuild-design.md,
this was the cleanup planned at the end of Phase 4.

Deleted services and code:
- App\Services\Prediction\Signals\* (the old six-signal aggregator —
  trend, supermarket, day-of-week, brand-behaviour, stickiness,
  regional-momentum, oil — replaced by RidgeRegressionModel).
- App\Services\NationalFuelPredictionService (the post-Phase-4 thin
  shim; StationSearchService now depends on WeeklyForecastService
  directly, set up in the previous commit).
- App\Services\LlmPrediction\* (AbstractLlmPredictionProvider plus
  the four provider implementations — Anthropic, OpenAI, Gemini, and
  the OilPredictionProvider router. Replaced by LlmOverlayService).
- App\Services\BrentPricePredictor and App\Services\Ewma. The Ewma
  helper had no callers left after BrentPricePredictor went.
- App\Models\PricePrediction and its factory.
- App\Console\Commands\PredictOilPrices (the oil:predict command).
- App\Filament\Resources\OilPredictionResource and its Pages.

Schema and dashboard:
- Drop the price_predictions table via a new migration.
- Repoint the Filament StatsOverviewWidget tile from PricePrediction
  to WeeklyForecast so the dashboard reflects the new pipeline.
- Remove the OilPredictionProvider binding from AppServiceProvider.

Test cleanup:
- Delete tests for every retired service.
- Update StatsOverviewWidgetTest to seed weekly_forecasts instead of
  price_predictions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 08:40:28 +01:00

96 lines
2.9 KiB
PHP

<?php
namespace App\Filament\Widgets;
use App\Models\ApiLog;
use App\Models\Search;
use App\Models\Station;
use App\Models\User;
use App\Models\WeeklyForecast;
use Carbon\Carbon;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class StatsOverviewWidget extends BaseWidget
{
protected ?string $pollingInterval = '30s';
protected function getStats(): array
{
return [
$this->usersStat(),
$this->searchesStat(),
$this->stationsStat(),
$this->weeklyForecastStat(),
$this->apiErrorsStat(),
];
}
private function usersStat(): Stat
{
return Stat::make('Total users', User::count())
->icon('heroicon-o-users')
->color('primary');
}
private function searchesStat(): Stat
{
return Stat::make('Total searches', Search::count())
->icon('heroicon-o-magnifying-glass')
->url(route('filament.admin.resources.searches.index'))
->color('primary');
}
private function stationsStat(): Stat
{
$count = Station::count();
$lastSeen = Station::max('last_seen_at');
$description = $lastSeen
? 'Last seen '.Carbon::parse($lastSeen)->diffForHumans()
: 'No stations yet';
return Stat::make('Stations in DB', number_format($count))
->description($description)
->url(route('filament.admin.resources.stations.index'))
->icon('heroicon-o-map-pin')
->color('success');
}
private function weeklyForecastStat(): Stat
{
$forecast = WeeklyForecast::query()->latest('generated_at')->first();
if ($forecast === null) {
return Stat::make('Latest weekly forecast', 'None')
->icon('heroicon-o-beaker')
->color('gray');
}
$ageHours = $forecast->generated_at->diffInHours(now());
$color = $ageHours > 168 ? 'warning' : 'success'; // weekly forecast → stale after a week
$directionLabel = ucfirst($forecast->direction);
$value = $directionLabel.' · '.$forecast->ridge_confidence.'%';
return Stat::make('Latest weekly forecast', $value)
->description('For week of '.$forecast->forecast_for->toDateString())
->icon('heroicon-o-beaker')
->color($color);
}
private function apiErrorsStat(): Stat
{
$errors = ApiLog::where('created_at', '>=', now()->subDay())
->where(fn ($q) => $q->where('status_code', '>=', 400)
->orWhereNull('status_code')
->orWhereNotNull('error'))
->count();
$color = $errors > 0 ? 'danger' : 'success';
return Stat::make('API errors (24h)', $errors)
->icon('heroicon-o-exclamation-triangle')
->url(route('filament.admin.resources.api-logs.index'))
->color($color);
}
}