Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Services\Maturity\ArtworkMaturityAuditService;
use App\Services\Maturity\ArtworkMaturityService;
use App\Services\Vision\VisionService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Throwable;
final class AuditArtworkMaturityThumbnailsCommand extends Command
{
protected $signature = 'artworks:audit-thumbnail-maturity
{--id= : Audit only this artwork ID}
{--after-id=0 : Skip artworks with ID less than or equal to this value}
{--limit= : Stop after processing this many artworks}
{--chunk=25 : Number of artworks to scan per batch}
{--variant= : Thumbnail variant to analyze (defaults to vision.image_variant)}
{--refresh : Re-scan artworks that already have an open audit finding}
{--dry-run : Report candidates without writing audit findings}';
protected $description = 'Scan artwork thumbnails for possible mature content without mutating artwork maturity fields.';
public function handle(VisionService $vision, ArtworkMaturityAuditService $audit): int
{
$artworkId = $this->option('id') !== null ? max(1, (int) $this->option('id')) : null;
$afterId = max(0, (int) $this->option('after-id'));
$limit = $this->option('limit') !== null ? max(1, (int) $this->option('limit')) : null;
$chunkSize = max(1, min((int) $this->option('chunk'), 200));
$dryRun = (bool) $this->option('dry-run');
$refresh = (bool) $this->option('refresh');
$variant = trim((string) ($this->option('variant') ?: config('vision.image_variant', 'md')));
if (! $vision->isEnabled()) {
$this->error('Vision maturity analysis is disabled.');
return self::FAILURE;
}
if (! $dryRun && ! Schema::hasTable('artwork_maturity_audit_findings')) {
$this->error('Artwork maturity audit findings table is missing. Run the latest database migrations first.');
return self::FAILURE;
}
$this->info(sprintf(
'Starting artwork maturity thumbnail audit. order=id_desc variant=%s chunk=%d limit=%s refresh=%s dry_run=%s',
$variant !== '' ? $variant : 'md',
$chunkSize,
$limit !== null ? (string) $limit : 'all',
$refresh ? 'yes' : 'no',
$dryRun ? 'yes' : 'no',
));
$query = $audit->eligibleArtworkQuery($refresh)
->orderByDesc('id');
if ($artworkId !== null) {
$query->whereKey($artworkId);
}
if ($afterId > 0) {
$query->where('id', '>', $afterId);
}
$processed = 0;
$flagged = 0;
$safe = 0;
$written = 0;
$failed = 0;
$query->chunkByIdDesc($chunkSize, function ($artworks) use ($vision, $audit, $variant, $limit, $dryRun, $refresh, &$processed, &$flagged, &$safe, &$written, &$failed) {
foreach ($artworks as $artwork) {
if ($limit !== null && $processed >= $limit) {
return false;
}
try {
$assessment = (array) ($vision->analyzeArtworkMaturityDetailed($artwork, (string) $artwork->hash, $variant)['assessment'] ?? []);
$processed++;
if ($audit->shouldOpenFinding($assessment)) {
$flagged++;
$message = sprintf(
'Artwork %d flagged for moderator review. action=%s confidence=%s label=%s',
(int) $artwork->id,
(string) ($assessment['action_hint'] ?? 'unknown'),
is_numeric($assessment['confidence'] ?? null) ? number_format((float) $assessment['confidence'], 4, '.', '') : 'n/a',
(string) ($assessment['maturity_label'] ?? 'unknown'),
);
$this->warn($message);
Log::warning('artworks:audit-thumbnail-maturity candidate detected', [
'artwork_id' => (int) $artwork->id,
'title' => (string) $artwork->title,
'assessment' => $assessment,
'variant' => $variant,
]);
if (! $dryRun) {
$audit->recordFinding($artwork, $assessment, $variant !== '' ? $variant : 'md');
$written++;
}
continue;
}
if (($assessment['status'] ?? ArtworkMaturityService::AI_STATUS_FAILED) === ArtworkMaturityService::AI_STATUS_SUCCEEDED) {
$safe++;
$this->line(sprintf('Artwork %d scanned safe for audit purposes.', (int) $artwork->id));
if (! $dryRun && $refresh) {
$audit->markFindingCleared($artwork, 'Thumbnail maturity rescan no longer indicates moderator review.');
}
continue;
}
$failed++;
$this->warn(sprintf(
'Artwork %d maturity audit failed: %s',
(int) $artwork->id,
(string) ($assessment['advisory'] ?? $assessment['status'] ?? 'unknown failure'),
));
} catch (Throwable $exception) {
$processed++;
$failed++;
$this->warn(sprintf('Artwork %d audit failed: %s', (int) $artwork->id, $exception->getMessage()));
Log::warning('artworks:audit-thumbnail-maturity failed', [
'artwork_id' => (int) $artwork->id,
'title' => (string) $artwork->title,
'variant' => $variant,
'error' => $exception->getMessage(),
]);
}
}
return true;
});
$this->info(sprintf(
'Artwork maturity thumbnail audit complete. processed=%d flagged=%d safe=%d written=%d failed=%d',
$processed,
$flagged,
$safe,
$written,
$failed,
));
return $failed > 0 ? self::FAILURE : self::SUCCESS;
}
}