Zum Hauptinhalt springen

Scoring

Postbox berechnet zwei unabhaengige Score-Typen: Den Postbox Score (Profil-Level) und den Video Performance Score (VPS) (Video-Level, nur YouTube).

Postbox Score

Score von 0-100, tier-normalisiert, damit schnell wachsende kleine Kanaele dieselben Scores erreichen koennen wie grosse etablierte Kanaele.

Gewichtung

KomponenteGewichtBeschreibung
Growth40%Relatives Follower-Wachstum vs. Tier-Erwartung
Momentum30%Beschleunigung des Wachstums (letzte 7d vs. vorherige 7d)
Consistency20%Datenabdeckung der letzten 14 Tage
Engagement10%Plattformspezifische Aktivitaets-Metriken

Tier-Definitionen

TierFollower-BereichErwartete taegliche Growth-Rate
micro0 - 10.0000,5%
small10.000 - 100.0000,3%
medium100.000 - 500.0000,2%
large500.000 - 1.000.0000,1%
mega1.000.000+0,05%
public const TIERS = [
'micro' => [0, 10_000],
'small' => [10_000, 100_000],
'medium' => [100_000, 500_000],
'large' => [500_000, 1_000_000],
'mega' => [1_000_000, PHP_INT_MAX],
];

Location: app/Services/Scoring/ProfileScoreCalculator.php

Status

StatusDatenpunkteBeschreibung
no_data0Kein Datenpunkt innerhalb von 5 Tagen
pending< 7Zu wenig Daten fuer Berechnung
preliminary7 - 9Vorlaeufiger Score
stable10+Stabiler Score
public const MIN_DATA_POINTS = 7;
public const STABLE_DATA_POINTS = 10;
public const MAX_DATA_GAP_DAYS = 5;

Growth Score (40%)

Vergleicht die durchschnittliche taegliche Growth-Rate mit der Tier-Erwartung:

$dailyGrowthRate = $totalGrowthRate / $days;
$score = min(1, ($dailyGrowthRate / ($expectation * 2)) + 0.25);

Tier-Erwartung erreichen = Score ~0.5. Doppelte Erwartung = Score ~1.0.

Momentum Score (30%)

Vergleicht das Wachstum der letzten 7 Tage mit den vorherigen 7 Tagen:

  • Positiver Momentum (Wachstum beschleunigt sich) = Score > 0.5
  • Negativer Momentum (Wachstum verlangsamt sich) = Score < 0.5
  • Normalisierung: -0.05 bis +0.05 Acceleration mapped auf 0-1

Consistency Score (20%)

Lineare Skalierung basierend auf Datenabdeckung:

$score = ($daysWithData - MIN_DATA_POINTS) / (14 - MIN_DATA_POINTS);

7 Datenpunkte = 0, 14 Datenpunkte = 1.0.

Engagement Score (10%) — V2: Unified Upload Activity

Seit V2 verwenden beide Plattformen eine einheitliche Upload-Aktivitaets-Metrik fuer faire Vergleichbarkeit:

PlattformMetrikErwartung (Max Score)Beschreibung
YouTubevideo_count Growth0.15/Tag (≈ 1 Video/Woche)Upload-Frequenz basierend auf video_count Delta
Instagrampost_count Growth0.3/Tag (≈ 2 Posts/Woche)Upload-Frequenz basierend auf post_count Delta

YouTube Bonus: Wenn view_count verfuegbar ist, wird der Engagement-Score aus 60% Upload-Aktivitaet + 40% View-Engagement (views/follower, max bei 10) gemischt.

Hintergrund: Die V1-Formel nutzte fuer YouTube views/follower (lifetime) und fuer Instagram posts/day. Das fuehrte zu Instagram-Dominanz in den Top Scores, da 2 Posts/Woche = max Score, waehrend YouTube unrealistische View/Follower-Ratios brauchte.

Location: app/Services/Scoring/ProfileScoreCalculator.php (Methode calculateEngagementScore())

Tier-Relevanz-Multiplier (V2)

Micro/Small-Profile erhalten einen leichten Score-Abzug, damit groessere, relevantere Profile in Rankings dominieren:

TierMultiplierEffekt
micro0.85-15%
small0.95-5%
medium1.0kein Abzug
large1.0kein Abzug
mega1.0kein Abzug

Konfigurierbar via config('postbox.scoring.tier_relevance_multiplier').

Follower-Filter (V2)

Zwei separate Schwellenwerte:

ConfigDefaultBedeutung
postbox.scoring.min_followers_for_scoring100Minimum fuer Score-Berechnung
postbox.scoring.min_followers_for_rankings500Minimum fuer Explore-Rankings (Top Scores, Breakouts)

Wichtig: V2 prueft social_profiles.followers_count (aktuell) statt social_profile_daily_metrics.followers_count (historisch). Profile die unter den Schwellenwert gefallen sind, werden nicht mehr gescored.

