83 lines
3.6 KiB
PHP
83 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Moderation;
|
|
|
|
use App\Enums\ModerationStatus;
|
|
use App\Models\ContentModerationFinding;
|
|
use App\Models\User;
|
|
|
|
class UserRiskScoreService
|
|
{
|
|
/**
|
|
* @param array<int, string> $domains
|
|
* @return array{risk_score:int,score_modifier:int,signals:array<string, int>}
|
|
*/
|
|
public function assess(?int $userId, array $domains = []): array
|
|
{
|
|
if (! $userId) {
|
|
return ['risk_score' => 0, 'score_modifier' => 0, 'signals' => []];
|
|
}
|
|
|
|
$user = User::query()->with('statistics:user_id,uploads_count')->find($userId);
|
|
if (! $user) {
|
|
return ['risk_score' => 0, 'score_modifier' => 0, 'signals' => []];
|
|
}
|
|
|
|
$summary = ContentModerationFinding::query()
|
|
->where('user_id', $userId)
|
|
->selectRaw('COUNT(*) as total_findings')
|
|
->selectRaw("SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as confirmed_spam_count", [ModerationStatus::ConfirmedSpam->value])
|
|
->selectRaw("SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as safe_count", [ModerationStatus::ReviewedSafe->value])
|
|
->selectRaw("SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as pending_count", [ModerationStatus::Pending->value])
|
|
->first();
|
|
|
|
$confirmedSpam = (int) ($summary?->confirmed_spam_count ?? 0);
|
|
$safeCount = (int) ($summary?->safe_count ?? 0);
|
|
$pendingCount = (int) ($summary?->pending_count ?? 0);
|
|
$domainRepeatCount = ContentModerationFinding::query()
|
|
->where('user_id', $userId)
|
|
->whereNotNull('matched_domains_json')
|
|
->get(['matched_domains_json'])
|
|
->reduce(function (int $carry, ContentModerationFinding $finding) use ($domains): int {
|
|
return $carry + (empty(array_intersect((array) $finding->matched_domains_json, $domains)) ? 0 : 1);
|
|
}, 0);
|
|
|
|
$accountAgeDays = max(0, \now()->diffInDays($user->created_at));
|
|
$uploadsCount = (int) ($user->statistics?->uploads_count ?? 0);
|
|
|
|
$riskScore = 0;
|
|
$riskScore += $confirmedSpam * 18;
|
|
$riskScore += $pendingCount * 5;
|
|
$riskScore += min(20, $domainRepeatCount * 6);
|
|
$riskScore -= min(18, $safeCount * 4);
|
|
$riskScore -= $accountAgeDays >= 365 ? 8 : ($accountAgeDays >= 90 ? 4 : 0);
|
|
$riskScore -= $uploadsCount >= 25 ? 6 : ($uploadsCount >= 10 ? 3 : 0);
|
|
$riskScore = max(0, min(100, $riskScore));
|
|
|
|
$modifier = 0;
|
|
if ($riskScore >= 75) {
|
|
$modifier = (int) \app('config')->get('content_moderation.user_risk.high_modifier', 18);
|
|
} elseif ($riskScore >= 50) {
|
|
$modifier = (int) \app('config')->get('content_moderation.user_risk.medium_modifier', 10);
|
|
} elseif ($riskScore >= 25) {
|
|
$modifier = (int) \app('config')->get('content_moderation.user_risk.low_modifier', 4);
|
|
} elseif ($riskScore <= 5 && $accountAgeDays >= 180 && $uploadsCount >= 10) {
|
|
$modifier = (int) \app('config')->get('content_moderation.user_risk.trusted_modifier', -6);
|
|
} elseif ($riskScore <= 12 && $safeCount >= 2) {
|
|
$modifier = -3;
|
|
}
|
|
|
|
return [
|
|
'risk_score' => $riskScore,
|
|
'score_modifier' => $modifier,
|
|
'signals' => [
|
|
'confirmed_spam_count' => $confirmedSpam,
|
|
'safe_count' => $safeCount,
|
|
'pending_count' => $pendingCount,
|
|
'domain_repeat_count' => $domainRepeatCount,
|
|
'account_age_days' => $accountAgeDays,
|
|
'uploads_count' => $uploadsCount,
|
|
],
|
|
];
|
|
}
|
|
} |