Commit graph

19 commits

Author SHA1 Message Date
Jeuner
75b4c3b97d UI: Hell/Dunkel-Schalter + bessere Filter-UX
- Manueller Farbmodus-Button in der App-Bar: Auto -> Hell -> Dunkel,
  in localStorage gemerkt, ohne Aufblitzen (Inline-Head-Script),
  color-scheme + Adressleisten-Farbe ziehen mit. CSS auf data-theme-
  Override umgestellt (dark-Tokens für [data-theme=dark] UND System-
  Automatik, außer manuell „hell"). AAA in beiden Modi erhalten.
- Filter: leere Gruppen werden beim Filtern ausgeblendet -> Treffer
  stehen sofort oben, kein Suchen mehr zwischen leeren Boxen. Sichtbares
  Feedback (Treffer-Zähler im Header + sanfte Einblende-Animation,
  reduced-motion-safe), klarer Leer-Zustand bei 0 Treffern + Reset.
- QC: axe-core 0 Verletzungen hell+dunkel (inkl. AAA), 77 Tests grün,
  Toggle/Persistenz/Filter live verifiziert, keine JS-Fehler.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 23:20:37 +02:00
Jeuner
4b833f3785 Branding: App heißt MABOTO (Marktbeobachtungstool) + Icon/Favicon
- Eigenes Icon: Lupe über aufsteigenden Preis-Balken (Beobachtung + Markt)
  in Markengrün, als skalierbares SVG. Favicon via /favicon.svg-Route
  (image/svg+xml, gecached) + Inline-Motiv im App-Bar-Logo.
- App-Bar-Wortmarke „MABOTO / Marktbeobachtung", Seitentitel, Intro-Kopf
  und FastAPI-App-Titel umbenannt; README-H1 + Doku-Screenshots neu.
- Tests: Index prüft jetzt auf MABOTO, neuer favicon.svg-Test (77 grün).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 23:09:32 +02:00
Jeuner
aed664dc16 UI-Redesign: App-Anmutung + adaptiv hell/dunkel + WCAG-AAA
- App-Shell: sticky App-Bar mit Wortmarke, Orts-Chip (spiegelt PLZ),
  ⚙-Einstieg in die LLM-Konfig; Karten mit Tiefe statt Hairline-Doku-Look
- Adaptives Theme via prefers-color-scheme (hell + dunkel), color-scheme-
  Meta + theme-color je Theme
- AAA-Kontrast: beide Paletten auf >=7:1 ausgelegt und mit axe-core
  verifiziert (0 Verletzungen, inkl. color-contrast-enhanced, 30 Regeln ok)
- A11y-Semantik: Kategorie-Chip & Gruppen-Header sind echte <button>
  (Tastatur), aria-expanded/aria-current, aria-live auf Status + LLM-
  Fortschritt, sichtbare :focus-visible-Ringe, Skip-Link
- Schnitt-Transparenz bleibt sichtbar, nur schöner: Quelle als ruhiges
  „🔗 marktguru"-Badge statt Roh-URL; deterministisch/LLM-Badges erhalten
- LLM-arbeitet-Indikator bleibt prominent (Grundregel)
- README: Screenshots neu (hell/dunkel) + Barrierefrei-&-adaptiv-Abschnitt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 22:38:02 +02:00
Jeuner
4ba5f65f39 README als Lern-Referenz, MIT-Lizenz, CI-Workflow
- README führt jetzt mit der Lehre (der Schnitt, Architektur als
  prüfbare Regeln, Cache->Mini-Model, ehrliche Grenzen, KI-Sichtbarkeit)
  + Live-Demo-Link + ehrlicher Datenquellen-Hinweis (Bildungskontext,
  robots.txt, gekapselter Adapter)
- LICENSE (MIT)
- .github/workflows/tests.yml: volle Suite (inkl. Playwright-E2E)
  bei jedem Push -> Badge im README

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:33:36 +02:00
Jeuner
20012a7d46 LLM-Arbeit sichtbar machen + als Grundregel verankern
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>
2026-06-03 21:06:36 +02:00
Jeuner
b63dad74a0 Frontend: API-Pfade pfad-agnostisch (BASE-Prefix) für Reverse-Proxy
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>
2026-06-03 20:52:24 +02:00
Jeuner
25bb790517 Ergebnis-Navigation: Sidebar -> horizontales sticky Gruppen-Band
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>
2026-06-03 20:41:57 +02:00
Jeuner
e1c7afef7e Ergebnisansicht: Sidebar + Header, Kategorie-Chip, manuelle Korrektur
- 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>
2026-06-03 19:42:17 +02:00
Jeuner
077a877480 Produkt->Kategorie-Cache: bekannte Produkte ohne LLM (SQLite, modellübergreifend)
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>
2026-06-03 18:37:12 +02:00
Jeuner
2029eb9fcf Kategorisierung parallelisieren (bis zu 8 Batches gleichzeitig)
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>
2026-06-03 18:15:14 +02:00
Jeuner
aa60331f7f Fix: UI wählt empfohlenes deepseek-Default vor statt gedrosseltem Free-Modell
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>
2026-06-03 17:59:03 +02:00
Jeuner
62f5af533e README: Ollama-Anbieter, deepseek-Default, Modell-Sichtbarkeit, Persistenz
LLM-Konfig-Sektion um Anbieter-Umschalter (OpenRouter/Ollama) erweitert,
ehrlicher Ollama-Modell-Hinweis, gemerkte Auswahl + sichtbares Modell.
Test-Zahl auf 57 (inkl. E2E-Persistenz) aktualisiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:50:58 +02:00
Jeuner
2ffb89a6d2 Ollama-Konfig: Persistenz (localStorage) + E2E-Test, ehrliche Modell-Grenze
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>
2026-06-03 17:34:34 +02:00
Jeuner
2e80f0d826 Ollama als lokaler Anbieter + gewähltes LLM dauerhaft sichtbar
- 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>
2026-06-03 14:54:32 +02:00
Jeuner
59d7d916ef Default-OpenRouter-Modell: deepseek-v4-flash (günstig + verlässlich)
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>
2026-06-03 14:13:51 +02:00
Jeuner
1ded067928 README: Web-UI + zweistufiger Flow dokumentiert, mit Screenshots
- 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>
2026-06-03 14:08:31 +02:00
Jeuner
11f1444599 Web-UI: zweistufiger Flow (Rohdaten holen+speichern / Kategorisieren)
- Stufe 1 (/api/rohdaten): deterministischer Fetch + Persistenz pro PLZ/Woche
  in data/roh/, ohne LLM/Key. speicher.py serialisiert belegte Angebote
  verlustfrei (fehlende Felder bleiben null).
- OpenRouter-Konfig als separates Panel (gilt für Stufe 2).
- Stufe 2 (/api/kategorisieren): LLM-Schritt auf den GESPEICHERTEN Rohdaten,
  gesperrt solange keine vorliegen (400). Fetcht nicht erneut.
- Funktionales Premium-Redesign: zwei nummerierte Stufen-Karten mit Status-
  Flags, erzwungene Reihenfolge, belegte Rohliste, ehrlicher Footer.
- 47 Tests (+11: speicher Round-Trip, Endpoint-Sperre, Rohdaten offline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:44:14 +02:00
Jeuner
d6d9b07a99 Version 0.1.0
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 09:34:46 +02:00
Jeuner
39b8a98bc2 Initiale Implementierung: Angebots-Übersicht (Fetch + Kategorisierung + Web-UI)
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>
2026-06-03 09:29:59 +02:00