BuyingAgent/.specs/buying-agent-source-discovery-spec-v0.1.md
Lars b276758ee1
All checks were successful
Deploy Development / deploy (push) Successful in 1m19s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-checks (push) Successful in 28s
Stelle den Frontend-MVP und die v0.3-API auf develop bereit, damit Dev API und PWA zusammen deployen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 13:04:59 +02:00

987 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Buying Agent – Source & Discovery Specification v0.1
**Status:** implementierungsbereit
**Spezifikationsversion:** 0.1
**Stand:** 2026-10-04
**Ergänzt:** `buying-agent-v0.1-spec-v1.1.md` und `buying-agent-architecture-addendum-v1.md`
## 1. Zweck
Diese Spezifikation definiert den ersten real nutzbaren Discovery-Slice des Buying Agents.
Ziel ist, externe Produktsuchen automatisiert aus den Kriterien eines BuyingProjects abzuleiten, regelmäßig auszuführen, Ergebnisse dauerhaft zu verwalten, Veränderungen zu erkennen und die Treffer anschließend durch die eigene Buying-Engine zu bewerten.
Der erste Pilot erfolgt mit **Geizhals, Kategorie Notebooks**. Die Architektur darf jedoch nicht notebook-spezifisch sein.
```text
BuyingProject
↓
Canonical Criteria
↓
Source Taxonomy + Mapping
↓
generierte Geizhals-Suche
↓
periodischer SearchRun
↓
persistierte Produkte + Beobachtungen
↓
Diff zum vorherigen Lauf
↓
NEW / CHANGED / MISSING / DISAPPEARED
↓
interne Constraints + Ranking
↓
Shortlist
↓
später Deep Enrichment
↓
später Decision
```
## 2. Fachliche Leitprinzipien
1. Geizhals ist Discovery-Quelle, nicht Decision Engine.
2. Die eigenen BuyingProject-Kriterien bleiben die fachliche Quelle der Wahrheit.
3. Geizhals wird als möglichst starker Vorfilter genutzt.
4. Nicht auf Geizhals abbildbare Kriterien bleiben interne Kriterien.
5. Discovery, Ranking und Decision bleiben getrennte fachliche Stufen.
6. Externe Filtertaxonomien werden **on demand** gelernt, nicht vollständig vorab katalogisiert.
7. URL-Encoding und Filtersyntax dürfen nicht vom LLM erfunden werden.
8. Produktidentität wird zunächst konservativ und pragmatisch behandelt.
9. Bei Unsicherheit erfolgt keine automatische Zusammenführung.
10. Suchläufe speichern strukturierte Zustände, keine HTML-Archive.
## 3. Scope v0.1
### Enthalten
- Geizhals als erste Discovery-Quelle
- Pilotkategorie Notebooks
- On-Demand-Erfassung der Geizhals-Filtertaxonomie
- Speicherung von Source-Kategorien, Filterdefinitionen, Optionen und Wertebereichen
- Mapping zwischen Canonical Criteria und Source-Filtern
- reproduzierbare Generierung von Geizhals-Such-URLs
- periodische SearchRuns
- vollständige Ergebnisaufnahme einer aktiven Suche
- persistierte SourceProductReferences
- Historisierung sichtbarer Attribute und Preise
- Diffing zwischen Suchläufen
- Status `NEW`, `CHANGED`, `UNCHANGED`, `MISSING`, `DISAPPEARED`
- interne Bewertung der Kandidaten
- konfigurierbare Shortlist, Default 5
- defensives Fehler-/Backoff-Verhalten
- Tests
### Nicht enthalten
- Frontend-Implementierung
- Keepa
- Idealo
- LLM-Automation
- automatische Kaufentscheidung
- Kaufautomatisierung
- Warenkorbmanagement
- Authentifizierung / RBAC
- Multi-User-UI
- automatische Deep-Enrichment-Quellen
- Umgehung von Schutzmechanismen externer Websites
## 4. Source Taxonomy
### 4.1 SourceCategory
Repräsentiert eine Kategorie einer externen Quelle.
```json
{
"source": "geizhals",
"external_category_key": "nb",
"name": "Notebooks",
"source_url": "https://geizhals.de/?cat=nb",
"status": "ACTIVE",
"last_verified_at": "2026-10-04T10:00:00Z"
}
```
### 4.2 SourceFilterDefinition
Beschreibt einen von Geizhals angebotenen Filter.
```json
{
"source": "geizhals",
"category_key": "nb",
"external_filter_key": "17023",
"label": "Arbeitsspeicher",
"filter_type": "NUMERIC_THRESHOLD",
"canonical_unit": "GB",
"status": "VERIFIED"
}
```
Mögliche `filter_type`-Werte:
```text
BOOLEAN
ENUM
MULTI_ENUM
NUMERIC_RANGE
NUMERIC_THRESHOLD
TEXT
UNKNOWN
```
### 4.3 SourceFilterOption
Für diskrete Filterwerte:
```json
{
"filter_key": "17023",
"source_value": "ab 24GB",
"normalized_value": 24,
"operator": ">=",
"unit": "GB"
}
```
### 4.4 SourceFilterRange
Für Bereiche bzw. frei parametrisierbare Werte:
```json
{
"filter_key": "price",
"minimum": 0,
"maximum": null,
"step": 1,
"unit": "EUR"
}
```
## 5. On-Demand Taxonomy Learning
Die Taxonomie wird nur für tatsächlich verwendete Kategorien gelernt.
Initial:
```text
Geizhals → Notebooks
```
Später z. B.:
```text
Monitore
Fernseher
Kameras
Rasenmäher
...
```
### Ablauf
```text
Kategorie öffnen
↓
verfügbare Filter erkennen
↓
Filterdefinitionen speichern
↓
Optionen / Bereiche erfassen
↓
URL-Verhalten beobachten
↓
Mapping erzeugen
↓
technisch verifizieren
```
### Mapping-Status
```text
DISCOVERED
LEARNED_UNVERIFIED
VERIFIED
BROKEN
DISABLED
```
Nur `VERIFIED` darf für automatische produktive Suchläufe verwendet werden.
### Änderbarkeit
Die gelernten Mappings sollen später im Frontend administrierbar sein.
v0.1 benötigt dafür noch keine UI, aber die Persistenz muss Änderungen ermöglichen, ohne Source-Code anzupassen.
## 6. Canonical Criteria
Canonical Criteria sind die interne fachliche Darstellung eines Suchkriteriums.
Sie sollen sich stark an der Source-Taxonomie orientieren, aber nicht fest an Geizhals gekoppelt sein.
```json
{
"key": "ram_gb",
"value_type": "number",
"unit": "GB",
"semantic": "working_memory"
}
```
Ein BuyingProject verwendet daraus konkrete Anforderungen:
```json
{
"criterion": "ram_gb",
"operator": ">=",
"value": 24,
"mandatory": true
}
```
### Source Mapping
```text
Canonical Criterion
ram_gb >= 24
↓
Geizhals Mapping
filter_key = 17023
source_value = "ab 24GB"
↓
URL fragment
xf=17023_ab+24GB
```
Nicht jedes Canonical Criterion muss ein Source Mapping besitzen.
Fehlt ein Mapping:
```text
Criterion → INTERNAL_ONLY
```
Das Kriterium wird dann erst nach Discovery intern bewertet.
## 7. DiscoverySearch
Eine `DiscoverySearch` gehört zu einem BuyingProject und einer Source.
Sie enthält mindestens:
```text
id
buying_project_id
source_id
source_category_id
criteria_version
mapping_version
generated_url
status
schedule_interval_minutes
broad_result_threshold
created_at
updated_at
last_successful_run_at
```
Defaults:
```text
schedule_interval_minutes = 180
shortlist_size = 5
broad_result_threshold = 300
```
## 8. Search URL Generation
Die URL wird ausschließlich aus:
```text
BuyingProject Criteria
+
VERIFIED CriterionMappings
+
SourceCategory
+
konfigurierbaren Source-Optionen
```
generiert.
Die URL ist abgeleitet, nicht die fachliche Quelle der Wahrheit.
Eine Änderung der BuyingProject-Kriterien erzeugt eine neue `criteria_version`.
Wenn die gespeicherte DiscoverySearch nicht mehr zur aktuellen Criteria-/Mapping-Version passt:
```text
status = OUT_OF_SYNC
```
Danach muss die URL neu generiert werden.
## 9. Ergebnisumfang
Aktive Suchläufe werden grundsätzlich vollständig eingelesen.
Keine stille Begrenzung auf Top-N.
Nur vollständige Ergebnismengen erlauben belastbare Erkennung von:
```text
NEW
CHANGED
MISSING
DISAPPEARED
```
### TOO_BROAD
Überschreitet die Treffermenge den konfigurierten Schwellwert:
```text
result_count > broad_result_threshold
```
wird die Suche markiert:
```text
TOO_BROAD
```
Die Suche darf technisch weiter ausgeführt werden, erzeugt aber einen Hinweis, dass die Source-Kriterien geschärft werden sollten.
Spätere Versionen dürfen breite Suchen differenziert behandeln, etwa seltenere Full-Reconciliation.
## 10. SearchRun
Jeder Abruf erzeugt einen unveränderlichen `SearchRun`.
Felder:
```text
id
discovery_search_id
criteria_version
mapping_version
generated_url
started_at
finished_at
status
result_count
page_count
http_status_summary
error_code
```
Status:
```text
RUNNING
SUCCESS
PARTIAL
FAILED
BLOCKED
```
Es wird kein HTML-Snapshot gespeichert.
## 11. SourceProductReference
Ein Treffer einer externen Quelle wird als `SourceProductReference` dauerhaft angelegt.
Mindestens verfügbare Felder:
```text
id
source_id
source_category_id
source_url
source_title
manufacturer
model
manufacturer_sku
gtin_ean
normalized_spec_fingerprint
first_seen_at
last_seen_at
status
```
Keines dieser Felder außer der internen UUID wird pauschal als garantiert stabil betrachtet.
## 12. Produktidentität – Pilotansatz
v0.1 implementiert bewusst keine komplexe globale Identity Engine.
Für den Notebook-Pilot werden folgende Signale verwendet:
1. vorhandene stabile Source-ID, falls entdeckt
2. GTIN/EAN, falls vorhanden
3. Hersteller-SKU, falls vorhanden
4. kanonische Source-URL
5. normalisierter Titel
6. Hersteller + Modell
7. differenzierende technische Specs
Für Notebooks kann der initiale Fingerprint beispielsweise enthalten:
```text
manufacturer
model
cpu
ram
storage
display_size
gpu
```
### Matching-Regeln
- Hohe Sicherheit → existierende SourceProductReference verwenden
- Unsicherheit → `MATCH_UNCERTAIN`
- Keine sichere Übereinstimmung → neue SourceProductReference
- niemals zwei Produkte allein aufgrund ähnlicher Namen automatisch verschmelzen
Die tatsächliche Stabilität der Identitätsmerkmale wird im Notebook-Pilot beobachtet und danach verfeinert.
## 13. Produktbeobachtungen und Historie
Technische Eigenschaften und Preise werden nicht einfach überschrieben.
Jeder SearchRun kann neue Beobachtungen erzeugen.
### ProductObservation
```text
source_product_reference_id
search_run_id
observed_at
raw_title
best_price
currency
raw_attributes
normalized_attributes
availability
seller_count
```
Veränderte Werte bleiben historisch nachvollziehbar.
Beispiele:
```text
Preis: 1499 → 1299
RAM: 16 → 32
Anbieterzahl: 8 → 12
Verfügbarkeit geändert
```
Bei technischen Attributänderungen muss das System berücksichtigen, dass auch fehlerhaftes Matching vorliegen könnte.
## 14. Diffing
Jeder erfolgreiche Full SearchRun wird mit dem vorherigen erfolgreichen Full SearchRun derselben DiscoverySearch verglichen.
### Status
```text
NEW
CHANGED
UNCHANGED
MISSING
DISAPPEARED
```
### Regeln
**NEW**
Produkt war im vorherigen vollständigen Lauf nicht vorhanden.
**CHANGED**
Produkt vorhanden, aber mindestens ein relevantes beobachtetes Feld hat sich verändert.
**UNCHANGED**
Produkt vorhanden und relevante Felder unverändert.
**MISSING**
Produkt fehlt in einem vollständigen Lauf.
**DISAPPEARED**
Produkt fehlt in drei aufeinanderfolgenden vollständigen Läufen.
Produkte werden nicht gelöscht.
Historie bleibt erhalten.
Ein später erneut auftauchendes Produkt wird reaktiviert und nicht als neues Produkt dupliziert, sofern das Matching ausreichend sicher ist.
## 15. Lifecycle eines BuyingProjects
Während ein BuyingProject aktiv ist:
```text
ACTIVE
↓
DiscoverySearches laufen
↓
Produkte + Historie wachsen
↓
Ranking + Shortlist
```
Nach finalem Kauf:
```text
PURCHASED
↓
BuyingProject archivieren
↓
Discovery stoppen
```
Produkte, Beobachtungen und Evaluationen können mit dem Projekt archiviert bzw. aus aktiven Ansichten entfernt werden.
Keine physische Löschung historischer Daten als Standardverhalten.
## 16. Ranking
Nach Discovery übernimmt ausschließlich die interne Buying Engine.
```text
Source Results
↓
interne Hard Constraints
↓
ProductScore
↓
Ranking
```
Geizhals-Sortierung darf für die technische Ausgabe genutzt werden, ist aber keine fachliche Ranking-Entscheidung.
## 17. Shortlist
Nach internem Ranking wird eine Shortlist gebildet.
Default:
```text
shortlist_size = 5
```
Der Wert ist pro BuyingProject konfigurierbar.
Shortlist-Kandidaten erhalten:
```text
SHORTLISTED
```
Andere gültige Kandidaten:
```text
CANDIDATE
```
Die spätere Deep-Enrichment-Stufe arbeitet primär auf der Shortlist.
## 18. Deep Enrichment
Nicht Bestandteil dieses Implementierungsslices.
Zielbild:
```text
Shortlist
↓
Herstellerdaten
Tests
Benchmarks
Reviews
weitere Händler
Preisportale
Qualitätsinformationen
↓
erneute Bewertung
```
Discovery darf daher nicht voraussetzen, dass Geizhals alle für die finale Kaufentscheidung notwendigen Daten liefert.
## 19. Discovery, Ranking und Decision
Die drei fachlichen Stufen müssen getrennt bleiben.
### Discovery
```text
Welche Produkte kommen potenziell infrage?
```
### Ranking
```text
Welche dieser Produkte passen am besten zum BuyingProject?
```
### Decision
```text
Ist jetzt der richtige Zeitpunkt, Kandidat X zu kaufen?
```
Decision wird später u. a. berücksichtigen können:
- Zielpreis
- Deadline
- historische Preisentwicklung
- aktuelle Verfügbarkeit
- Product Fit
- Offer Fit
- Alternativen
- Beschaffungsdringlichkeit
Decision ist nicht Bestandteil dieses Slices.
## 20. Scheduling und defensiver Abruf
Default:
```text
alle 180 Minuten
```
pro DiscoverySearch konfigurierbar.
### Anforderungen
- keine parallelen Massenabrufe
- Pagination sequenziell oder stark begrenzt parallel
- Caching, wo sinnvoll
- Timeout
- Retry mit exponentiellem Backoff
- Jitter
- Stop bei wiederholten Fehlern
- keine Umgehung von Captchas oder Bot-Schutz
### Reaktion
**HTTP 429**
```text
BACKOFF
```
**HTTP 403**
```text
BLOCKED
```
**Captcha / Challenge erkannt**
```text
BLOCKED
```
Automatische weitere Abrufe der betroffenen DiscoverySearch werden pausiert.
Keine Schutzmechanismen umgehen.
## 21. Source Adapter Contract
Ein Discovery-Adapter soll mindestens folgende Fähigkeiten kapseln:
```python
class DiscoverySourceAdapter(Protocol):
def discover_taxonomy(category_ref): ...
def build_query(criteria, mappings): ...
def fetch_results(query, pagination): ...
def parse_results(response): ...
def normalize_result(result): ...
```
Taxonomy Learning und Result Retrieval dürfen intern getrennte Services sein.
Der Rest des Systems darf keine Geizhals-spezifische URL- oder HTML-Logik kennen.
## 22. Services
Mindestens:
```text
TaxonomyService
CriterionMappingService
DiscoverySearchService
SearchExecutionService
SourceProductIdentityService
SearchDiffService
CandidateRankingService
ShortlistService
```
Keine Businesslogik in n8n.
n8n kann später den Scheduler bzw. externe Events orchestrieren.
## 23. Persistenz
Neue bzw. ergänzende Tabellen können sein:
```text
source_categories
source_filter_definitions
source_filter_options
source_filter_ranges
criterion_definitions
criterion_mappings
discovery_searches
search_runs
source_product_references
product_observations
project_candidates
candidate_status_history
```
Bestehende globale Entitäten aus der Hauptspezifikation bleiben bestehen:
```text
Product
ProductVariant
Offer
PriceObservation
```
Diese dürfen nicht unnötig dupliziert werden.
Der Discovery-Slice kann zunächst mit SourceProductReference arbeiten und erst bei ausreichender Identität eine Verknüpfung zu canonical Product/ProductVariant herstellen.
## 24. API – minimaler Vertrag
### Taxonomie
```text
POST /api/v1/sources/geizhals/categories/{category}/discover-taxonomy
GET /api/v1/sources/geizhals/categories/{category}/filters
```
### Mapping
```text
GET /api/v1/criterion-mappings
POST /api/v1/criterion-mappings
PATCH /api/v1/criterion-mappings/{id}
```
### DiscoverySearch
```text
POST /api/v1/discovery-searches
GET /api/v1/discovery-searches/{id}
POST /api/v1/discovery-searches/{id}/generate-query
POST /api/v1/discovery-searches/{id}/run
```
### 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}/shortlist
```
Falls BuyingProject im aktuellen Code noch nicht existiert, muss die Integration mit der im Architecture Addendum vorgesehenen nächsten Domain-Erweiterung abgestimmt werden.
## 25. Tests
### Taxonomy
- Notebook-Kategorie kann on demand eingelesen werden
- Filterdefinitionen werden reproduzierbar gespeichert
- Optionen werden nicht dupliziert
- Filteränderungen werden erkannt
- BROKEN-Mapping wird nicht produktiv verwendet
### Query Generation
- identische Criteria + Mapping-Version erzeugen identische URL
- RAM >= 24 erzeugt erwartetes Geizhals-Fragment
- Preisbereich korrekt
- mehrere Filter kombinierbar
- INTERNAL_ONLY-Kriterien verändern die Source-URL nicht
- veraltete Mapping-Version markiert Search als OUT_OF_SYNC
### Search Execution
- Pagination vollständig
- Ergebniszahl korrekt
- TOO_BROAD bei Überschreitung
- kein stilles Top-N-Abschneiden
- 429 → Backoff
- 403/Captcha → BLOCKED
### Identity
- identische SKU wird wiedererkannt
- identische EAN wird wiedererkannt
- gleiche URL wird wiedererkannt
- leicht geänderter Titel bei stabilen Specs wird erkannt
- unsicheres Match erzeugt MATCH_UNCERTAIN
- ähnliche Produktnamen werden nicht automatisch verschmolzen
### Diffing
- neuer Treffer → NEW
- geänderter Preis → CHANGED
- unverändert → UNCHANGED
- einmal fehlend → MISSING
- dreimal fehlend → DISAPPEARED
- Wiederauftauchen reaktiviert bestehende Referenz
### Historie
- Preisänderungen bleiben historisiert
- technische Änderungen bleiben historisiert
- alte SearchRuns bleiben unverändert
- Projektarchivierung löscht keine History
### Ranking
- nur interne Engine bestimmt Ranking
- Source-Sortierung beeinflusst ProductScore nicht
- Shortlistgröße Default 5
- Shortlistgröße konfigurierbar
## 26. Implementierungsreihenfolge
1. Bestehende Implementierung gegen diese Spec und das Architecture Addendum prüfen.
2. Notwendige BuyingProject-/ProjectCandidate-Minimalstruktur ergänzen, falls für Discovery erforderlich.
3. Source Taxonomy + Mapping persistieren.
4. Geizhals-Taxonomie-Pilot für Notebooks implementieren.
5. reproduzierbare Query Generation implementieren.
6. SearchRun + Pagination implementieren.
7. SourceProductReference + ProductObservation implementieren.
8. Identity Pilot implementieren.
9. Diffing implementieren.
10. interne Ranking-Engine anbinden.
11. Shortlist bilden.
12. Scheduling-Schnittstelle vorbereiten bzw. internen Scheduler minimal anbinden.
13. Tests vollständig ausführen.
14. Notebook-Pilot mit realer Suche validieren.
## 27. Definition of Done
- [ ] Geizhals-Notebook-Taxonomie wird on demand eingelesen und gespeichert.
- [ ] Source-Filter und Optionen werden persistiert.
- [ ] Filter-Mappings besitzen Lifecycle-Status.
- [ ] BuyingProject-Kriterien erzeugen reproduzierbar eine Geizhals-URL.
- [ ] Nicht abbildbare Kriterien bleiben INTERNAL_ONLY.
- [ ] Aktive Suche kann vollständig ausgeführt werden.
- [ ] Pagination wird vollständig verarbeitet.
- [ ] TOO_BROAD wird erkannt.
- [ ] SearchRuns werden gespeichert.
- [ ] Produkte werden dauerhaft als SourceProductReference angelegt.
- [ ] Preise und relevante Attribute werden historisiert.
- [ ] NEW / CHANGED / UNCHANGED / MISSING / DISAPPEARED funktionieren.
- [ ] DISAPPEARED erst nach drei aufeinanderfolgenden fehlenden Full Runs.
- [ ] Unsichere Produktidentität wird nicht automatisch verschmolzen.
- [ ] interne Engine bewertet Kandidaten.
- [ ] Shortlist Default 5 funktioniert.
- [ ] Suchfrequenz ist konfigurierbar, Default 180 Minuten.
- [ ] 403/429/Captcha führen zu defensivem Stop/Backoff.
- [ ] keine HTML-Archive.
- [ ] keine Notebook-spezifische Businesslogik außerhalb des Geizhals-Pilotmappings.
- [ ] Tests grün.
- [ ] Ergebnisse des Notebook-Piloten dokumentiert.
# Coding-Agent-Arbeitsauftrag
Verwende als verbindliche Anforderungen:
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`
Die neue Source-/Discovery-Spec ergänzt die bestehenden Dokumente. Bei Konflikten in ihrem Fachbereich hat sie Vorrang.
## Vorgehen
### 1. Zuerst analysieren
Prüfe den aktuellen Repository-Stand gegen alle drei Dokumente.
Berichte vor größeren Änderungen:
- welche Teile bereits kompatibel sind,
- welche Domain-/DB-/API-Erweiterungen erforderlich sind,
- ob bestehende Implementierung refaktoriert werden muss,
- welche Änderungen rein additiv möglich sind.
### 2. Minimal integrieren
Implementiere nur den beschriebenen Source-/Discovery-Slice.
Nicht implementieren:
- Frontend
- Keepa
- Idealo
- LLM-Automation
- Auth/RBAC
- Kaufautomatisierung
- Warenkorb
### 3. Besonders beachten
- nicht notebook-spezifisch modellieren
- Geizhals-spezifische Logik ausschließlich im Source Adapter / Mapping Layer
- URL-Encoding nur aus verifizierten Source-Mappings
- vollständige SearchRuns, kein stilles Top-N-Limit
- konservatives Product Matching
- keine automatische Zusammenführung bei Unsicherheit
- alle Änderungen historisieren
- Discovery, Ranking und Decision getrennt halten
### 4. Tests
Führe Unit-, Integration- und End-to-End-Tests aus.
Mindestens:
- Taxonomy Discovery
- Query Generation
- Pagination
- Identity Matching
- Diffing
- History
- Ranking
- Shortlist
- Backoff / BLOCKED
### 5. Abschlussbericht
Dokumentiere:
- geänderte Dateien
- neue Migrationen
- neue API-Endpunkte
- Testergebnisse
- real durchgeführten Notebook-Pilot
- erkannte Geizhals-Eigenheiten
- verbleibende Risiken
- insbesondere Beobachtungen zur Stabilität der Produktidentität