refactor: split oil price ingestion and prediction into separate services + commands
- BrentPriceFetcher owns ingestion (fetchFromEia / fetchFromFred, each throws on failure) - BrentPricePredictor owns prediction and marks latest brent_prices row as generated - oil:fetch command tries EIA, falls back to FRED, fails loudly if both fail - oil:predict command prompts if latest price already has a prediction; --force bypasses - add prediction_generated_at column to brent_prices - delete OilPriceService (replaced by the two focused services)
This commit is contained in:
37
app/Console/Commands/FetchOilPrices.php
Normal file
37
app/Console/Commands/FetchOilPrices.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\BrentPriceFetcher;
|
||||
use App\Services\BrentPriceSources\BrentPriceFetchException;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
#[Signature('oil:fetch')]
|
||||
#[Description('Fetch latest Brent crude prices (EIA primary, FRED fallback)')]
|
||||
class FetchOilPrices extends Command
|
||||
{
|
||||
public function handle(BrentPriceFetcher $fetcher): int
|
||||
{
|
||||
try {
|
||||
$fetcher->fetchFromEia();
|
||||
$this->info('Fetched Brent prices from EIA.');
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (BrentPriceFetchException $e) {
|
||||
$this->warn('EIA fetch failed: '.$e->getMessage().'. Trying FRED...');
|
||||
}
|
||||
|
||||
try {
|
||||
$fetcher->fetchFromFred();
|
||||
$this->info('Fetched Brent prices from FRED.');
|
||||
|
||||
return self::SUCCESS;
|
||||
} catch (BrentPriceFetchException $e) {
|
||||
$this->error('Both EIA and FRED failed: '.$e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,37 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\OilPriceService;
|
||||
use App\Services\BrentPricePredictor;
|
||||
use Illuminate\Console\Command;
|
||||
use Throwable;
|
||||
|
||||
class PredictOilPrices extends Command
|
||||
{
|
||||
protected $signature = 'oil:predict {--fetch : Fetch latest FRED prices before predicting}';
|
||||
protected $signature = 'oil:predict {--force : Generate even if the latest price already has a prediction}';
|
||||
|
||||
protected $description = 'Generate a Brent crude oil price direction prediction';
|
||||
|
||||
public function handle(OilPriceService $service): int
|
||||
public function handle(BrentPricePredictor $predictor): int
|
||||
{
|
||||
try {
|
||||
if ($this->option('fetch')) {
|
||||
$this->info('Fetching latest Brent crude prices from FRED...');
|
||||
$service->fetchBrentPrices();
|
||||
$latest = $predictor->latestPrice();
|
||||
|
||||
if ($latest?->prediction_generated_at !== null && ! $this->option('force')) {
|
||||
$message = sprintf(
|
||||
'Prediction already generated for %s at %s.',
|
||||
$latest->date->toDateString(),
|
||||
$latest->prediction_generated_at->toDateTimeString(),
|
||||
);
|
||||
|
||||
if (! $this->confirm($message.' Run again anyway?', default: false)) {
|
||||
$this->info('Skipped.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Generating prediction...');
|
||||
$prediction = $service->generatePrediction();
|
||||
$prediction = $predictor->generatePrediction();
|
||||
|
||||
if ($prediction === null) {
|
||||
$this->error('Could not generate a prediction — not enough price data.');
|
||||
|
||||
Reference in New Issue
Block a user