Files
WikiJS/Profice-services/feedgine.md

36 KiB
Raw Permalink Blame History

title, description, published, date, tags, editor, dateCreated
title description published date tags editor dateCreated
FEEDGINE true 2026-03-26T13:33:30.807Z markdown 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
  2. Berechnungsformeln
  3. Programmfunktionen
  4. Installation ohne Docker (Direktinstallation)
  5. Installation mit Docker
  6. Konfiguration
  7. Betrieb und Überwachung
  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

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:

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):

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

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

# 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

# .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):

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

# 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:

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

# 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:

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:

[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.

# 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

# 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

# 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

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

# 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:

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:

# 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

# 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

# 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

# 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

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:

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:

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:

# 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:

# 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

# Aktuellen Feed ansehen
ls -lh feed-serve/

# Produktanzahl im Feed zählen
grep -c '<item>' feed-serve/poas_feed.xml

# Letzte bekannte Produktanzahl
cat feed-serve/.last_feed_count

Beispielbefehle für die Direktinstallation.

Docker:

docker compose exec nginx ls -lh /usr/share/nginx/feeds/

7.4 Sofortige manuelle Ausführung

# 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

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)

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)

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.