BuyingAgent/.specs/buying-agent-frontend-mvp-spec-v0.3.md

1709 lines
31 KiB
Markdown
Raw Normal View History

# Buying Agent – Frontend MVP Specification v0.3
**Status:** Review Candidate – vor Implementierung erneut durch den Coding-Agenten zu prüfen
**Spezifikationsversion:** 0.3
**Stand:** 2026-10-04
**Ersetzt vollständig:** `buying-agent-frontend-mvp-spec-v0.2.md`
**Gilt zusammen mit:**
1. `buying-agent-v0.1-spec-v1.1.md`
2. `buying-agent-architecture-addendum-v1.md`
3. `buying-agent-source-discovery-spec-v0.1.md`
Bei Widersprüchen innerhalb des Frontend-/MVP-Bereichs hat diese v0.3 Vorrang.
Sie präzisiert außerdem die für den Frontend-MVP notwendigen Backend-Verträge.
---
# 1. Zielbild
Der Buying Agent soll als **nutzbare PWA** einen vollständigen vertikalen Nutzerfluss abbilden.
Der Nutzer soll ohne technische Kenntnisse:
```text
BuyingProject anlegen
↓
Produktkategorie auswählen
↓
verfügbare Quellen für diese Kategorie bestimmen
↓
verfügbare Kriterien aus der Source-Taxonomie laden
↓
gewünschte Suchparameter auswählen und Werte setzen
↓
externe Suche generieren
↓
SearchRun starten / Status verfolgen
↓
Kandidaten sehen
↓
Shortlist / Ranking sehen, soweit konfiguriert
↓
Kandidat öffnen
↓
Attribute, Preise, Historie und bisherigen Status sehen
↓
Kriterien anpassen
↓
neue Suche ausführen
```
Die Oberfläche soll einfach bleiben. Die technische Komplexität verbleibt im Backend.
---
# 2. Grundsätzliche Architektur
## 2.1 Backend bleibt System of Record
Das Frontend enthält keine fachliche Kernlogik.
Es konsumiert ausschließlich die versionierte `/api/v1`-API.
Fachliche Entscheidungen über:
- Taxonomie
- Mapping
- Query-Generierung
- Produktidentität
- Diffing
- Ranking
- Historisierung
- Lifecycle
liegen im Backend.
## 2.2 Keine Canonical-Criteria-Schicht
Es wird **keine separate globale Canonical-Criteria-Ontologie** eingeführt.
Für eine unterstützte Source/Kategorie bildet deren gelernte Taxonomie zunächst den primären Kriterienkatalog.
Jedes Source-Kriterium erhält lediglich eine stabile interne ID:
```text
CriterionDefinition.id = interne UUID
```
Diese ID dient als Persistenz- und Referenzidentität.
Sie ist keine Behauptung, dass das Kriterium global quellenunabhängig kanonisch sei.
## 2.3 Source-Kriterien und Ergebnisattribute werden verbunden
Ein Kriterium muss sowohl mit dem Suchfilter als auch mit den aus Suchergebnissen bzw. Produktdetailseiten extrahierten Attributen verbunden werden können.
Beispiel:
```text
CriterionDefinition
id = <uuid>
origin = SOURCE
source = geizhals
category = notebook
label = "Arbeitsspeicher"
SourceFilterMapping:
Geizhals Filter 17023 -> CriterionDefinition.id
SourceAttributeMapping:
"RAM" -> CriterionDefinition.id
"Arbeitsspeicher" -> CriterionDefinition.id
ggf. source_attribute_id -> CriterionDefinition.id
```
Eine Produktbeobachtung soll dann auf dieselbe interne CriterionDefinition referenzieren:
```text
ProductAttributeObservation
- source_product_reference_id
- criterion_definition_id
- raw_name
- raw_value
- normalized_value
- unit
- observed_at
- search_run_id
```
## 2.4 INTERNAL-Kriterien
Zusätzliche interne Kriterien werden nur angelegt, wenn für die Kaufentscheidung relevante Eigenschaften von der Source-Taxonomie nicht angeboten werden.
Beispiele:
- externer Akku-Benchmark
- Reparierbarkeit
- Testscore
- Lautstärke unter Last
```text
CriterionDefinition.origin = INTERNAL
```
Diese besitzen naturgemäß keinen Source-Filter, können aber später von anderen Datenquellen befüllt werden.
---
# 3. Produktkategorien
## 3.1 Interner Katalog
Das BuyingProject referenziert eine interne `ProductCategory`.
Beispiel:
```text
ProductCategory
id = notebook
name = Notebooks
status = ACTIVE
```
Erforderlicher Endpunkt:
```text
GET /api/v1/product-categories
```
Response mindestens:
```text
id
name
status
```
Der initiale Pilot enthält mindestens:
```text
notebook
```
## 3.2 Source-Zuordnung
Die Source-spezifische Kategorie wird separat gemappt:
```text
SourceCategory
source = geizhals
external_category_key = nb
product_category_id = notebook
```
Damit ist das BuyingProject nicht direkt an `geizhals/nb` gekoppelt.
---
# 4. BuyingProject
Ein BuyingProject ist die konkrete Instanz eines Kaufvorhabens.
Es kann **ohne ProductProfile** angelegt werden.
Mindestens:
```text
id
workspace_id nullable
name
product_category_id
market
currency
target_budget nullable
shortlist_size
status
template_id nullable
template_version nullable
criteria_version
created_at
updated_at
archived_at nullable
```
Status:
```text
ACTIVE
PURCHASED
ARCHIVED
```
Default:
```text
shortlist_size = 5
criteria_version = 1
```
## 4.1 Kein doppelter Suchpreis
Ein extern wirksames Preismaximum wird **nicht zusätzlich als BuyingProject-Feld** gehalten.
Wenn die Source einen Preisfilter unterstützt, wird das maximale Suchbudget als normales ProjectCriterion modelliert.
`target_budget` bleibt optional als spätere Ranking-/Decision-Information.
Damit existieren nicht zwei konkurrierende Wahrheiten für denselben externen Preisfilter.
---
# 5. Rolle von ProductProfile / BuyingTemplate
Das bisherige `ProductProfile` ist für neue BuyingProjects **nicht verpflichtend**.
Fachlich wird es als optionale Vorlage verstanden.
Zielbild:
```text
BuyingTemplate
"Mobiler Entwicklungsrechner"
↓ optional beim Projektstart
BuyingProject
```
Eine Vorlage kann z. B. liefern:
- vorgeschlagene Suchkriterien
- Ranking-Konfiguration
- Default-Zielbudget
- Kategorie
- Markt / Währung
Beim Erstellen eines Projekts werden relevante Werte in die Projektkonfiguration übernommen.
Spätere Änderungen an der Vorlage verändern bestehende Projekte **nicht rückwirkend**.
Zur Nachvollziehbarkeit:
```text
template_id
template_version
```
Erforderlicher Endpunkt:
```text
GET /api/v1/buying-templates
```
Response mindestens:
```text
id
version
name
product_category_id
market
currency
description nullable
```
Die bestehende ProductProfile-Implementierung darf technisch weiterverwendet werden, solange sie nicht mehr als zwingende Voraussetzung für ein BuyingProject fungiert.
---
# 6. ProjectCriteria und Versionierung
## 6.1 Zweck
ProjectCriteria beschreiben die vom Nutzer gewählten Suchparameter eines BuyingProjects.
Sie referenzieren die dynamisch geladenen CriterionDefinitions.
## 6.2 Immutable Versions
ProjectCriteria werden nicht still überschrieben.
Ein fachlich relevanter Criteria-Wechsel erzeugt eine neue `criteria_version`.
Schema:
```text
ProjectCriterion
- id
- project_id
- criteria_version
- criterion_definition_id
- operator
- value
- enabled_for_discovery
- created_at
```
Für eine Project-/Criteria-Version gilt:
```text
(project_id, criteria_version, criterion_definition_id)
```
als fachlich eindeutige Kombination.
`BuyingProject.criteria_version` zeigt auf die aktuell aktive Version.
Änderung:
```text
aktuelle criteria_version = 3
↓
Nutzer ändert Kriterien
↓
neue immutable Kriterienmenge version 4
↓
BuyingProject.criteria_version = 4
```
SearchRuns und DiscoverySearches müssen die verwendete `criteria_version` speichern.
## 6.3 Standard-UI
Die Standardoberfläche zeigt keine technischen Ranking-Parameter wie:
- weight
- acceptable
- ideal
- strategy
- scoring_version
Der Nutzer wählt lediglich:
```text
Kriterium
Operator
Wert / Wertebereich
für externe Suche aktiv ja/nein
```
Optional kann später eine einfache Kennzeichnung wie „Muss“ / „Wunsch“ ergänzt werden, sie ist für v0.3 nicht Voraussetzung.
---
# 7. Ranking-Konfiguration
Discovery und Ranking bleiben getrennte fachliche Bereiche.
ProjectCriteria dürfen keine versteckten Scoring-Regeln erzwingen.
Ein Projekt kann optional eine Ranking-Konfiguration besitzen.
Mögliche Herkunft:
1. aus einem BuyingTemplate/ProductProfile übernommen
2. später explizit durch Advanced UI
3. später von KI vorgeschlagen
4. später durch eine dokumentierte Default-Strategie erzeugt
Für den MVP gilt:
- Ein Projekt ohne Ranking-Konfiguration darf trotzdem Discovery ausführen.
- In diesem Zustand zeigt das Frontend Kandidaten, aber kennzeichnet Ranking als `NOT_CONFIGURED`.
- Für den Notebook-Pilot kann das vorhandene Profil `mobile-development-notebook` als optionale Vorlage genutzt werden, damit ProductScore/Shortlist end-to-end getestet werden können.
- Es dürfen keine stillen, nicht dokumentierten Scoring-Regeln erfunden werden.
---
# 8. Brücke CriterionDefinition -> bestehende Ranking-Engine
Die bestehende Ranking-/ProductProfile-Engine arbeitet weiterhin mit Attribut-Keys wie:
```text
ram_gb
storage_gb
weight_kg
```
Dafür erhält `CriterionDefinition` optional:
```text
scoring_attribute_key nullable
```
Beispiel:
```text
CriterionDefinition.id = <uuid>
label = "Arbeitsspeicher"
scoring_attribute_key = "ram_gb"
```
Wichtig:
- `scoring_attribute_key` ist **kein Canonical-Criterion-Layer**.
- Es ist ausschließlich ein technischer Adapter zur bestehenden Scoring-Engine.
- Source-Kriterium und interne UUID bleiben die primäre Persistenzidentität.
- Ist kein Ranking-Mapping vorhanden, bleibt `scoring_attribute_key = null`.
- Ein fehlender Mapping-Key darf keine Discovery verhindern.
So kann ein aus Geizhals beobachteter Wert:
```text
criterion_definition_id = <uuid>
normalized_value = 32
```
für einen Engine-Snapshot zusätzlich als:
```text
ram_gb = 32
```
bereitgestellt werden.
---
# 9. Source-Auswahl im Wizard
Der Nutzer soll die technische Source möglichst nicht auswählen müssen, solange nur eine geeignete Source existiert.
Nach Auswahl einer ProductCategory fragt das Frontend:
```text
GET /api/v1/product-categories/{product_category_id}/sources
```
Response mindestens:
```text
source
source_category_id
display_name
status
```
Regeln:
- genau eine aktive Source -> automatisch auswählen
- mehrere aktive Sources -> Source-Picker anzeigen
- keine aktive Source -> verständlicher Empty State
Der Nutzer muss die externe SourceCategory-ID wie `nb` nicht kennen.
---
# 10. Criterion-Katalog vor Projektanlage
Der Projekt-Wizard muss Kriterien laden können, bevor eine Project-ID existiert.
Erforderliche Endpunkte:
```text
GET /api/v1/product-categories
GET /api/v1/product-categories/{product_category_id}/sources
GET /api/v1/product-categories/{product_category_id}/available-criteria?source=geizhals
```
Backend löst intern:
```text
ProductCategory
+ Source
↓
SourceCategory
↓
CriterionDefinitions / SourceFilterDefinitions
```
auf.
`available-criteria` liefert mindestens:
```text
criterion_definition_id
label
description nullable
origin
source
product_category_id
filter_type
unit nullable
operators
options / range metadata
mapping_status
supports_discovery
scoring_attribute_key nullable
```
Nur Source-Kriterien mit nutzbarer Filterdefinition werden als externe Suchparameter angeboten.
INTERNAL-Kriterien dürfen separat sichtbar sein, wenn sie für die Kategorie bekannt sind.
---
# 11. Taxonomie-Lernen
Taxonomie wird on demand pro SourceCategory gelernt.
Initial:
```text
Geizhals -> Notebooks
```
Die gelernte Taxonomie soll später im Admin-Bereich bearbeitbar sein.
Mapping-Status:
```text
DISCOVERED
LEARNED_UNVERIFIED
VERIFIED
BROKEN
DISABLED
```
Nur `VERIFIED` darf automatisch für produktive Source-Query-Generierung verwendet werden.
Technisch eindeutige automatische Verifikation ist zulässig, wenn der Backend-Service einen reproduzierbaren Roundtrip sicher nachweisen kann.
---
# 12. DiscoverySearch
Persistenz bleibt **1:n**:
```text
BuyingProject
└── DiscoverySearch[]
```
Für den ersten MVP gilt als fachliche Einschränkung:
```text
maximal eine aktive DiscoverySearch
pro BuyingProject + Source + SourceCategory
```
Diese Einschränkung darf nicht als dauerhafte 1:1-Beziehung in das Datenmodell eingebaut werden.
Mindestens:
```text
id
buying_project_id
source_id
source_category_id
criteria_version
mapping_version
generated_url
status
schedule_interval_minutes
next_run_at nullable
last_successful_run_at nullable
created_at
updated_at
```
## 12.1 Scheduling
Das Suchintervall gehört fachlich an die `DiscoverySearch`.
`BuyingProject` hält **kein zweites maßgebliches schedule_interval_minutes**.
Ein Template oder Projekt-Wizard darf einen Defaultwert vorgeben, dieser wird beim Anlegen der DiscoverySearch kopiert.
Ab dann ist:
```text
DiscoverySearch.schedule_interval_minutes
```
die maßgebliche Quelle.
Default:
```text
180 Minuten
```
---
# 13. Statuswelten strikt trennen
## 13.1 Project Lifecycle
```text
ACTIVE
PURCHASED
ARCHIVED
```
## 13.2 DiscoverySearch Status
```text
ACTIVE
OUT_OF_SYNC
BLOCKED
DISABLED
```
## 13.3 SearchRun Status
```text
RUNNING
SUCCESS
PARTIAL
FAILED
BLOCKED
TOO_BROAD
```
## 13.4 Run Diff
```text
NEW
CHANGED
UNCHANGED
MISSING
```
## 13.5 Product Lifecycle
```text
ACTIVE
DISAPPEARED
ARCHIVED
```
## 13.6 Identity / Matching
```text
MATCHED
MATCH_UNCERTAIN
```
Diese Status dürfen in DB, API und UI nicht zu einem gemeinsamen Statusfeld vermischt werden.
---
# 14. Diff- und Lifecycle-Regeln
Nur vollständige erfolgreiche Full Runs dürfen fehlende Produkte ableiten.
```text
SUCCESS Full Run:
- NEW
- CHANGED
- UNCHANGED
- MISSING
```
`PARTIAL` darf **keine** MISSING-/DISAPPEARED-Transition auslösen.
Lifecycle:
```text
1 vollständiger Run fehlend -> MISSING im Run-Diff
3 aufeinanderfolgende vollständige Runs fehlend -> Product Lifecycle DISAPPEARED
```
Wiederauftauchen reaktiviert bestehende SourceProductReference, sofern Identity ausreichend sicher ist.
---
# 15. SearchRun Summary
SearchRun-Responses und History-Listen müssen Aggregatzähler liefern.
Mindestens:
```text
id
discovery_search_id
started_at
finished_at nullable
status
result_count
page_count
new_count
changed_count
unchanged_count
missing_count
error_code nullable
```
`disappeared_count` darf optional zusätzlich als Lifecycle-Aggregat geliefert werden, gehört aber nicht zum Run-Diff-Enum.
---
# 16. ProductObservation und Attribute
Eine Observation gehört mindestens zu:
```text
SourceProductReference
+
SearchRun
```
Schema konzeptionell:
```text
ProductObservation
- id
- source_product_reference_id
- search_run_id
- observed_at
- best_price
- currency
- availability nullable
- seller_count nullable
```
Dynamische Attribute:
```text
ProductAttributeObservation
- product_observation_id
- criterion_definition_id
- raw_name
- raw_value
- normalized_value
- unit nullable
```
Damit ist jede Beobachtung eindeutig einem Produkt und einem konkreten SearchRun zugeordnet.
---
# 17. Candidate Summary DTO
Listen und Shortlist müssen genügend Daten liefern, damit das Frontend nicht für jede Zeile Einzelrequests benötigt.
Mindestens:
```text
candidate_id
source_product_reference_id
title
manufacturer nullable
model nullable
source_url
current_best_price nullable
currency nullable
current_dynamic_attributes
product_score nullable
product_eligibility nullable
ranking_status
shortlist_status
current_diff_status
product_lifecycle_status
match_status
first_seen_at
last_seen_at
```
`current_dynamic_attributes` ist metadata-driven und keine feste Notebook-Struktur.
---
# 18. Candidate Detail DTO
Erforderlicher Endpunkt:
```text
GET /api/v1/buying-projects/{project_id}/candidates/{candidate_id}
```
Mindestens:
```text
Candidate Summary
+
alle aktuellen normalisierten Attribute
+
Source-Metadaten
+
aktuelle Observation
+
Ranking-/Eligibility-Daten, soweit vorhanden
```
Das Frontend darf keine Gründe/Empfehlungen behaupten, die nicht tatsächlich persistiert sind.
---
# 19. Observation History
Erforderlicher Endpunkt:
```text
GET /api/v1/buying-projects/{project_id}/candidates/{candidate_id}/observations
```
Mindestens:
```text
observation_id
observed_at
best_price
currency
availability nullable
seller_count nullable
dynamic_attributes
search_run_id
```
Diese Historie ermöglicht:
- Preisentwicklung
- Attributänderungen
- Last Seen
- Verlauf über SearchRuns
Kein HTML-Archiv.
---
# 20. Project Summary DTO
Die Projektliste soll nicht N+1-Requests erzeugen.
Erforderlich:
```text
GET /api/v1/buying-projects
```
Pro Projekt mindestens:
```text
id
name
product_category
status
last_successful_run_at nullable
next_run_at nullable
discovery_status nullable
candidate_count
shortlist_count
new_count
changed_count
missing_count
current_search_result_count nullable
```
---
# 21. Projekt-Wizard
Route:
```text
/projects/new
```
## Schritt 1 – Basis
```text
Projektname
Produktkategorie
Markt
Währung
Zielbudget optional
Shortlist-Größe
optional BuyingTemplate
```
## Schritt 2 – Source
Frontend lädt:
```text
GET /product-categories/{id}/sources
```
Bei genau einer Source automatische Auswahl.
Bei mehreren Sources Auswahl durch Nutzer.
## Schritt 3 – Kriterien laden
Frontend lädt:
```text
GET /product-categories/{id}/available-criteria?source=...
```
Die UI zeigt Source-Kriterien als verständliche Liste.
Beispiel Notebooks:
```text
[ ] Hersteller
[ ] Arbeitsspeicher
[ ] SSD
[ ] Gewicht
[ ] Displaygröße
[ ] CPU
[ ] Grafikkarte
...
```
## Schritt 4 – Parameter setzen
Nach Aktivierung eines Kriteriums:
```text
Arbeitsspeicher
Operator: mindestens
Wert: 24 GB
```
Control-Typen:
```text
BOOLEAN
ENUM
MULTI_ENUM
NUMERIC_RANGE
NUMERIC_THRESHOLD
TEXT
```
Das UI rendert dynamisch.
Keine hartcodierten Notebook-Felder.
## Schritt 5 – Projekt speichern
Erst jetzt wird das BuyingProject angelegt und die ProjectCriteria-Version gespeichert.
Danach wird die initiale DiscoverySearch für die ausgewählte Source erzeugt.
---
# 22. Kriterienänderung im bestehenden Projekt
Route:
```text
/projects/:projectId/criteria
```
Ändert der Nutzer relevante ProjectCriteria:
```text
neue immutable criteria_version
BuyingProject.criteria_version = neue Version
DiscoverySearch.status = OUT_OF_SYNC
```
Das Frontend zeigt:
> Suchparameter wurden geändert. Die externe Suche muss aktualisiert werden.
Aktion:
```text
Suche aktualisieren
```
Backend:
```text
generate-query
→ VERIFIED Mappings verwenden
→ neue URL speichern
→ optional SearchRun starten
```
---
# 23. PATCH BuyingProject
Erforderlicher Endpunkt:
```text
PATCH /api/v1/buying-projects/{id}
```
Im MVP explizit änderbar:
```text
name
target_budget
shortlist_size
status
```
Nicht über diesen PATCH ändern:
```text
product_category_id
market
currency
criteria
source
source_category
schedule_interval
```
Suchrelevante Änderungen laufen über die Criteria-/DiscoverySearch-Verträge.
Falls Kategorie/Markt/Währung später änderbar werden, benötigt dies einen expliziten Migrations-/Reinitialisierungsflow und ist nicht Teil des MVP.
---
# 24. Hauptscreens
## 24.1 Projektliste
```text
/projects
```
Anzeige:
- Name
- Kategorie
- Project Lifecycle
- letzter erfolgreicher Run
- Discovery-Status
- Kandidatenzahl
- Shortlistzahl
- NEW / CHANGED Counts
- nächste Suche
## 24.2 Projektdetail
```text
/projects/:projectId
```
Bereiche:
```text
Übersicht
Kandidaten
Shortlist
Kriterien
Historie
```
## 24.3 Kandidaten
```text
/projects/:projectId/candidates
```
Desktop:
- Tabelle / kompakte Liste
Mobile:
- Cards / List Items
## 24.4 Kandidatendetail
```text
/projects/:projectId/candidates/:candidateId
```
Zeigt nur tatsächlich vorhandene Daten:
- Titel / Hersteller / Modell
- aktueller Preis
- dynamische Attribute
- Eligibility / ProductScore, soweit vorhanden
- Ranking Status
- Diff Status
- Product Lifecycle
- Match Status
- First Seen / Last Seen
- Observation History
Nicht anzeigen, solange Backend es nicht erzeugt:
- Deep-Enrichment-Ergebnisse
- Review-Zusammenfassungen
- finale Kaufentscheidung
- nicht persistierte Begründungen
## 24.5 Shortlist
```text
/projects/:projectId/shortlist
```
Zeigt Top-N, falls Ranking konfiguriert.
Ohne Ranking-Konfiguration:
```text
Ranking ist noch nicht konfiguriert.
```
## 24.6 Historie
```text
/projects/:projectId/history
```
SearchRuns mit:
```text
Zeitpunkt
Run Status
Trefferzahl
Page Count
NEW
CHANGED
UNCHANGED
MISSING
Fehler / BLOCKED / TOO_BROAD
```
---
# 25. Admin-Bereich
Routen:
```text
/admin/sources
/admin/sources/:source/categories
/admin/sources/:source/categories/:category
```
Funktionen:
- Kategorien sehen
- Taxonomie laden/neu laden
- SourceFilterDefinitions sehen
- Optionen / Ranges sehen
- CriterionDefinitions sehen
- SearchFilter-Mapping sehen
- SourceAttributeMappings sehen
- scoring_attribute_key sehen/pflegen, soweit administrativ vorgesehen
- Mapping-Status ändern
- kleine Mapping-Korrekturen durchführen
- BROKEN / DISABLED erkennen
Technische IDs sind nur hier sichtbar.
Das Frontend generiert keine Source-URL-Encodings selbst.
---
# 26. Frontend-Technologie
Verbindlich:
- React
- TypeScript
- Vite
- PWA
- TanStack Query
- React Router oder vergleichbare schlanke Lösung
- Playwright für E2E
Nicht erforderlich:
- Next.js
- SSR
- SEO
---
# 27. Responsive Architektur
Von Anfang an zwei bewusste Hauptlayouts.
## Desktop
```text
Sidebar + Hauptinhalt + optional Detailbereich
```
## Mobile
```text
Bottom Navigation / kompakte Navigation
+ Vollbild-Detailseiten
+ projektinterne Tabs
```
Keine bloße Verkleinerung des Desktop-Layouts.
---
# 28. Mitai-Bezug
Wenn der Mitai-Quellcode dem Coding-Agenten im Repository oder über einen konkret angegebenen Pfad zugänglich ist, sollen passende Muster wiederverwendet werden:
- PWA-Shell
- Responsive Breakpoints
- Navigation
- Layout
- ggf. Basiskomponenten
Ohne zugänglichen Pfad darf der Agent keine Annahmen über konkrete Mitai-Komponenten treffen.
Dann nur dieselben Architekturprinzipien umsetzen.
---
# 29. CORS
Backend muss den Frontend-Origin konfigurierbar zulassen.
Keine harte Produktions-URL im Code.
Beispiel:
```text
CORS_ALLOWED_ORIGINS
```
Lokale und produktive Origins über Environment.
---
# 30. Ports
Ports sind Deployment-Konfiguration und keine Fachlogik.
Sie werden über:
- Docker Compose
- `.env`
- `.env.example`
konfiguriert.
Keine fachliche Spec darf einen unveränderlichen Frontend-Port voraussetzen.
---
# 31. Docker Compose
Der nutzbare MVP enthält mindestens:
```text
api
postgres
frontend
```
Das Frontend muss über Root-Compose startbar sein.
Development und Production Build dürfen unterschiedliche Docker Targets verwenden.
---
# 32. CI
CI muss mindestens enthalten:
Backend:
- Unit Tests
- Integration Tests
- API E2E
Frontend:
- TypeScript typecheck
- lint
- unit/component tests
- production build
E2E:
- Playwright
- vollständiger Notebook-Pilot mit Fixture/Mock-Source
- Desktop viewport
- Mobile viewport
Live-Geizhals-Abfragen gehören nicht in CI.
---
# 33. PWA
Mindestens:
- Manifest
- installierbare App
- App Icons
- Responsive App Shell
- Offline-Fallback
Keine komplexe Offline-Synchronisation im MVP.
---
# 34. Loading / Empty / Error States
Jeder wesentliche Screen braucht:
```text
Loading
Empty
Error
Retry
```
Beispiele:
- Noch keine Projekte vorhanden.
- Noch keine Kandidaten gefunden.
- Noch kein SearchRun ausgeführt.
- Taxonomie wurde noch nicht geladen.
- Keine Source für diese Produktkategorie verfügbar.
- Suche ist OUT_OF_SYNC.
- Suche wurde BLOCKED.
- Ranking ist nicht konfiguriert.
---
# 35. API-Verträge – Mindestumfang
## Produktkategorien
```text
GET /api/v1/product-categories
GET /api/v1/product-categories/{id}/sources
GET /api/v1/product-categories/{id}/available-criteria?source=...
```
## Templates
```text
GET /api/v1/buying-templates
```
## Projekte
```text
GET /api/v1/buying-projects
POST /api/v1/buying-projects
GET /api/v1/buying-projects/{id}
PATCH /api/v1/buying-projects/{id}
```
## Kriterien
```text
GET /api/v1/buying-projects/{id}/criteria
PUT /api/v1/buying-projects/{id}/criteria
```
`PUT` erzeugt eine neue immutable criteria_version.
## Source / Taxonomie
```text
GET /api/v1/sources
GET /api/v1/sources/{source}/categories
POST /api/v1/sources/{source}/categories/{category}/discover-taxonomy
```
## Admin Mapping
```text
GET /api/v1/sources/{source}/categories/{category}/filters
GET /api/v1/criterion-mappings
POST /api/v1/criterion-mappings
PATCH /api/v1/criterion-mappings/{id}
GET /api/v1/source-attribute-mappings
POST /api/v1/source-attribute-mappings
PATCH /api/v1/source-attribute-mappings/{id}
```
## Discovery Search
```text
GET /api/v1/buying-projects/{id}/discovery-searches
POST /api/v1/buying-projects/{id}/discovery-searches
POST /api/v1/discovery-searches/{id}/generate-query
POST /api/v1/discovery-searches/{id}/run
PATCH /api/v1/discovery-searches/{id}
```
`PATCH DiscoverySearch` darf im MVP mindestens ändern:
```text
schedule_interval_minutes
status ACTIVE/DISABLED
```
## SearchRuns
```text
GET /api/v1/discovery-searches/{id}/runs
GET /api/v1/search-runs/{id}
```
## Candidates
```text
GET /api/v1/buying-projects/{id}/candidates
GET /api/v1/buying-projects/{id}/candidates/{candidateId}
GET /api/v1/buying-projects/{id}/candidates/{candidateId}/observations
GET /api/v1/buying-projects/{id}/shortlist
```
---
# 36. Nutzerfreundlichkeit
Die Standardoberfläche zeigt keine Begriffe wie:
```text
CriterionDefinition
SourceFilterDefinition
mapping_version
source_filter_id
normalized_spec_fingerprint
scoring_attribute_key
```
Stattdessen:
```text
Arbeitsspeicher
mindestens
24 GB
```
Technische Details gehören in den Admin-Bereich.
---
# 37. KI-Erweiterung – bewusst vorbereitet, nicht implementiert
Später soll KI den heute manuellen Kriterien-Auswahlschritt übernehmen können.
Ziel:
```text
Nutzer beschreibt Wunsch in natürlicher Sprache
↓
KI erhält verfügbare Source-Kriterien
↓
KI schlägt konkrete Filterparameter vor
↓
Nutzer bestätigt / korrigiert
↓
ProjectCriteria werden gespeichert
```
Wichtig:
Die KI soll Source-Kriterien möglichst aus dem tatsächlich verfügbaren Katalog auswählen.
INTERNAL-Kriterien dürfen nur vorgeschlagen werden, wenn sie im System bekannt sind und eine Datenquelle bzw. Bewertungsstrategie existiert.
Keine KI-Implementierung in v0.3.
---
# 38. Tests
## Frontend Unit / Component
- dynamischer Criteria Renderer pro Filtertyp
- ProductCategory Picker
- automatische Source-Auswahl bei genau einer Source
- Source Picker bei mehreren Sources
- Projekt-Wizard
- Mapping-/Statusdarstellung
- Candidate List
- Candidate Detail
- Mobile Navigation
- Desktop Navigation
- Loading / Empty / Error States
## API Integration
- ProductCategory-Katalog laden
- Sources pro ProductCategory laden
- available-criteria vor Projektanlage laden
- Projekt ohne Profil anlegen
- Projekt optional aus Template anlegen
- Criteria versioniert speichern
- DiscoverySearch anlegen
- Suchintervall an DiscoverySearch ändern
- Search generieren
- SearchRun starten
- SearchRun Summary laden
- Candidate Summary laden
- Candidate Detail laden
- Observation History laden
- Shortlist laden
## E2E Notebook-Pilot
```text
App öffnen
→ neues Projekt
→ Notebooks wählen
→ verfügbare Source automatisch bestimmen
→ Geizhals-Kriterien laden
→ RAM >= 24 GB auswählen
→ Preismaximum als Source-Kriterium setzen
→ Projekt speichern
→ DiscoverySearch erzeugen
→ Query generieren
→ SearchRun ausführen
→ Kandidaten anzeigen
→ Kandidat öffnen
→ dynamische Attribute sehen
→ Preis / Observation History sehen
→ Kriterien ändern
→ neue criteria_version
→ OUT_OF_SYNC sehen
→ Query neu generieren
```
Wenn optional ein Ranking-Template verwendet wird:
```text
→ scoring_attribute_key Brücke verwenden
→ ProductScore sichtbar
→ Shortlist sichtbar
```
## Responsive E2E
Mindestens:
- Desktop Viewport
- Mobile Viewport
---
# 39. Nicht Bestandteil dieses Slices
- Auth / Login
- RBAC
- Einladungen
- Multi-User UI
- Keepa
- Idealo
- LLM-Automation
- Deep Enrichment
- finale Decision Engine
- Kaufautomatisierung
- Warenkorb
- komplexe Benachrichtigungen
Die Architektur darf diese Erweiterungen nicht verbauen.
---
# 40. Definition of Done
## Architektur
- [ ] Keine separate Canonical-Criteria-Schicht.
- [ ] ProductCategory-Katalog existiert.
- [ ] Source-Zuordnung pro ProductCategory existiert.
- [ ] Source-Kriterien besitzen stabile interne CriterionDefinition-IDs.
- [ ] SearchFilter und Ergebnisattribute können auf dieselbe CriterionDefinition-ID gemappt werden.
- [ ] Optionales `scoring_attribute_key` verbindet Kriterien mit bestehender Ranking-Engine.
- [ ] INTERNAL-Kriterien sind möglich.
- [ ] BuyingProject ist ohne ProductProfile anlegbar.
- [ ] ProductProfile kann optional als Vorlage dienen.
- [ ] ProjectCriteria sind immutable versioniert.
- [ ] SearchRun und DiscoverySearch referenzieren criteria_version.
- [ ] DiscoverySearch-Persistenz bleibt 1:n-fähig.
- [ ] Scheduling liegt fachlich an DiscoverySearch.
- [ ] Statuswelten sind getrennt.
## API
- [ ] `GET /product-categories` existiert.
- [ ] Sources pro ProductCategory existieren.
- [ ] available-criteria funktioniert vor Projektanlage.
- [ ] BuyingTemplate-Liste existiert.
- [ ] Candidate Summary DTO enthält Preis, Attribute, Diff, Lifecycle, last_seen und Ranking-Felder.
- [ ] Candidate Detail existiert.
- [ ] Observation History existiert.
- [ ] SearchRun Summary enthält Diff-Aggregate.
- [ ] Project Summary DTO enthält Aggregate.
- [ ] PATCH-Felder sind explizit definiert.
- [ ] CORS ist konfigurierbar.
## Frontend
- [ ] React + TypeScript + Vite.
- [ ] PWA installierbar.
- [ ] Desktop-Layout.
- [ ] Mobile-Layout.
- [ ] Projektliste.
- [ ] Projekt-Wizard.
- [ ] ProductCategory Picker.
- [ ] Source wird automatisch gewählt, wenn nur eine verfügbar ist.
- [ ] Kriterien dynamisch aus Source-Taxonomie.
- [ ] Keine notebook-spezifisch hartcodierten Criteria Controls.
- [ ] Projekt-Detail.
- [ ] Kandidatenliste.
- [ ] Kandidatendetail.
- [ ] Observation History.
- [ ] Shortlist, wenn Ranking verfügbar.
- [ ] Admin-Bereich für Taxonomie/Mappings.
- [ ] OUT_OF_SYNC sichtbar und korrigierbar.
- [ ] Loading / Empty / Error States.
## Integration
- [ ] Frontend in Docker Compose.
- [ ] Ports über Environment/Compose konfigurierbar.
- [ ] Frontend Build/Tests in CI.
- [ ] Backend Tests grün.
- [ ] Frontend Tests grün.
- [ ] Playwright Desktop E2E grün.
- [ ] Playwright Mobile E2E grün.
- [ ] Notebook-Pilot über die UI erfolgreich.
---
# 41. Arbeitsauftrag an den Coding-Agenten
Diese Datei ersetzt `buying-agent-frontend-mvp-spec-v0.2.md` vollständig.
Bitte **noch nicht implementieren**.
Führe erneut denselben Review-Prozess durch wie bei v0.1 und v0.2.
Prüfe die aktuelle Repository-Implementierung gegen:
1. `buying-agent-v0.1-spec-v1.1.md`
2. `buying-agent-architecture-addendum-v1.md`
3. `buying-agent-source-discovery-spec-v0.1.md`
4. `buying-agent-frontend-mvp-spec-v0.3.md`
Prüfe insbesondere explizit, ob:
- die bisherigen F1–F6 geschlossen sind,
- R1 ProductCategory-Katalog geschlossen ist,
- R2 Source-Auswahl vor Projektanlage geschlossen ist,
- R3 CriterionDefinition-UUID ↔ Scoring-Attribut-Key geschlossen ist,
- R4 ProjectCriteria-Versionierung geschlossen ist,
- die bisherigen kleineren Restlücken zu Template-API, PATCH-Semantik,
SearchRun-Zählern, Observation-Zuordnung, Scheduling, CORS, Ports,
Compose, CI und Teststack geschlossen sind.
Berichte ausschließlich:
- verbleibende echte fachliche Widersprüche,
- noch fehlende Verträge,
- technisch riskante Annahmen,
- notwendige Backend-Änderungen,
- notwendige Frontend-Änderungen.
Keine neue Fachlogik erfinden, um Lücken still zu schließen.
Wenn keine blockierenden Punkte mehr bestehen:
1. erstelle einen aktualisierten Implementierungsplan,
2. plane **vertikal** statt „Backend komplett, danach Frontend“,
3. jeder Slice muss unmittelbar über die UI testbar sein,
4. beginne die Implementierung erst nach ausdrücklicher Freigabe dieses Reviews.