Plattform-Balance in Explore Top Scores (V2)

Statt die Top 14 Profile rein nach Score zu sortieren (was zu Instagram-Dominanz fuehrte), werden pro Plattform separate Top-N abgefragt und dann gemischt:

'top_scores_per_platform' => [
'youtube' => 7,
'instagram' => 7,
],

Konfigurierbar via config('postbox.scoring.top_scores_per_platform').

Manual Penalty

User und Admins koennen eine manuelle Score-Strafe (0-100%) setzen. Der Standard-Toggle setzt 25%:

if ($profile->manual_penalty > 0) {
$penaltyMultiplier = 1 - ($profile->manual_penalty / 100);
$rawScore = $rawScore * $penaltyMultiplier;
}

Die Penalty wird NACH dem Tier-Relevanz-Multiplier und Following-Penalty angewandt, d.h. der 25%-Buffer reduziert den bereits adjustierten Score.

Eine Penalty von 25% reduziert den Score proportional (z.B. 80 wird zu 60).

Location: app/Livewire/Watchers/Show.php (Methode toggleScorePenalty())

Video Performance Score (VPS)

Score von 0-100 pro YouTube-Video, ebenfalls tier-normalisiert basierend auf dem Follower-Tier des Kanals.

Gewichtung

KomponenteGewichtBeschreibung
View Performance40%Views relativ zu Tier-Erwartung
Engagement25%Like-to-View + Comment-to-View Ratios
Growth Trajectory20%View-Wachstums-Beschleunigung
Freshness15%Recency-Bonus fuer neuere Videos

View Performance (40%)

Vergleicht durchschnittliche taegliche Views mit Tier-Erwartungen:

TierErwartete Views/Tag
micro500
small2.000
medium10.000
large50.000
mega200.000

Sehr kurze Videos (< 60 Sekunden) erhalten einen milden View-Performance-Abzug (1.5x hoehere Erwartung), da sie oft durch Autoplay inflated Views akkumulieren.

private const SHORT_DURATION_VIEW_MULTIPLIER = 1.5;

Logarithmische Skalierung verhindert, dass Ausreisser dominieren:

$score = 0.5 + 0.5 * log10(max(0.01, $ratio));

Engagement (25%)

Gewichtet Like-to-View (70%) und Comment-to-View (30%) Ratios:

TierErwartete Like-Rate
micro5%
small4%
medium3,5%
large3%
mega2,5%

Growth Trajectory (20%)

Vergleicht die View-Wachstumsrate der juengeren Haelfte mit der aelteren Haelfte der Datenpunkte. Beschleunigende Videos scoren hoeher.

Freshness (15%)

Linearer Decay ueber 30 Tage:

private const MAX_FRESHNESS_DAYS = 30;
// Heute veroeffentlicht = 1.0, 30+ Tage alt = 0.0
$score = 1.0 - ($ageInDays / self::MAX_FRESHNESS_DAYS);

VPS Status

StatusDatenpunkteBeschreibung
no_data0Keine Metriken
pending< 3Zu wenig Daten
preliminary3 - 6Vorlaeufiger Score
stable7+Stabiler Score

Location: app/Services/Scoring/VideoScoreCalculator.php

Schedule

Scores werden taeglich um 05:00 UTC berechnet:

php artisan scores:calculate

Der Command iteriert ueber alle aktiven Profile (nicht blocked, nicht sanitized, nicht archived, tracking enabled) und berechnet den Score fuer den aktuellen Tag.

Explore-Integration: Score-Fallback-Logik

Die Explore-Seiten (/explore, /explore/browse) zeigen den Postbox Score auf allen Profilkarten an. Da scores:calculate taeglich um 05:00 UTC laeuft, koennen zwischen Mitternacht und 05:00 UTC keine Scores fuer den Vortag existieren.

Fallback-Mechanismus

Statt strikt date = yesterday zu joinen, nutzt die Query den neuesten verfuegbaren Score innerhalb eines 7-Tage-Fensters:

$yesterday = now()->subDay()->toDateString();
$scoreFloor = now()->subDays(7)->toDateString();

// Schritt 1: Neuestes Score-Datum pro Profil finden
$latestScoreDates = DB::table('social_profile_scores')
->select('social_profile_id', DB::raw('MAX(date) as max_date'))
->where('date', '<=', $yesterday)
->where('date', '>=', $scoreFloor)
->groupBy('social_profile_id');

// Schritt 2: Score fuer dieses Datum joinen
->leftJoinSub($latestScoreDates, 'latest_score_dates', ...)
->leftJoin('social_profile_scores', fn ($join) =>
$join->on('social_profile_scores.date', '=', 'latest_score_dates.max_date')
)

Verhalten nach Tageszeit

