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; } }