73 lines
2.6 KiB
PHP
73 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Offer;
|
|
use App\Models\Ride;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Str;
|
|
|
|
class RecommendationEngine
|
|
{
|
|
public function recommendFor(Ride $ride, int $limit = 3): Collection
|
|
{
|
|
$zone = Str::lower((string) $ride->context_zone);
|
|
$destination = Str::lower((string) $ride->destination_label);
|
|
$hour = optional($ride->scheduled_for)->hour ?? now()->hour;
|
|
|
|
return Offer::query()
|
|
->where('status', 'published')
|
|
->get()
|
|
->map(function (Offer $offer) use ($zone, $destination, $hour) {
|
|
$score = (int) $offer->priority_score;
|
|
$reasons = [];
|
|
$haystack = Str::lower(trim(($offer->location_label ?? '').' '.$offer->title.' '.$offer->excerpt.' '.$offer->description));
|
|
|
|
if ($offer->available_now) {
|
|
$score += 18;
|
|
$reasons[] = 'Available right now';
|
|
}
|
|
|
|
if ($offer->is_featured) {
|
|
$score += 14;
|
|
$reasons[] = 'Featured partner';
|
|
}
|
|
|
|
foreach (array_filter([$zone, $destination]) as $needle) {
|
|
if ($needle !== '' && Str::contains($haystack, $needle)) {
|
|
$score += 22;
|
|
$reasons[] = 'Matches destination context';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($hour >= 18 && $offer->category === 'restaurant') {
|
|
$score += 12;
|
|
$reasons[] = 'Good fit for the evening';
|
|
}
|
|
|
|
if ($hour >= 10 && $hour <= 18 && in_array($offer->category, ['experience', 'activity'], true)) {
|
|
$score += 12;
|
|
$reasons[] = 'Works well while waiting or after arrival';
|
|
}
|
|
|
|
if (($offer->duration_minutes ?? 0) > 0 && $offer->duration_minutes <= 120) {
|
|
$score += 8;
|
|
$reasons[] = 'Easy to fit into today';
|
|
}
|
|
|
|
if (($offer->price_from ?? 0) > 0 && $offer->price_from <= 50) {
|
|
$score += 6;
|
|
$reasons[] = 'Easy price point';
|
|
}
|
|
|
|
$offer->demo_score = $score;
|
|
$offer->demo_reason = collect($reasons)->unique()->take(2)->implode(' · ') ?: 'Strong local fit';
|
|
return $offer;
|
|
})
|
|
->sortByDesc(fn (Offer $offer) => [$offer->demo_score, $offer->is_featured, $offer->priority_score])
|
|
->take($limit)
|
|
->values();
|
|
}
|
|
}
|