From bf48d5781a9a56f86c6ce2b1d3a6380bc5c7fa4c Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 26 Mar 2026 13:12:33 +0000 Subject: [PATCH] docs: create Profice-services/feedgine --- Profice-services/feedgine.md | 1138 ++++++++++++++++++++++++++++++++++ 1 file changed, 1138 insertions(+) create mode 100644 Profice-services/feedgine.md diff --git a/Profice-services/feedgine.md b/Profice-services/feedgine.md new file mode 100644 index 0000000..8938b31 --- /dev/null +++ b/Profice-services/feedgine.md @@ -0,0 +1,1138 @@ +--- +title: FEEDGINE +description: +published: true +date: 2026-03-26T13:12:31.315Z +tags: +editor: markdown +dateCreated: 2026-03-26T13:12:31.315Z +--- + +# POAS Feed Enricher — Technische Dokumentation + +**Projekt:** POAS Feed Enricher +**Sprache:** Python 3.11 +**Stand:** März 2026 + +--- + +## Inhaltsverzeichnis + +1. [Systemübersicht](#1-systemübersicht) +2. [Berechnungsformeln](#2-berechnungsformeln) +3. [Programmfunktionen](#3-programmfunktionen) +4. [Installation ohne Docker (Direktinstallation)](#4-installation-ohne-docker-direktinstallation) +5. [Installation mit Docker](#5-installation-mit-docker) +6. [Konfiguration](#6-konfiguration) +7. [Betrieb und Überwachung](#7-betrieb-und-überwachung) +8. [Systemarchitektur (Diagramme)](#8-systemarchitektur-diagramme) + +--- + +## 1. Systemübersicht + +Der **POAS Feed Enricher** ist eine automatisierte Datenpipeline, die täglich einen Google-Shopping-Produktfeed mit profitabilitätsbasierten Custom Labels anreichert und an Google Merchant Center (GMC) ausliefert. + +Das Programm verbindet drei Datenquellen: + +| Datenquelle | Inhalt | +|---|---| +| Shop-XML-Feed (ZIP-Download) | Produktdaten: ID, Titel, Preis, Bild-URL, Kategorie usw. | +| Google Ads API | Werbeleistung pro Produkt: Klicks, Conversions, Kosten, Umsatz | +| COGS-CSV (Einkaufspreise) | Netto-Einkaufspreis je Artikel-ID | + +Das Ergebnis ist ein angereicherter XML-Feed, in dem jedes Produkt zwei zusätzliche Felder erhält: + +- **`g:custom_label_0`** — Hauptgruppe (`Longtail`, `Core`, `Feeder`) +- **`g:custom_label_4`** — Untergruppe (z. B. `stars`, `potentials`, `underperformer`, `invisibles`, `nd_low_value` usw.) + +Diese Labels werden in Google Ads als Segmentierungskriterien für Gebotsstrategien verwendet. + +### Ablauf auf einen Blick + +```mermaid +flowchart TD + A["Shop-XML-Feed (ZIP)"] --> D["Pipeline"] + B["Google Ads API"] --> D + C["COGS-CSV (Einkaufspreise)"] --> D + D --> E["Angereicherter XML-Feed"] + E --> F["Google Merchant Center"] + E --> G["nginx Feed-Server"] + D --> H["Telegram-Benachrichtigung"] +``` + +--- + +## 2. Berechnungsformeln + +### 2.1 POAS — Profit on Ad Spend + +**POAS** (Profit on Ad Spend) ist die zentrale Kennzahl des Systems. Sie misst, wie profitabel der Werbeeinsatz für ein einzelnes Produkt ist. + +#### Formel + +$$\text{Marge} = \text{Conversion-Umsatz} - (\text{Einkaufspreis} \times \text{Anzahl Conversions})$$ + +$$\text{POAS} = \frac{\text{Marge}}{\text{Werbekosten}}$$ + +#### Schritt-für-Schritt-Erklärung + +**Schritt 1 — Marge berechnen:** + +``` +Marge = conversions_value − (cogs_eur × conversions) +``` + +> *Dieses Beispiel dient ausschließlich zur Veranschaulichung der Formel.* + +| Variable | Bedeutung | Quelle | +|---|---|---| +| `conversions_value` | Gesamtumsatz aller Conversions für dieses Produkt (in EUR) | Google Ads API | +| `cogs_eur` | Netto-Einkaufspreis pro Einheit (in EUR) | COGS-CSV | +| `conversions` | Anzahl der erzielten Käufe im Betrachtungszeitraum | Google Ads API | + +**Schritt 2 — POAS berechnen:** + +``` +POAS = Marge / cost_eur +``` + +> *Dieses Beispiel dient ausschließlich zur Veranschaulichung der Formel.* + +| Variable | Bedeutung | Quelle | +|---|---|---| +| `cost_eur` | Gesamte Werbeausgaben für dieses Produkt (in EUR) | Google Ads API (`cost_micros ÷ 1.000.000`) | + +#### Rechenbeispiel — Verlustbringendes Produkt + +| Größe | Wert | +|---|---| +| Werbekosten (30 Tage) | 10,00 € | +| Conversion-Umsatz | 120,00 € | +| Einkaufspreis pro Einheit | 50,00 € | +| Anzahl Conversions | 3 | + +``` +Marge = 120,00 € − (50,00 € × 3) = 120,00 € − 150,00 € = −30,00 € +POAS = −30,00 € / 10,00 € = −3,0 +``` + +> *Beispielrechnung zur Veranschaulichung — keine realen Daten.* + +In diesem Fall ist das Produkt **verlustbringend** (negativer POAS). Es wird der Gruppe **Feeder** zugeordnet. + +#### Rechenbeispiel — Profitables Produkt + +``` +Werbekosten: 10,00 € +Umsatz: 120,00 € +EK-Preis: 30,00 € +Conversions: 2 + +Marge = 120,00 € − (30,00 € × 2) = 60,00 € +POAS = 60,00 € / 10,00 € = 6,0 → Gruppe: Core +``` + +> *Beispielrechnung zur Veranschaulichung — keine realen Daten.* + +--- + +### 2.2 Produktgruppen-Zuordnung + +Die Zuordnung zu Gruppen und Untergruppen erfolgt nach folgender Logik: + +```mermaid +flowchart TD + START(["Produkt"]) --> A{"Klicks = 0?"} + + A -- Ja --> LT["Longtail"] + LT --> LT1{"Preis?"} + LT1 -- "unter 50 €" --> LT_L["nd_low_value"] + LT1 -- "50 € bis 200 €" --> LT_M["nd_mid_value"] + LT1 -- "über 200 €" --> LT_H["nd_high_value"] + + A -- Nein --> B{"POAS >= 1,0?"} + + B -- Ja --> CORE["Core"] + CORE --> C{"Klicks > 30 UND Conversions >= 2?"} + C -- Ja --> CORE_S["stars"] + C -- Nein --> CORE_P["potentials"] + + B -- Nein --> FEEDER["Feeder"] + FEEDER --> D{"Klicks > 3?"} + D -- Ja --> F_U["underperformer"] + D -- Nein --> F_I["invisibles"] +``` + +#### Gruppe: Longtail + +**Bedingung:** Das Produkt hatte im Betrachtungszeitraum **keine Klicks** (d. h. es läuft keine oder nur sehr geringe Werbung). + +| Untergruppe | Bedingung | Bedeutung | +|---|---|---| +| `nd_low_value` | Preis < 50 € | Günstige Produkte ohne Werbehistorie | +| `nd_mid_value` | 50 € ≤ Preis ≤ 200 € | Mittlere Preisklasse ohne Werbehistorie | +| `nd_high_value` | Preis > 200 € | Hochpreisige Produkte ohne Werbehistorie | + +**Strategie:** Geringe Gebote, organische Sichtbarkeit nutzen. + +--- + +#### Gruppe: Core + +**Bedingung:** Das Produkt hat Klicks **und** einen POAS ≥ 1,0 (profitabel). + +| Untergruppe | Bedingung | Bedeutung | +|---|---|---| +| `stars` | Klicks > 30 **und** Conversions ≥ 2 | Bewiesene Top-Performer mit hohem Volumen | +| `potentials` | Alle anderen Core-Produkte | Profitabel, aber noch geringes Volumen | + +**Strategie:** Gebote erhöhen, Budget aufstocken. + +--- + +#### Gruppe: Feeder + +**Bedingung:** Das Produkt hat Klicks, aber POAS < 1,0 (defizitär). + +| Untergruppe | Bedingung | Bedeutung | +|---|---|---| +| `underperformer` | Klicks > 3 | Generiert Klicks, aber keine rentablen Conversions | +| `invisibles` | Klicks ≤ 3 | Kaum sichtbar, schwache Performance | + +**Strategie:** Gebote senken, Anzeigen und Landingpages optimieren. + +--- + +### 2.3 Schwellenwerte (Konfigurierbar) + +| Parameter | Standardwert | Beschreibung | +|---|---|---| +| `POAS_CORE_THRESHOLD` | `1.0` | POAS-Grenzwert für Profitabilität | +| `LONGTAIL_LOW_MAX` | `50.0` | Preisgrenze Longtail „low" | +| `LONGTAIL_HIGH_MIN` | `200.0` | Preisgrenze Longtail „high" | +| `CORE_STARS_MIN_CLICKS` | `30` | Mindestklicks für „stars" | +| `CORE_STARS_MIN_CONVERSIONS` | `2` | Mindest-Conversions für „stars" | +| `FEEDER_UNDERPERFORMER_CLICKS` | `3` | Klick-Grenze für „underperformer" | +| `GOOGLE_ADS_LOOKBACK_DAYS` | `90` | Betrachtungszeitraum in Tagen | + +--- + +## 3. Programmfunktionen + +### 3.1 Scheduler (`Startup/scheduler.py`) + +Der Scheduler ist der Haupteinstiegspunkt. Er läuft dauerhaft und startet die Pipeline täglich zur konfigurierten Uhrzeit. + +**Funktionen:** +- Berechnung des nächsten Ausführungszeitpunkts aus `SCHEDULE_HOUR` und `SCHEDULE_MINUTE` +- Wartemodus in 60-Sekunden-Intervallen (CPU-schonend) +- Unterstützt `--run-now` (sofortige Ausführung), `--once` (einmalig, dann Beenden), `--lookback-days N` +- Sendet Telegram-Benachrichtigung beim Start des Schedulers + +### 3.2 Watchdog / Pipeline-Ausführer (`Startup/watchdog.py`) + +Der Watchdog orchestriert den gesamten Ausführungsablauf und implementiert Fehlerbehandlung. + +**Funktionen:** +- Erstellt datierte Ein- und Ausgabeordner (`Input DD.MM.YYYY`, `Output DD.MM.YYYY`) +- Lädt ZIP-Feeds herunter und entpackt die XML-Dateien +- Startet die Pipeline als Subprozess (mit Timeout) +- Retry-Logik: bis zu `MAX_RETRIES` Versuche mit `RETRY_DELAY_SECONDS` Pause +- **Integritätsprüfungen** vor der Veröffentlichung (siehe 3.6) +- Telegram-Benachrichtigungen bei Erfolg und Fehler (inkl. Log-Anhang) + +### 3.3 Google Ads Datenabruf (`poas_feed_enricher/app.py`) + +Ruft Werbeleistungsdaten aller Produkte über die Google Ads API ab. + +**Funktionen:** +- OAuth2-Authentifizierung via `google-ads.yaml` +- Automatische Erkennung und Iteration über alle Unterkonten eines MCC (Multi-Client-Account) +- GAQL-Abfrage auf `shopping_performance_view` für den konfigurierten Zeitraum +- Aggregation: Klicks, Impressionen, Kosten, Conversions, Umsatz je Produkt-ID +- Speicherung als `feed_performance.csv` +- Exponentielles Backoff bei transienten API-Fehlern (`UNAVAILABLE`, `DEADLINE_EXCEEDED`, `RESOURCE_EXHAUSTED`) + +**GAQL-Abfrage (vereinfacht):** + +```sql +SELECT + segments.product_item_id, + metrics.conversions_value, + metrics.conversions, + metrics.cost_micros, + metrics.impressions, + metrics.clicks +FROM shopping_performance_view +WHERE segments.date DURING LAST_90_DAYS +``` + +> *Dies ist kein direkt ausführbarer Code, sondern dient nur zur Veranschaulichung der Abfragestruktur.* + +### 3.4 Feed-Transformation (`poas_feed_enricher/2_data_transform.py`) + +Reichert den Shop-XML-Feed mit POAS-Labels an. + +**Funktionen:** +- **Streaming-XML-Verarbeitung** mit `lxml.iterparse` (speichereffizient für Feeds > 200 MB) +- Laden der Einkaufspreise (COGS) aus CSV-Datei +- Laden der Ads-Performance-Daten aus `feed_performance.csv` +- Produktfilter: Entfernt Produkte ohne gültiges Bild und mit Preis ≤ 0 +- POAS-Berechnung und Label-Zuordnung je Produkt +- Passthrough aller Originalfelder + Einfügen von `g:custom_label_0` und `g:custom_label_4` +- Generierung eines Audit-Reports (Anzahl Produkte, Trefferquoten, Label-Verteilung) + +### 3.5 Label Engine (`poas_feed_enricher/processors/label_engine.py`) + +Reine Berechnungsfunktion (keine Seiteneffekte) zur POAS-Labelberechnung. + +**Eingaben:** Preis, Einkaufspreis, Ads-Performance-Daten +**Ausgabe:** `LabelResult` mit `custom_label_0` und `custom_label_4` + +### 3.6 Integritätsprüfungen (`Startup/watchdog.py`) + +Vor der Veröffentlichung des Feeds werden drei Sicherheitsprüfungen durchgeführt: + +| Prüfung | Bedingung | Fehlerfall | +|---|---|---| +| Zero-Check | Ausgabe-Feed enthält > 0 Produkte | Feed ist leer | +| Integritäts-Check | Keine Produkte mit fehlender `g:id` | Strukturfehler im XML | +| Sanity-Check | Produktanzahl weicht < ±1000 vom Vorrun ab | Anomaler Produktverlust | + +Schlägt eine Prüfung fehl, wird der Feed **nicht** veröffentlicht und eine Telegram-Warnung gesendet. + +### 3.7 Feed-Veröffentlichung (`Startup/feed_publisher.py`) + +Kopiert den fertigen Feed in das nginx-Ausgabeverzeichnis. + +**Funktionen:** +- Kopiert den erzeugten XML-Feed nach `feed-serve/poas_feed.xml` +- Erstellt ein ZIP-Archiv als Download-Variante +- Aktualisiert `.last_feed_count` für den Sanity-Check des nächsten Runs + +### 3.8 Ordner-Rotation (`Startup/cleanup.py`) + +Verwaltet Speicherplatz durch automatische Rotation der datierten Ordner. + +**Funktionen:** +- Listet alle `Input DD.MM.YYYY`- und `Output DD.MM.YYYY`-Ordner auf +- Behält die neuesten `KEEP_LAST_N` (Standard: 3) Ordner +- Löscht ältere Ordner automatisch + +### 3.9 Telegram-Benachrichtigungen (`Startup/telegram_notifier.py`) + +Sendet Status-Nachrichten an konfigurierte Telegram-Chats. + +**Nachrichten-Typen:** + +| Ereignis | Inhalt | +|---|---| +| Scheduler-Start | Programmstart, geplante Uhrzeit, Lookback-Tage | +| Pipeline gestartet | Ausführungsbeginn | +| **Erfolg** | Feed-Statistiken, Label-Verteilung, Feed-URL | +| **Fehler** | Fehlermeldung + vollständige Log-Datei als Anhang | +| Feed gesperrt | Grund der Sicherheitsprüfung (Zero/Integrität/Sanity) | + +**Beispielformat einer Erfolgsmeldung:** + +``` +POAS Pipeline — SUCCESS +Datum: TT.MM.JJJJ HH:MM +Ausgabe: Output TT.MM.JJJJ + +--- Feed-Zusammenfassung --- +Quell-XML: XXX.XXX +Endgültiger Feed: XXX.XXX +Ads-Treffer: XXX.XXX + +--- Label-Verteilung --- +Longtail: XX.XXX (XX,X %) + nd_low_value: XX.XXX (XX,X %) + nd_mid_value: XX.XXX (XX,X %) + nd_high_value: XX.XXX (XX,X %) +Core: XX.XXX (XX,X %) + stars: XX.XXX (XX,X %) + potentials: XX.XXX (XX,X %) +Feeder: XX.XXX (XX,X %) + underperformer: XX.XXX (XX,X %) + invisibles: XX.XXX (XX,X %) + +Feed veröffentlicht: OK +GMC Feed: https://[FEED-SERVER]/[CLIENT_PREFIX]/poas_feed.xml +``` + +> *Dies ist ein Beispielformat — Zahlen und URLs sind Platzhalter.* + +--- + +## 4. Installation ohne Docker (Direktinstallation) + +### Voraussetzungen + +- Ubuntu 24.04 LTS (oder kompatible Linux-Distribution) +- Python 3.11 +- nginx +- Git + +### 4.1 System-Abhängigkeiten installieren + +```bash +sudo apt-get update +sudo apt-get install -y \ + python3.11 python3.11-venv python3-pip \ + gcc libxml2-dev libxslt1-dev \ + nginx git unzip curl +``` + +> *Beispielbefehl für Ubuntu 24.04 — Paketnamen können je nach Distribution abweichen.* + +### 4.2 Projektdateien einrichten + +```bash +# Ins gewünschte Verzeichnis wechseln +cd /opt + +# Projektarchiv entpacken +tar -xzf poas-feed-enricher.tar.gz +mv poas-feed-enricher poas-pipeline +cd poas-pipeline + +# Python Virtual Environment erstellen +python3.11 -m venv .venv +source .venv/bin/activate + +# Python-Abhängigkeiten installieren +pip install --upgrade pip +pip install -r requirements.txt +``` + +> *Beispielbefehle zur Veranschaulichung — Pfade und Dateinamen sind an die eigene Umgebung anzupassen.* + +### 4.3 Umgebungskonfiguration + +```bash +# .env-Datei erstellen und befüllen +cp .env.example .env +nano .env +``` + +> *Beispielbefehl — der tatsächliche Editor und Dateiname können abweichen.* + +Die folgenden Variablen **müssen** gesetzt werden (Details siehe [Abschnitt 6](#6-konfiguration)): + +```dotenv +TELEGRAM_BOT_TOKEN=... +TELEGRAM_CHAT_IDS=... +SHOP_NAME=... +SHOP_URL=... +FEED_ZIPS=... +GOOGLE_ADS_DEVELOPER_TOKEN=... +GOOGLE_ADS_CLIENT_ID=... +GOOGLE_ADS_CLIENT_SECRET=... +GOOGLE_ADS_REFRESH_TOKEN=... +GOOGLE_ADS_LOGIN_CUSTOMER_ID=... +``` + +> *Platzhalter — alle Werte sind durch eigene Zugangsdaten zu ersetzen.* + +### 4.4 Eingabedateien bereitstellen + +```bash +# Einkaufspreise (COGS) — Pflichtdatei +cp /pfad/zu/einkaufspreise.csv Datei/einkaufspreise.csv + +# Google Produktkategorie-Mapping (optional) +cp /pfad/zu/kategorien.csv Datei/google_product_category_mapping.csv +``` + +> *Pfade sind an die eigene Umgebung anzupassen.* + +**Format der COGS-Datei** (semikolon-getrennt, **ohne** Kopfzeile): + +``` +ARTIKEL-001;12.50 +ARTIKEL-002;0.00 +ARTIKEL-003;89.99 +``` + +> *Beispieldaten zur Veranschaulichung des Formats — keine realen Artikel.* + +**Format der Kategorie-Datei** (komma-getrennt, **mit** Kopfzeile): + +``` +id,google_product_category +ARTIKEL-001,8227 +ARTIKEL-002,3025 +``` + +> *Beispieldaten zur Veranschaulichung des Formats — keine realen Artikel.* + +### 4.5 Google Ads API konfigurieren + +Die Datei `poas_feed_enricher/google-ads.yaml` wird beim Docker-Start automatisch generiert. Bei Direktinstallation muss sie manuell erstellt werden: + +```yaml +developer_token: "[IHR_DEVELOPER_TOKEN]" +client_id: "[IHR_CLIENT_ID]" +client_secret: "[IHR_CLIENT_SECRET]" +refresh_token: "[IHR_REFRESH_TOKEN]" +login_customer_id: "[IHR_MCC_ID]" +use_proto_plus: true +``` + +> *Alle Felder in eckigen Klammern sind Platzhalter und müssen durch echte Google Ads API-Zugangsdaten ersetzt werden.* + +### 4.6 nginx konfigurieren + +```bash +# nginx-Konfiguration einspielen +sudo cp nginx.client.conf /etc/nginx/sites-available/poas-feed +sudo ln -s /etc/nginx/sites-available/poas-feed /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +> *Beispielbefehle für Ubuntu mit nginx — an die eigene Serverumgebung anpassen.* + +Ausgabeverzeichnis für den Feed erstellen: + +```bash +mkdir -p feed-serve +sudo chown -R $USER:www-data feed-serve +sudo chmod -R 755 feed-serve +``` + +> *Beispielbefehle — Benutzer und Berechtigungen sind je nach Serversetup anzupassen.* + +### 4.7 Als Systemd-Service einrichten + +Servicedatei unter `/etc/systemd/system/poas-pipeline.service` anlegen: + +```ini +[Unit] +Description=POAS Feed Enricher Pipeline +After=network.target + +[Service] +Type=simple +User=[SYSTEMBENUTZER] +WorkingDirectory=/opt/poas-pipeline +ExecStart=/opt/poas-pipeline/.venv/bin/python Startup/scheduler.py +Restart=on-failure +RestartSec=30 +EnvironmentFile=/opt/poas-pipeline/.env +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +``` + +> *Vorlage für eine systemd-Servicedatei — Pfade und Benutzername sind anzupassen.* + +```bash +# Service aktivieren und starten +sudo systemctl daemon-reload +sudo systemctl enable poas-pipeline +sudo systemctl start poas-pipeline + +# Status prüfen +sudo systemctl status poas-pipeline + +# Logs verfolgen +sudo journalctl -fu poas-pipeline +``` + +> *Beispielbefehle zur Veranschaulichung — Servicename ist ggf. anzupassen.* + +### 4.8 Manuelle Testausführung + +```bash +# Virtual Environment aktivieren +source .venv/bin/activate + +# Einmalige sofortige Ausführung (ohne auf geplante Zeit zu warten) +python Startup/scheduler.py --once + +# Sofort ausführen und danach weiter nach Zeitplan laufen +python Startup/scheduler.py --run-now + +# Mit abweichendem Lookback-Zeitraum +python Startup/scheduler.py --once --lookback-days 60 +``` + +> *Beispielbefehle zur Veranschaulichung der verfügbaren Startoptionen.* + +--- + +## 5. Installation mit Docker + +### Voraussetzungen + +- Docker Engine ≥ 24.0 +- Docker Compose Plugin ≥ 2.20 +- Mindestens 4 GB freier Speicher (Feeds können mehrere hundert MB groß sein) + +### 5.1 Docker und Docker Compose installieren + +```bash +# Docker installieren (Ubuntu) +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER +newgrp docker + +# Version prüfen +docker --version +docker compose version +``` + +> *Installationsbefehle für Ubuntu — für andere Systeme die offizielle Docker-Dokumentation verwenden.* + +### 5.2 Projektdateien einrichten + +```bash +cd /opt +tar -xzf poas-feed-enricher.tar.gz +mv poas-feed-enricher poas-pipeline +cd poas-pipeline +``` + +> *Beispielbefehle — Dateiname und Zielverzeichnis sind anzupassen.* + +### 5.3 Docker Compose konfigurieren + +```bash +# Vorlage kopieren +cp docker-compose.sample.yaml docker-compose.yaml + +# Konfiguration bearbeiten +nano docker-compose.yaml +``` + +> *Beispielbefehle — alle Platzhalter in der kopierten Datei anschließend mit echten Werten befüllen.* + +Alle Platzhalter `[WERT]` müssen durch eigene Werte ersetzt werden. Vollständige Konfigurationsstruktur: + +```yaml +services: + pipeline: + build: . + container_name: poas-pipeline + restart: unless-stopped + environment: + # Telegram + TELEGRAM_BOT_TOKEN: "[BOT_TOKEN]" + TELEGRAM_CHAT_IDS: "[CHAT_ID_1],[CHAT_ID_2]" + + # Shop-Informationen + SHOP_NAME: "[SHOPNAME]" + SHOP_URL: "https://[SHOP-DOMAIN]" + SHOP_DESCRIPTION: "[KURZBESCHREIBUNG]" + + # Feed-ZIP-URLs (Format: URL|ZIP-Name|XML-Name, mehrere durch ; getrennt) + FEED_ZIPS: "https://[SHOP-DOMAIN]/export/feed1.zip|feed1.zip|feed1.xml" + + # Google Ads API + GOOGLE_ADS_DEVELOPER_TOKEN: "[DEVELOPER_TOKEN]" + GOOGLE_ADS_CLIENT_ID: "[CLIENT_ID]" + GOOGLE_ADS_CLIENT_SECRET: "[CLIENT_SECRET]" + GOOGLE_ADS_REFRESH_TOKEN: "[REFRESH_TOKEN]" + GOOGLE_ADS_LOGIN_CUSTOMER_ID: "[MCC_KONTO_ID]" + + # Zeitplan (UTC) + SCHEDULE_HOUR: "0" + SCHEDULE_MINUTE: "0" + + # Lookback-Zeitraum + LOOKBACK_DAYS: "90" + + # Fehlerbehandlung + MAX_RETRIES: "3" + RETRY_DELAY_SECONDS: "60" + KEEP_LAST_N: "3" + + # Feed-Veröffentlichung + FEED_PUBLISH_ENABLED: "true" + CLIENT_PREFIX: "[SHOP-KÜRZEL]" + + volumes: + - poas-logs:/app/Startup/logs + - feed-serve:/app/feed-serve + + nginx: + image: nginx:alpine + container_name: poas-nginx + restart: unless-stopped + ports: + - "8525:80" + volumes: + - feed-serve:/usr/share/nginx/feeds:ro + - ./nginx.client.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - pipeline + +volumes: + poas-logs: + feed-serve: +``` + +> *Vorlage für docker-compose.yaml — alle Werte in eckigen Klammern sind Platzhalter und müssen ersetzt werden.* + +### 5.4 Eingabedateien bereitstellen + +Die Eingabedateien müssen **vor dem ersten Start** in den `Datei/`-Ordner gelegt werden, da dieser in das Docker-Image gebacken wird: + +```bash +# Einkaufspreise (Pflicht) +cp /pfad/zu/einkaufspreise.csv Datei/einkaufspreise.csv + +# Kategorie-Mapping (optional) +cp /pfad/zu/kategorien.csv Datei/google_product_category_mapping.csv +``` + +> *Pfade sind an die eigene Umgebung anzupassen.* + +> **Hinweis:** Nach Änderungen an Dateien im `Datei/`-Ordner muss das Docker-Image neu gebaut werden (`docker compose build`). + +### 5.5 Container starten + +```bash +# Image bauen und Container starten +docker compose up -d + +# Logs verfolgen +docker compose logs -f pipeline + +# Status prüfen +docker compose ps +``` + +> *Standardbefehle für Docker Compose v2.* + +### 5.6 Manuelle Ausführung im Container + +```bash +# Sofortige einmalige Ausführung +docker compose exec pipeline python Startup/scheduler.py --once + +# Sofort ausführen (Scheduler läuft weiter) +docker compose exec pipeline python Startup/scheduler.py --run-now + +# Mit angepasstem Lookback-Zeitraum +docker compose exec pipeline python Startup/scheduler.py --once --lookback-days 60 +``` + +> *Beispielbefehle zur Veranschaulichung der manuellen Ausführungsoptionen.* + +### 5.7 Container-Verwaltung + +```bash +# Container stoppen +docker compose stop + +# Container stoppen und entfernen +docker compose down + +# Container stoppen, Volumes löschen +# Achtung: Logs und Feed-Daten gehen dabei verloren! +docker compose down -v + +# Image neu bauen (nach Code- oder Dateiänderungen) +docker compose build --no-cache +docker compose up -d +``` + +> *Beispielbefehle für gängige Container-Verwaltungsaufgaben.* + +### 5.8 Architektur mit Docker + +```mermaid +graph TB + subgraph Host["Server"] + subgraph DockerNetwork["Docker-Netzwerk"] + PIPELINE["poas-pipeline\nPython 3.11-slim"] + NGINX["poas-nginx\nnginx:alpine, Port 8525"] + end + subgraph Volumes["Docker Volumes"] + V1[("poas-logs")] + V2[("feed-serve")] + end + end + + INTERNET["Internet\nGoogle Ads API / Telegram / Shop-ZIP"] + GMC["Google Merchant Center"] + TELEGRAM["Telegram"] + + PIPELINE --> V1 + PIPELINE --> V2 + NGINX --> V2 + PIPELINE --> INTERNET + NGINX -->|"Port 8525"| GMC + PIPELINE --> TELEGRAM +``` + +--- + +## 6. Konfiguration + +Alle Konfigurationsparameter werden über die `.env`-Datei (Direktinstallation) oder die `environment`-Sektion in `docker-compose.yaml` gesetzt. + +### 6.1 Pflichtparameter + +| Variable | Beispielformat | Beschreibung | +|---|---|---| +| `TELEGRAM_BOT_TOKEN` | `[TOKEN]:AAG...` | Token des Telegram-Bots | +| `TELEGRAM_CHAT_IDS` | `[ID1],[ID2]` | Chat-IDs (kommagetrennt) | +| `SHOP_NAME` | `[Shopname]` | Name des Shops (für Feed-Titel) | +| `SHOP_URL` | `https://[shop-domain]` | Shop-URL (für Feed-Link) | +| `SHOP_DESCRIPTION` | `[Beschreibung]` | Shop-Beschreibung | +| `FEED_ZIPS` | (siehe Format unten) | ZIP-Feed-URLs mit Dateinamen | +| `GOOGLE_ADS_DEVELOPER_TOKEN` | `[TOKEN]` | Google Ads Developer Token | +| `GOOGLE_ADS_CLIENT_ID` | `[ID].apps.googleusercontent.com` | OAuth2 Client-ID | +| `GOOGLE_ADS_CLIENT_SECRET` | `GOCSPX-[...]` | OAuth2 Client-Secret | +| `GOOGLE_ADS_REFRESH_TOKEN` | `1//04[...]` | OAuth2 Refresh-Token (langlebig) | +| `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | `[10-stellige ID]` | Google Ads MCC-Konto-ID | + +**Format von `FEED_ZIPS`:** + +Mehrere ZIP-Feeds werden durch `;` getrennt. Jeder Feed besteht aus drei durch `|` getrennten Teilen: + +``` +URL|ZIP-Dateiname|XML-Dateiname +``` + +> *Formatbeschreibung — keine funktionsfähige Konfiguration.* + +Beispiel mit zwei Feeds: + +```dotenv +FEED_ZIPS=https://[SHOP]/export/feed1.zip|feed1.zip|feed1.xml;https://[SHOP]/export/feed2.zip|feed2.zip|feed2.xml +``` + +> *Beispielformat — Platzhalter durch eigene URLs und Dateinamen ersetzen.* + +### 6.2 Optionale Parameter + +| Variable | Standard | Beschreibung | +|---|---|---| +| `SCHEDULE_HOUR` | `0` | Stunde der täglichen Ausführung (UTC) | +| `SCHEDULE_MINUTE` | `0` | Minute der täglichen Ausführung (UTC) | +| `LOOKBACK_DAYS` | `90` | Google Ads Betrachtungszeitraum (Tage) | +| `MAX_RETRIES` | `3` | Maximale Wiederholungsversuche bei Fehler | +| `RETRY_DELAY_SECONDS` | `60` | Wartezeit zwischen Versuchen (Sekunden) | +| `KEEP_LAST_N` | `3` | Anzahl zu behaltender datierter Ordner | +| `CLIENT_PREFIX` | `[shop-kuerzel]` | Präfix für Feed-URL-Pfad auf nginx | +| `FEED_PUBLISH_ENABLED` | `false` | Feed nach nginx veröffentlichen | +| `FEED_SERVER_PORT` | `8080` | Lokaler Feed-Server-Port | +| `FILTER_OUT_OF_STOCK` | `false` | Nicht vorrätige Produkte ausfiltern | + +--- + +## 7. Betrieb und Überwachung + +### 7.1 Täglicher Normalbetrieb + +Im Normalbetrieb ist **kein manueller Eingriff** erforderlich. Der Scheduler startet täglich zur konfigurierten Uhrzeit automatisch: + +```mermaid +sequenceDiagram + participant S as Scheduler + participant W as Watchdog + participant API as Google Ads API + participant ZIP as Shop-ZIP-Server + participant N as nginx + participant TG as Telegram + + S->>TG: Scheduler gestartet + Note over S: Wartet bis SCHEDULE_HOUR:SCHEDULE_MINUTE + + loop Täglich + S->>W: execute_pipeline() + W->>ZIP: ZIP-Feeds herunterladen + ZIP-->>W: XML-Dateien + W->>API: Google Ads Daten abrufen + API-->>W: feed_performance.csv + W->>W: XML anreichern (POAS-Labels) + W->>W: Integritätsprüfungen + W->>N: Feed veröffentlichen + W->>W: Alte Ordner löschen + W->>TG: Erfolgsmeldung mit Statistiken + end +``` + +### 7.2 Logs einsehen + +**Docker:** + +```bash +# Live-Logs des Pipeline-Containers +docker compose logs -f pipeline + +# Spezifische Log-Datei lesen +docker compose exec pipeline cat Startup/logs/pipeline_run_JJJJMMTT_HHMMSS.txt + +# Alle Log-Dateien auflisten +docker compose exec pipeline ls -lht Startup/logs/ +``` + +> *Beispielbefehle — Dateinamen mit echtem Datum und Uhrzeit ersetzen.* + +**Direktinstallation:** + +```bash +# Systemd-Journal +sudo journalctl -fu poas-pipeline + +# Einzelne Log-Datei +cat Startup/logs/pipeline_run_JJJJMMTT_HHMMSS.txt +``` + +> *Dateinamen mit echtem Datum und Uhrzeit ersetzen.* + +### 7.3 Feed-Status prüfen + +```bash +# Aktuellen Feed ansehen +ls -lh feed-serve/ + +# Produktanzahl im Feed zählen +grep -c '' feed-serve/poas_feed.xml + +# Letzte bekannte Produktanzahl +cat feed-serve/.last_feed_count +``` + +> *Beispielbefehle für die Direktinstallation.* + +**Docker:** + +```bash +docker compose exec nginx ls -lh /usr/share/nginx/feeds/ +``` + +### 7.4 Sofortige manuelle Ausführung + +```bash +# Docker +docker compose exec pipeline python Startup/scheduler.py --once + +# Direktinstallation +source .venv/bin/activate +python Startup/scheduler.py --once +``` + +> *Beispielbefehle für beide Betriebsarten.* + +### 7.5 Feed-URL-Struktur + +Nach erfolgreicher Ausführung und Veröffentlichung ist der Feed über nginx erreichbar: + +| Endpunkt | Beschreibung | +|---|---| +| `http://[SERVER-IP]:[PORT]/poas_feed.xml` | Aktueller GMC-Feed (XML) | +| `http://[SERVER-IP]:[PORT]/download` | Feed als ZIP-Download | + +Bei Verwendung eines zentralen Reverse-Proxies: + +``` +https://[FEED-SERVER]/[CLIENT_PREFIX]/poas_feed.xml +``` + +> *Platzhalter in eckigen Klammern durch die eigene Serveradresse und den konfigurierten Präfix ersetzen.* + +### 7.6 Häufige Fehler und Lösungen + +| Fehler | Ursache | Lösung | +|---|---|---| +| `google-ads.yaml nicht gefunden` | Credentials-Datei fehlt | `entrypoint.sh` ausführen oder Datei manuell erstellen | +| `Invalid OAuth2 credentials` | Refresh-Token abgelaufen | Neuen Refresh-Token via OAuth-Flow generieren | +| `ZIP download failed` | Shop-Server nicht erreichbar | Netzwerk prüfen, URL in `FEED_ZIPS` prüfen | +| Feed leer (0 Produkte) | Alle Produkte ausgefiltert | Filter-Logik und Eingabe-XML prüfen | +| Sanity-Check fehlgeschlagen | Produktzahl ändert sich um > 1000 | `.last_feed_count` prüfen, ggf. zurücksetzen | +| `RESOURCE_EXHAUSTED` (Google Ads) | API-Kontingent erschöpft | `LOOKBACK_DAYS` reduzieren oder nächsten Tag abwarten | + +--- + +## 8. Systemarchitektur (Diagramme) + +### 8.1 Komponentenübersicht + +```mermaid +graph TD + subgraph INPUT["Eingabedaten"] + ZIP["Shop-XML-Feeds (ZIP)"] + COGS["COGS-CSV (Einkaufspreise)"] + ADS["Google Ads API"] + end + + subgraph PIPELINE["Pipeline (Python 3.11)"] + SCHED["scheduler.py — Zeitsteuerung"] + WATCH["watchdog.py — Orchestrator"] + APP["app.py — Ads-Datenabruf"] + TRANS["2_data_transform.py — XML-Anreicherung"] + LABEL["label_engine.py — POAS-Berechnung"] + PUB["feed_publisher.py — Veröffentlichung"] + CLEAN["cleanup.py — Speicherverwaltung"] + TG["telegram_notifier.py — Benachrichtigung"] + end + + subgraph OUTPUT["Ausgabe"] + FEED["poas_feed.xml (angereicherter GMC-Feed)"] + NGINX["nginx — Feed-Server"] + TELEGRAM["Telegram — Benachrichtigung"] + end + + ZIP --> WATCH + COGS --> TRANS + ADS --> APP + APP --> TRANS + SCHED --> WATCH + WATCH --> APP + WATCH --> TRANS + TRANS --> LABEL + LABEL --> TRANS + WATCH --> PUB + PUB --> FEED + FEED --> NGINX + WATCH --> CLEAN + WATCH --> TG + TG --> TELEGRAM +``` + +### 8.2 Dateistruktur + +``` +poas-pipeline/ +├── .env ← Konfiguration & Credentials +├── Dockerfile ← Docker-Image-Definition +├── docker-compose.yaml ← Docker-Deployment +├── docker-compose.sample.yaml ← Vorlage für docker-compose.yaml +├── entrypoint.sh ← Docker-Startskript (generiert google-ads.yaml) +├── nginx.client.conf ← nginx-Konfiguration +├── requirements.txt ← Python-Abhängigkeiten +├── setup.sh ← Direktinstallations-Skript (Ubuntu) +│ +├── poas_feed_enricher/ ← Kern-Pipeline +│ ├── config.py ← Schwellenwerte & Konstanten +│ ├── run.py ← Einstiegspunkt (Schritt 1 + 2) +│ ├── app.py ← Google Ads API-Abruf +│ ├── 2_data_transform.py ← XML-Anreicherung +│ ├── google-ads.yaml ← Google Ads Credentials (auto-generiert) +│ ├── loaders/ +│ │ ├── ads_loader.py ← CSV-basierter Ads-Loader +│ │ ├── cogs_loader.py ← COGS-CSV-Loader +│ │ └── google_ads_api.py ← Live Google Ads API-Client +│ └── processors/ +│ ├── label_engine.py ← POAS-Labelberechnung +│ └── gmc_validator.py ← GMC XML-Validierung +│ +├── Startup/ ← Orchestrierungsschicht +│ ├── config.py ← Zentrale .env-Konfiguration +│ ├── scheduler.py ← Täglicher Zeitplaner +│ ├── watchdog.py ← Pipeline-Ausführer + Retry +│ ├── cleanup.py ← Ordner-Erstellung & -Rotation +│ ├── feed_publisher.py ← Feed nach nginx kopieren +│ ├── telegram_notifier.py ← Telegram-Benachrichtigungen +│ └── logs/ ← Ausführungs-Logs +│ +├── Datei/ ← Statische Eingabedaten +│ ├── einkaufspreise.csv ← Einkaufspreise (COGS) +│ └── google_product_category_mapping.csv +│ +└── feed-serve/ ← Ausgabe (nginx-Verzeichnis) + ├── poas_feed.xml ← Aktueller GMC-Feed + ├── poas_feed.zip ← Feed als ZIP-Download + └── .last_feed_count ← Sanity-Check-Baseline +``` + +> *Schematische Dateistruktur zur Orientierung — tatsächliche Dateinamen können abweichen.* + +### 8.3 Pipeline-Sequenzdiagramm (detailliert) + +```mermaid +sequenceDiagram + participant SCH as Scheduler + participant WDG as Watchdog + participant CLN as Cleanup + participant APP as Ads-Abruf + participant TRN as XML-Anreicherung + participant PUB as Feed-Publisher + participant TG as Telegram + + SCH->>TG: Scheduler gestartet + Note over SCH: Wartet bis SCHEDULE_HOUR:SCHEDULE_MINUTE (UTC) + + SCH->>WDG: execute_pipeline(lookback_days) + WDG->>TG: Pipeline gestartet + + WDG->>CLN: Datierten Input-Ordner erstellen + CLN->>CLN: ZIP-Feeds herunterladen + CLN->>CLN: XMLs entpacken und umbenennen + CLN-->>WDG: Liste der XML-Dateien + + WDG->>APP: Google Ads Daten abrufen + APP->>APP: Credentials laden + APP->>APP: MCC-Unterkonten auflisten + loop Je Unterkonto + APP->>APP: GAQL-Abfrage ausführen + end + APP->>APP: feed_performance.csv schreiben + APP-->>WDG: Fertig + + WDG->>TRN: XML anreichern + TRN->>TRN: COGS-CSV laden + TRN->>TRN: Performance-Daten laden + loop Je Produkt (Streaming) + TRN->>TRN: Preis und Bild-URL prüfen + TRN->>TRN: POAS berechnen + TRN->>TRN: Label zuweisen + TRN->>TRN: XML mit Labels schreiben + end + TRN-->>WDG: poas_feed.xml + Statistiken + + WDG->>WDG: Integritätsprüfungen (Zero / Integrität / Sanity) + + alt Alle Prüfungen bestanden + WDG->>PUB: Feed veröffentlichen + PUB->>PUB: XML nach feed-serve/ kopieren + PUB->>PUB: ZIP-Archiv erstellen + PUB-->>WDG: OK + WDG->>CLN: Alte Ordner rotieren + WDG->>TG: Erfolgsmeldung mit Statistiken + else Prüfung fehlgeschlagen + WDG->>TG: Warnung: Feed gesperrt (Grund) + end +``` + +### 8.4 POAS-Entscheidungsbaum (Labelzuordnung) + +```mermaid +flowchart TD + P(["Produkt"]) --> F1{"Preis = 0 oder kein Bild?"} + F1 -- Ja --> OUT["Verworfen — nicht im Feed"] + F1 -- Nein --> F2{"Klicks = 0?"} + + F2 -- Ja --> LT["Longtail — custom_label_0"] + LT --> F3{"Preis?"} + F3 -- "unter 50 EUR" --> LL["nd_low_value — custom_label_4"] + F3 -- "50 bis 200 EUR" --> LM["nd_mid_value — custom_label_4"] + F3 -- "über 200 EUR" --> LH["nd_high_value — custom_label_4"] + + F2 -- Nein --> CALC["POAS berechnen"] + CALC --> F4{"POAS >= 1,0?"} + + F4 -- Ja --> CORE["Core — custom_label_0"] + CORE --> F5{"Klicks > 30 UND Conversions >= 2?"} + F5 -- Ja --> CS["stars — custom_label_4"] + F5 -- Nein --> CP["potentials — custom_label_4"] + + F4 -- Nein --> FEEDER["Feeder — custom_label_0"] + FEEDER --> F6{"Klicks > 3?"} + F6 -- Ja --> FU["underperformer — custom_label_4"] + F6 -- Nein --> FI["invisibles — custom_label_4"] +``` + +--- + +*Dokumentation für POAS Feed Enricher — alle Namen, URLs, IDs und Zahlen in dieser Dokumentation sind Platzhalter und dienen ausschließlich der Veranschaulichung.*