--- title: FEEDGINE description: published: true date: 2026-03-26T13:33:30.807Z tags: editor: markdown dateCreated: 2026-03-26T13:12:31.315Z --- # POAS Feed Enricher — Technische Dokumentation **Projekt:** POAS Feed Enricher **Sprache:** Python 3.11 **Stand:** 26.01.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["Chat- & E-Mail-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 Chat- und E-Mail-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) - Chat- und E-Mail-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 Chat- sowie E-Mail-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 Chat- und E-Mail-Benachrichtigungen (`Startup/notifier.py`) Sendet Status-Nachrichten über zwei Kanäle: als **Chat-Nachricht** (z. B. an einen konfigurierten Messaging-Dienst) und als **E-Mail** an definierte Empfängeradressen. **Nachrichten-Typen:** | Ereignis | Chat-Nachricht | E-Mail | |---|---|---| | Scheduler-Start | Programmstart, geplante Uhrzeit, Lookback-Tage | Startbestätigung | | Pipeline gestartet | Ausführungsbeginn | — | | **Erfolg** | Feed-Statistiken, Label-Verteilung, Feed-URL | Zusammenfassung mit allen Kennzahlen | | **Fehler** | Fehlermeldung + Log-Anhang | Detaillierte Fehlermeldung + Log-Datei im Anhang | | Feed gesperrt | Grund der Sicherheitsprüfung (Zero/Integrität/Sanity) | Detaillierte Sperrmeldung mit Empfehlung | **Beispielformat einer Chat-Erfolgsnachricht:** ``` 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.* **Beispielformat einer E-Mail-Erfolgsnachricht:** ``` Betreff: [POAS Pipeline] Feed-Lauf erfolgreich — TT.MM.JJJJ Guten Tag, der tägliche Feed-Lauf wurde erfolgreich abgeschlossen. Datum: TT.MM.JJJJ HH:MM Ausgabeordner: Output TT.MM.JJJJ Feed-Zusammenfassung: Quell-XML: XXX.XXX Produkte Endgültiger Feed: XXX.XXX Produkte Ads-Treffer: XXX.XXX Produkte Label-Verteilung: Longtail: XX.XXX (XX,X %) Core: XX.XXX (XX,X %) Feeder: XX.XXX (XX,X %) Feed-URL: https://[FEED-SERVER]/[CLIENT_PREFIX]/poas_feed.xml Mit freundlichen Grüßen POAS Pipeline ``` > *Dies ist ein Beispielformat — Zahlen, Adressen 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 NOTIFY_CHAT_TOKEN=... NOTIFY_CHAT_IDS=... NOTIFY_EMAIL_TO=... NOTIFY_EMAIL_FROM=... NOTIFY_EMAIL_SMTP=... 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: # Chat-Benachrichtigung NOTIFY_CHAT_TOKEN: "[CHAT_BOT_TOKEN]" NOTIFY_CHAT_IDS: "[CHAT_ID_1],[CHAT_ID_2]" # E-Mail-Benachrichtigung NOTIFY_EMAIL_TO: "[empfaenger@beispiel.de]" NOTIFY_EMAIL_FROM: "[absender@beispiel.de]" NOTIFY_EMAIL_SMTP: "[smtp.beispiel.de]" NOTIFY_EMAIL_SMTP_PORT: "587" # 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 / Shop-ZIP"] GMC["Google Merchant Center"] NOTIFY["Chat- & E-Mail-Benachrichtigung"] PIPELINE --> V1 PIPELINE --> V2 NGINX --> V2 PIPELINE --> INTERNET NGINX -->|"Port 8525"| GMC PIPELINE --> NOTIFY ``` --- ## 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 | |---|---|---| | `NOTIFY_CHAT_TOKEN` | `[TOKEN]` | API-Token für den Chat-Benachrichtigungsdienst | | `NOTIFY_CHAT_IDS` | `[ID1],[ID2]` | Empfänger-IDs für Chat-Nachrichten (kommagetrennt) | | `NOTIFY_EMAIL_TO` | `[empfaenger@beispiel.de]` | E-Mail-Empfängeradresse(n), kommagetrennt | | `NOTIFY_EMAIL_FROM` | `[absender@beispiel.de]` | Absenderadresse für E-Mail-Benachrichtigungen | | `NOTIFY_EMAIL_SMTP` | `[smtp.beispiel.de]` | SMTP-Serveradresse | | `NOTIFY_EMAIL_SMTP_PORT` | `587` | SMTP-Port (587 für STARTTLS, 465 für SSL) | | `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 MSG as Chat & E-Mail S->>MSG: 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->>MSG: 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["notifier.py — Chat- & E-Mail-Benachrichtigung"] end subgraph OUTPUT["Ausgabe"] FEED["poas_feed.xml (angereicherter GMC-Feed)"] NGINX["nginx — Feed-Server"] NOTIFY["Chat- & E-Mail-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 --> NOTIFY ``` ### 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 │ ├── notifier.py ← Chat- & E-Mail-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 MSG as Chat & E-Mail SCH->>MSG: Scheduler gestartet Note over SCH: Wartet bis SCHEDULE_HOUR:SCHEDULE_MINUTE (UTC) SCH->>WDG: execute_pipeline(lookback_days) WDG->>MSG: 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->>MSG: Erfolgsmeldung + Statistiken (Chat & E-Mail) else Prüfung fehlgeschlagen WDG->>MSG: Warnung: Feed gesperrt (Chat & E-Mail) 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.*