find($this->pressReleaseId); if ($pressRelease === null) { return; } $analyzedHash = $this->contentHash ?? $pressRelease->content_hash; $result = $this->classify($manager, $pressRelease); // Inhalt seit der Einplanung geändert? Dann gilt das Urteil für einen // Text, den es nicht mehr gibt: verwerfen und den aktuellen prüfen. $currentHash = PressRelease::withoutGlobalScope(PortalScope::class)->whereKey($pressRelease->id)->value('content_hash'); if ($analyzedHash !== null && $currentHash !== null && $currentHash !== $analyzedHash) { KiAudit::query()->create([ 'press_release_id' => $pressRelease->id, 'type' => KiAudit::TYPE_CLASSIFICATION, 'provider' => $result->provider, 'model' => $result->model, 'result' => $result->classification->value, 'reason' => 'Verworfen: Inhalt wurde während der Prüfung geändert. '.$result->reasonText(), 'raw_response' => $result->rawResponse, 'content_hash' => $analyzedHash, 'discarded_at' => now(), 'created_at' => now(), ]); self::dispatch($pressRelease->id, $this->route, $this->providerOverride, $currentHash)->onQueue('classification'); return; } $pressRelease->forceFill([ 'classification' => $result->classification->value, 'classified_at' => now(), ])->save(); KiAudit::query()->create([ 'press_release_id' => $pressRelease->id, 'type' => KiAudit::TYPE_CLASSIFICATION, 'provider' => $result->provider, 'model' => $result->model, 'result' => $result->classification->value, 'reason' => $result->reasonText(), 'raw_response' => $result->rawResponse, 'content_hash' => $analyzedHash, 'created_at' => now(), ]); if (! $this->route) { return; } try { $service->routeByClassification($pressRelease, $result->classification, $result->reasonText()); } catch (BlacklistViolationException) { // publish() hat die PM bereits abgelehnt und den Autor benachrichtigt. } catch (QuotaExceededException $exception) { // Kontingent zwischen Einreichung und KI-Freigabe aufgebraucht: PM // bleibt in Review, damit Autor/Admin bewusst entscheiden — kein // Job-Retry, der die Klassifikation sinnlos wiederholt. Log::info('Auto-Veröffentlichung übersprungen: PM-Kontingent aufgebraucht, PM bleibt in Review.', [ 'press_release_id' => $pressRelease->id, ]); } } /** * Klassifiziert über den aktiven (oder explizit gewählten) Treiber; bei * Fehler greift der deterministische Fallback, damit das Ergebnis * nachvollziehbar bleibt. */ private function classify(ClassificationManager $manager, PressRelease $pressRelease): ClassificationResult { $provider = $this->providerOverride ?: $manager->getDefaultDriver(); try { return $manager->driver($provider)->classify($pressRelease); } catch (\Throwable $exception) { Log::warning('KI-Klassifikation fiel auf den deterministischen Treiber zurück.', [ 'press_release_id' => $pressRelease->id, 'provider' => $provider, 'error' => $exception->getMessage(), ]); return $manager->driver('deterministic')->classify($pressRelease); } } }