feat: ship creator journey v2 and profile updates
This commit is contained in:
219
app/Services/Maturity/ArtworkMaturityAuditService.php
Normal file
219
app/Services/Maturity/ArtworkMaturityAuditService.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Maturity;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkMaturityAuditFinding;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class ArtworkMaturityAuditService
|
||||
{
|
||||
public function eligibleArtworkQuery(bool $includeExistingOpenFindings = false): Builder
|
||||
{
|
||||
$query = Artwork::query()
|
||||
->whereNotNull('hash')
|
||||
->whereNotNull('thumb_ext')
|
||||
->whereRaw('TRIM(hash) != ?',[ '' ])
|
||||
->whereRaw('TRIM(thumb_ext) != ?',[ '' ]);
|
||||
|
||||
$this->applyLegacyUnsetFilter($query);
|
||||
|
||||
if (! $includeExistingOpenFindings && Schema::hasTable('artwork_maturity_audit_findings')) {
|
||||
$query->whereDoesntHave('maturityAuditFinding', function (Builder $finding): void {
|
||||
$finding->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN);
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function openFindingsQuery(): Builder
|
||||
{
|
||||
return ArtworkMaturityAuditFinding::query()
|
||||
->with(['artwork.user.profile', 'artwork.group', 'artwork.categories.contentType'])
|
||||
->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
|
||||
->whereHas('artwork', function (Builder $query): void {
|
||||
$this->applyLegacyUnsetFilter($query);
|
||||
});
|
||||
}
|
||||
|
||||
public function openFindingsCount(): int
|
||||
{
|
||||
if (! Schema::hasTable('artwork_maturity_audit_findings')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) $this->openFindingsQuery()->count();
|
||||
}
|
||||
|
||||
public function isArtworkEligible(Artwork $artwork): bool
|
||||
{
|
||||
return ! (bool) $artwork->is_mature
|
||||
&& in_array((string) ($artwork->maturity_level ?? ArtworkMaturityService::LEVEL_SAFE), ['', ArtworkMaturityService::LEVEL_SAFE], true)
|
||||
&& in_array((string) ($artwork->maturity_status ?? ArtworkMaturityService::STATUS_CLEAR), ['', ArtworkMaturityService::STATUS_CLEAR], true)
|
||||
&& in_array((string) ($artwork->maturity_source ?? ArtworkMaturityService::SOURCE_LEGACY), ['', ArtworkMaturityService::SOURCE_LEGACY], true)
|
||||
&& $artwork->maturity_declared_at === null
|
||||
&& $artwork->maturity_reviewed_at === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $assessment
|
||||
*/
|
||||
public function shouldOpenFinding(array $assessment): bool
|
||||
{
|
||||
$status = Str::lower(trim((string) ($assessment['status'] ?? ArtworkMaturityService::AI_STATUS_FAILED)));
|
||||
if ($status !== ArtworkMaturityService::AI_STATUS_SUCCEEDED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actionHint = Str::lower(trim((string) ($assessment['action_hint'] ?? '')));
|
||||
if (in_array($actionHint, [ArtworkMaturityService::AI_ACTION_REVIEW, ArtworkMaturityService::AI_ACTION_FLAG_HIGH], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$label = Str::lower(trim((string) ($assessment['maturity_label'] ?? '')));
|
||||
$confidence = is_numeric($assessment['confidence'] ?? null) ? (float) $assessment['confidence'] : 0.0;
|
||||
|
||||
return $label === ArtworkMaturityService::LEVEL_MATURE
|
||||
&& $confidence >= (float) config('maturity.ai.threshold', 0.68);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $assessment
|
||||
*/
|
||||
public function recordFinding(Artwork $artwork, array $assessment, string $thumbnailVariant): ArtworkMaturityAuditFinding
|
||||
{
|
||||
$finding = ArtworkMaturityAuditFinding::query()->updateOrCreate(
|
||||
['artwork_id' => (int) $artwork->id],
|
||||
[
|
||||
'status' => ArtworkMaturityAuditFinding::STATUS_OPEN,
|
||||
'thumbnail_variant' => $thumbnailVariant,
|
||||
'ai_label' => $this->nullableLowerString($assessment['maturity_label'] ?? null),
|
||||
'ai_confidence' => $this->nullableFloat($assessment['confidence'] ?? null),
|
||||
'ai_score' => $this->nullableFloat($assessment['score'] ?? ($assessment['confidence'] ?? null)),
|
||||
'ai_labels' => $this->normalizeLabels($assessment['labels'] ?? []),
|
||||
'ai_model' => $this->nullableString($assessment['model'] ?? null),
|
||||
'ai_threshold_used' => $this->nullableFloat($assessment['threshold_used'] ?? null),
|
||||
'ai_analysis_time_ms' => is_numeric($assessment['analysis_time_ms'] ?? null) ? (int) $assessment['analysis_time_ms'] : null,
|
||||
'ai_action_hint' => $this->nullableLowerString($assessment['action_hint'] ?? null),
|
||||
'ai_status' => $this->nullableLowerString($assessment['status'] ?? ArtworkMaturityService::AI_STATUS_FAILED) ?? ArtworkMaturityService::AI_STATUS_FAILED,
|
||||
'ai_advisory' => $this->nullableString($assessment['advisory'] ?? null),
|
||||
'detected_at' => now(),
|
||||
'last_scanned_at' => now(),
|
||||
'resolution_action' => null,
|
||||
'resolution_note' => null,
|
||||
'resolved_by' => null,
|
||||
'resolved_at' => null,
|
||||
],
|
||||
);
|
||||
|
||||
return $finding->fresh(['artwork']);
|
||||
}
|
||||
|
||||
public function markFindingCleared(Artwork $artwork, ?string $note = null): void
|
||||
{
|
||||
ArtworkMaturityAuditFinding::query()
|
||||
->where('artwork_id', (int) $artwork->id)
|
||||
->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
|
||||
->update([
|
||||
'status' => ArtworkMaturityAuditFinding::STATUS_CLEARED,
|
||||
'resolution_action' => 'auto_cleared',
|
||||
'resolution_note' => $note,
|
||||
'resolved_at' => now(),
|
||||
'last_scanned_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function resolveFindingForReview(Artwork $artwork, Authenticatable $moderator, string $action, ?string $note = null): void
|
||||
{
|
||||
$moderatorId = (int) $moderator->getAuthIdentifier();
|
||||
|
||||
ArtworkMaturityAuditFinding::query()
|
||||
->where('artwork_id', (int) $artwork->id)
|
||||
->where('status', ArtworkMaturityAuditFinding::STATUS_OPEN)
|
||||
->update([
|
||||
'status' => ArtworkMaturityAuditFinding::STATUS_REVIEWED,
|
||||
'resolution_action' => Str::lower(trim($action)),
|
||||
'resolution_note' => $note,
|
||||
'resolved_by' => $moderatorId,
|
||||
'resolved_at' => now(),
|
||||
'last_scanned_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function applyLegacyUnsetFilter(Builder $query): Builder
|
||||
{
|
||||
return $query
|
||||
->where(function (Builder $builder): void {
|
||||
$builder->whereNull('maturity_declared_at')
|
||||
->whereNull('maturity_reviewed_at')
|
||||
->where(function (Builder $state): void {
|
||||
$state->whereNull('maturity_source')
|
||||
->orWhere('maturity_source', ArtworkMaturityService::SOURCE_LEGACY);
|
||||
})
|
||||
->where(function (Builder $state): void {
|
||||
$state->whereNull('maturity_status')
|
||||
->orWhere('maturity_status', ArtworkMaturityService::STATUS_CLEAR);
|
||||
})
|
||||
->where(function (Builder $state): void {
|
||||
$state->whereNull('maturity_level')
|
||||
->orWhere('maturity_level', ArtworkMaturityService::LEVEL_SAFE);
|
||||
})
|
||||
->where(function (Builder $state): void {
|
||||
$state->whereNull('is_mature')
|
||||
->orWhere('is_mature', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function nullableFloat(mixed $value): ?float
|
||||
{
|
||||
return is_numeric($value) ? (float) $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function nullableString(mixed $value): ?string
|
||||
{
|
||||
$resolved = trim((string) $value);
|
||||
|
||||
return $resolved !== '' ? $resolved : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function nullableLowerString(mixed $value): ?string
|
||||
{
|
||||
$resolved = $this->nullableString($value);
|
||||
|
||||
return $resolved !== null ? Str::lower($resolved) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return list<string>|null
|
||||
*/
|
||||
private function normalizeLabels(mixed $value): ?array
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$labels = array_values(array_filter(array_map(
|
||||
static fn (mixed $label): string => Str::lower(trim((string) $label)),
|
||||
$value,
|
||||
)));
|
||||
|
||||
return $labels !== [] ? array_values(array_unique($labels)) : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user