Files
WikiJS/Profice-services/feedgine.md

1139 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 '<item>' 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.*