Zeitpunkt (UTC)Abgefragtes DatumErgebnis
14.02. 02:00Score bis max 13.02.Zeigt 12.02. Score (13.02. existiert noch nicht)
14.02. 05:15Score bis max 13.02.Zeigt 13.02. Score (gerade berechnet)
14.02. 23:00Score bis max 13.02.Zeigt 13.02. Score
15.02. 00:01Score bis max 14.02.Zeigt 13.02. Score (14.02. noch nicht berechnet)

Score-Anzeige auf Profilkarten

Zwei Varianten je nach Datenverfuegbarkeit:

  1. Postbox Score vorhanden → Blauer Gradient-Bar (from-cyan-500 to-blue-600) + Zahl
  2. Kein Postbox Score, aber Trending Score > 0 → Oranger Gradient-Bar (from-orange-500 to-red-500) als Fallback

Location: app/Livewire/Explore/Index.php, app/Livewire/Explore/ProfileGrid.php, resources/views/components/explore/profile-card.blade.php

UI-Integration

Score Chart (Watchers\ScoreChart)

Zeigt den Score-Verlauf der letzten 30 Tage auf der Watcher-Detailseite. Nutzt ApexCharts (CDN) mit Alpine.js-Integration (siehe Technical Debt fuer Hintergrund).

Features:

  • Zwei Serien: Durchgezogene Linie (echte Datenpunkte) + gestrichelte Linie (interpolierte Luecken)
  • Gap-Interpolation: Fehlende Tage werden linear zwischen den Nachbarpunkten interpoliert. Boundary-Points ueberlappen, damit die gestrichelte Linie nahtlos anschliesst.
  • Custom Tooltips: Datum (deutsch formatiert), Score-Wert, Delta zum Vortag mit Farb-Indikator
  • Feste Y-Achse: 0-100 (Score-Bereich)
  • Score-Box: Aktuelle Score-Zahl links, Delta vs. gestern, Status-Badge (Vorlaeufig/Stabil)
  • "Aehnliche anzeigen"-Button: Link zu /explore/browse?sort=score&minScore=X&maxScore=Y
  • Erklaerungsmodal: Aufschluesselung der Score-Gewichtung

Location: app/Livewire/Watchers/ScoreChart.php, resources/views/livewire/watchers/score-chart.blade.php

Video Performance Summary

Auf der Watcher-Detailseite wird eine Video Performance Summary angezeigt:

  • Durchschnitts-Score
  • Anzahl gescorter Videos
  • Top 3 und Flop 3 Videos
// Auf Basis der juengsten YouTubeVideoScore-Eintraege
$scores = YouTubeVideoScore::where('social_profile_id', $profileId)
->where('date', $latestDate)
->whereNotNull('score')
->orderByDesc('score')
->get();

Location: app/Livewire/Watchers/Show.php (Methode buildVideoPerformanceSummary())

Video Score Chart (Watchers\VideoScoreChart)

Per-Video Score-Verlauf (30 Tage) auf der Watcher-Detailseite. Adaptiert vom ScoreChart-Pattern, mit Video-spezifischer Score-Erklaerung (View Performance 40%, Engagement 25%, Growth 20%, Freshness 15%).

Features:

  • Gleiche Zwei-Serien-Logik (solid + dashed) wie ScoreChart
  • Gap-Interpolation fuer fehlende Tage
  • Wird nur gerendert wenn >= 2 Score-Datenpunkte vorhanden (chartEligible)
  • Kein "Aehnliche anzeigen"-Button, kein Followers-Check
  • Eingebettet unterhalb jedes Video-Cards

Location: app/Livewire/Watchers/VideoScoreChart.php, resources/views/livewire/watchers/video-score-chart.blade.php

Eigene Top-Level-Seite mit Video-Leaderboards. Vier Sektionen:

  1. KPI-Boxen (Videos mit Score, Ø Score, Top Performer, Im Trend)
  2. Top/Flop Performer (2 Spalten, Score-sortiert)
  3. Groesstes Wachstum / Rueckgang (2 Spalten, View-Growth-sortiert)
  4. Aktuell im Trend (2-Spalten-Grid, Trending-Score-sortiert)

Filter: Duration, Kategorie, Favoriten, Zeitraum (7d/30d). Caching: 1h TTL (global), Favorites in-memory.

Location: app/Livewire/VideoTrends/Index.php, resources/views/livewire/video-trends/index.blade.php

Watcher Show: Video-Sektion fuer alle User

Die Video-Sektion auf der Watcher-Detailseite (YouTube Videos) ist fuer alle authentifizierten User sichtbar. Admin-only Bereiche:

  • Score-Breakdown-Bars (Views/Engagement/Wachstum/Aktualitaet)
  • "Weitere Details" (JSON-Payloads, Metadaten)

Nicht-Admin-User sehen: Video-Thumbnail, Titel, Score-Badge, Performance Score, und den VideoScoreChart (wenn eligible).

Location: resources/views/livewire/watchers/show.blade.php