ProjekteFallstudie

Signals

Zwei Werkzeuge, die zuhören: Trenderkennung für 37 Subreddits und Live-Untertitel für Vorträge in drei Sprachen.

Rolle
Solo · Konzept und Statistik, gebaut mit Agent-Workflow
Zeitraum
2026
Stack
Python 3.13, ClickHouse, Grafana, NumPy, ntfy, Docker Compose, Node.js · React, Deepgram · DeepL
Die Station „Signals“ des Threads auf metz.uk
  • 37beobachtete Subreddits
  • 2 Mio.getestete Begriffe pro LangzeitlaufBenchmark in Produktionsgröße, synthetische Daten
  • 262 sfür ein Jahr Verlauf im Großformat1,03 Milliarden Zeilen mit Tageswerten, ein synthetisches Jahr, 4 CPUs
  • 3Sprachen live untertitelt

Das Problem

Welche Wörter gewinnen in einer Community an Anteil am Gespräch, gemessen an dem, was dort normal ist? Diese Frage habe ich mir 2020 zum ersten Mal gestellt, mit WGRA (Word Growth Rate Analyzer) v1 in TypeScript und MongoDB. Die erste Version konnte sagen, wie sich ein Wort entwickelt, das man schon kannte. Die Wörter, nach denen man nicht zu fragen wusste, fand sie nicht, und ein nächtlicher Job löschte den Long Tail, also genau die Stelle, an der neue Trends entstehen.

Entdecken heißt: jeden Begriff in jeder Community bei jedem Lauf bewerten und dann entscheiden, welche von Tausenden Zahlen echt sind. Das ist zuerst ein Statistikproblem und erst danach ein Speicherproblem.

Das zweite Werkzeug ist einfacher: Wer bei einem Vortrag im Publikum sitzt, soll in der eigenen Sprache mitlesen können.

Was ich gebaut habe

WGRA v2 (Code im Branch wgra-v2) ist ein Python-Dienst für einen einzigen Rechner. Ein Ingester fragt 37 Subreddits, eingeteilt in sechs Gruppen, als Multireddit-Feeds mit je bis zu 25 Subreddits ab, dazu eine Stichprobe aus r/all als Hintergrundvokabular, und bleibt dabei deutlich innerhalb des kostenlosen API-Kontingents von Reddit. Jeder Kommentar und jeder Post wird zu einer Zeile pro Begriff (Wörter, Phrasen aus zwei oder drei Wörtern, verlinkte Domains, erwähnte Subreddits) in einer ClickHouse-Tabelle mit Null-Engine. Materialized Views fassen sie zu stündlichen und täglichen Zählungen zusammen, mit einem HyperLogLog-Sketch der verschiedenen Autoren. Autoren landen nur als Keyed Hash in der Datenbank, Rohtext wird nach 48 Stunden gelöscht.

Alle 15 Minuten bewertet ein Analyzer kurzfristige Ausschläge: die letzten 24 Stunden gegen die 28 Tage davor, die letzten 6 Stunden gegen dieselben Uhrzeiten der Vortage. Einmal am Tag sucht er nach stetigen Trends über 13, 26 und 52 Wochen und lernt, welche Subreddits Begriffe früher aufgreifen als andere. Die Ergebnisse landen in Grafana-Dashboards, die aus Code erzeugt werden; die stärksten schickt ntfy auf mein Handy.

Entworfen und gesteuert habe ich das am 10. und 11. Oktober 2026 mit einem Agent-Workflow. Das Architekturdokument hält jede Entscheidung und jede Messung fest.

LiveTranslate (Repository) ist ein Untertitel-Bildschirm im 16:9-Format für Vorträge. Der Browser nimmt das Mikrofon in Stücken von einer Sekunde als WebM auf und streamt sie per WebSocket an einen kleinen Node-Server, der eine Live-Verbindung zu Deepgram hält. Jede fertige Zeile übersetzt DeepL in die beiden anderen Sprachen aus Deutsch, Englisch und Portugiesisch.

Die schwierigen Teile

Ausschläge bewerten, ohne in seltenen Wörtern zu ertrinken. Ein einfaches Verhältnis von Anteilen (Kommentare mit dem Begriff durch alle Kommentare) explodiert bei seltenen Begriffen: Eine Erwähnung gegen keine ist unendliches Wachstum. Ich nutze das gewichtete Log-Odds-Verhältnis mit informativem Dirichlet-Prior aus Fightin’ Words von Monroe, Colaresi und Quinn. Pseudo-Counts rund um den Anteil des Begriffs in der r/all-Stichprobe ziehen dünne Evidenz in Richtung „normal für Reddit“, und z = δ / σ sortiert nach der Stärke der Evidenz statt nach der Größe des Verhältnisses.

Das Paper vergleicht Korpora ähnlicher Größe. Hier ist der Referenzzeitraum 28-mal so lang wie das Fenster, und gleiche Pseudo-Counts zogen die kleine Seite viel stärker: Ein stabiler Begriff mit 0,1 % in einem Subreddit und 5 % auf ganz Reddit kam auf 3,15× (z 6,5), obwohl sich nichts änderte. Deshalb bekommt jede Seite Pseudo-Counts im Verhältnis zu ihrer eigenen Größe, und ein Begriff mit unverändertem Anteil erhält genau null. Eine Untergrenze von einer Pseudo-Erwähnung hält den Score ganz neuer Namen endlich, und am ersten Tag ersetzt der Reddit-weite Anteil die fehlende Historie. Aus v2/src/wgra/detect/scoring.py:

