39498-vm/app/Http/Controllers/RideController.php
2026-04-06 06:10:54 +00:00

121 lines
4.0 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Ride;
use App\Models\RideRecommendation;
use App\Services\EventTracker;
use App\Services\RecommendationEngine;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class RideController extends Controller
{
public function __construct(
protected RecommendationEngine $recommendationEngine,
protected EventTracker $eventTracker,
) {
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'pickup_label' => ['required', 'string', 'max:255'],
'destination_label' => ['required', 'string', 'max:255'],
'scheduled_for' => ['nullable', 'date'],
'source_channel' => ['required', 'in:app,hotel,reception,web'],
'locale' => ['nullable', 'string', 'max:10'],
]);
$ride = Ride::create([
'pickup_label' => $validated['pickup_label'],
'destination_label' => $validated['destination_label'],
'scheduled_for' => $validated['scheduled_for'] ?? null,
'status' => 'confirmed',
'eta_minutes' => $this->estimateEta($validated['pickup_label'], $validated['destination_label']),
'source_channel' => $validated['source_channel'],
'context_zone' => $this->resolveZone($validated['destination_label']),
'locale' => $validated['locale'] ?? app()->getLocale(),
]);
$this->eventTracker->log('request_created', $ride, meta: [
'source_channel' => $ride->source_channel,
'context_zone' => $ride->context_zone,
]);
$this->syncRecommendations($ride);
return redirect()->route('rides.confirmed', $ride);
}
public function confirmed(Ride $ride): View
{
$ride->load(['recommendations.offer']);
if ($ride->recommendations->isEmpty()) {
$this->syncRecommendations($ride);
$ride->load(['recommendations.offer']);
}
foreach ($ride->recommendations as $recommendation) {
$this->eventTracker->logOncePerSession(
'recommendation_viewed',
'rec-viewed-'.$recommendation->id,
$ride,
$recommendation->offer,
null,
$recommendation,
['position' => $recommendation->position]
);
if (!$recommendation->was_viewed) {
$recommendation->forceFill(['was_viewed' => true])->save();
}
}
return view('rides.confirmed', [
'ride' => $ride,
'recommendations' => $ride->recommendations,
]);
}
protected function syncRecommendations(Ride $ride): void
{
$offers = $this->recommendationEngine->recommendFor($ride, 3);
$ride->recommendations()->delete();
foreach ($offers as $index => $offer) {
RideRecommendation::create([
'ride_id' => $ride->id,
'offer_id' => $offer->id,
'position' => $index + 1,
'score' => $offer->demo_score,
'reason' => $offer->demo_reason,
'was_viewed' => false,
'was_clicked' => false,
]);
}
}
protected function resolveZone(string $destinationLabel): string
{
$normalized = mb_strtolower($destinationLabel);
return match (true) {
str_contains($normalized, 'beach') => 'beach',
str_contains($normalized, 'old town') => 'old town',
str_contains($normalized, 'marina') => 'marina',
str_contains($normalized, 'center'), str_contains($normalized, 'centre') => 'city center',
default => trim($destinationLabel),
};
}
protected function estimateEta(string $pickup, string $destination): int
{
$seed = strlen($pickup) + strlen($destination);
return max(4, min(16, ($seed % 13) + 4));
}
}