File manager - Edit - /var/www/ratemypay_dev/app/Http/Controllers/HomeController.php
Back
<?php namespace App\Http\Controllers; use App\Models\JobExperience; use App\Models\JobTitle; use App\Services\SalaryMarketComparisonService; use DB; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; class HomeController extends Controller { public function index(Request $request) { // return DB::getDefaultConnection(); // return $user = auth()->user(); // Fetch Recent Jobs $jobs = JobExperience::published() ->whereNotNull('country') ->where('country', '!=', '') ->whereNotNull('job_title') ->whereNotNull('currency') ->latest() ->limit(3) ->get(); $country = session('db_connection'); // 4️⃣ Ensure country is either US or CA if (!in_array($country, ['CA', 'US'])) { $country = 'CA'; } if ($country == 'CA') { $country = "Canada"; } if ($country == 'US') { $country = "United States"; } if ($jobs->count() < 5) { $jobs = array_map(fn($j) => (object) $j, $this->getRandomJobsByCountry($country, 3)); } // Job Titles List $jobTitles = []; // Countries List $countries = Cache::remember('all_countries', 60 * 60 * 24, function () { try { $response = Http::get('https://restcountries.com/v3.1/all?fields=name,cca2'); if ($response->successful()) { return collect($response->json()) ->map(fn($c) => $c['name']['common']) ->sort() ->values() ->toArray(); } } catch (\Exception $e) { // Silent fail } return ['United States', 'United Kingdom', 'Canada', 'Nigeria', 'Germany', 'India']; }); // return session('db_connection'); return view('home', compact('jobs', 'jobTitles', 'countries')); } public function seeSalary(Request $request) { $countries = Cache::remember('all_countries', 60 * 60 * 24, function () { try { $response = Http::get('https://restcountries.com/v3.1/all?fields=name,cca2'); if ($response->successful()) { return collect($response->json()) ->map(fn($c) => $c['name']['common']) ->sort() ->values() ->toArray(); } } catch (\Exception $e) { // Silent fail } return ['United States', 'United Kingdom', 'Canada', 'Nigeria', 'Germany', 'India', 'France', 'Australia']; }); $jobTitle = $request->query('job_title'); $locationSearch = $request->query('location'); $stateSearch = $request->query('state'); $citySearch = $request->query('city'); $postcodeSearch = $request->query('postcode'); $currency = $request->query('currency') ?? 'USD'; $pay_frequency = $request->query('pay_frequency') ?? 'annually'; // Shared view payload so we never repeat ourselves on the early return. $viewData = [ 'jobTitle' => $jobTitle, 'location' => $locationSearch, 'state' => $stateSearch, 'postcode' => $postcodeSearch, 'countries' => $countries, 'currency' => $currency, 'pay_frequency' => $pay_frequency, 'salaryData' => [], 'overview' => null, ]; if (!$jobTitle) { return view('salary.index', $viewData); } /* |-------------------------------------------------------------------------- | 1️⃣ Always Call AI Service First |-------------------------------------------------------------------------- */ $aiPayload = [ 'job_title' => $jobTitle, 'level' => $request->query('level'), 'experience' => (int) $request->query('experience', 0), 'location' => [ 'country' => $locationSearch, 'state' => $stateSearch, 'city' => $citySearch, ], 'pay_frequency' => $pay_frequency, 'base_salary' => null, 'company' => [ 'size' => null, 'industry' => null, ], ]; $marketService = app()->make(SalaryMarketComparisonService::class); try { $marketComparison = Cache::remember( "ai_salary_{$jobTitle}_{$locationSearch}_{$currency}_{$pay_frequency}", 3600, fn() => $marketService->compare($aiPayload) ); } catch (\Throwable $e) { report($e); $marketComparison = null; } // Normalise to an array so every downstream lookup is safe. $marketComparison = is_array($marketComparison) ? $marketComparison : []; // The currency the AI returned should drive what we DISPLAY. Fall back to the // requested currency, then USD. The requested $currency is kept only for // filtering community (JobExperience) rows, which are stored per-currency. $displayCurrency = $marketComparison['currency'] ?? $currency; $aiData = null; if (is_array($marketComparison) && isset($marketComparison['percentiles'], $marketComparison['market_comparison'])) { $values = [ 'Lower Pay' => (int) $marketComparison['percentiles']['p25'], 'Average' => (int) $marketComparison['percentiles']['p50'], 'Above Pay' => (int) $marketComparison['percentiles']['p75'], 'Top Pay' => (int) $marketComparison['percentiles']['p90'], ]; asort($values); $aiData = [ 'source' => 'ai', 'currency' => $marketComparison['currency'] ?? $currency, 'market_average' => (int) $marketComparison['market_comparison']['avg'], 'chart' => [ 'values' => $values, 'max' => max($values), ], ]; } /* |-------------------------------------------------------------------------- | 2️⃣ Fetch JobExperience Data (If Any) and adjust for pay frequency |-------------------------------------------------------------------------- */ $query = JobExperience::published() ->whereNotNull('country') ->where('country', '!=', '') ->where('job_title', 'LIKE', "%{$jobTitle}%"); if ($locationSearch) { $query->where('country', 'LIKE', "%{$locationSearch}%"); } if ($stateSearch) { $query->where('state', 'LIKE', "%{$stateSearch}%"); } if ($postcodeSearch) { $query->where('post_code', 'LIKE', "%{$postcodeSearch}%"); } if ($currency) { $query->where('currency', $currency); } $experiences = $query->get(); // Conversion factor based on frequency (annual -> requested cadence) $factor = match (strtolower($pay_frequency)) { 'monthly' => 12, 'semi-monthly' => 24, 'biweekly' => 26, 'weekly' => 52, 'hourly' => 2080, // 40h/week * 52 weeks default => 1, // annually }; $experienceData = null; if ($experiences->isNotEmpty()) { $minSalary = $experiences->min('annual_base_salary') / $factor; $maxSalary = $experiences->max('annual_base_salary') / $factor; $avgSalary = $experiences->avg('annual_base_salary') / $factor; $experienceData = [ 'source' => 'experience', 'currency' => $currency, 'min' => round($minSalary, 2), 'max' => round($maxSalary, 2), 'average' => round($avgSalary, 2), 'companies' => $experiences->pluck('company_name')->filter()->unique()->count(), 'count' => $experiences->count(), ]; } /* |-------------------------------------------------------------------------- | 3️⃣ Build the rich "overview" payload that the dashboard renders |-------------------------------------------------------------------------- | We prefer real values from the AI service / community data and fall back | to derived estimates. Anything purely illustrative is flagged as | 'is_estimate' => true so the front-end / future you can treat it as such. */ $percentiles = $marketComparison['percentiles'] ?? null; $median = (int) ( $percentiles['p50'] ?? ($marketComparison['market_comparison']['avg'] ?? null) ?? ($experienceData['average'] ?? 0) ); $p25 = $percentiles['p25'] ?? ($median ? (int) round($median * 0.85) : 0); $p50 = $percentiles['p50'] ?? $median; $p75 = $percentiles['p75'] ?? ($median ? (int) round($median * 1.20) : 0); $p90 = $percentiles['p90'] ?? ($median ? (int) round($median * 1.55) : 0); $p10 = $percentiles['p10'] ?? ($median ? (int) round($median * 0.70) : 0); // Nothing usable from either source — render the empty state, not a broken page. if ($median <= 0 && empty($aiData) && empty($experienceData)) { return view('salary.index', array_merge($viewData, [ 'salaryData' => [ [ 'job_title' => $jobTitle, 'location' => [ 'country' => $locationSearch, 'state' => $stateSearch, 'city' => $citySearch, 'postcode' => $postcodeSearch, 'flag' => $this->getFlagByCountryName($locationSearch), ], 'ai_market_data' => null, 'experience_data' => null, ] ], 'overview' => null, ])); } // Salary distribution points for the bell curve / percentile strip. $distribution = [ ['label' => '10%', 'value' => $p10], ['label' => '25%', 'value' => $p25], ['label' => '50%', 'value' => $p50], ['label' => '75%', 'value' => $p75], ['label' => '90%', 'value' => $p90], ]; // Compensation breakdown (donut). Use AI breakdown if present, else split the median. $compBreakdown = $marketComparison['compensation_breakdown'] ?? null; if (!$compBreakdown && $median) { $compBreakdown = [ ['label' => 'Base Salary', 'value' => (int) round($median * 0.78), 'pct' => 78], ['label' => 'Bonus', 'value' => (int) round($median * 0.12), 'pct' => 12], ['label' => 'Profit Sharing', 'value' => (int) round($median * 0.06), 'pct' => 6], ['label' => 'Other', 'value' => (int) round($median * 0.04), 'pct' => 4], ]; $isCompEstimate = true; } else { $isCompEstimate = false; } // Pay by city — derived from community data grouped by city, else from AI service. $payByCity = []; if ($experiences->isNotEmpty()) { $cityMedian = $median ?: 1; $payByCity = $experiences ->filter(fn($e) => !empty($e->city)) ->groupBy('city') ->map(function ($group, $city) use ($currency, $factor, $cityMedian) { $cMed = round($group->avg('annual_base_salary') / $factor); // Purchasing power relative to the national median for this role. $ratio = $cityMedian ? $cMed / $cityMedian : 1; $power = $ratio >= 1.05 ? 'High' : ($ratio >= 0.9 ? 'Moderate' : 'Low'); return [ 'city' => $city, 'median' => $cMed, 'low' => round($group->min('annual_base_salary') / $factor), 'high' => round($group->max('annual_base_salary') / $factor), 'purchasing_power' => $power, 'currency' => $currency, ]; }) ->sortByDesc('median') ->take(6) ->values() ->toArray(); } if (empty($payByCity)) { $payByCity = $marketComparison['pay_by_city'] ?? []; } // Pay by experience (line chart). Prefer AI service, else derive from percentiles. $payByExperience = $marketComparison['pay_by_experience'] ?? null; if (!$payByExperience && $median) { $payByExperience = [ ['band' => '0-2', 'value' => (int) round($median * 0.70)], ['band' => '3-5', 'value' => (int) round($median * 0.82)], ['band' => '6-10', 'value' => (int) round($median * 1.00)], ['band' => '11-15', 'value' => (int) round($median * 1.25)], ['band' => '15+', 'value' => (int) round($median * 1.45)], ]; } // Top paying employers — from community data, else AI service. $topEmployers = []; if ($experiences->isNotEmpty()) { $topEmployers = $experiences ->filter(fn($e) => !empty($e->company_name)) ->groupBy('company_name') ->map(function ($group, $company) use ($currency, $factor) { return [ 'company' => $company, 'median' => round($group->avg('annual_base_salary') / $factor), 'confidence' => $group->count() >= 5 ? 'High' : ($group->count() >= 2 ? 'Moderate' : 'Low'), 'currency' => $currency, ]; }) ->sortByDesc('median') ->take(5) ->values() ->toArray(); } if (empty($topEmployers)) { $topEmployers = $marketComparison['top_employers'] ?? []; } $overview = [ 'job_title' => $jobTitle, 'location' => $locationSearch, 'flag' => $this->getFlagByCountryName($locationSearch), 'currency' => $displayCurrency, 'data_points' => $marketComparison['data_points'] ?? ($experienceData['count'] ?? 0), 'updated_at' => $marketComparison['updated_at'] ?? now()->translatedFormat('M Y'), // Market snapshot strip 'snapshot' => [ 'median' => $p50, 'range_low' => $p25, 'range_high' => $p90, 'top10' => $p90, 'market_demand' => $marketComparison['market_demand'] ?? 'High', 'demand_trend' => $marketComparison['demand_trend'] ?? 'Growing', 'talent_supply' => $marketComparison['talent_supply'] ?? 'Limited', 'confidence' => $marketComparison['confidence'] ?? 'High', 'is_estimate' => empty($percentiles), // true when no real AI percentiles backed this ], 'distribution' => $distribution, 'comp_breakdown' => $compBreakdown, 'comp_breakdown_estimate' => $isCompEstimate, 'pay_by_city' => $payByCity, 'pay_by_experience' => $payByExperience, 'top_employers' => $topEmployers, // User-relative fields for the "How you compare" widget. 'base_salary' => $request->query('base_salary') !== null ? (int) $request->query('base_salary') : null, 'positioning' => $marketComparison['positioning'] ?? null, 'confidence_score' => $marketComparison['confidence_score'] ?? null, 'experience_years' => (int) $request->query('experience', 0), // Community-reported block (separate from AI estimate) for the view. 'community' => $experienceData, // The following come straight from the AI service when available. 'testimonials' => $marketComparison['testimonials'] ?? [], 'comp_drivers' => $marketComparison['compensation_drivers'] ?? [], 'career_path' => $marketComparison['career_path'] ?? [], 'market_trends' => $marketComparison['market_trends'] ?? [ 'compensation_trend' => $marketComparison['compensation_trend'] ?? ($marketComparison['yoy_change'] ?? null), 'demand' => $marketComparison['market_demand'] ?? 'High', 'growth_areas' => $marketComparison['growth_areas'] ?? [], 'shortage_areas' => $marketComparison['shortage_areas'] ?? [], ], 'similar_jobs' => $marketComparison['similar_jobs'] ?? [], 'tips' => $marketComparison['tips'] ?? [], // Extra detail surfaced only inside the "view more" modals. 'negotiation_tips' => $marketComparison['negotiation_tips'] ?? [], 'methodology' => $marketComparison['methodology'] ?? null, 'market_report' => $marketComparison['market_report'] ?? null, ]; // Keep the original results array intact for backward compatibility. $results = [ [ 'job_title' => $jobTitle, 'location' => [ 'country' => $locationSearch, 'state' => $stateSearch, 'city' => $citySearch, 'postcode' => $postcodeSearch, 'flag' => $overview['flag'], ], 'ai_market_data' => $aiData, 'experience_data' => $experienceData, ] ]; return view('salary.index', array_merge($viewData, [ 'salaryData' => $results, 'overview' => $overview, ])); } public function viewSalary(Request $request, string $location, string $jobTitle) { $countryName = urldecode($location); $decodedJobTitle = urldecode($jobTitle); $state = $request->query('state'); $city = $request->query('city'); $postcode = $request->query('postcode'); $currency = $request->currency ?? "USD"; // Default pay frequency is annually $pay_frequency = $request->query('pay_frequency') ?? 'annually'; /* |-------------------------------------------------------------------------- | 1️⃣ COMMUNITY DATA (JobExperience) |-------------------------------------------------------------------------- */ if ($currency == 'all') { $query = JobExperience::published() ->where('country', 'LIKE', "%{$countryName}%"); } else { $query = JobExperience::published() ->where('country', 'LIKE', "%{$countryName}%") ->where('currency', $currency); } if ($decodedJobTitle && strtolower($decodedJobTitle) !== 'all-roles') { $query->where('job_title', 'LIKE', "%{$decodedJobTitle}%"); } if ($state) { $query->where('state', 'LIKE', "%{$state}%"); } if ($city) { $query->where('city', 'LIKE', "%{$city}%"); } if ($postcode) { $query->where('post_code', 'LIKE', "%{$postcode}%"); } $allMatchingSalaries = (clone $query)->get(); $experienceData = null; $avgSalary = 0; $records = []; if ($allMatchingSalaries->isNotEmpty()) { // Conversion factor based on pay frequency $factor = match (strtolower($pay_frequency)) { 'monthly' => 12, 'semi-monthly' => 24, 'biweekly' => 26, 'weekly' => 52, 'hourly' => 2080, // 40h/week * 52 weeks default => 1, // annually }; $minSalary = $allMatchingSalaries->min('annual_base_salary') / $factor; $maxSalary = $allMatchingSalaries->max('annual_base_salary') / $factor; $avgSalary = $allMatchingSalaries->avg('annual_base_salary') / $factor; $experienceData = [ 'min' => round($minSalary, 2), 'max' => round($maxSalary, 2), 'average' => round($avgSalary, 2), 'companies' => $allMatchingSalaries->pluck('company_name')->unique()->count(), 'currency' => $currency, ]; $records = $allMatchingSalaries ->pluck('annual_base_salary') ->map(fn($salary) => round($salary / $factor, 2)) ->sort() ->values() ->toArray(); $records = array_merge([0], $records, [0]); } $experiences = $query->latest()->paginate(20); /* |-------------------------------------------------------------------------- | 2️⃣ AI MARKET DATA (ALWAYS CALL) |-------------------------------------------------------------------------- */ $aiPayload = [ 'job_title' => $decodedJobTitle, 'experience' => 0, 'location' => [ 'country' => $countryName, 'state' => $state, 'city' => $city, ], 'base_salary' => null, 'company' => [ 'size' => null, 'industry' => null, ], 'pay_frequency' => $pay_frequency, ]; $marketService = app()->make(SalaryMarketComparisonService::class); $marketComparison = Cache::remember( "ai_salary_{$decodedJobTitle}_{$countryName}_{$currency}_{$pay_frequency}_{$pay_frequency}", 3600 * 4, fn() => $marketService->compare($aiPayload) ); $aiData = null; if (is_array($marketComparison) && isset($marketComparison['percentiles'], $marketComparison['market_comparison'])) { // Adjust AI salaries if pay frequency is not annual $factor = match (strtolower($pay_frequency)) { 'monthly' => 12, 'semi-monthly' => 24, 'biweekly' => 26, 'weekly' => 52, 'hourly' => 2080, default => 1, }; $values = [ 'p25' => (int) $marketComparison['percentiles']['p25'] / $factor, 'p50' => (int) $marketComparison['percentiles']['p50'] / $factor, 'p75' => (int) $marketComparison['percentiles']['p75'] / $factor, 'p90' => (int) $marketComparison['percentiles']['p90'] / $factor, ]; $aiData = [ 'min' => round(min($values), 2), 'max' => round(max($values), 2), 'average' => round((int) $marketComparison['market_comparison']['avg'] / $factor, 2), 'currency' => $marketComparison['currency'], ]; } /* |-------------------------------------------------------------------------- | 3️⃣ RETURN VIEW |-------------------------------------------------------------------------- */ $dataAll = [ 'location' => $countryName, 'jobTitle' => $decodedJobTitle, 'currency' => $currency, 'pay_frequency' => $pay_frequency, 'country_flag' => $this->getFlagByCountryName($countryName), 'experienceData' => $experienceData, 'aiData' => $aiData, 'avg_salary' => $avgSalary, 'records' => $records, 'experiences' => $experiences, 'state' => $state, 'city' => $city, 'postcode' => $postcode, ]; return view('salary.view', $dataAll); } private function getFlagByCountryName($countryName) { $countryNameEncoded = rawurlencode($countryName); $url = "https://restcountries.com/v3.1/name/{$countryNameEncoded}?fullText=true"; try { $response = @file_get_contents($url); if ($response) { $data = json_decode($response, true); if (!empty($data[0]['flags']['png'])) { return $data[0]['flags']['png']; } } } catch (\Exception $e) { Log::error('Flag fetch error: ' . $e->getMessage()); } return 'https://via.placeholder.com/48x36.png?text=?'; } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.30 | Generation time: 0 |
proxy
|
phpinfo
|
Settings