Grundregel (CLAUDE.md): So wie das System sichtbar abbricht, muss es auch
sichtbar zeigen, wenn es arbeitet. Jede LLM-Aktion = laufende Aktivität mit
Fortschritt + animiertem Indikator; nie wie ein eingefrorenes UI.
UI: prominenter LLM-Indikator beim Kategorisieren -- rotierender Spinner,
'🤖 LLM kategorisiert … Batch X/Y', Modellname, Fortschrittsbalken (bestimmt
oder Shimmer bei unbekanntem Total), Button im Lade-Zustand ('⏳ LLM arbeitet').
prefers-reduced-motion respektiert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Alle fetch('/api/...') laufen über api(p)=BASE+p; BASE aus location.pathname
(lokal '/', deployed z.B. '/angebote/'). Ermöglicht Betrieb hinter einem
nginx-Reverse-Proxy mit Unterpfad ohne 404. Lokal unverändert (E2E grün).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Die linke Sidebar verursachte einen Layout-Sprung (einspaltige Stufen oben,
zweispaltiges Ergebnis darunter) und kostete viel Fläche. Ersetzt durch eine
sticky Chip-Leiste im Header: Gruppen als Pills mit Count + unsicher-Badge,
horizontal scrollbar, Klick springt zur Gruppe. Volle Breite für die Angebote,
konsistentes einspaltiges Layout. Kategorie-Chip pro Angebot + Korrektur bleiben.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Layout: Grid sidebar|main. Linke Seitenleiste mit den Produktgruppen
(Counts + unsicher-Badges, Klick springt zur Gruppe), sticky Header (Ort,
Anzahl, Cache-Statistik, Modell, unsicher, Filter).
- Kategorie pro Angebot als Chip sichtbar; Chip ist zugleich Korrektur-Anker.
- POST /api/korrektur {titel,marke,gruppe,plz}: schreibt die manuelle Gruppe
(modell='manuell') in den Produkt-Cache -- die hochwertigste Cache-Quelle;
patcht den UI-Ergebnis-Cache der PLZ (Angebot wandert, unsicher-Flag weg).
Kein LLM, kein Fetch; Whitelist erzwungen. Frontend hängt das Angebot
client-seitig um, ohne neuen Lauf.
- +6 Tests (gültig/400/400, modell=manuell, manuelle Zuordnung -> Cache-Hit
kein LLM, Ergebnis-Cache-Patch). 76 Tests grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neuer produktcache.py (Stufe 2): speichert pro Produkt (Titel+Marke, mengen-
invariant) die einmal ermittelte Gruppe in SQLite, bulk-load ins dict -> O(1).
Schnitt gewahrt: kein LLM-Import, nur Gruppe (nie Angebotsdaten), Whitelist
beim Lesen+Schreiben, nur SICHERE Zuordnungen gecacht.
kategorisiere(cache=, statistik=): Lookup vor dem LLM, Dedup im Lauf (ein
Produkt = ein Posten), Write-Back danach. Parallel-/id-Logik unverändert.
als_struktur/web/cli verdrahtet (Statistik 'X aus Cache · Y neu', --no-cache).
Live verifiziert (1903 Angebote PLZ 60487): Lauf 1 (gemini) 1551 neu; Lauf 2
(deepseek, anderes Modell) nur 110 neu, 1765 aus Cache -> ~93% weniger LLM-Calls,
modellübergreifend. +12 Tests (Round-Trip, Whitelist, Hit-vermeidet-Call, Dedup,
nur-sichere, Schnitt). 70 Tests grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Die 77 LLM-Calls liefen bisher sequenziell -> bei langsamer Modell-Latenz
minutenlang. Jetzt ThreadPoolExecutor (parallel=8); id-basiertes Mapping ist
reihenfolge-unabhängig, Logik unverändert. Voller deepseek-Lauf: 162s statt
sequenziell ~20min bei der heutigen Latenz (~16s/Call). Schnelle Modelle
(gemini-flash) entsprechend ~15-20s. +1 Test (parallel ordnet alle Batches
vollständig zu). 58 Tests grün.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ursache des 429-'es hängt': Die UI wählte beim Laden das erste Top-Free-Modell
vor; OpenRouter-Free-Modelle sind hart gedrosselt -> Lauf lief in 5x Retry +
Abbruch. Jetzt:
- /api/modelle stellt den Default (deepseek-v4-flash) als 'empfohlen' voran.
- UI wählt das empfohlene Modell vor, markiert Free als 'oft gedrosselt' und
stellt ein gemerktes Free-Modell NICHT automatisch wieder her.
- Server-seitiges Fortschritts-Logging ([Stufe 2] Batch X/Y) fürs Live-Log.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Goal 'Ollama-Konfig bleibt bestehen & klappt', mit Tests:
- Persistenz-Fix: Anbieter + Modell in localStorage gemerkt, init() stellt sie
wieder her (URL-Param > gemerkt > Default). Behebt das Zurückspringen auf
OpenRouter nach Reload.
- E2E-Test (Playwright): Anbieter überlebt echten Reload. content-JSON-Fallback
mit 3 Tests abgesichert. 57 Tests grün.
- Ehrlich dokumentiert (Code-Untersuchung + UI-Hinweis): kleine lokale Modelle
(qwen2.5-coder, gemma4, qwen3.5, llama3.2) liefern kein brauchbares Batch-
Tool-Calling -> Ergebnis 'Sonstiges/unsicher' (markiert, nicht geraten).
Brauchbare lokale Kategorisierung braucht ein starkes tool-Modell; Cloud
(deepseek) bleibt die verlässliche Wahl.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- OllamaKategorisierer (lokaler OpenAI-kompatibler Endpoint, kein Key/Netz),
baue_kategorisierer('ollama'), Default-Ollama-Modell.
- modelle.lade_ollama_modelle: /api/tags + /api/show (Tool-Fähigkeit), nur
tool-fähige taugen; leere Liste wenn Ollama aus.
- web: /api/ollama-modelle, Anbieter im Kategorisier-Flow + Cache-Key,
Modell+Anbieter im Ergebnis (als_struktur).
- UI: Anbieter-Umschalter (OpenRouter/Ollama), gewähltes Modell als Chip im
Konfig-Kopf (auch zugeklappt) + 'kategorisiert mit … (anbieter)' im Ergebnis,
bookmarkbarer ?modell/?anbieter/?auto-Start.
- content-JSON-Fallback fürs Tool-Parsing (manche lokale Modelle liefern die
Antwort als Text-JSON). +6 Tests (53 gesamt).
Ehrlich: lokal installierte Modelle (qwen2.5-coder/gemma4/qwen3.5) liefern kein
brauchbares Tool-Calling -> Ergebnis dort 'Sonstiges/unsicher' (ehrlich markiert,
nicht geraten). Cloud-Default deepseek-v4-flash voll verifiziert (1903 Angebote,
modellstabil).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verifiziert gegen Alternativen: sauberes Tool-Calling, über mehrere Batches
konsistent, ~1,8 Cent pro vollem Lauf (5x günstiger als gemini-flash-lite).
glm-4.7-flash/seed-1.6-flash riefen das Tool nicht sauber auf, free-Modelle
sind hart gedrosselt. Per --modell / Web-UI weiter frei wählbar.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Neue Web-UI-Sektion: Stufe 1 (Rohdaten holen+speichern), separate
OpenRouter-Konfig, Stufe 2 (Kategorisieren, gesperrt bis Rohdaten da).
- Zwei Screenshots unter docs/ (Stufen-Ansicht + gruppiertes Ergebnis).
- Nutzung um OpenRouter/--anbieter/--modelle ergänzt, Struktur und Test-
zahl (47) nachgezogen, localhost-Hinweis.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Der Schnitt aus CLAUDE.md ist durchgehalten:
- Fetch (deterministisch, kein LLM): marktguru-Adapter mit geprüftem
Ortsbezug (zipCode), Wochen-Cache, robots.txt-Respekt, ehrlicher Regel-4-
Abbruch bei fehlendem Beleg statt Krücke.
- Kategorisierung (einziger LLM-Ort): geschlossene Liste + Daten-Integrität
als Code erzwungen; austauschbar via Protokoll (OpenRouter/Anthropic),
mit Drosselung/Retry und ehrlichem Abbruch.
- FastAPI-Web-UI als dünne Schicht: Modellauswahl (Liste/Suche/Refresh),
Live-Fortschritt, gruppierte Ergebnisse mit Filtern, Ergebnis-Cache.
- 36 Tests gegen die Architektur-Regeln (kein Auffüllen, Abbruch, Integrität,
geschlossene Liste, Unsicherheit, Schnitt) und die Web-Schicht.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>