def score(self, prior: Prior) -> Score:
    # see "the first day" above
    missing_history = max(self.total - self.reference_total, 0)
    return log_odds(
        self.mentions,
        self.total,
        self.reference_mentions,
        self.reference_total,
        prior.pseudo_counts(self.sample_mentions, prior.weight * self.total),
        prior.pseudo_counts(
            self.sample_mentions, prior.weight * self.reference_total + missing_history
        ),
    )

Langzeittrends, die multiples Testen überleben. Wöchentliche Anteile sind verrauscht, oft null und voller Ausreißer. Eine Ausgleichsgerade nach kleinsten Quadraten wählt in ClickHouse günstig die 100 vielversprechendsten Begriffe pro Scope und Zeitfenster aus; auf diese wendet Python die robuste Statistik an. Theil–Sen, der Median aller paarweisen Steigungen, schätzt das Wachstum pro Monat: In Testdaten ergab eine einzige Woche mit dem 20-fachen Anteil bei kleinsten Quadraten +34 % im Monat, bei Theil–Sen +2 %. Mann–Kendall prüft, ob es überhaupt einen Trend gibt, indem er steigende gegen fallende Wochenpaare zählt, mit Korrektur für Bindungen in Wochen ohne Erwähnung.

Benjamini–Hochberg kontrolliert danach die False Discovery Rate über alle Begriffe, die der Lauf getestet hat; Nicht-Kandidaten zählen mit p = 1. Nur über die Kandidaten korrigiert, fand das Verfahren 105 „Trends“ in zwei Subreddits aus reinem Rauschen, weil die Kandidaten die extremsten von Tausenden sind. Über alle gerechnet blieb unter 2 Millionen Rauschbegriffen keiner übrig. Der Preis: Bei 13 Wochenwerten ist das kleinste mögliche Mann–Kendall-p 2,6·10⁻⁶, ein 13-Wochen-Trend muss also fast perfekt monoton sein. Ein Lauf über ein Jahr synthetischer Daten in Produktionsgröße, 1,03 Milliarden Zeilen mit Tageswerten, dauert 262 s auf 4 CPUs mit 8 GB.

Lead–Lag: Zusammenhang ist keine Richtung. Jeder starke Trend wird zu einem Ereignis. Jedes Subreddit und jede Gruppe bekommt dafür einen Onset: die erste Stunde, in der die letzten 24 Stunden die Schwellen gegen die eigene Vergangenheit überschreiten. Gruppen werden zusätzlich ohne jedes einzelne Mitglied gemessen, damit „r/LocalLLaMA führt tech an“ nicht schon per Konstruktion stimmt. Für jedes Paar aus Leader und Follower prüft ein einseitiger Binomialtest über 180 Tage, ob der Follower innerhalb von 7 Tagen häufiger nachzieht, als seine Basisrate erklärt, korrigiert über alle Paare. Im Benchmark fand das die 5 eingebauten Paare und rund 240 zufällige: Große Geschichten erreichen viele Subreddits, also treten beliebige zwei gemeinsam auf.

Die Lösung war ein zweiter Test. Unter den Ereignissen, bei denen beide die Schwelle überschritten, muss der Leader signifikant häufiger zuerst kommen als bei einem Münzwurf, und in mindestens 70 % der Fälle. Übrig blieben 14 Kanten: die 5 eingebauten Paare, 8 Gruppen- und Themenvarianten davon und eine weitere. Die Wahrscheinlichkeiten stammen aus einer Beta-Posterior, die mit vier Pseudo-Ereignissen zur Basisrate hin geschrumpft wird, damit 3 Treffer aus 3 bei einer Basisrate von 0,2 als 54 % (30–77 %) erscheinen und nicht als 100 %. Kanten werden täglich neu gelernt, ohne Blick in die Zukunft.

Was das Replay zeigt und was nicht. Im End-to-End-Test führt ein synthetisches Subreddit ein anderes bei 18 eingebauten Begriffen um drei Tage. Ein tägliches Replay liegt bei 13 von 15 Prognosen richtig (87 % Präzision), bei 23 % Recall und einem Brier-Score von 0,099 gegenüber 0,330 für die reine Basisrate. Im größeren Benchmark, in dem die eingebauten Follower nur in rund 40 % der Fälle folgen, liegt die Präzision bei 43 %, kalibriert (Brier 0,249 gegenüber 0,276). Beides sind Replays auf Daten mit eingebauten Mustern: Sie zeigen, dass die Methode vorhandene Struktur findet, und noch nichts über Reddit.

Untertitel, die mithalten. Eine Zeile endet nach 300 ms Stille. Auf den Bildschirm kommen nur fertige Zeilen, Zeichen für Zeichen getippt, und nur sie gehen an DeepLs latenzoptimiertes Modell; Deepgrams Zwischenergebnisse bleiben unsichtbar.

Stand

Die ersten Live-Tage von WGRA v2 liefen auf meinem Rechner. Die Kurzfristerkennung braucht vier Wochen für ihren vollen Referenzzeitraum, Lead–Lag-Kanten vier bis sechs Wochen, Langzeittrends 13 vollständige Wochen. Echte Langzeitdaten sammeln sich erst ab November 2026. Erklärungen der Trends per LLM sind entworfen, aber nicht gebaut. LiveTranslate besteht aus einem einzigen Commit vom Juli 2026 und braucht Schlüssel für Deepgram und DeepL.