> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kameleoon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Experimentergebnisse nach Notion exportieren

> Rufen Sie ein Experiment und seine Ergebnisse mit der Automation API ab, ermitteln Sie anhand der Bayesianischen Erfolgswahrscheinlichkeit die leistungsstärkste Variation und schreiben Sie den Datensatz mit einem einzigen Python-Skript per Upsert in eine Notion-Datenbank.

Rufen Sie ein Experiment und seine Ergebnisse mit der Automation API ab, wandeln Sie sie in Notion-Seiteneigenschaften um und schreiben Sie den Datensatz mit einem einzigen Python-Skript per Upsert in eine Notion-Datenbank.

## Ziel

Dieses Tutorial beschreibt Schritt für Schritt, wie das Skript [kameleoon\_to\_notion.py](/assets/developer-docs/script/apis/automation-api-rest/tutorials/kameleoon_to_notion.py.zip) funktioniert. Anhand einer Kameleoon-**Experiment-ID** und einer Notion-**Datenbank-ID** ruft das Skript die Experimentmetadaten und die statistischen Ergebnisse ab, ermittelt die leistungsstärkste Variation, bildet die Daten auf eine Notion-Datenbank *Experiments* ab und schreibt den Datensatz zurück nach Notion. Ein erneuter Lauf des Skripts für dasselbe Experiment aktualisiert die bestehende Seite, statt ein Duplikat zu erstellen.

Die Schritte 1–5 verwenden denselben Ablauf für Anfrage und Abfrage wie das [Airtable-Export-Tutorial](./exporting-experiment-results-to-airtable); nur das Ziel ändert sich.

<Note>
  Die API von Notion verfügt über keinen nativen Upsert. Das Skript emuliert einen: Es fragt die Data Source der Datenbank nach einer Seite ab, deren Titel dem Namen des Experiments entspricht, aktualisiert diese Seite bei einem Treffer oder erstellt andernfalls eine neue. Dieses Tutorial richtet sich an die Notion-API-Version `2025-09-03`, die jede Datenbank um eine oder mehrere **Data Sources** organisiert.
</Note>

## Voraussetzungen

