predict(FuelType::B7Standard); expect($result['predicted_direction'])->toBe('stable') ->and($result['signals']['trend']['enabled'])->toBeFalse() ->and($result['action'])->toBe('no_signal'); }); it('detects rising trend from consistently increasing daily averages', function () { $station = Station::factory()->create(); // 7 days of prices rising at ~100 pence/day for ($daysAgo = 6; $daysAgo >= 0; $daysAgo--) { StationPrice::factory()->create([ 'station_id' => $station->node_id, 'fuel_type' => FuelType::B7Standard, 'price_pence' => 14000 + ((6 - $daysAgo) * 100), 'price_effective_at' => now()->subDays($daysAgo)->setTime(12, 0), ]); } $result = app(NationalFuelPredictionService::class)->predict(FuelType::B7Standard); expect($result['signals']['trend']['direction'])->toBe('up') ->and($result['signals']['trend']['enabled'])->toBeTrue() ->and($result['predicted_direction'])->toBe('up') ->and($result['action'])->toBe('fill_now'); }); it('detects falling trend from consistently decreasing daily averages', function () { $station = Station::factory()->create(); for ($daysAgo = 6; $daysAgo >= 0; $daysAgo--) { StationPrice::factory()->create([ 'station_id' => $station->node_id, 'fuel_type' => FuelType::B7Standard, 'price_pence' => 16000 - ((6 - $daysAgo) * 100), 'price_effective_at' => now()->subDays($daysAgo)->setTime(12, 0), ]); } $result = app(NationalFuelPredictionService::class)->predict(FuelType::B7Standard); expect($result['signals']['trend']['direction'])->toBe('down') ->and($result['predicted_direction'])->toBe('down') ->and($result['action'])->toBe('wait'); }); it('returns current_avg from station_prices_current', function () { $station = Station::factory()->create(); StationPriceCurrent::factory()->create([ 'station_id' => $station->node_id, 'fuel_type' => FuelType::B7Standard, 'price_pence' => 14750, ]); $result = app(NationalFuelPredictionService::class)->predict(FuelType::B7Standard); expect($result['current_avg'])->toBe(147.5); }); it('includes all required keys in response', function () { $result = app(NationalFuelPredictionService::class)->predict(FuelType::B7Standard); expect($result)->toHaveKeys([ 'fuel_type', 'current_avg', 'predicted_direction', 'predicted_change_pence', 'confidence_score', 'confidence_label', 'action', 'reasoning', 'prediction_horizon_days', 'region_key', 'methodology', 'signals', ]); expect($result['signals'])->toHaveKeys([ 'trend', 'day_of_week', 'brand_behaviour', 'national_momentum', 'regional_momentum', 'price_stickiness', ]); }); it('disables trend signal when r_squared is below 0.5', function () { $station = Station::factory()->create(); // Highly erratic prices (zigzag pattern) — low R² $prices = [14000, 16000, 13000, 17000, 12000, 18000, 14500]; foreach ($prices as $daysAgo => $price) { StationPrice::factory()->create([ 'station_id' => $station->node_id, 'fuel_type' => FuelType::B7Standard, 'price_pence' => $price, 'price_effective_at' => now()->subDays(count($prices) - 1 - $daysAgo)->setTime(12, 0), ]); } $result = app(NationalFuelPredictionService::class)->predict(FuelType::B7Standard); // Trend signal may be disabled if both 5-day and 14-day lookbacks fail R² threshold expect($result['signals']['trend']['data_points'])->toBeInt(); });