Files
Aime/docs/08-changelog.md
T
Claude 48fa6b4f9e Diarization v2: Fenster-Clustering mit gelernter Schwelle + Live-Namensmarker
#74 (22 Teilnehmer) ergab automatisch 44 Sprecher, dieselbe Stimme mehrfach.
Ursache gemessen: ein Zentroid pro sherpa-Cluster aus max. 3 Schnitten, fester
Kosinus auf rohen Embeddings (Kreuz-Turn-Kosinus ~0,4 = keine Trennschaerfe).

v2: sherpa liefert nur Turn-Grenzen, Identitaet aus 3-s-Fenster-Embeddings,
Mittelwert-Zentrierung, Average-Linkage-AHC, Weak-Fold, Schwelle pro Aufnahme
per 2-Gauss-EM gelernt. Validiert: #74 44->20, #70 ->4, #68 ->2.

Live-Namensmarker: Namen waehrend der Aufnahme tippen; Zeitstempel benennt
spaeter den Cluster und dient als must-/cannot-link-Zwangsbedingung.
2026-07-27 17:00:45 +02:00

29 KiB
Raw Blame History

08 — Changelog

Chronik der Entwicklungsschritte. Regel: jede zukünftige Änderung bekommt hier einen Eintrag (Datum, Was, Warum, betroffene Dateien/Backups).

