200 lines
6.0 KiB
PHP
200 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\ModerationEscalationStatus;
|
|
use App\Enums\ModerationContentType;
|
|
use App\Enums\ModerationSeverity;
|
|
use App\Enums\ModerationStatus;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
/**
|
|
* @property ModerationContentType $content_type
|
|
* @property int $content_id
|
|
* @property int|null $artwork_id
|
|
* @property int|null $user_id
|
|
* @property ModerationStatus $status
|
|
* @property ModerationSeverity $severity
|
|
* @property int $score
|
|
* @property string $content_hash
|
|
* @property string $scanner_version
|
|
* @property array|null $reasons_json
|
|
* @property array|null $matched_links_json
|
|
* @property array|null $matched_domains_json
|
|
* @property array|null $matched_keywords_json
|
|
* @property string|null $content_snapshot
|
|
* @property int|null $reviewed_by
|
|
* @property \Illuminate\Support\Carbon|null $reviewed_at
|
|
* @property string|null $action_taken
|
|
* @property string|null $admin_notes
|
|
* @property string|null $content_hash_normalized
|
|
* @property string|null $group_key
|
|
* @property array|null $rule_hits_json
|
|
* @property array|null $domain_ids_json
|
|
* @property int|null $user_risk_score
|
|
* @property bool $is_auto_hidden
|
|
* @property string|null $auto_action_taken
|
|
* @property \Illuminate\Support\Carbon|null $auto_hidden_at
|
|
* @property int|null $resolved_by
|
|
* @property \Illuminate\Support\Carbon|null $resolved_at
|
|
* @property int|null $restored_by
|
|
* @property \Illuminate\Support\Carbon|null $restored_at
|
|
* @property string|null $content_target_type
|
|
* @property int|null $content_target_id
|
|
* @property string|null $campaign_key
|
|
* @property int|null $cluster_score
|
|
* @property string|null $cluster_reason
|
|
* @property int|null $priority_score
|
|
* @property string|null $ai_provider
|
|
* @property string|null $ai_label
|
|
* @property int|null $ai_confidence
|
|
* @property string|null $ai_explanation
|
|
* @property bool $is_false_positive
|
|
* @property int $false_positive_count
|
|
* @property string|null $policy_name
|
|
* @property string|null $review_bucket
|
|
* @property ModerationEscalationStatus $escalation_status
|
|
* @property array|null $score_breakdown_json
|
|
*/
|
|
class ContentModerationFinding extends Model
|
|
{
|
|
protected $table = 'content_moderation_findings';
|
|
|
|
protected $fillable = [
|
|
'content_type',
|
|
'content_id',
|
|
'artwork_id',
|
|
'user_id',
|
|
'status',
|
|
'severity',
|
|
'score',
|
|
'content_hash',
|
|
'scanner_version',
|
|
'reasons_json',
|
|
'matched_links_json',
|
|
'matched_domains_json',
|
|
'matched_keywords_json',
|
|
'content_snapshot',
|
|
'reviewed_by',
|
|
'reviewed_at',
|
|
'action_taken',
|
|
'admin_notes',
|
|
'content_hash_normalized',
|
|
'group_key',
|
|
'rule_hits_json',
|
|
'domain_ids_json',
|
|
'user_risk_score',
|
|
'is_auto_hidden',
|
|
'auto_action_taken',
|
|
'auto_hidden_at',
|
|
'resolved_by',
|
|
'resolved_at',
|
|
'restored_by',
|
|
'restored_at',
|
|
'content_target_type',
|
|
'content_target_id',
|
|
'campaign_key',
|
|
'cluster_score',
|
|
'cluster_reason',
|
|
'priority_score',
|
|
'ai_provider',
|
|
'ai_label',
|
|
'ai_confidence',
|
|
'ai_explanation',
|
|
'is_false_positive',
|
|
'false_positive_count',
|
|
'policy_name',
|
|
'review_bucket',
|
|
'escalation_status',
|
|
'score_breakdown_json',
|
|
];
|
|
|
|
protected $casts = [
|
|
'content_type' => ModerationContentType::class,
|
|
'status' => ModerationStatus::class,
|
|
'severity' => ModerationSeverity::class,
|
|
'score' => 'integer',
|
|
'reasons_json' => 'array',
|
|
'matched_links_json' => 'array',
|
|
'matched_domains_json' => 'array',
|
|
'matched_keywords_json' => 'array',
|
|
'rule_hits_json' => 'array',
|
|
'domain_ids_json' => 'array',
|
|
'user_risk_score' => 'integer',
|
|
'is_auto_hidden' => 'boolean',
|
|
'reviewed_at' => 'datetime',
|
|
'auto_hidden_at' => 'datetime',
|
|
'resolved_at' => 'datetime',
|
|
'restored_at' => 'datetime',
|
|
'content_target_id' => 'integer',
|
|
'cluster_score' => 'integer',
|
|
'priority_score' => 'integer',
|
|
'ai_confidence' => 'integer',
|
|
'is_false_positive' => 'boolean',
|
|
'false_positive_count' => 'integer',
|
|
'escalation_status' => ModerationEscalationStatus::class,
|
|
'score_breakdown_json' => 'array',
|
|
];
|
|
|
|
public function artwork(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Artwork::class);
|
|
}
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function reviewer(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'reviewed_by');
|
|
}
|
|
|
|
public function resolver(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'resolved_by');
|
|
}
|
|
|
|
public function restorer(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'restored_by');
|
|
}
|
|
|
|
public function actionLogs(): HasMany
|
|
{
|
|
return $this->hasMany(ContentModerationActionLog::class, 'finding_id')->orderByDesc('created_at');
|
|
}
|
|
|
|
public function aiSuggestions(): HasMany
|
|
{
|
|
return $this->hasMany(ContentModerationAiSuggestion::class, 'finding_id')->orderByDesc('created_at');
|
|
}
|
|
|
|
public function feedback(): HasMany
|
|
{
|
|
return $this->hasMany(ContentModerationFeedback::class, 'finding_id')->orderByDesc('created_at');
|
|
}
|
|
|
|
public function isPending(): bool
|
|
{
|
|
return $this->status === ModerationStatus::Pending;
|
|
}
|
|
|
|
public function isReviewed(): bool
|
|
{
|
|
return in_array($this->status, [
|
|
ModerationStatus::ReviewedSafe,
|
|
ModerationStatus::ConfirmedSpam,
|
|
ModerationStatus::Resolved,
|
|
], true);
|
|
}
|
|
|
|
public function hasMatchedDomains(): bool
|
|
{
|
|
return ! empty($this->matched_domains_json);
|
|
}
|
|
}
|