# 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