$context * @param array $suggestion * @return array{campaign_key:string,cluster_score:int,cluster_reason:string} */ public function classify(string $content, ModerationResultData $result, array $context = [], array $suggestion = []): array { $domains = array_values(array_filter($result->matchedDomains)); $keywords = array_values(array_filter($result->matchedKeywords)); $reason = 'normalized_content'; if ($domains !== [] && $keywords !== []) { $reason = 'domain_keyword_cta'; $key = 'campaign:' . sha1(implode('|', [implode(',', array_slice($domains, 0, 3)), implode(',', array_slice($keywords, 0, 3))])); } elseif ($domains !== []) { $reason = 'domain_fingerprint'; $key = 'campaign:' . sha1(implode(',', array_slice($domains, 0, 3)) . '|' . ($result->contentHashNormalized ?? $result->contentHash)); } elseif (! empty($suggestion['campaign_tags'])) { $reason = 'suggested_cluster'; $key = 'campaign:' . sha1(implode('|', (array) $suggestion['campaign_tags'])); } else { $key = 'campaign:' . sha1((string) ($result->groupKey ?? $result->contentHashNormalized ?? $result->contentHash)); } $clusterScore = min(100, $result->score + (count($domains) * 8) + (count($keywords) * 4)); return [ 'campaign_key' => $key, 'cluster_score' => $clusterScore, 'cluster_reason' => $reason, ]; } public function syncFinding(ContentModerationFinding $finding): void { if (! $finding->campaign_key) { return; } $query = ContentModerationFinding::query()->where('campaign_key', $finding->campaign_key); $findings = $query->get(['id', 'user_id', 'matched_domains_json', 'matched_keywords_json', 'review_bucket', 'cluster_score', 'created_at']); $domains = $findings ->flatMap(static fn (ContentModerationFinding $item): array => (array) $item->matched_domains_json) ->filter() ->unique() ->values(); $keywords = $findings ->flatMap(static fn (ContentModerationFinding $item): array => (array) $item->matched_keywords_json) ->filter() ->unique() ->take(8) ->values(); ContentModerationCluster::query()->updateOrCreate( ['campaign_key' => $finding->campaign_key], [ 'cluster_reason' => $finding->cluster_reason, 'review_bucket' => $finding->review_bucket, 'escalation_status' => $finding->escalation_status?->value ?? (string) $finding->escalation_status, 'cluster_score' => (int) ($findings->max('cluster_score') ?? $finding->cluster_score ?? 0), 'findings_count' => $findings->count(), 'unique_users_count' => $findings->pluck('user_id')->filter()->unique()->count(), 'unique_domains_count' => $domains->count(), 'latest_finding_at' => $findings->max('created_at') ?: now(), 'summary_json' => [ 'domains' => $domains->take(8)->all(), 'keywords' => $keywords->all(), ], ], ); $clusterSize = $findings->count(); if ($clusterSize > 1) { $query->update(['priority_score' => $finding->priority_score + min(25, ($clusterSize - 1) * 3)]); } } }