Commit Graph

20 Commits

Author SHA1 Message Date
Dotty Dotter
c5489eabaa #16/#17 match_answers.py und match_claims.py: Cross-Episode-Matching via Embeddings + Qwen
scripts/match_answers.py (#17):
- Laedt offene Fragen (genuine, follow_up; answered='no').
- Embedded jede Frage und sucht den besten Kandidat-Absatz aus einer anderen
  Episode (optional cross-podcast) per Cosinus-Aehnlichkeit ueber die paragraph-
  embeddings.
- Bei score >= 0.55: Qwen-Verifikation 'Beantwortet B die Frage in A?'
  (yes/partial/no), bei yes/partial wird answered + answered_by_* in der
  questions-Tabelle gesetzt.
- Hard-Budget 1,50 USD, --rerun setzt bestehende Matches neu.

scripts/match_claims.py (#16 Stufe 2):
- Analoge Mechanik fuer claims: Embedding, Cosinus-Suche, Qwen-Verifikation
  in der vier-stufigen Skala 'belegt' / 'widerspricht' / 'erweitert' / 'kein_bezug'.
- Schreibt Treffer (ohne 'kein_bezug') in neue Tabelle claim_matches.
- Default nur verifizierbare Claims (--include-non-verifiable kippt das),
  --cross-podcast erlaubt Cross-Podcast-Treffer.

Beide Skripte nutzen json_utils.parse_llm_json fuer robustes Parsing und sind
gegen NaN-Vektoren in den Embeddings abgesichert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:21:49 +02:00
Dotty Dotter
6f53f35c09 #10 URL-Routing fuer Podcast-Tiefenlinks
Backend:
- SPA-Fallback: catch-all-Route liefert index.html, falls keine statische Datei
  matcht (mit Ausnahme von /api/* und /audio/*). Dadurch funktionieren Tiefen-
  Links wie /ldn oder /neu-denken direkt.

Frontend:
- loadApp() liest pathname und laedt den passenden Podcast direkt, falls die ID
  in /api/podcasts vorkommt; sonst klassischer Selector.
- selectPodcast() updated den Pfad per history.pushState, damit Bookmarks und
  Sharing funktionieren.
- popstate-Handler reagiert auf Browser-Back/Forward.
- showPodcastList() setzt den Pfad auf '/'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:20:26 +02:00
Dotty Dotter
e1f6f18524 #14/#15/#16 Heatmaps und Drift-Kurve in den Analyse-Views
Backend:
- /api/analyses/density: Faktendichte je Episode in 20 Bins ueber die
  Paragraph-Achse, getrennt nach total und verifizierbar (#16).

Frontend:
- ShiftsView (#15): Inline-SVG-Sparkline ueber die gesamte Drift-Sequenz je Theme,
  mit Schwellen-Linie bei 50% und klickbaren Spike-Markern.
- GapsView (#14): Cluster-Heatmap mit zwei Zeilen (LdN, NEU DENKEN), Cluster-Breite
  proportional zur Cluster-Groesse, Farbe interpoliert von kuehl (geringer Anteil im
  Podcast) zu warm (hoher Anteil); Klick filtert die darunter liegende Liste.
- DensityView (#16): neue View 'Faktendichte', sortiert nach Claims/Absatz,
  pro Episode eine 20-Bin-Heatmap (gruen = verifizierbar, warm = normativ),
  Filter nach Podcast und Sortierung; Klick oeffnet die Episode.
- AnalysisView (#17 questions): zeigt jetzt 'Antwort: <Episode>@p<idx>'-Link
  fuer Fragen mit answered_by_episode; Klick navigiert zur Antwort-Stelle.
- escAttr-Helper, hide-Cascade um DensityView erweitert, Buttons in Selector
  und init() hinzugefuegt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:17:31 +02:00
Dotty Dotter
b73534d1c3 #13/#18 Argumentketten- und Debatten-Views
Backend:
- /api/analyses/debates: liefert kuratierte Cross-Podcast-Gegenueberstellungen mit
  topic, agreement, divergence, insight, beiden Quellabsaetzen und Episodenmetadaten;
  Filter ueber topic, source_podcast, target_podcast.
- /api/analyses/arguments: liefert klassifizierte Argumentketten mit relation,
  confidence, explanation und beiden Quellabsaetzen; Filter ueber relation, podcast,
  episode. Wortwoertlich identische gleicher_punkt-Paare werden ausgeblendet.

Frontend:
- DebatesView: Topic-Chips als Filter, Split-Screen-Quotes je Debatte, Chips fuer
  Uebereinstimmung/Divergenz/Erkenntnis, Klick fuehrt zur Episode mit Audio-Sprung.
- ArgumentsView: farbcodierte Relations-Chips (erweitert blau, widerspricht rot,
  belegt gruen, relativiert grau, gleicher_punkt violett, kein_bezug grau), Konfidenz-
  Anzeige, Filter ueber Podcast, Klick fuehrt zur Episode-Stelle.
- escAttr-Helper fuer onclick-Werte mit Anfuehrungszeichen.
- hide-Cascade aller Views um die beiden neuen erweitert.
- Buttons in showPodcastSelector und init() fuer beide Views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:04:49 +02:00
Dotty Dotter
330b740573 Service-Worker: Cache-Version bumpen + index.html network-first
CACHE_NAME von v1 auf v2 erhoeht; activate-Handler loescht damit den alten Cache und liefert beim Reload die aktuelle index.html. Zusaetzlich laeuft '/' und '/index.html' jetzt network-first, sodass weitere Code-Updates schon beim naechsten Reload greifen statt erst beim uebernaechsten. Der Cache-Fallback bleibt fuer Offline-Betrieb.

Hintergrund: Mit stale-while-revalidate fuer die App-Shell sah der Browser nach dem Mindmap-Fix weiterhin die kaputte index.html, weil die alte Version aus dem Cache kam und das Update erst im Hintergrund geholt wurde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:42:48 +02:00
Dotty Dotter
121b51445b Fix: Mindmap rendert nach Podcast-Auswahl wieder
Beim Wechsel von der Podcast-Auswahl zu einer konkreten Mindmap warf init() einen TypeError, weil showPodcastSelector() das #welcome-panel-Div ueberschrieben hatte und die anschliessende Zuweisung an dessen innerHTML auf null traf. Folge: catch in selectPodcast() loggte den Fehler, init() lief nicht zu Ende, der SVG-Bereich blieb leer.

Zwei Fixes:
- showPodcastSelector setzt die ID 'welcome-panel' beim Replace wieder.
- init() legt das Div bei Bedarf neu an, falls jemand sonst panel.innerHTML ueberschreibt.

Verifiziert via Headless-Browser (puppeteer): LdN 568 Knoten/555 Kanten, NEU DENKEN 186 Knoten/209 Kanten, force-Simulation laeuft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 01:38:06 +02:00
Dotty Dotter
83669c528b #12/#14/#15 webapp: AnalysisView, GapsView, ShiftsView; Mindmap+Timeline-Fallback
- AnalysisView je Episode mit Tabs fuer Claims (#16), Questions (#17), Argumentketten (#13) und Debatten (#18).
- GapsView (#14): Leerstellen-Cluster mit Filtern (Mindestgroesse, fehlt-in-Podcast); Querverweise zu vorhandenen Beispielen.
- ShiftsView (#15): Narrative-Shift je Theme als Drift-Sequenz, Filter ueber Podcast, Theme und Mindest-Drift.
- Mindmap- und Timeline-Komponenten zeigen jetzt einen Fallback-Status statt leerem Bereich, wenn themes oder quotes fehlen (z.B. waehrend laufender Quote-Extraktion).
- Wort-Highlighting (#12): synchronisiert mit Audio-Position via /transcript/.../words-Endpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:31:17 +02:00
Dotty Dotter
d6ccea006a #14/#15/#16/#17 backend: Endpoints fuer Gaps, Shifts, Claims und Questions
- /api/podcasts/{id}/episodes/{ep}/claims: Behauptungen einer Episode, optional gefiltert nach claim_type.
- /api/podcasts/{id}/episodes/{ep}/questions: Fragen der Episode, gefiltert nach Typ und Antwort-Status.
- /api/podcasts/{id}/episodes/{ep}/analyses-summary: Zaehler fuer die UI-Buttons (claims, questions, unbeantwortet).
- /api/analyses/gaps: Leerstellen aus data/gaps_analysis.json (#14), Filter ueber min_size und missing_in.
- /api/analyses/shifts: Narrative-Shift-Drift aus data/narrative_shifts.json (#15), Filter ueber podcast, theme und min_drift.
- Wort-Timestamps via /api/podcasts/{id}/transcript/{ep}/words; Tabelle wird via _table_exists graceful behandelt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:31:02 +02:00
Dotty Dotter
f9d8c677ae extract_quotes.py: Auto-Quote-Extraktion je Episode via Qwen-plus
- Pro Episode: Paragraphen mit [P0]-Markern an qwen-plus, Antwort 3-5 markante Zitate als JSON-Array (para_idx, text, verbatim, speaker, is_top, themes).
- Theme-IDs werden gegen die in der DB hinterlegten themes-Liste validiert; unbekannte Themes fallen auf das leere Array zurueck.
- Audio-Timestamps kommen aus der paragraphs-Tabelle ueber para_idx, dadurch keine SRT-Reparsing-Schritte noetig.
- Hard-Budget 1,50 USD je Lauf, Skip vorhandener Episoden, Crash-Sicherheit durch Commit nach jeder Episode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:30:54 +02:00
Dotty Dotter
839ae2c27e #13/#18 Robuster JSON-Parser + --rerun-errors-Modus
- scripts/json_utils.py: parse_llm_json() mit Codefence-Strip, balanced-brace-Extractor, Truncation-Repair, Inner-Quote-Escape und Trailing-Comma-Strip.
- scripts/analyse_arguments.py + scripts/curate_debates.py: nutzen den neuen Parser, drei Retries bei Netz/Rate-Limit, --rerun-errors-Pfad fuer das Reparieren bestehender error-Records, busy_timeout=60s gegen SQLite-Locks.
- scripts/rerun_errors.py: Standalone-Re-Runner fuer beide Tabellen (debates.topic='error' und argument_links.relation='error') mit Budget-Limit, behaelt IDs via UPDATE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:30:45 +02:00
Dotty Dotter
aa60f22820 Fix Mindmap-Rendering: Timing + Fallback-Dimensionen
- requestAnimationFrame vor buildGraph() (DOM muss SVG erst rendern)
- Fallback W/H wenn Container noch kein Layout hat (< 100px)
- Verhindert leere Mindmap nach Podcast-Auswahl

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 11:56:24 +02:00
Dotty Dotter
255d769005 Fix: overflow:hidden verhinderte Podcast-Selector-Anzeige
Der #mindmap-Container hatte overflow:hidden — die Podcast-Cards wurden
gerendert aber abgeschnitten. Jetzt overflow:auto im Selector-Modus.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 11:51:33 +02:00
Dotty Dotter
c146d7ddb2 Fix: Podcast-Selector im Hauptbereich statt nur im Seitenpanel
Der Selector war vorher nur im 400px-Panel rechts sichtbar — auf Mobile
oder bei schmalem Fenster quasi unsichtbar. Jetzt wird er zentriert im
Mindmap-Bereich angezeigt, bis ein Podcast gewählt wird.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 10:39:10 +02:00
Dotty Dotter
78d66bef21 #12 Wort-Highlighting Frontend, #14 Leerstellen-Detektor, #15 Narrative Shift,
#13/#16/#17/#18 Qwen-Analyse-Scripts

- Frontend: Wort-Level-Highlighting im Transkript — jedes Wort als <span> mit
  Timestamp, Karaoke-Style Sync bei Wiedergabe, CSS word-active/word-spoken
- API: /api/.../words Endpoint liefert Wort-Timestamps
- #14 detect_gaps.py: K-Means-Clustering über 3727 Embeddings, identifiziert
  Leerstellen (Themen die in einem Podcast fehlen). Ergebnis: gaps_analysis.json
- #15 detect_narrative_shift.py: Embedding-Drift pro Thema über Episodenfolge,
  erkennt Framing-Wechsel. Ergebnis: narrative_shifts.json
- #13 analyse_arguments.py: Qwen klassifiziert logische Relationen (erweitert,
  widerspricht, belegt, relativiert) zwischen semantisch ähnlichen Absätzen
- #16 extract_claims.py: Qwen extrahiert prüfbare Behauptungen (Zahlen, Statistiken)
- #17 extract_questions.py: Qwen extrahiert und klassifiziert Fragen
- #18 curate_debates.py: Qwen kuratiert Cross-Podcast-Gegenüberstellungen
- run_all_qwen.sh: Sequentielle Pipeline für alle Qwen-Tasks (vermeidet DB-Locks)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 22:29:41 +02:00
Dotty Dotter
e678f75ee1 #8 Multi-Podcast-Dashboard, #9 PWA, #10 Cross-Podcast-Links, #12 Wort-Timestamps
- Backend: /api/compare Endpoint für Podcast-Vergleich (Stats, gemeinsame Topics,
  Top-Querverbindungen), /api/.../words Endpoint für Wort-Timestamps
- Frontend: Podcast-Vergleichsansicht mit Statistiken und Cross-Links,
  Cross-Podcast-Suche-Toggle, semantische Links im Transkript (lazy-loaded),
  Podcast-Switcher mit Zurück-Navigation
- PWA: manifest.json, Service Worker (stale-while-revalidate für Assets,
  network-first für API, cache-on-success für Audio), Icons
- Scripts: transcribe_words.py (mlx-whisper Batch-Transkription mit Wort-Timestamps),
  import_words.py (Wort-Timestamps in DB importieren)
- Dockerfile: PWA-Assets in Container kopieren

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 20:53:06 +02:00
Dotty Dotter
cb5978132c Phase 2: Vorberechnete semantische Similarity + API
- precompute.py: Berechnet paarweise Cosine-Similarity aller Absätze,
  speichert Top-10-Nachbarn pro Absatz in semantic_links Tabelle
- API: /api/similar-precomputed/{podcast}/{episode}/{idx} — liefert
  vorberechnete ähnliche Stellen in <1ms
- Getestet: 728 Absätze, 7144 Links (Threshold 0.55)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 21:23:31 +02:00
Dotty Dotter
b0649cea49 Phase 1+2: FastAPI-Backend, SQLite, Embeddings, Semantische Suche
Phase 1:
- FastAPI-Backend (backend/app.py) mit REST-API
- SQLite-Datenbank für Podcasts, Episoden, Absätze, Zitate
- Auto-Import aus mindmap_data.json + srt_index.json beim Start
- Webapp als SPA: API-first mit Static-File-Fallback
- Audio als gemountetes Volume statt im Docker-Image
- Docker-Compose mit Traefik-Labels

Phase 2:
- Qwen text-embedding-v3 via DashScope (1024-dim Vektoren)
- Embedding aller Transkript-Absätze (728 für NEU DENKEN)
- Semantische Suche: /api/semantic-search?q=...
- Similarity-API: /api/similar/{podcast}/{episode}/{paragraph}
- Cosine-Similarity auf normalisierten Vektoren, <100ms
- Findet thematisch verwandte Stellen über Episoden hinweg,
  auch bei komplett unterschiedlicher Wortwahl

Vorbereitet für Multi-Podcast (#10): Datenstruktur unterstützt
mehrere Podcasts, Cross-Podcast-Similarity ist ein Parameter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:24:53 +02:00
Dotty Dotter
8d7c16f8f6 #2 Obsidian-Links, #6 Soundbite-Export, #7 Timeline
- #2: Topic-Tags pro Transkript-Absatz (27 Themen-Taxonomie),
  Backlinks zu verwandten Stellen in anderen Episoden.
  Neues Script: scripts/index_topics.py
- #6: Audio-Clip-Export direkt im Browser (Web Audio API → WAV).
  Kein serverseitiges ffmpeg nötig.
- #7: Timeline-Ansicht als Alternative zur Mindmap.
  Staffeln → Episoden → Zitate auf Zeitachse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 08:03:12 +02:00
Dotty Dotter
67bf9ec7b1 Webapp v2: UX-sicheres Audio, Transkript-Mitlesen, Volltextsuche
- #3: Audio startet nicht mehr durch Card-Klick. Separater Play-Button
  pro Zitat, Audio-Bar mit Play/Pause. Bewusste Aktion statt Versehen.
- #4: Navigation durch Themes/Episoden stoppt laufendes Audio nicht.
  Audio-State ist komplett vom Panel-State getrennt.
- #1: Transkript-Ansicht mit synchronem Mitlesen. Aktiver Absatz wird
  hervorgehoben und auto-gescrollt. Klick auf Absatz springt im Audio.
- #5: Suchfeld im Header durchsucht alle Transkripte und Zitate.
  Treffer mit Kontext, Klick öffnet Transkript an der Stelle.

AudioPlayer als eigenständiges Objekt, TranscriptView und Search
als separate Module. Alle dynamisch aus mindmap_data.json + srt_index.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 03:01:59 +02:00
Dotty Dotter
e6164e6696 Initial commit: podcast-mindmap tool
Generic tool for building interactive mindmap visualizations from podcast transcripts.
Includes: audio download, SRT conversion, quote-timestamp matching, D3.js mindmap webapp.
Configurable via project.yaml — no podcast-specific content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 01:25:42 +02:00