1709 lines
31 KiB
Markdown
1709 lines
31 KiB
Markdown
|
|
# 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.
|