Remove prediction API endpoint and integrate into stations search
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

Consolidate prediction functionality by merging /api/prediction endpoint into /api/stations response. Move prediction logic from PredictionController into StationController, returning prediction data alongside station results. Replace usePrediction composable with unified useStations that returns {stations, meta, prediction}. Remove PredictionRequest, related tests, and unused Vue components (FuelFinderTest, MapTest, RecommendationTest, StationListTest). Add PredictionFull component and UpsellBanner. Extend NationalFuelPredictionService to include weekly_summary (7-day series, yesterday/today averages, cheapest/priciest days) and oil signal from price_predictions table. Update Home.vue to consume prediction from stations response. Add Plan::resolveCadenceForUser helper and configure Cashier to use custom Subscription model.
This commit is contained in:
Ovidiu U
2026-04-29 13:28:33 +01:00
parent ee6de23709
commit 088fd11058
29 changed files with 1046 additions and 499 deletions

View File

@@ -63,10 +63,19 @@ class AuthController extends Controller
public function me(Request $request): JsonResponse
{
$user = $request->user();
$subscription = $user->subscription();
$expiresAt = $subscription?->ends_at ?? $subscription?->current_period_end;
return response()->json(array_merge(
$user->toArray(),
['tier' => Plan::resolveForUser($user)->name],
[
'tier' => Plan::resolveForUser($user)->name,
'subscription_cancelled' => $subscription?->canceled() ?? false,
'subscription_cadence' => Plan::resolveCadenceForUser($user),
'subscribed_at' => $subscription?->created_at?->toIso8601String(),
'subscription_expires_at' => $expiresAt?->toIso8601String(),
],
));
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\PredictionRequest;
use App\Services\NationalFuelPredictionService;
use App\Services\PlanFeatures;
use Illuminate\Http\JsonResponse;
class PredictionController extends Controller
{
public function __construct(
private readonly NationalFuelPredictionService $predictionService,
) {}
public function index(PredictionRequest $request): JsonResponse
{
$lat = $request->filled('lat') ? (float) $request->input('lat') : null;
$lng = $request->filled('lng') ? (float) $request->input('lng') : null;
$result = $this->predictionService->predict($lat, $lng);
$user = $request->user();
$canSeeFull = $user !== null && PlanFeatures::for($user)->can('ai_predictions');
if (! $canSeeFull) {
return response()->json([
'fuel_type' => $result['fuel_type'],
'predicted_direction' => $result['predicted_direction'],
'tier_locked' => true,
]);
}
return response()->json($result);
}
}

View File

@@ -8,6 +8,9 @@ use App\Http\Requests\Api\NearbyStationsRequest;
use App\Http\Resources\Api\StationResource;
use App\Models\Search;
use App\Models\Station;
use App\Models\User;
use App\Services\NationalFuelPredictionService;
use App\Services\PlanFeatures;
use App\Services\PostcodeService;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\JsonResponse;
@@ -16,7 +19,10 @@ use Illuminate\Validation\ValidationException;
class StationController extends Controller
{
public function __construct(private readonly PostcodeService $postcodeService) {}
public function __construct(
private readonly PostcodeService $postcodeService,
private readonly NationalFuelPredictionService $predictionService,
) {}
public function index(NearbyStationsRequest $request): JsonResponse
{
@@ -115,6 +121,31 @@ class StationController extends Controller
'outdated' => (int) $reliabilityCounts->get(PriceReliability::Outdated->value, 0),
],
],
'prediction' => $this->predictionFor($request->user(), $lat, $lng),
]);
}
/**
* Returns the prediction payload for embedding in the search response.
* Free/guest users get a stripped teaser; users with the ai_predictions
* feature get the full multi-signal payload.
*
* @return array<string, mixed>
*/
private function predictionFor(?User $user, float $lat, float $lng): array
{
$result = $this->predictionService->predict($lat, $lng);
$canSeeFull = $user !== null && PlanFeatures::for($user)->can('ai_predictions');
if (! $canSeeFull) {
return [
'fuel_type' => $result['fuel_type'],
'predicted_direction' => $result['predicted_direction'],
'tier_locked' => true,
];
}
return $result;
}
}