160 lines
5.0 KiB
PHP
160 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\Brand;
|
|
use App\Models\Business;
|
|
use App\Services\Cannaiq\BrandAnalysisService;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Background job to pre-calculate Brand Analysis metrics.
|
|
*
|
|
* This job runs in the background to compute expensive engagement and sentiment
|
|
* metrics for brands, caching the results for 2 hours. This prevents N+1 queries
|
|
* and expensive aggregations from running on page load.
|
|
*
|
|
* Schedule: Every 2 hours via Horizon
|
|
* Queue: default (or 'analytics' if available)
|
|
*
|
|
* Key benefits:
|
|
* - Aggregates CRM message counts, response rates, and quote/order metrics in batch
|
|
* - Pre-computes buyer engagement scores
|
|
* - For CannaiQ-enabled businesses, also pre-computes sentiment scores
|
|
* - Uses existing BrandAnalysisService caching mechanism (2-hour TTL)
|
|
*/
|
|
class CalculateBrandAnalysisMetrics implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
/**
|
|
* The business to calculate metrics for (null = all seller businesses)
|
|
*/
|
|
public ?int $businessId;
|
|
|
|
/**
|
|
* The brand to calculate metrics for (null = all brands in business)
|
|
*/
|
|
public ?int $brandId;
|
|
|
|
/**
|
|
* Create a new job instance.
|
|
*/
|
|
public function __construct(?int $businessId = null, ?int $brandId = null)
|
|
{
|
|
$this->businessId = $businessId;
|
|
$this->brandId = $brandId;
|
|
}
|
|
|
|
/**
|
|
* Execute the job.
|
|
*/
|
|
public function handle(BrandAnalysisService $service): void
|
|
{
|
|
$startTime = microtime(true);
|
|
$processedCount = 0;
|
|
|
|
try {
|
|
if ($this->businessId && $this->brandId) {
|
|
// Single brand calculation
|
|
$this->calculateForBrand($service, $this->businessId, $this->brandId);
|
|
$processedCount = 1;
|
|
} elseif ($this->businessId) {
|
|
// All brands for a single business
|
|
$processedCount = $this->calculateForBusiness($service, $this->businessId);
|
|
} else {
|
|
// All seller businesses with active brands
|
|
$processedCount = $this->calculateForAllBusinesses($service);
|
|
}
|
|
|
|
$duration = round(microtime(true) - $startTime, 2);
|
|
Log::info('CalculateBrandAnalysisMetrics completed', [
|
|
'business_id' => $this->businessId ?? 'all',
|
|
'brand_id' => $this->brandId ?? 'all',
|
|
'brands_processed' => $processedCount,
|
|
'duration_seconds' => $duration,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
Log::error('CalculateBrandAnalysisMetrics failed', [
|
|
'business_id' => $this->businessId,
|
|
'brand_id' => $this->brandId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate metrics for all seller businesses
|
|
*/
|
|
private function calculateForAllBusinesses(BrandAnalysisService $service): int
|
|
{
|
|
$processedCount = 0;
|
|
|
|
Business::where('type', 'seller')
|
|
->where('status', 'approved')
|
|
->chunk(10, function ($businesses) use ($service, &$processedCount) {
|
|
foreach ($businesses as $business) {
|
|
$processedCount += $this->calculateForBusiness($service, $business->id);
|
|
}
|
|
});
|
|
|
|
return $processedCount;
|
|
}
|
|
|
|
/**
|
|
* Calculate metrics for all active brands in a business
|
|
*/
|
|
private function calculateForBusiness(BrandAnalysisService $service, int $businessId): int
|
|
{
|
|
$business = Business::find($businessId);
|
|
if (! $business) {
|
|
return 0;
|
|
}
|
|
|
|
$brands = Brand::where('business_id', $businessId)
|
|
->where('is_active', true)
|
|
->get();
|
|
|
|
foreach ($brands as $brand) {
|
|
$this->calculateForBrand($service, $businessId, $brand->id);
|
|
}
|
|
|
|
return $brands->count();
|
|
}
|
|
|
|
/**
|
|
* Calculate metrics for a single brand
|
|
*/
|
|
private function calculateForBrand(BrandAnalysisService $service, int $businessId, int $brandId): void
|
|
{
|
|
$business = Business::find($businessId);
|
|
$brand = Brand::find($brandId);
|
|
|
|
if (! $business || ! $brand) {
|
|
return;
|
|
}
|
|
|
|
// This triggers the full analysis calculation and caches it
|
|
// The BrandAnalysisService handles caching internally with 2-hour TTL
|
|
$service->refreshAnalysis($brand, $business);
|
|
}
|
|
|
|
/**
|
|
* The job failed to process.
|
|
*/
|
|
public function failed(\Throwable $exception): void
|
|
{
|
|
Log::error('CalculateBrandAnalysisMetrics job failed', [
|
|
'business_id' => $this->businessId,
|
|
'brand_id' => $this->brandId,
|
|
'exception' => $exception->getMessage(),
|
|
]);
|
|
}
|
|
}
|