diff --git a/app/Filament/Resources/OilPredictionResource.php b/app/Filament/Resources/OilPredictionResource.php new file mode 100644 index 0000000..ef66e60 --- /dev/null +++ b/app/Filament/Resources/OilPredictionResource.php @@ -0,0 +1,131 @@ +columns([ + TextColumn::make('predicted_for') + ->date('d M Y') + ->sortable(), + TextColumn::make('source') + ->badge() + ->formatStateUsing(fn (PredictionSource $state) => strtoupper($state->value)) + ->color(fn (PredictionSource $state) => match ($state) { + PredictionSource::Llm => 'success', + PredictionSource::Ewma => 'info', + }), + TextColumn::make('direction') + ->badge() + ->color(fn (TrendDirection $state) => match ($state) { + TrendDirection::Rising => 'danger', + TrendDirection::Falling => 'success', + TrendDirection::Flat => 'gray', + }), + TextColumn::make('confidence') + ->suffix('%') + ->sortable(), + TextColumn::make('reasoning') + ->limit(60) + ->placeholder('—'), + TextColumn::make('generated_at') + ->dateTime('d M Y H:i') + ->sortable(), + ]) + ->defaultSort('predicted_for', 'desc') + ->filters([ + SelectFilter::make('source') + ->options([ + PredictionSource::Llm->value => 'LLM', + PredictionSource::Ewma->value => 'EWMA', + ]), + SelectFilter::make('direction') + ->options([ + TrendDirection::Rising->value => 'Rising', + TrendDirection::Falling->value => 'Falling', + TrendDirection::Flat->value => 'Flat', + ]), + Filter::make('predicted_for') + ->schema([ + DatePicker::make('from')->label('From'), + DatePicker::make('until')->label('Until'), + ]) + ->query(function (Builder $query, array $data) { + $query + ->when($data['from'], fn ($q, $d) => $q->whereDate('predicted_for', '>=', $d)) + ->when($data['until'], fn ($q, $d) => $q->whereDate('predicted_for', '<=', $d)); + }), + ]) + ->recordActions([ + ViewAction::make(), + ]); + } + + public static function infolist(Schema $schema): Schema + { + return $schema->components([ + Section::make('Prediction')->schema([ + TextEntry::make('predicted_for')->date('d M Y'), + TextEntry::make('source') + ->badge() + ->formatStateUsing(fn (PredictionSource $state) => strtoupper($state->value)) + ->color(fn (PredictionSource $state) => match ($state) { + PredictionSource::Llm => 'success', + PredictionSource::Ewma => 'info', + }), + TextEntry::make('direction') + ->badge() + ->color(fn (TrendDirection $state) => match ($state) { + TrendDirection::Rising => 'danger', + TrendDirection::Falling => 'success', + TrendDirection::Flat => 'gray', + }), + TextEntry::make('confidence')->suffix('%'), + TextEntry::make('generated_at')->dateTime('d M Y H:i:s'), + ])->columns(3), + Section::make('Reasoning')->schema([ + TextEntry::make('reasoning') + ->columnSpanFull() + ->placeholder('No reasoning recorded'), + ]), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => ListOilPredictions::route('/'), + 'view' => ViewOilPrediction::route('/{record}'), + ]; + } +} diff --git a/app/Filament/Resources/OilPredictionResource/Pages/ListOilPredictions.php b/app/Filament/Resources/OilPredictionResource/Pages/ListOilPredictions.php new file mode 100644 index 0000000..f3c4167 --- /dev/null +++ b/app/Filament/Resources/OilPredictionResource/Pages/ListOilPredictions.php @@ -0,0 +1,42 @@ +label('Run Prediction Now') + ->icon('heroicon-o-cpu-chip') + ->requiresConfirmation() + ->modalHeading('Run oil price prediction?') + ->modalDescription('This will fetch the latest FRED prices and generate a new prediction. May take a few seconds.') + ->action(function () { + $result = Artisan::call('oil:predict', ['--fetch' => true]); + + if ($result === 0) { + Notification::make() + ->title('Prediction generated successfully') + ->success() + ->send(); + } else { + Notification::make() + ->title('Prediction failed') + ->body('Check API Logs for details.') + ->danger() + ->send(); + } + }), + ]; + } +} diff --git a/app/Filament/Resources/OilPredictionResource/Pages/ViewOilPrediction.php b/app/Filament/Resources/OilPredictionResource/Pages/ViewOilPrediction.php new file mode 100644 index 0000000..03eef5d --- /dev/null +++ b/app/Filament/Resources/OilPredictionResource/Pages/ViewOilPrediction.php @@ -0,0 +1,16 @@ +admin = User::factory()->admin()->create(); + $this->actingAs($this->admin); +}); + +it('renders the oil prediction list', function () { + $predictions = PricePrediction::factory()->count(3)->create(); + + Livewire::test(ListOilPredictions::class) + ->assertOk() + ->assertCanSeeTableRecords($predictions); +}); + +it('has a run prediction header action', function () { + Livewire::test(ListOilPredictions::class) + ->assertActionExists('runPrediction'); +});