Zum Hauptinhalt springen

System Jobs

Plattformübergreifende und systemweite Jobs: AI-Detection, Cross-Platform Matching, Profile Retry, Dashboard-Berechnung, Tag-Consolidation, Notifications und Tracking.

DetectProfileLanguage

Erkennt Sprache, Land, Kategorie, Keywords und Beschreibung eines Profils via Google Gemini AI. Rate-Limited über Laravel Queue Middleware.

Location: app/Jobs/DetectProfileLanguage.php

EigenschaftWert
Queueai-detection
Tries3
Backoff1min, 5min, 15min
retryUntil24 Stunden
UniqueJa (24h, detect-language-{profileId})
TriggerScheduler (social:detect-languages --queue, alle 15 min)

Rate Limiting

public function middleware(): array
{
// Rate limit konfigurierbar via AI_ENHANCER_RATE_PER_MINUTE (default: 15)
return [new RateLimited('ai-detection')];
}

Der Scheduler teilt das stündliche Limit in Chunks auf:

Schedule::command('social:detect-languages --queue --queue-limit='
.max(1, intdiv((int) config('services.gemini.hourly_limit', 100), 4)))

Ablauf

  1. Profil laden, Guards prüfen:
    • Bereits vollständige YouTube-Daten (country + language)? Skip
    • Kürzlich erkannt (innerhalb AI_ENHANCER_COOLDOWN_DAYS, default 365)? Skip
  2. ChannelLanguageDetector::detect() aufrufen (Gemini API)
  3. Ergebnisse speichern:
    • detected_language / detected_country (nur wenn YouTube nichts liefert)
    • ai_category + ai_category_confidence
    • ai_keywords + ai_keywords_confidence (mit Tag-Alias-Auflösung)
    • ai_description + ai_description_confidence
    • ai_social_links (nur high-confidence Einträge)
  4. detected_at Timestamp setzen

Tag-Alias-Auflösung

Keywords werden automatisch über TagAlias::resolve() aufgelöst, sodass Gemini-generierte Tags unter ihren konsolidierten Zielnamen gespeichert werden:

$updates['ai_keywords'] = array_values(array_unique(
array_map(fn ($kw) => TagAlias::resolve($kw), $result['keywords'])
));

FindCrossPlatformRelatedProfiles

Findet plattformübergreifende Related Profiles: YouTube-Kanäle zu Instagram-Profilen und umgekehrt. Nutzt ausschließlich lokale Daten (keine API-Calls).

Location: app/Jobs/CrossPlatform/FindCrossPlatformRelatedProfiles.php

EigenschaftWert
Queuecross-platform-related
Tries3
Backoff1min, 5min, 15min
UniqueJa (1h, find-cross-platform-related-{profileId})
TriggerScheduler (cross-platform:queue-related) / User-Klick / Auto-Fill

Ablauf

  1. Profil mit Relations laden (latestMetric, exploreMetrics, instagramKeywords)
  2. Status auf running setzen
  3. Plattformspezifische Berechnung delegieren:
    • YouTube -> CrossPlatformRelatedCalculator::findInstagramForYouTube()
    • Instagram -> CrossPlatformRelatedCalculator::findYouTubeForInstagram()
  4. Status auf completed setzen mit cross_platform_related_calculated_at
  5. RelatedProfilesCalculated Event broadcasten (mit cross_platform_youtube/cross_platform_instagram als Platform)

RetryInactiveProfileScrape

Versucht ein durch Fail-Streak deaktiviertes Profil erneut zu scrapen. Bei Erfolg wird das Profil reaktiviert, bei Misserfolg wird der nächste Retry-Termin berechnet oder das Profil archiviert.

Location: app/Jobs/RetryInactiveProfileScrape.php

EigenschaftWert
Queueprofile-retry
Tries1
Timeout60s
UniqueNein
Triggerprofiles:retry-inactive Command / Admin-Button

Ablauf

  1. Profil laden, Guards prüfen (bereits reaktiviert? archiviert? leere URL?)
  2. EnsureSocialProfileFromUrl::execute() aufrufen (Scrape-Versuch)
  3. Erfolg: Profil vollständig reaktivieren + ProfileReactivated Event dispatchen
  4. Fehler: Retry-Schedule aktualisieren oder archivieren