* **Kameleoon-API-Anmeldedaten.** Die Automation API erfordert ein Access Token. Das Skript ruft dieses programmgesteuert anhand einer `client_id` und eines `client_secret` über den `client_credentials`-Grant ab. Siehe [Access Token abrufen](/de/developer-docs/apis/automation-api-rest/get-started/get-started#1-ein-zugriffstoken-erhalten).

* **Ein internes Notion-Integrationstoken.** Erstellen Sie eine Integration unter [notion.so/my-integrations](https://www.notion.so/my-integrations) und kopieren Sie deren Token.

* **Eine Notion-Datenbank** mit dem Schema *Experiments* und den folgenden Eigenschaften: `Experiment Name` (title), `Status` (select), `Start date` (date), `End date` (date), `Notes` (rich text), `Actual` (number), `Probability` (select) und `Result` (select).

* **Die Notion-Datenbank-ID.** Öffnen Sie die Datenbank als vollständige Seite (die ID ist die 32-stellige Zeichenkette in der URL, vor dem View-Parameter `?v=`).

* **Python 3.9+** mit der Bibliothek `requests` (`pip install requests`).

<Warning>
  Teilen Sie die Datenbank mit Ihrer Integration, sonst gibt jede Anfrage `object_not_found` zurück. Öffnen Sie die Datenbank, gehen Sie zu **`•••` → Connections → Add connections**, und wählen Sie Ihre Integration aus.
</Warning>

<Warning>
  Speichern Sie alle Anmeldedaten in Umgebungsvariablen. Codieren Sie Geheimnisse niemals fest im Skript.
</Warning>

```bash theme={null}
export KAMELEOON_CLIENT_ID="..."
export KAMELEOON_CLIENT_SECRET="..."
export NOTION_TOKEN="..."
```

Das Tutorial verwendet das Beispielexperiment **Product Page Redesign** (ID `188308`) mit zwei Variationen zusätzlich zur Originalversion: *Redesign 1* (ID `828220`) und *Redesign 2* (ID `828221`).

## 1. Bei der Automation API authentifizieren

**Endpoint:** Rufen Sie ein Access Token ab, indem Sie eine POST-Anfrage an den Token-Endpoint senden.

```
POST https://api.kameleoon.com/oauth/token
```

| Feld           | Typ    | Beschreibung                      |
| -------------- | ------ | --------------------------------- |
| grant\_type    | String | Auf `client_credentials` setzen.  |
| client\_id     | String | Ihre Automation-API-Client-ID.    |
| client\_secret | String | Ihr Automation-API-Client-Secret. |

**Beispiel:**

```python theme={null}
def kameleoon_token(client_id, client_secret):
    resp = requests.post(
        "https://api.kameleoon.com/oauth/token",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data={
            "grant_type": "client_credentials",
            "client_id": client_id,
            "client_secret": client_secret,
        },
    )
    resp.raise_for_status()
    return resp.json()["access_token"]
```

**Antwort:**

```json theme={null}
{ "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..." }
```

Der zurückgegebene `access_token` wird als `Bearer`-Token bei jeder weiteren Anfrage an die Automation API gesendet. Access Tokens sind standardmäßig 2 Stunden lang gültig.

## 2. Das Experiment abrufen

**Endpoint:** Rufen Sie die Experimentmetadaten ab, indem Sie eine GET-Anfrage an den Endpoint [Get an experiment](/api-reference/experiment/get-an-experiment) senden.

```
GET https://api.kameleoon.com/experiments/{experimentId}
```

| Feld         | Typ     | Beschreibung                                                        |
| ------------ | ------- | ------------------------------------------------------------------- |
| experimentId | Integer | Obligatorischer Pfadparameter. Die ID des abzurufenden Experiments. |

**Beispiel:**

```python theme={null}
def get_experiment(token, experiment_id):
    resp = requests.get(
        f"https://api.kameleoon.com/experiments/{experiment_id}",
        headers={"Authorization": f"Bearer {token}"},
    )
    resp.raise_for_status()
    return resp.json()
```

**Antwort (gekürzt):**

```json theme={null}
{
  "id": 188308,
  "name": "Product Page Redesign",
  "status": "STOPPED",
  "dateStarted": "2025-01-15T09:00:00Z",
  "dateEnded": "2025-02-12T18:00:00Z",
  "description": "Testing two redesigns of the product page.",
  "mainGoalId": 279599,
  "variations": [828220, 828221]
}
```

Das Skript liest `name`, `status`, `dateStarted`, `dateEnded` und `description` für die Notion-Seite sowie `mainGoalId`, um die Ergebnisanfrage im nächsten Schritt einzugrenzen.

<Note>
  Die API gibt `mainGoalId` standardmäßig zurück, sodass das Skript keinen `optionalFields`-Parameter benötigt, um sie zu lesen. Die Automation API veröffentlicht kein festes Enum für das Feld `status`, und die Token können sich weiterentwickeln. [Schritt 7](#7-die-daten-auf-notion-eigenschaften-abbilden) vergleicht `status` ohne Berücksichtigung der Groß-/Kleinschreibung, sodass Unterschiede in der Schreibweise zwischen Konten das Mapping nicht beeinträchtigen.
</Note>

## 3. Die Ergebnisse des Experiments anfordern

**Endpoint:** Lösen Sie die Erstellung des Ergebnisberichts aus, indem Sie eine POST-Anfrage an den Endpoint [Request experiment's results](/api-reference/experiment/request-experiments-results) senden.

```
POST https://api.kameleoon.com/experiments/{experimentId}/results
```

| Feld                 | Typ     | Beschreibung                                                                                                                                                                      |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| experimentId         | Integer | Obligatorischer Pfadparameter.                                                                                                                                                    |
| goalsIds             | Array   | Beschränkt den Bericht auf die angegebenen Ziel-IDs. Das Skript übergibt die `mainGoalId` des Experiments.                                                                        |
| referenceVariationId | String  | Die Variation, die als Referenz für den Vergleich verwendet wird. `"0"` verwendet die Originalseite.                                                                              |
| visitorData          | Boolean | `false` für besuchsbasierte Daten, `true` für besucherbasierte Daten.                                                                                                             |
| sequentialTesting    | Boolean | Auf `true` setzen, um Sequential Testing für Konfidenzintervalle anstelle der Bayesianischen Erfolgswahrscheinlichkeit zu verwenden. Aktivieren Sie nur eine der beiden Methoden. |
| bayesian             | Boolean | Auf `true` setzen, um die Bayesianische Erfolgswahrscheinlichkeit in den Bericht aufzunehmen. Das Skript liest diesen Wert aus, um die Eigenschaft *Probability* zu befüllen.     |
| conversionType       | String  | `ALL_CONVERSION` oder `CONVERTED_VISITS`.                                                                                                                                         |

**Beispiel:**

```python theme={null}
def request_results(token, experiment_id, goal_id):
    body = {
        "visitorData": False,
        "sequentialTesting": False,
        "bayesian": True,
        "referenceVariationId": "0",
        "conversionType": "ALL_CONVERSION",
        "goalsIds": [goal_id] if goal_id else None,
    }
    resp = requests.post(
        f"https://api.kameleoon.com/experiments/{experiment_id}/results",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
            "Accept": "*/*",
        },
        json=body,
    )
    resp.raise_for_status()
    return resp.json()["dataCode"]
```

**Antwort:**

```json theme={null}
{ "dataCode": "14443931880924098266207585267983330260134079899081739889989435434342588016765" }
```

Kameleoon erstellt den Bericht asynchron. Der Endpoint gibt einen `dataCode` zurück, der im nächsten Schritt zum Abfragen des Ergebnisses verwendet wird.

<Note>
  Dieses Skript erfordert `bayesian: true` und setzt `sequentialTesting: false`. `bayesian` und `sequentialTesting` sind alternative Methoden zur Berechnung der Signifikanz, und dieses Tutorial gibt die Bayesianische Erfolgswahrscheinlichkeit aus. Bei aktiviertem Bayesian-Modus trägt der Wert `reliability` des Berichts die **Bayesianische Erfolgswahrscheinlichkeit** (die Wahrscheinlichkeit, dass eine Variation die Referenz übertrifft), die das Skript auf die Eigenschaft *Probability* abbildet. Gleichen Sie den Wert mit demselben Bericht in der Kameleoon-App ab, wenn Ihr Konto eine andere Standardstatistikmethode verwendet.
</Note>

## 4. Die Ergebnisse abfragen

**Endpoint:** Rufen Sie den Bericht ab, indem Sie GET-Anfragen an den Endpoint [Poll results](/api-reference/data/poll-results) senden, bis er bereit ist.

```
GET https://api.kameleoon.com/results?dataCode={dataCode}
```

| Feld     | Typ    | Beschreibung                                                                    |
| -------- | ------ | ------------------------------------------------------------------------------- |
| dataCode | String | Obligatorischer Query-Parameter, der vom vorherigen Schritt zurückgegeben wird. |

Der `status` der Antwort lautet `WAITING`, solange der Bericht berechnet wird, `READY`, sobald die Daten verfügbar sind, oder `ERROR` beziehungsweise `TIMEOUT` bei einem Fehler. Wenn der Status `ERROR` oder `TIMEOUT` lautet, enthält die Antwort ein `errorDescription`-Feld auf oberster Ebene. Das Skript fragt in einem festen Intervall ab, bis der Status `READY` lautet.

**Beispiel:**

```python theme={null}
def poll_results(token, data_code, max_attempts=30, delay=2.0):
    for _ in range(max_attempts):
        resp = requests.get(
            "https://api.kameleoon.com/results",
            headers={"Authorization": f"Bearer {token}"},
            params={"dataCode": data_code},
        )
        resp.raise_for_status()
        payload = resp.json()
        status = payload.get("status")
        if status == "READY":
            return payload["data"]
        if status in ("ERROR", "TIMEOUT"):
            raise RuntimeError(payload.get("errorDescription") or status)
        time.sleep(delay)  # status == "WAITING"
    raise TimeoutError("Timed out waiting for results.")
```

**Antwort (gekürzt):**

```json theme={null}
{
  "status": "READY",
  "data": {
    "variationData": {
      "_reference": { "breakdownData": { "_reference": { "generalData": {
        "goalsData": { "279599": { "conversionRate": 0.0149 } } } } } },
      "828220": { "breakdownData": { "_reference": { "generalData": {
        "goalsData": { "279599": {
          "reliability": 100.0,
          "improvementRate": 211.48,
          "conversionRate": 0.0466
        } } } } } },
      "828221": { "breakdownData": { "_reference": { "generalData": {
        "goalsData": { "279599": {
          "reliability": 100.0,
          "improvementRate": -43.33,
          "conversionRate": 0.0085
        } } } } } }
    }
  }
}
```

## 5. Die leistungsstärkste Variation auswählen

Die Ergebnisse enthalten unter `variationData` einen Eintrag pro Variation sowie die Zeile `_reference` für die Originalseite. Für jede Variation liegen die Metriken für das angeforderte Ziel unter `breakdownData._reference.generalData.goalsData[goalId]`.

Das Skript überspringt den Eintrag `_reference`, liest für jede Variation `improvementRate` und `reliability` (die Bayesianische Erfolgswahrscheinlichkeit) und wählt die Variation mit der höchsten Verbesserungsrate als leistungsstärkste aus. Enthält das `goalsData` einer Variation die angeforderte Goal-ID nicht, greift das Skript auf das vorhandene Goal zurück, welches auch immer das ist; da [Schritt 3](#3-die-ergebnisse-des-experiments-anfordern) die Anfrage bereits auf ein einzelnes Goal über `goalsIds` beschränkt, gibt es bei diesem Fallback normalerweise nichts anderes zur Auswahl. Die später abgebildete Eigenschaft *Result* erfasst, ob diese Variation eine ausreichend hohe Erfolgswahrscheinlichkeit bei einer positiven Verbesserung erreicht hat, um als echter Gewinner zu zählen.

**Beispiel:**

```python theme={null}
def pick_best_variation(result_data, goal_id):
    best, best_improvement = {}, float("-inf")
    for variation_id, vdata in result_data["variationData"].items():
        if variation_id == "_reference":
            continue
        general = vdata["breakdownData"]["_reference"]["generalData"]
        goals_data = general.get("goalsData", {})
        if not goals_data:
            continue
        key = str(goal_id) if str(goal_id) in goals_data else next(iter(goals_data))
        metrics = goals_data[key]
        improvement = metrics.get("improvementRate")
        if improvement is not None and improvement > best_improvement:
            best_improvement = improvement
            best = {
                "variation_id": variation_id,
                "improvement_rate": improvement,
                "bayesian_probability": metrics.get("reliability"),
            }
    return best
```

Im Beispiel erreichen beide Variationen eine Bayesianische Erfolgswahrscheinlichkeit von 100 %, aber *Redesign 1* (`828220`) zeigt eine Verbesserung von +211,48 % gegenüber -43,33 % bei *Redesign 2*. *Redesign 1* ist daher die leistungsstärkste Variation und, mit einer Wahrscheinlichkeit über 95 % und einer positiven Verbesserung, ein echter Gewinner.

## 6. Die Notion-Data-Source auflösen

Seit Version `2025-09-03` ist eine Notion-Datenbank ein Container für eine oder mehrere **Data Sources**, und Seiten-Schreibvorgänge sowie Abfragen zielen auf eine Data-Source-ID statt auf die Datenbank-ID. Die beiden IDs sind nicht austauschbar.

**Endpoint:** Rufen Sie die Datenbank ab, um ihre Data Sources zu ermitteln, indem Sie eine GET-Anfrage an den Endpoint [Retrieve a database](https://developers.notion.com/reference/retrieve-a-database) senden.

```
GET https://api.notion.com/v1/databases/{databaseId}
```

Jede Notion-Anfrage sendet das Integrationstoken als `Bearer`-Token sowie den Header `Notion-Version`.

**Beispiel:**

```python theme={null}
NOTION_VERSION = "2025-09-03"


def notion_headers(token):
    return {
        "Authorization": f"Bearer {token}",
        "Notion-Version": NOTION_VERSION,
        "Content-Type": "application/json",
    }


def get_data_source_id(token, database_id):
    resp = requests.get(
        f"https://api.notion.com/v1/databases/{database_id}",
        headers=notion_headers(token),
    )
    resp.raise_for_status()
    data_sources = resp.json().get("data_sources", [])
    if not data_sources:
        raise RuntimeError("The database has no data sources.")
    return data_sources[0]["id"]
```

**Antwort (gekürzt):**

```json theme={null}
{
  "object": "database",
  "id": "255104cd-477e-808c-b279-d39ab803a7d2",
  "data_sources": [
    { "id": "bc1211ca-e3f1-4939-ae34-5260b16f627c", "name": "Experiments" }
  ]
}
```

Das Skript verwendet die erste Data Source. Wenn Ihre Datenbank mehrere bereitstellt, wählen Sie diejenige aus, deren Schema den *Experiments*-Eigenschaften entspricht.

## 7. Die Daten auf Notion-Eigenschaften abbilden

Das Skript wandelt die Experimentmetadaten und die Metriken der leistungsstärksten Variation in Notion-Eigenschaftswerte um. Jeder Eigenschaftstyp hat seine eigene JSON-Struktur.

| Notion-Eigenschaft | Typ       | Quelle                                                       | Transformation                                                                                                                                                           |
| ------------------ | --------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Experiment Name    | Title     | `experiment.name`                                            | Direkt. Wird als Upsert-Schlüssel verwendet.                                                                                                                             |
| Status             | Select    | `experiment.status`                                          | Zugeordnet, ohne Berücksichtigung der Groß-/Kleinschreibung: `active → Running`, `draft`/`planned → Implementing`, `stopped`/`diverted → Completed`, `paused → Defunct`. |
| Start date         | Date      | `experiment.dateStarted`                                     | Datum/Uhrzeit, gekürzt auf ein ISO-Datum (`YYYY-MM-DD`).                                                                                                                 |
| End date           | Date      | `experiment.dateEnded`                                       | Datum/Uhrzeit, gekürzt auf ein ISO-Datum.                                                                                                                                |
| Notes              | Rich text | `experiment.description`                                     | Direkt.                                                                                                                                                                  |
| Actual             | Number    | `improvementRate` der besten Variation                       | Direkt (gemessene Verbesserung, %).                                                                                                                                      |
| Probability        | Select    | Bayesianische Erfolgswahrscheinlichkeit der besten Variation | In Stufen eingeteilt: `≥95 → 80% - High`, `≥80 → 50% - Medium`, sonst `20% - Low`.                                                                                       |
| Result             | Select    | Bayesianische Erfolgswahrscheinlichkeit + `improvementRate`  | Wahrscheinlichkeit `≥ 95` und Verbesserung > 0 → `Success`; `≥ 95` und Verbesserung \< 0 → `Failure`; andernfalls `Inconclusive`.                                        |

Das Skript lässt leere Werte aus, sodass bestehende Eigenschaftswerte bei einer Aktualisierung niemals mit leeren Werten überschrieben werden. Notion erstellt fehlende *Select*-Optionen automatisch, aber die Eigenschaften selbst müssen bereits mit den korrekten Typen im Schema der Data Source vorhanden sein.

**Beispiel:**

```python theme={null}
STATUS_MAP = {
    "ACTIVE": "Running",
    "DRAFT": "Implementing",
    "PLANNED": "Implementing",
    "PAUSED": "Defunct",
    "STOPPED": "Completed",
    "DIVERTED": "Completed",
}


def map_status(status):
    return STATUS_MAP.get((status or "").upper())


def to_iso_date(value):
    return value[:10] if value else None


def bayesian_to_probability(probability):
    if probability is None:
        return None
    if probability >= 95:
        return "80% - High"
    if probability >= 80:
        return "50% - Medium"
    return "20% - Low"


def derive_result(probability, improvement):
    if probability is None or improvement is None:
        return "Inconclusive"
    if probability >= 95 and improvement > 0:
        return "Success"
    if probability >= 95 and improvement < 0:
        return "Failure"
    return "Inconclusive"


def build_notion_properties(experiment, best):
    probability = best.get("bayesian_probability")
    improvement = best.get("improvement_rate")
    props = {}

    name = experiment.get("name")
    if name:
        props["Experiment Name"] = {"title": [{"text": {"content": name}}]}

    status = map_status(experiment.get("status"))
    if status:
        props["Status"] = {"select": {"name": status}}

    start = to_iso_date(experiment.get("dateStarted"))
    if start:
        props["Start date"] = {"date": {"start": start}}

    end = to_iso_date(experiment.get("dateEnded"))
    if end:
        props["End date"] = {"date": {"start": end}}

    notes = experiment.get("description")
    if notes:
        props["Notes"] = {"rich_text": [{"text": {"content": notes}}]}

    if improvement is not None:
        props["Actual"] = {"number": improvement}

    bucket = bayesian_to_probability(probability)
    if bucket:
        props["Probability"] = {"select": {"name": bucket}}

    result = derive_result(probability, improvement)
    if result:
        props["Result"] = {"select": {"name": result}}

    return props
```

<Note>
  Notion erlaubt nur eine Title-Eigenschaft pro Data Source. Das Skript verwendet die Eigenschaft mit dem Namen `Experiment Name` als Upsert-Schlüssel; wenn Ihre Title-Eigenschaft einen anderen Namen hat, benennen Sie sie hier und im Abfragefilter in [Schritt 8](#8-die-seite-per-upsert-in-notion-schreiben) entsprechend um.
</Note>

## 8. Die Seite per Upsert in Notion schreiben

Notion verfügt über keinen Upsert-Endpoint, daher fragt das Skript die Data Source nach einer Seite ab, deren `Experiment Name` übereinstimmt, und aktualisiert diese Seite oder erstellt eine neue.

**Suchen:** Fragen Sie die Data Source mit einem Title-Filter über den Endpoint [Query a data source](https://developers.notion.com/reference/query-a-data-source) ab.

```
POST https://api.notion.com/v1/data_sources/{dataSourceId}/query
```

**Erstellen:** Fügen Sie über den Endpoint [Create a page](https://developers.notion.com/reference/post-page) eine Seite hinzu, deren übergeordnetes Element die Data Source ist.

```
POST https://api.notion.com/v1/pages
```

**Aktualisieren:** Überschreiben Sie die Eigenschaften der gefundenen Seite über den Endpoint [Update page properties](https://developers.notion.com/reference/patch-page).

```
PATCH https://api.notion.com/v1/pages/{pageId}
```

**Beispiel:**

```python theme={null}
def find_page(token, data_source_id, name):
    resp = requests.post(
        f"https://api.notion.com/v1/data_sources/{data_source_id}/query",
        headers=notion_headers(token),
        json={
            "filter": {"property": "Experiment Name", "title": {"equals": name}},
            "page_size": 1,
        },
    )
    resp.raise_for_status()
    results = resp.json().get("results", [])
    return results[0]["id"] if results else None


def upsert_page(token, data_source_id, properties):
    title = properties.get("Experiment Name", {}).get("title")
    name = title[0]["text"]["content"] if title else None

    page_id = find_page(token, data_source_id, name) if name else None
    if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=notion_headers(token),
            json={"properties": properties},
        )
        action = "Updated"
    else:
        resp = requests.post(
            "https://api.notion.com/v1/pages",
            headers=notion_headers(token),
            json={
                "parent": {"type": "data_source_id", "data_source_id": data_source_id},
                "properties": properties,
            },
        )
        action = "Created"
    resp.raise_for_status()
    return resp.json(), action
```

**Antwort (gekürzt):**

```json theme={null}
{
  "object": "page",
  "id": "1a2b3c4d-5e6f-7081-9abc-def012345678",
  "properties": {
    "Experiment Name": { "title": [{ "plain_text": "Product Page Redesign" }] },
    "Status": { "select": { "name": "Completed" } },
    "Start date": { "date": { "start": "2025-01-15" } },
    "End date": { "date": { "start": "2025-02-12" } },
    "Actual": { "number": 211.48 },
    "Probability": { "select": { "name": "80% - High" } },
    "Result": { "select": { "name": "Success" } }
  }
}
```

## 9. Das Skript ausführen

Übergeben Sie die Experiment-ID und die Notion-Datenbank-ID als Argumente:

```bash theme={null}
python kameleoon_to_notion.py \
  --experiment-id 188308 \
  --database-id 255104cd477e808cb279d39ab803a7d2
```

Das Skript gibt jeden Schritt aus: die Authentifizierung, das abgerufene Experiment, die leistungsstärkste Variation, die aufgelöste Data Source, die zugeordneten Eigenschaften sowie, ob die Notion-Seite erstellt oder aktualisiert wurde.

## Anpassungshinweise

* **Status-Mapping** befindet sich in der Konstante `STATUS_MAP`, die anhand der von der API zurückgegebenen Status-Token indiziert ist und ohne Berücksichtigung der Groß-/Kleinschreibung abgeglichen wird. Passen Sie die Zielwerte an, wenn Ihre *Status*-Optionen von `Running` / `Implementing` / `Completed` / `Defunct` abweichen, und bestätigen Sie die Token, die Ihr Konto zurückgibt, mit einer einzelnen `GET /experiments/{experimentId}`-Anfrage.
* **Probability** wird aus der gemessenen Bayesianischen Erfolgswahrscheinlichkeit abgebildet, wofür `bayesian: true` in der Ergebnisanfrage erforderlich ist. Wenn Ihre Eigenschaft *Probability* stattdessen eine manuell eingetragene Schätzung vor dem Experiment ist, entfernen Sie den `Probability`-Block aus `build_notion_properties`.
* **Zielauswahl** verwendet die `mainGoalId` des Experiments. Um über ein anderes Ziel zu berichten, übergeben Sie dessen ID an `request_results` und `pick_best_variation`.
* **Upsert-Schlüssel.** Der Titelabgleich erfolgt exakt, sodass Unterschiede bei Groß-/Kleinschreibung oder Leerzeichen in `Experiment Name` eine neue Seite erstellen, statt die bestehende zu aktualisieren. Da der Ablauf aus Suchen und anschließendem Schreiben nicht atomar ist, vermeiden Sie es, zwei Exporte für dasselbe Experiment gleichzeitig auszuführen.
* **API-Version.** Das Skript legt `Notion-Version: 2025-09-03` fest. Wenn Sie der Datenbank später eine zweite Data Source hinzufügen, aktualisieren Sie `get_data_source_id`, damit die richtige anhand ihres Namens ausgewählt wird.
* **Rate Limits.** Die Automation API erlaubt bis zu 50 Anfragen pro 10 Sekunden und 1.000 pro Stunde; die Notion API erlaubt im Durchschnitt etwa 3 Anfragen pro Sekunde. Wenn Sie viele Experimente in Batches verarbeiten, cachen Sie Tokens und fügen Sie eine Drosselung hinzu.