2026-07-27 — Diarization v2 (Fenster-Clustering, gelernte Schwelle) + Live-Namensmarker

  • Problem: #74 (33-min-Weekly, 22 Teilnehmer + ein Kind) automatisch neu analysiert → 44 Sprecher, dieselbe Stimme mehrfach als verschiedene Personen. Ursache (gemessen, nicht geraten): v1 bildete EIN Zentroid pro sherpa-Cluster aus max. 3 Schnitten und verglich mit fester Kosinus-Schwelle auf ROHEN Embeddings — unzentrierte TitaNet-Vektoren einer Aufnahme haben aber eine große gemeinsame Komponente (Kreuz-Turn-Kosinus ≈ 0,4), damit ist keine Schwelle trennscharf. 51 Roh-Cluster → 44.
  • Fix — diar.js v2 (.bak-v2clust): sherpa liefert nur noch Turn-GRENZEN (--min-duration-off=0, threshold 0,2 — Labels verworfen). Danach: 3-s-Fenster (Hop 1,5 s, PCM-Slicing ohne ffmpeg) → TitaNet-Embedding pro Fenster → L2 / Aufnahme-Mittelwert abziehen / L2 (Kanal-Kompensation: Kreuz-Kosinus ≈ 0, d' 3,5) → Average-Linkage-AHC Stufe A (fein 0,45) → Weak-Fold (< min(20 s, 2 % Sprechzeit)) → Stufe B bis zur pro Aufnahme gelernten Schwelle (2-Gauß-EM auf den Paar-Ähnlichkeiten, mergeThreshold(), Clamp 0,05–0,35) oder bis zur gepinnten Anzahl. Turn-Label = dauergewichtete Mehrheit seiner Fenster. DIAR_MODE=legacy = alter Pfad.
  • Warum gelernte Schwelle: fest 0,30 → #74 21 Sprecher (gut), #70 aber 9 (statt 4); fest 0,15 → #70 4, #74 kollabiert auf 14. Die Verteilung ist pro Aufnahme verschieden (gleicher Sprecher 0,21–0,30 je nach Audioqualität). Gelernt: #74 0,202 · #70 0,070 · #68 0,050.
  • Validierung (Labor + Produktion): #74 44 → 20 · #70 (4 bekannte Sprecher) → 4 · #68 (2 Personen) → 2. Modell-Benchmark auf #74 (1181 Fenster): TitaNet-large d' 3,75/197 s, CAM++ 3,72/82 s, TitaNet-small 3,54/57 s, wespeaker-VoxCeleb-resnet34 2,43/185 s → durch die Pipeline geschickt alle innerhalb eines Sprechers → TitaNet-small bleibt (schnell + alle gelernten Voiceprints bleiben gültig). Voiceprints tragen jetzt die Spalte model.
  • NEU: Live-Namensmarker (Wunsch des Nutzers) — während der Aufnahme den Namen der gerade sprechenden Person eintippen (Kopfzeilen-Feld, Enter). Tabelle markers (t_ms, name, speaker, conf); POST/GET/DELETE /api/record/marker(s) live, POST /api/recordings/:id/markers zum Nachtragen an der Abspielposition, POST /api/recordings/:id/apply-markers zum Neu-Zuordnen ohne sherpa-Lauf. Zuordnung diar.resolveMarkers(): asymmetrisches Fenster 25 s zurück / 8 s vorwärts (man tippt NACH dem Hören), Turn der den Marker überdeckt gewinnt, conf high/mid/low. Zusätzlich Supervision der Trennung: gleicher Name = must-link, verschiedene Namen = cannot-link in Stufe B; ein Name auf mehreren Clustern → Cluster werden zusammengeführt. Benannte Sprecher werden automatisch als Voiceprint gelernt.
  • Re-Diarization löscht jetzt speaker_names (server.js): die Cluster werden neu durchnummeriert, alte Namen zeigten danach auf fremde Stimmen. Marker + Auto-ID füllen sofort neu.
  • Dateien: diar.js (v2 + resolveMarkers), server.js (applyMarkers, Marker-Endpunkte, Voiceprint-Modellbindung), db.js (markers-Tabelle, voiceprints.model), recorder.js (Marker-Puffer während der Aufnahme), spk.js (modelId(), Modell-Doku), web/src/main.js + style.css (Kopfzeilen-Eingabe, Marker-Chips, Marker-Liste im Detail). Backups .bak-v2clust, .bak-markers. Bundle index-q6bHhDgg.js. Labor-Skripte (Segment-Dump, Fenster-Embeddings, Cluster-Grid, Schwellen-Fit) liegen auf dem Board unter /opt/callrec/lab/ und im Repo unter app/lab/ — damit ist das Tuning reproduzierbar.
  • GOTCHA (Betrieb): zwei schwere Jobs parallel (Labor-sherpa + App-Analyse, deren finally den Takt auf 2,25 GHz zurückstellt) haben die A76 auf 101,9 °C getrieben — lüfterloses Board, das ist Shutdown-Nähe. Schwere Jobs immer serialisieren, Cap 1,4–1,6 GHz.

2026-07-17 — Round-Robin-Meetings: Sektions-Pfad statt Whole-Pass ab 5 Sprechern (Recency-Bias-Fix)

  • Problem: #74 (33-min-Weekly, 13 Sprecher, 31k Zeichen) lief knapp unter WHOLE_MAX_CHARS als Whole-Pass → Summary enthielt fast nur die letzten Sprecher. 4B kann 15+ gleichwertige Themen in einem ~10k-Token-Prompt nicht gewichten; Whole-Pass taugt nur für narrative Gespräche mit wenigen Sprechern.
  • Fix (llm.js, .bak-standup): analyze() routet labeled Transkripte mit ≥ 5 Sprechern (MANY_SPEAKERS, countSpeakers()) immer in analyzeLong (Sektions-Konglomerat). Sektions-Pfad auf Besprechungsnotizen-Niveau ausgebaut: thema-Überschrift pro Chunk, Pflicht zur Erwähnung JEDER berichtenden Person, Smalltalk-Dämpfung (Privates max. Halbsatz), Todos nur beruflich mit Verantwortlichem in Klammern, Überblick-Mikro-Call (PROMPT_OVERVIEW, Key-tolerant: Qwen schreibt teils "überblick"), Summary-Cap 6 000 → 16 000 (alter Cap schnitt 10 Sektionen mitten im Satz ab).
  • Validierung: 3 Iterationen per reanalyze-llm auf #74 — final alle 13 Sprecher + alle Themen abgedeckt (Intro-Absatz, 10 Themen-Sektionen, 30 saubere Todos mit Verantwortlichen), kein Recency-Bias. Temporärer /api/debug/ask fürs Overview-Prompt-Tuning wieder entfernt (server.js unverändert).

2026-07-17 — GUI: Aufnahme-Tab gestrichen, Record-Button in der Kopfzeile + Stille-MP3-Fix

  • Der Reiter "Aufnahme" ist weg: Quelle (CallRecMix), HFP-Codec (auto) und Sprecherzahl (auto) waren eh die Defaults. Aufnahme-Knopf (roter Kreis) + Laufzeit sitzen jetzt MITTIG in der Kopfzeile zwischen Logo und Tabs; beim Aufnehmen pulsiert er rot. Start legt SOFORT ein rotes "Aufnahme laeuft..."-Kaestchen mit Live-Timer ganz oben in der Liste an; Stop erzeugt daraus die echte Zeile (transcribing) und oeffnet sie rechts. Standard-Tab ist jetzt "Aufnahmen".
  • recorder.js (.bak-silentmp3): lame crashte auf komplett stiller Aufnahme (psymodel.c calc_energy: el >= 0 — seit dem Mic-Gate real: niemand sprach = Null-Samples, loudnorm-Kette triggert die Assertion) -> Fallback-Encode ohne Filter statt Stop-Fehler. E2E getestet (3-s-Stille-Aufnahme: Stop ok, Zeile erstellt, transkribiert, wieder geloescht). Bundle index-UPMXRPJp.js.

2026-07-17 — GUI: Scrollbalken im Logo-Kupfer

Alle Scrollbalken (Liste, Transkript-Panel, Sprechblase, Seite) jetzt im Akzent-Kupfer: Firefox via scrollbar-color, Chromium/WebKit via ::-webkit-scrollbar-* (10 px, runder Daumen, transparente Schiene). Bundle index-U7lT-ig5.js.

2026-07-17 — GUI: Listenbreite per Splitter ziehbar

Zwischen Liste und Detail sitzt jetzt ein 6-px-Splitter (Hover/Ziehen = Kupfer-Schimmer, col-resize-Cursor): Liste zwischen 340 px (alte Festbreite = Minimum) und 680 px (Maximum = doppelt) frei ziehbar, Breite persistiert in localStorage['aime-list-width']. Auf schmalen Screens (gestapelt) ausgeblendet. Playwright-getestet (Clamp 340/680 + Persistenz). Bundle index-BbmdfFEI.js.

2026-07-17 — GUI: Kupfer-Zaehler, Favicon, Tab-Reihenfolge

Tages-Zaehler in der Liste jetzt im Logo-Kupfer (accent-soft-Pille); Wellenform-Logo als Favicon (SVG-data-URI in index.html, Browser-Tab); Tab-Reihenfolge neu: Aufnahme, Aufnahmen, Stimmen, Glossar, Bluetooth (Bluetooth ans Ende). Bundle index-Bt2IsJIF.js.

2026-07-17 — GUI: Liste startet mit nur dem neuesten Tag geoeffnet

Beim Betreten des Aufnahmen-Tabs ist nur noch die aktuellste Tagesgruppe aufgeklappt, alle aelteren sind eingeklappt (Zaehler-Badge zeigt die Anzahl). Manuelles Auf-/Zuklappen bleibt bis zum naechsten Tab-Wechsel erhalten (state.resetCollapse-Flag, angewandt beim ersten Listen-Build). Bundle index-C_-UUeU5.js.

2026-07-17 — GUI: Transkript/Am-Tisch-Panel ebenfalls verschiebbar (zwei Drop-Spalten)

Alle Bloecke — Zusammenfassung, ToDos UND das Transkript-/Tisch-Panel — sind jetzt per Drag & Drop frei anordenbar, auch SPALTENUEBERGREIFEND: zwei Drop-Zonen (links 54 % / rechts 46 %), Layout persistiert als localStorage['aime-detail-layout'] {main:[...], side:[...]} (altes aime-detail-order wird migriert). Eine leere Spalte kollabiert zu einem schmalen gestrichelten Streifen und bleibt Drop-Ziel (JS-Klasse .empty — :empty scheitert an Template-Whitespace). Bundle index-B1y2-Z_b.js.

2026-07-17 — GUI: Sprecher-Modul entfernt, Umbenennen direkt am Verlauf

Das separate "Sprecher"-Modul war redundant zu den Spur-Labels. Jetzt: Klick auf einen Namen links im Sprecher-Verlauf oeffnet ein Popover (Name aendern + "als Stimme lernen" + Speichern) — gleiche API wie vorher (POST /speaker). Klick-Seek in den Spuren zuendet nur noch IN einer Spur, nicht mehr auf der Label-Spalte. Bundle index-Bj1pQQtk.js.

2026-07-17 — GUI: Blasen-Oeffnung zeigt exakt auf den Sprecher

Bei mehreren Sprechern je Tischkante (5-/6-Sitz-Layouts) zeigte der Schwanz nur auf die Kantenmitte. Jetzt wird die Sitz-x-Position in Blasen-Koordinaten umgerechnet (CSS-Variable --ax, BUBBLE_W in main.js synchron zur CSS-Breite) — die Oeffnung wandert entlang der Ober-/Unterkante exakt zum Sprechenden; bottomleft/bottomright-Sonderklassen entfallen. Bundle index-FKbNFs-A.js.

2026-07-17 — GUI: Tisch-Blase fix, Oeffnung wandert zum Sprecher

Feinschliff nach User-Feedback: Die Sprechblase liegt wieder FEST auf dem Tisch (54 % Breite), nur ihr Schwanz/die Oeffnung springt per arrow-Klasse zum jeweils Sprechenden; Rand-/Namensfarbe wechseln weich mit (border-color-Transition). Wort-Highlight, internes Mitscrollen und Klick-Seek bleiben. Bundle index-CpZJOt3u.js.

2026-07-17 — GUI: Sprechblase am Sprecher mit Wort-Highlight + Mitscrollen

Tisch-Ansicht verfeinert: Die Blase haengt jetzt klassisch am MUND des Sprechers (dynamisch zwischen Sitz und Tischmitte positioniert, Schwanz zeigt zum Kopf) statt zentral auf dem Tisch. Inhalt = der komplette Redebeitrag als einzelne Wort-Spans: aktives Wort wird synchron zum Audio gehighlightet (wie im Transkript), die Blase scrollt intern mit (innere .tb-scroll-Huelle, damit der Pfeil nicht mitscrollt), Klick auf ein Wort springt im Audio dorthin. Bundle index-BK0gpRp9.js.

2026-07-17 — GUI: Konferenztisch-Ansicht (Umschalter neben "Analyse neu")

Neuer Umschalt-Button (Icon: drei Personen am Tisch <-> Textblatt) in der Status-Zeile: ersetzt das Transkript rechts durch eine animierte Szene — Sprecher als Figuren in ihren Spurfarben um einen abgerundeten Tisch, Sprechblase AUF dem Tisch (Rand/Name in Sprecherfarbe, Pfeil zeigt zum Sprecher, Text = aktueller Redebeitrag), alle anderen Personen drehen die Pupillen zum Sprechenden, dessen Mund animiert beim Reden (reduced-motion respektiert). Sitzordnungen fuer 1-6 Teilnehmer vordefiniert. Wahl bleibt in localStorage['aime-side-view']. Bundle index-DXTNo40e.js.

2026-07-17 — GUI: Aufnahmen-Tab nutzt die volle Fensterbreite

#app.wide war auf 1180 px gedeckelt — auf breiten Monitoren blieb rechts/links viel Leerraum und die Zusammenfassung wurde unnötig schmal/lang. Deckel entfernt (max-width: none, Padding 28 px); die Liste bleibt 340 px fix, das Detail bekommt den Rest. Bundle index-RpsXFTh8.js.

2026-07-17 — GUI: Live-Text-Box entfernt

Die Box rechts neben den Sprecher-Spuren (aktueller Redebeitrag) flog auf User-Wunsch raus — redundant zum mitscrollenden Transkript daneben. Spuren nutzen jetzt die volle Breite. Bundle index-BiCA5LX5.js.

2026-07-17 — GUI: Sprecher-Verlauf als Vollbreiten-Streifen unter dem Player

Der Sprecher-Verlauf (Spuren + Live-Text) sitzt jetzt fix unter der Audio-Laufleiste und zieht sich ueber BEIDE Spalten (oberhalb von Zusammenfassung und Transkript); aus den Drag&Drop-Modulen entfernt. Lanes ~2/3, Live-Box ~1/3 Breite. Bundle index-COpkdtd3.js.

2026-07-17 — GUI: Transkript als eigene Scroll-Spalte

Transkript (+ Dialog) aus den Drag&Drop-Modulen herausgelöst: eigene, sticky Spalte rechts neben Zusammenfassung/Verlauf/ToDos/Sprecher, Höhe auf Viewport begrenzt, scrollt INTERN durch und folgt dem aktiven Wort im Panel (statt die Seite zu ziehen). Unter 1100 px stapeln die Spalten (Transkript max. 60 vh). Bundle index-DQmY8pnQ.js.

2026-07-17 — GUI: Sprecher-Verlauf-Visualisierung + Drag&Drop-Module

Detail-Ansicht ist jetzt modular: fester Kopf (Titel, Player, Status), darunter Blöcke Zusammenfassung / Sprecher-Verlauf / ToDos / Sprecher / Transkript — jeder Block hat einen Grip-Header (⠿) und lässt sich per Drag & Drop umsortieren; die Reihenfolge liegt in localStorage['aime-detail-order'] und gilt für alle Aufnahmen.

Sprecher-Verlauf (neu): pro Sprecher eine farbige Spur (sp0–sp3) übereinander mit Redeabschnitts-Blöcken (Wörter mit ≤ 1,2 s Lücke zusammengefasst), Kupfer-Playhead wandert beim Abspielen mit, die Spur des gerade Sprechenden leuchtet (Label fett, Punkt pulsiert; respektiert prefers-reduced-motion), Klick in die Spuren springt an die Stelle. Rechts daneben die Live-Box: zeigt Sprecher + Text des aktuellen Redebeitrags. Per Playwright-Screenshots verifiziert (beide Themes, Playback mit aktiver Spur). Bundle index-WaE171bv.js.

2026-07-17 — GUI: „Weggezogen"-Bug gefixt + Redesign mit Dark/Light-Switch (.bak-theme)

  • Bug: lief beim ausgewählten Eintrag eine Analyse, feuerte ein 5-s-Timer openRecording(alteId) als stale closure — auch nachdem der Nutzer längst eine andere Aufnahme hörte → riss ihn zurück und stoppte den Player. Fix: Poll bricht bei Auswahl-/Tab-Wechsel ab, prüft während der Analyse NUR den Status (kein DOM-Rebuild) und baut das Detail genau EINMAL neu, wenn die Ergebnisse da sind — inkl. Wiederherstellung von Player-Position und Play-Zustand.
  • Redesign: Themes über CSS-Variablen (data-theme auf <html>, vor First-Paint aus localStorage/ OS-Einstellung, Toggle ☀️/🌙 im Header). Dunkel = neutrales Anthrazit, Hell = warmes Papier; Akzent = Kupfer (#e8804c/#c05f2d — Rot bleibt der Aufnahme vorbehalten), Wortmarke mit Wellenform-Glyph, Pill-Tabs, weichere Karten, sichtbarer Tastatur-Fokus, prefers-reduced-motion respektiert. Sprecherfarben (sp0–sp3) als Variablen mit hell/dunkel abgestimmten Werten.
  • Bundle index-C2xK4eql.js / index-ClT74fBX.css. Dateien: web/index.html, web/src/main.js, web/src/style.css (Backups .bak-theme).

2026-07-17 — Hall auf der Gegenseite + verschmolzene Sprecher: Mikro→Mix jetzt GEGATET (.bak-gatedmix)

User: Gegenseite klingt „wie in einem schallenden Raum", Diarization macht aus zwei entfernten Personen eine. Diagnose per Cepstral-Analyse auf #71: Gegenseiten-Segment hat einen felsenstabilen Peak bei 79–80 ms (22/26 Frames) = deterministische verzögerte Kopie; User-Segment sauber. Ursache: der direkte cr_mic2mix-Loopback (immer offen) legte die Gegenseite doppelt in den Mix — digital (cr_pc2mix) UND als Kopfhörermuschel-Bleed durchs Mikro (~80 ms Latenz). Der Hall verschmiert zusätzlich die TitaNet-Embeddings → zwei Remote-Stimmen (gleicher Kanal + gleicher „Raum") rücken über die 0,5-Kosinus-Schwelle zusammen.

  • Fix (aime-usb-audio-setup.sh): direkter cr_mic2mix-Loopback ENTFERNT; stattdessen pw-link micgate.out → callmix — dieselbe Gate-Auskopplung wie beim Teams-Selbstecho-/iPhone-Uplink-Fix. In Sprechpausen des Nutzers ist das Mikro komplett aus der Aufnahme. Fallback ohne Gate = alter Loopback.
  • Verifiziert strukturell (pw-link -l: callmix ← cr_pc2mix + micgate.out, kein HSMIC-Direktpfad) und funktional (4 s callrec_mix bei Stille = RMS 0/Peak 0). Echter Call steht noch aus.
  • Nebenbei entdeckt: OOM-Ereignis 17:47 (die fünf 60-s-READY-Timeout-Retries beim ersten Qwen3-Start luden je 5,3 GB, NPU-Speicher der gekillten Loads wurde nicht schnell genug frei) riss mariadb, callrec UND pipewire weg — Audio-Topologie war seitdem TOT. Mit dem 180-s-Timeout (Changelog unten) behoben; Topologie neu aufgebaut. Lehre: nach OOM immer pw-link -l prüfen.

2026-07-16 — Glossar: Einträge bearbeitbar (.bak-gedit)

User-Wunsch: gespeicherte Glossar-Begriffe nachträglich ändern. Neu: PUT /api/glossary/:id (db.updateGlossary), GUI-Stift-Button je Eintrag lädt Begriff+Soundalikes ins Formular (Titel „… bearbeiten", Speichern/Abbrechen), Update statt Neuanlage. Bundle index-NRK6cGUI.js. API-verifiziert (POST→PUT→DELETE). Dateien: server.js, db.js, web/src/main.js.

2026-07-16 — rkllm vor Diar/Identify freigeben (TitaNet-OOM) + #71 validiert

  • Bug: residenter rkllm (5,9 GB) ließ den TitaNet-spk-embed-Helper OOM-sterben („spk beendet" pro Cluster) → Konsolidierung bekam NULL Centroids → #71 bekam still 48 Roh-Sprecher. Fix (server.js): llm.stopServer() am Anfang von rediarize() UND analyzeRecording() (Reload ~96 s, egal bei 15-min-Analysen).
  • #71 E2E danach: Diar 48 → 2 Sprecher (5 schwache gefoldet), Qwen3-Whole-Pass auf 31,3k Zeichen → „Steuergestaltung für wachsendes Unternehmen" mit konkreten Zahlen (30 MA, 3 Mio Umsatz, 15-20 %-Ziel, Kick-off Hamburg, Termin 23.) — alter Titel war generisch, alte Sprecherzahl 28.
  • OFFEN: identify … spk beendet schlägt auch MIT freiem RAM fehl → Voice-Auto-Naming (Voiceprints) aktuell tot; Teilnehmer erscheinen als „Sprecher N". Separater Bug, noch zu untersuchen (spk.js/Helper).

2026-07-16 — ✅ LLM → Qwen3-4B-Instruct-2507 (Nacht; Gemma-Rollback vorhanden)

Auf User-Wunsch Qwen3-4B selbst konvertiert und im A/B gegen Gemma getestet — 3/3 Prompts klar besser, produktiv geschaltet und E2E validiert.

  • Konvertierung: wiederverwendetes Kit (rkllm-convert:1.3.0-Docker neu gebaut), HF Qwen/Qwen3-4B-Instruct-2507 (7,6 GB bf16) → w8a8_g128/grq, max_context 16384, auf RTX 3090 in ~35 min → 5,3 GB .rkllm, pscp aufs Board.
  • A/B (gleiche Prompts wie Gemma-Tests): Chunk 1 (Com4Tec-Story) in 140 s mit ALLEN Nuancen (50 %-Kappa, kein Body Leasing, Perinko/Icon); Auto-Chunk 91 s inkl. Detail-Korrektur „Michael Wagner, nicht Fabienne"; Whole-Pass 21,8k Zeichen in 691 s fehlerfrei gewichtet — exakt der Fall, in dem Gemma englisch schwafelte bzw. nur das Ende zusammenfasste. Qwen3 = ChatML-Template statt Gemma-Turns (rkllm_server.cpp).
  • Produktiv-Swap: Drop-in RKLLM_MODEL=qwen3-4b-w8a8g128-16k.rkllm, prod rkllm_server mit ChatML neu gebaut (Rollback: .bak-gemmatmpl + Drop-in zurückstellen), WHOLE_MAX_CHARS 20 000 → 32 000 (bis ~35-min- Aufnahmen Single-Pass; Sektions-Konglomerat bleibt für längere). GOTCHA: Qwen3 lädt ~96 s → llm.js READY-Timeout 60 s → 180 s (sonst Endlos-Retry-Schleife, real passiert).
  • E2E validiert: #70 Whole-Pass 15,4 min → „Neue Kontakte und Stellenangebote", Auto raus, Comfort Tech/ Adem Marlock/Nico-Profil-Todo konkret; #68 Kurzclip (analyzeSingle+ChatML) 22,2 min inkl. Queue → sauber.
  • Damit ist die 4B-Synthese-Decke aus dem Sektions-Konglomerat-Eintrag (unten) für ≤ 32k Zeichen GESCHLOSSEN; das Konglomerat bleibt als >32k-Pfad und Fallback.

2026-07-16 — A/B-Test Qwen2.5-3B vs Gemma-3-4B im neuen Chunk-Pfad: Gemma bleibt

Auf User-Wunsch das noch vorhandene qwen2.5-3b.rkllm gegen die neuen 4,2k-Chunk-Prompts getestet (Test-Binary mit ctx 4096 in /tmp, direkt übers Stdin-Protokoll — kein App-Umbau). Ergebnis: 3 von 3 Outputs unbrauchbar (Feldbeschreibungs-Echo ohne Inhalt; Abbruch nach ~100 Zeichen mitten im Satz; Struktur-Müll mit der Feldbeschreibung als JSON-Key) — Gemma lieferte im selben Job 8/8 verwertbare Sektionen. Dazu prinzipiell: Qwen2.5 hat fix 4096 Kontext → der validierte Whole-Pass (≤ 20k Zeichen) wäre unmöglich. Testartefakte entfernt. Offene Alternative (ungetestet, ~½ Tag): Qwen3-4B neu konvertieren.

2026-07-16 — Sektions-Konglomerat statt LLM-Reduce (Abend, .bak-sectionsum)

Auftrag: Lang-Transkript-Summary (> 20k Zeichen) inhaltlich retten. Prompt-Lab auf #70 mit temporärem /api/debug/ask-Endpoint (nach Abschluss wieder entfernt), ~15 Reduce-/Chunk-Varianten in je ~2 min statt 9-min-Vollanalysen.

  • Befund 1: Chunk-Größe ist der Fidelity-Hebel. 8k-Chunks → generisches namenloses Blabla; 4k-Chunks → geerdete Summaries mit echten Eigennamen (Com4Tec, René, Tobi+Niklas, 20 Bewerbungen). CHUNK_CHARS 8000 → 4200.
  • Befund 2: Cross-Section-Synthese unmöglich auf Gemma-3-4B. Jede LLM-Reduce-Variante (Gewichtungsregeln, Abschnitts-Labels, Themen-Tabellen) → Regeln ignoriert, Namen durch Rollen ersetzt, Dubletten, Loops. LLM-Reduce abgeschafft.
  • Neue analyzeLong-Pipeline (llm.js): 4,2k-Chunks sequenziell mit Rolling Context (1-Satz- Kurzfassungen der Vorgänger → erkennt geänderte Entscheidungen: „jetzt X, vorher Y") + eigennamen- Extraktionsfeld; Echo-Guard (Feldbeschreibungs-Echo ist INHALTS-abhängig reproduzierbar → bis 3 Versuche mit variiertem Prompt: ohne Kontext, ohne Hint) + Feld-Salvage per Regex + Prosa-Rettung (≥ 60 Zeichen); Titel-Mikro-Call ohne letzten Abschnitt (Smalltalk-Filter); Konglomerat baut Code: „Gesprächsverlauf in n Teilen — genannte Namen: …" + „Teil i/n: …" chronologisch. Kap. 05 (Mermaid + Marotten aktualisiert).
  • E2E #70 (3 Läufe): Titel ohne Auto-Smalltalk ✅, alle Abschnitte mit echten Namen ✅, Auto nur noch in Teil 7 ✅, 12-14 min für 26-min-Aufnahme. Restgrenze: Abschnitts-Summaries variieren pro Lauf in Schärfe (Sampling), keine thematische Verdichtung ÜBER Abschnitte (4B-Decke). ≤ 20k Zeichen weiter Whole-Pass.

2026-07-16 — #70-Debug: Phantom-Sprecher, Summary-Fehlgewichtung, Thermal-Leck (Nachmittag)

Auslöser: Aufnahme #70 (26 min, 4 Personen) — 7 Sprecher erkannt, Titel/Summary aus Randthemen (Auto-Smalltalk der letzten 3 min statt Recruiting-Hauptteil). 9 Analyse-Iterationen auf #70.

  • Diarization Stufe 3 „Weak-Fold" (diar.js, .bak-weakfold): finale Sprecher < DIAR_MIN_TOTAL_SEC (20 s, Segment-Zeiten!) werden in den akustisch nächsten starken Sprecher gefaltet — ein einzelner ≥ 3-s-Streu-Cluster konnte vorher einen Phantom-Anchor eröffnen (#70: 3 Phantome mit 2,5–4,8 s). 10 s reichte NICHT (9,2-s-Phantom kam durch). Verifiziert: #70 konstant 50 Roh-Cluster → exakt 4. Kap. 04.
  • Whole-Pass-Grenze = Modellgrenze bewiesen (Runden 2–5): rkllm-Runtime-Kontext 8192 → 12288 (rkllm_server.cpp, .bak-ctx12k, Rebuild) und WHOLE_MAX_CHARS testweise 32k → Gemma-3-4B kippt

    ~6k Input-Token reproduzierbar: englisches Geplauder statt Analyse, Recency-Bias (fasst nur das Gesprächsende zusammen), JSON-Zerfall (typografische Quotes, Wiederholungsschleifen). WHOLE_MAX_CHARS zurück auf 20 000 (dort validiert gut); ctx 12288 bleibt (Headroom). Kap. 05 neuer Abschnitt.

  • Robustheit llm.js (.bak-ctx12k): End-Instruktion nach dem Transkript (ohne Platzhalter-Skelett — Gemma kopiert ... wörtlich!); JSON-Repair-Call (repariert kaputten Output OHNE das große Transkript); Sanity-Check (Zusammenfassung ≥ 20 Zeichen) → sonst Chunk-Fallback statt Hard-Error; extractJson() repariert typografische Anführungszeichen + trailing commas; 1 Retry pro Chunk (Parse = Sampling- Münzwurf: 3/3 ok vs 1/3 im nächsten Lauf; Retry live bestätigt); Reduce-Input mit „Abschnitt i von n"- Labels + Gewichtungsregeln (Hauptthema = mehrere Abschnitte; Nur-letzter-Abschnitt = Smalltalk).
  • Regression bestätigt: Chunk-Prompt MUSS minimal bleiben — eine zusätzliche Anweisungszeile (Namen/Firmen/Zusagen) kippte 2/3 Chunks in Prosa (Runde 6). Zurückgebaut.
  • Thermal-Leck geschlossen (server.js, .bak-anacap): analyzeRecording() lief als einziger schwerer Pfad OHNE A76-Takt-Cap → 95–97 °C bei langer Gemma-Generierung (lüfterlos!). Jetzt Cap wie transcribeWav/rediarize. Kap. 03/05.
  • Ehrliches Ergebnis #70: Sprecher exakt 4 ✅, Analyse crashfrei in ~9 min ✅, Summary führt jetzt mit Recruiting, ABER Auto-Smalltalk bleibt in Titel/Kernpunkten und konkrete Namen (Com4Tec, Kandidaten) fehlen — Qualitätsdecke des 4B-Modells im Chunk-Pfad (> 20k Zeichen). Aufnahmen ≤ ~23 min laufen über den validierten Whole-Pass.

2026-07-16

  • Codebase ins Repo aufgenommen: app/ = Spiegel von /opt/callrec (Backend, Frontend-Quellen, Inferenz-Python, Skripte, rkllm_server.cpp), system/ = systemd-Units + WirePlumber/PipeWire-Drop-ins + sudoers. Modelle/Binaries/Aufnahmen bewusst außen vor (.gitignore).
  • Projekt umbenannt in „Aime“ (vormals CallRec, wie der BT-Name des Boards): Gitea-Repo → sascha/Aime, Web-GUI-Titel/Header → „Aime“ (Bundle index-CuWeUh1j.js, Backups index.html.bak-aime/main.js.bak-aime), Doku rebranded. Technische Bezeichner auf dem Board bleiben callrec* (Stabilität).
  • Doku-Repo angelegt (dieses Repo). Ab jetzt verbindliche Ablage inkl. Diagrammen.

2026-07-15/16 — Strukturierte Analyse + Teilnehmer-Fix (.bak-structsum)

  • Teilnehmer kommen aus der Diarization statt vom LLM (das listete alle erwähnten Namen → 11 statt 3 bei #69).
  • Summary strukturiert: Überblick + „Kernpunkte“ + „Entscheidungen“ als Bullets (composeSummary, CSS pre-line).
  • Chunk-Salvage-JSON-Leak gefixt; Reduce-Input gekappt + Retry; cleanList filtert null/[object Object]/Objekt-Todos.
  • Referenz #69 (71,6 min): Analyse ~20 min, Ergebnis sauber. Bundle index-B2dSaBty.js.

2026-07-15 — LLM-Wechsel auf Gemma-3-4B + Marker-Fix

  • Qwen2.5-3B → Gemma-3-4B-it (gemma3-4b-w8a8g128-16k.rkllm, grouped w8a8; plain w8a8 kaputt, w4a16 nicht unterstützt).
  • rkllm-Runtime 1.2.1 → 1.3.0. analyzeWhole (≤ 20k Zeichen) ersetzt Map-Reduce im Mittelbereich.
  • Qualität ≈ Gemini, ~3× langsamer. Wort-Highlight-Marker lief ~1 Wort voraus → SYNC_MS=250 (index-CLero27-.js).

2026-07-14 — Kurzclip-Beschleunigung + Analyse-Timer

  • Clips ≤ 30 s über warmen CPU-Whisper-Daemon ohne rkllm-Swap: 10-s-Clip 120 s → 53 s (.bak-shortdaemon).
  • GUI zeigt Analysezeit live (analysis_ms/analysis_started_at, .bak-anatimer).

2026-07-13 — Diarization stabil + NPU-Meilenstein 2 + iPhone-Repair

  • Greedy-Anchor-Merge ersetzt Union-Find: derselbe Call ergab vorher 2/3/7 Sprecher, jetzt konstant 27→3 (.bak-anchor).
  • Meilenstein 2: Whisper-turbo-Encoder auf NPU produktiv — RTF 1,51 → 0,61, Temp 90 °C → 59 °C; batched (batch=8) → RTF 0,389.
  • iPhone neu gekoppelt, Trusted=yes gesetzt (fehlte → keine HFP-Nodes → #58 ohne Gegenseite).

2026-07-09 — Meilenstein-1-Snapshot, Auto-Diarization, Glossar, Whisper-Kalibrierung

  • Snapshot /opt/callrec/milestones/m1-20260709/ (Rollback-Punkt).
  • Auto-Diarization: 2-stufige Konsolidierung (17→2 verifiziert), reanalyze re-diarisiert immer (0=auto / >0=gepinnt).
  • Glossar Phase 1: deterministische Ganzwort-Ersetzung + LLM-Hinweis, GUI-Tab, „Aime“ geseedet.
  • Whisper-Kalibrierung: medium beam1 optimal (11,3 % WER vs large-v3); --initial-prompt mit Termliste = Echo-Loop (tabu).
  • 46-min-E2E über Nacht sauber (Long-Rec-Fixes produktionsvalidiert).

2026-07-08 — Echo-Fix, Thermal-Crashes, Whisper medium, Sprecher-Lernen, Long-Rec-Analyse

  • Selbst-Echo der Gegenseite gelöst: LADSPA-Noise-Gate micgate im Mikro→PC-Pfad (Thr −45 dB, Mic-HW niedrig); AEC verworfen. Reboot-verifiziert.
  • Thermal-Schutzabschaltungen gefixt: A76-Cap 1,6 GHz über die ganze transcribeWav (Whisper+Diar), SIGSTOP-Guard >90 °C; crash-sicheres asynchrones Speichern (stopAndSave persistiert vor Transkription); /retranscribe für hängende Zeilen.
  • Whisper small → medium (Hörfehler).
  • Sprecher-Umbenennen + Stimm-Lernen (Voiceprint-Zentroide) + Leertaste Play/Pause.
  • Lange Aufnahmen (> 20 min) analysierbar: Chunked Map-Reduce gegen 4096-Token-Limit; Diar-Timeout skaliert mit Dauer; Lösch-Confirm.

2026-07-07 — MEILENSTEIN 1: USB-Headset-Pfad komplett

  • Logi H390 ersetzt toten ES8323-Mikrofonpfad; callmix + 4 Loopbacks + callrec_mix-Remap; Volume-Bridge; Inline-Tasten.
  • Kaltstart-verifiziert. Clip-sicheres Gain-Staging (PC→Mix 60 %) — Übersteuerung hatte Diarization zerstört (14 Sprecher, #41).
  • Re-Diarization mit fester Sprecherzahl (numSpeakers), #41 14→2 verifiziert.
  • LLM-Fixes: Phantom-Todo (Prompt-Beispiel-Echo) entfernt, KV-Cache-Clear, leere Todos → [].

2026-07-06

  • ES8323-3,5mm-Mikro als defekt diagnostiziert (Verstärkung fest −42,14 dB) → Entscheidung für USB-Headset.

2026-07-05 — „Helikopter“-Debugging (Referenz)

  • ES8323 S16-Modus = Treiberbug (S24 nutzen); PipeWire/alsaloop-Capture des ES8323 injizierte ~13 Klicks/s, arecord nicht; Windows-Shared-Mode-AGC = separates 20-Hz-Pumpen (exklusiv sauber).
  • Sauberer Fallback damals: dual native-clock arecord. Durch Headset-Pfad obsolet.

Früher

  • iPhone-BT-HFP-Pfad aufgebaut (Rollen-Drop-in, frischer Pair, Echo-Fix Uplink←micgate, Route-Watcher) — Details Kap. 06.
  • BT-Buds No-Bonding-Quirk: LinkKey manuell persistiert (Referenz, Dongle entfernt).