Retry-Intervalle

// Konfigurierbar via config('postbox.profile_retry')
$initialRetryCount = 3; // Erste 3 Retries
$initialRetryDays = 14; // Alle 14 Tage
$monthlyRetryDays = 30; // Danach monatlich
$archiveAfterMonths = 6; // Nach 6 Monaten archivieren
Retry #IntervallBeschreibung
1-314 TageInitiale Phase
4+30 TageMonatliche Checks
Nach 6 Monaten--Permanent archiviert (archived_at)

Reaktivierung bei Erfolg

$profile->forceFill([
'tracking_enabled' => true,
'deactivated_at' => null,
'scrape_fail_streak' => 0,
'next_retry_at' => null,
'retry_count' => 0,
'api_status' => 'active',
]);

BuildOwnerDashboardSnapshots

Berechnet Dashboard-Rollups für einen einzelnen Owner. Wird von der Dashboard-View getriggert wenn Rollups veraltet sind.

Location: app/Jobs/BuildOwnerDashboardSnapshots.php

EigenschaftWert
Queuedefault (database connection)
Tries1
Timeout120s (2 Min)
UniqueNein
TriggerDashboard Livewire Component (DashboardSnapshotRefresher)

Hinweis: Dieser Job wurde von sync Connection auf die database Queue umgestellt. Auf der sync Connection gab es kein Timeout-Enforcement — der Job blockierte FPM-Worker fuer 2+ Stunden ohne Abbruch.

Ablauf

  1. Metriken-Range für Owner ermitteln (DashboardSnapshotRefresher::metricsRangeForOwner)
  2. Range auf max 12 Monate und bis gestern limitieren
  3. dashboard:rollup-daily-metrics --skip-leaderboards aufrufen (schnelles INSERT...SELECT)
  4. dashboard:rollup-leaderboards nur für den letzten Tag aufrufen (statt volle Range)
  5. Build-Marker aus Cache löschen

Optimierung (2026-03-12): Vorher rief der Job dashboard:rollup-daily-metrics ohne --skip-leaderboards auf, was automatisch dashboard:rollup-leaderboards für die gesamte 365-Tage-Range triggerte (~8.000 Queries, 600s+ Timeout). Jetzt wird das Leaderboard separat nur für den letzten Tag berechnet — reduziert die Laufzeit von 600s+ auf unter 30s.


ConsolidateTagChunk

Verarbeitet einen Chunk von AI-Tags durch Gemini für die Tag-Konsolidierung. High-Confidence Merges werden automatisch ausgeführt, Low-Confidence Vorschläge gehen an Admin-Review.

Location: app/Jobs/ConsolidateTagChunk.php

EigenschaftWert
Queuedefault
Tries3
Backoff2min, 5min, 15min
retryUntil12 Stunden
Rate LimitRateLimited('tag-consolidation')
Triggertags:consolidate Command (wöchentlich, Sonntag 18:00)

Ablauf

  1. TagConsolidator::consolidateChunk() aufrufen (Gemini API)
  2. High-Confidence Merges automatisch ausführen (TagMerger::executeMerge)
  3. Suppress-Aktionen immer ausführen (TagMerger::executeSuppress)
  4. Bei letztem Chunk: Admin-Notification über pending Low-Confidence Merges

SendNotificationEmail

Sendet Benachrichtigungs-E-Mails unter Berücksichtigung von Flood Guard, Quiet Hours und User-Preferences.

Location: app/Jobs/SendNotificationEmail.php

EigenschaftWert
Queueemails
Tries5
Backoff30s, 1min, 2min, 5min, 10min
TriggerNotificationService

Guards

  1. Flood Guard: Bei aktiver Pause -> release(300) (5 min Delay)
  2. User Preferences: E-Mail-Kanal für diesen Typ deaktiviert? -> Discard
  3. Quiet Hours: Innerhalb der Ruhezeiten? -> release() bis nextActiveTime

RefreshSocialProfile

Aktualisiert Profil-Daten via Scraper. Nur für YouTube-Profile aktiv (Instagram läuft über Collector).

Location: app/Jobs/RefreshSocialProfile.php

EigenschaftWert
Queuedefault
TriggerDiverse (Rescrape-Button, etc.)

