filled('postcode')) { $location = $this->postcodeService->resolve($request->string('postcode')->toString()); if ($location === null) { throw ValidationException::withMessages(['postcode' => 'Postcode not found.']); } $lat = $location->lat; $lng = $location->lng; } else { $lat = (float) $request->input('lat'); $lng = (float) $request->input('lng'); } $fuelType = $request->fuelType(); $radius = $request->radius(); $sort = $request->sort(); $all = Station::query() ->selectRaw( 'stations.*, spc.price_pence, spc.fuel_type, spc.price_effective_at, (6371 * acos(GREATEST(-1.0, LEAST(1.0, cos(radians(?)) * cos(radians(lat)) * cos(radians(lng) - radians(?)) + sin(radians(?)) * sin(radians(lat)) )))) AS distance_km', [$lat, $lng, $lat], ) ->join('station_prices_current as spc', function (JoinClause $join) use ($fuelType): void { $join->on('stations.node_id', '=', 'spc.station_id') ->where('spc.fuel_type', '=', $fuelType->value); }) ->where('stations.temporary_closure', false) ->where('stations.permanent_closure', false) ->get(); $filtered = $all->filter(fn ($s) => (float) $s->distance_km <= $radius); $stations = $sort === 'reliable' ? $filtered ->sort(function ($a, $b) { $weightA = PriceReliability::fromUpdatedAt( $a->price_effective_at ? Carbon::parse($a->price_effective_at) : null )->weight(); $weightB = PriceReliability::fromUpdatedAt( $b->price_effective_at ? Carbon::parse($b->price_effective_at) : null )->weight(); return $weightA <=> $weightB ?: ((int) $a->price_pence <=> (int) $b->price_pence) ?: ((float) $a->distance_km <=> (float) $b->distance_km); }) ->values() : $filtered->sortBy(match ($sort) { 'price' => fn ($s) => (int) $s->price_pence, 'updated' => fn ($s) => $s->price_effective_at ? -strtotime($s->price_effective_at) : PHP_INT_MAX, default => fn ($s) => (float) $s->distance_km, })->values(); $prices = $stations->pluck('price_pence'); $reliabilityCounts = $stations ->groupBy(fn ($s) => PriceReliability::fromUpdatedAt( $s->price_effective_at ? Carbon::parse($s->price_effective_at) : null )->value) ->map->count(); Search::create([ 'lat_bucket' => round($lat, 2), 'lng_bucket' => round($lng, 2), 'fuel_type' => $fuelType->value, 'results_count' => $stations->count(), 'lowest_pence' => $prices->min(), 'highest_pence' => $prices->max(), 'avg_pence' => $prices->isNotEmpty() ? round($prices->avg(), 2) : null, 'searched_at' => now(), 'ip_hash' => hash('sha256', $request->ip() ?? ''), ]); return response()->json([ 'data' => StationResource::collection($stations), 'meta' => [ 'count' => $stations->count(), 'fuel_type' => $fuelType->value, 'radius_km' => $radius, 'lat' => $lat, 'lng' => $lng, 'lowest_pence' => $prices->min(), 'highest_pence' => $prices->max(), 'cheapest_price_pence' => $prices->min(), 'avg_pence' => $prices->isNotEmpty() ? round($prices->avg(), 2) : null, 'reliability_counts' => [ 'reliable' => (int) $reliabilityCounts->get(PriceReliability::Reliable->value, 0), 'stale' => (int) $reliabilityCounts->get(PriceReliability::Stale->value, 0), 'outdated' => (int) $reliabilityCounts->get(PriceReliability::Outdated->value, 0), ], ], ]); } }