39498-vm/laravel_tmp/app/Services/RecommendationEngine.php
2026-04-06 06:10:54 +00:00

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();
}
}