Guards

  • Profil existiert nicht? Skip
  • tracking_enabled ist false? Skip
  • Platform ist Instagram? Skip (Collector-basiert)
  • Leere canonical_url? Skip

TrackMatomoPageView / TrackMatomoEvent

Server-seitiges Matomo-Tracking für Page Views und Custom Events.

Location: app/Jobs/TrackMatomoPageView.php, app/Jobs/TrackMatomoEvent.php

EigenschaftWert
Queuematomo
Tries5
Timeout15s
Backoff30s, 2min, 5min, 15min
TriggerTrackPageView Middleware / diverse Services

Fehlerbehandlung

Non-critical: Bei letztem Retry-Versuch wird nur eine Warning geloggt statt eines Error-Reports, um Noise bei Matomo-Downtime zu vermeiden:

if ($this->attempts() < $this->tries) {
throw $e; // Retry
}
Log::warning('Matomo page view tracking failed after all retries');

ImportContactLinkProfile

Importiert ein SocialProfile aus einem freigegebenen Kontaktlink. Erstellt bei Bedarf ein neues Profil (YouTube: Scrape, Instagram: minimal record) und verknüpft es mit einem Watcher im Workspace.

Location: app/Jobs/ImportContactLinkProfile.php

EigenschaftWert
Queueimports-youtube
Tries0 (unbegrenzt)
maxExceptions3
retryUntil24 Stunden
Backoff30s, 2min, 10min
UniqueJa (contact-link-{linkId})
TriggerAdmin-Approval in ContactLinks Component

Ablauf

  1. Link laden, Guards prüfen (existiert? bereits verknüpft? importierbar?)
  2. Profil suchen/erstellen:
    • YouTube: EnsureSocialProfileFromUrl (vollständiger Scrape)
    • Instagram: minimaler Record (Collector füllt später)
  3. Watcher prüfen/erstellen im Ziel-Workspace
  4. Link aktualisieren (linked_social_profile_id, import_status = completed)
  5. Auto-Approval: andere Links mit gleichem Handle+Platform werden auch verknüpft
  6. Cross-References: bei bidirektionalen Links → CrossPlatformRelatedProfile-Einträge
  7. Notification an Workspace + Broadcast Event

Fehlerbehandlung

  • Quota-Errors (quotaExceeded, rateLimitExceeded, HTTP 429): Release mit 1h Delay
  • Andere Fehler: import_status = failed, import_error gespeichert, Exception re-thrown
  • failed() Method: Setzt import_status = failed als Fallback

ProcessYouTubeResearchBatch

Verarbeitet eine Batch von YouTube-Research-Keywords sequentiell. Fuer jedes Keyword wird die YouTube Search API abgefragt, optional mit Qualitaetsfilter (Subscriber/Video-Count).

Location: app/Jobs/ProcessYouTubeResearchBatch.php

EigenschaftWert
Queueimports-youtube-priority
Tries3
Timeout600s (10 min)
UniqueJa (youtube-research-batch-{batchId})
TriggerAdmin/YouTubeResearch/Index::startBatchSearch()

Ablauf

  1. Alle YouTubeResearchQuery-Eintraege der Batch laden (Status pending)
  2. Fuer jedes Keyword: a. Status auf running setzen b. YouTube Search API abfragen (max 1000 Ergebnisse, Paginierung ueber nextPageToken) c. Optional: channels.list API fuer Qualitaetsfilter (50 Channels pro Chunk) d. Ergebnisse in result_urls, filtered_count speichern e. YouTubeResearchBatchProgress-Event broadcasten
  3. Batch-Status auf completed setzen

Qualitaetsfilter

Wenn min_subscribers oder min_videos gesetzt sind, wird fuer die gefundenen Channel-IDs ein channels.list-Call gemacht (1 Quota-Unit pro Call, 50 Channels pro Chunk). Channels unterhalb der Schwellwerte werden herausgefiltert. filtered_count zeigt die Anzahl nach Filterung.

Fehlerbehandlung

  • Per-Keyword-Fehler stoppen nicht die Batch (werden in error_message/error_payload gespeichert)
  • Quota-Fehler: Batch wird abgebrochen, Status failed
  • YouTubeResearchBatchProgress-Event wird auch bei Fehlern gesendet