# Zustandsdaten aus BMS und Service

> Ein Batteriemanagementsystem oder ein Servicewerkzeug meldet Messwerte je Einheit mit updateCondition; neuere Messungen werden angenommen, ältere übersprungen, und der Pass wird nach Ihrer Richtlinie neu veröffentlicht.

Dynamische Batteriedaten (Gesundheitszustand, Kapazitätsverlust, Vollzyklen, Temperaturen) kommen aus dem
Batteriemanagementsystem (BMS), einer Monitoring-Plattform oder einem Servicewerkzeug, manchmal über das
Servicemodul des ERP. In diesem Ablauf meldet die Middleware sie je Einheit mit
[`updateCondition`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/update-condition). Die Einzelheiten der Operation stehen in
[Zustandsdaten senden](https://passport.kabin.sh/docs/de/guides/push-condition-data); diese Seite zeigt den Ablauf drumherum.

```mermaid
sequenceDiagram
  participant BMS as BMS oder Servicewerkzeug
  participant MW as Middleware
  participant BP as BatteryPass-API
  BMS->>MW: neue Messwerte je Seriennummer
  MW->>MW: BatteryPass-ID in der eigenen Zuordnung nachschlagen
  alt Seriennummer noch nicht zugeordnet
    MW->>BP: listBatteries mit serial_prefix (Scope read)
    BP-->>MW: die Einheit
  end
  MW->>BP: updateCondition mit measured_at (Scope condition)
  BP-->>MW: accepted, skipped, publication
  BP->>BP: Ereignis condition_update mit dem Schlüssel, Neuveröffentlichung nach Richtlinie
```

**Auslöser:** Das BMS oder das Servicewerkzeug hat neue Messwerte einer Einheit.

**Scopes des Schlüssels:** read, condition

| # | Wer         | Was                                                                                                                                                                                                                                                  | Operation                                                                                                               |
| - | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| 1 | Middleware  | Sammelt die neuen Messwerte je Einheit mit dem Zeitpunkt der Messung.                                                                                                                                                                                |                                                                                                                         |
| 2 | Middleware  | Findet die Einheit über ihre Seriennummer (Filter serial_prefix) und merkt sich die BatteryPass-ID in einer eigenen Zuordnungstabelle.                                                                                                               | [`listBatteries`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-batteries.md) (Scope: read)            |
| 3 | Middleware  | Meldet die Werte mit measured_at; ältere oder gleich alte Messungen werden übersprungen, eine Wiederholung schadet also nicht.                                                                                                                       | [`updateCondition`](https://passport.kabin.sh/docs/de/api-reference/lifecycle/update-condition.md) (Scope: condition) |
| 4 | BatteryPass | Speichert die angenommenen Werte als Messungen (ohne Prüfer freigegeben, ADR-016), legt ein Lebenszyklus-Ereignis condition_update an, das den Schlüssel nennt, und veröffentlicht sofort oder täglich neu, wie die Organisation es eingestellt hat. |                                                                                                                         |

**Sicherheit**

- Ein eigener Schlüssel je BMS-Plattform oder Servicepartner mit den Scopes condition und read.
- Bei 429 wartet die Middleware die Sekunden aus Retry-After ab, sonst wiederholt sie mit exponentiellem Backoff und Jitter.

**Datenminimierung**

- Nur die dynamischen Felder der Einheit; keine Standortdaten, keine Fahrzeug- oder Halterdaten.

## Der Schlüssel

Ein Schlüssel je BMS-Plattform oder Servicepartner, mit den Scopes `condition` (zum Melden) und `read` (zum Finden
der Einheiten). Zwei Schlüssel mit je einem Scope gehen genauso. Ein `condition`-Schlüssel kann für jede Einheit
Ihrer Organisation melden; geben Sie einem Servicepartner also nur dann einen Schlüssel, wenn Sie ihm das zutrauen.

## Die Einheit einmal finden

Die Middleware führt eine Zuordnungstabelle von Seriennummer zu BatteryPass-ID. Für eine Seriennummer, die sie noch
nicht kennt, fragt sie [`listBatteries`](https://passport.kabin.sh/docs/de/api-reference/catalog/list-batteries) mit `serial_prefix` und wählt
die Einheit, deren `serial_number` genau passt (Seriennummern sind je Modell eindeutig; geben Sie
`battery_model_id` mit, wenn zwei Modelle dasselbe Nummernschema teilen):

```bash title="cURL"
curl -sS "$BATTERYPASS_URL/api/v1/batteries?serial_prefix=SV2027-000184&limit=10" \
  -H "Authorization: Bearer $BATTERYPASS_BMS_KEY" | jq '.items[] | select(.serial_number == "SV2027-000184") | .id'
```

## Messwerte melden

```bash title="cURL"
curl -sS -X PATCH "$BATTERYPASS_URL/api/v1/batteries/0192a3f0-5b7c-7d21-8e4f-6a2b9c1d3e5f/condition" \
  -H "Authorization: Bearer $BATTERYPASS_BMS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"measured_at": "2026-10-24T06:00:00Z", "note": "BMS-Tagesbericht",
       "values": [{"field_key": "condition.capacity_fade", "value": 3.5},
                  {"field_key": "condition.number_of_full_cycles", "value": 412}]}'
```

```ts title="TypeScript"
const BASE = `${process.env.BATTERYPASS_URL}/api/v1`;
const headers = { Authorization: `Bearer ${process.env.BATTERYPASS_BMS_KEY}`, 'Content-Type': 'application/json' };

type Reading = { fieldKey: string; value: number; measuredAt: string };

export async function report(batteryId: string, readings: Reading[]) {
  const body = {
    note: 'BMS-Tagesbericht',
    values: readings.map((r) => ({ field_key: r.fieldKey, value: r.value, measured_at: r.measuredAt })),
  };
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(`${BASE}/batteries/${batteryId}/condition`, { method: 'PATCH', headers, body: JSON.stringify(body) });
    if (res.ok) return res.json(); // { accepted, skipped, publication, event }
    const problem = await res.json().catch(() => ({}));
    const retry = res.status === 429 || res.status >= 500;
    if (!retry || attempt >= 4) throw new Error(`updateCondition: ${res.status} ${problem.code} (request_id ${problem.request_id})`);
    const wait = res.status === 429 ? Number(res.headers.get('Retry-After') ?? 1) * 1000 : 2 ** attempt * 1000;
    await new Promise((r) => setTimeout(r, wait + Math.random() * 500)); // Backoff mit Jitter
  }
}
```

```python title="Python"
import os
import random
import time

import requests

BASE = os.environ["BATTERYPASS_URL"] + "/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["BATTERYPASS_BMS_KEY"]


def report(battery_id, readings):
    """readings: [(field_key, value, measured_at)]; liefert accepted, skipped, publication und event."""
    body = {
        "note": "BMS-Tagesbericht",
        "values": [{"field_key": k, "value": v, "measured_at": at} for k, v, at in readings],
    }
    for attempt in range(5):
        res = session.patch(f"{BASE}/batteries/{battery_id}/condition", json=body, timeout=30)
        if res.ok:
            return res.json()
        if res.status_code != 429 and res.status_code < 500:
            problem = res.json()
            raise SystemExit(f"updateCondition: {res.status_code} {problem['code']} (request_id {problem.get('request_id')})")
        wait = int(res.headers.get("Retry-After", "1")) if res.status_code == 429 else 2**attempt
        time.sleep(wait + random.random() / 2)  # Backoff mit Jitter
    raise SystemExit("updateCondition: nach 5 Versuchen aufgegeben")
```

Weil eine gleich alte oder ältere Messung als die gespeicherte übersprungen wird, ändert derselbe Messwert nach einer
Zeitüberschreitung nichts mehr: Deshalb ist die Wiederholung oben sicher. Senden Sie die Messungen einer Einheit in
der Reihenfolge, in der sie entstanden sind.

## Was BatteryPass damit macht

- Angenommene Werte werden als **freigegebene Messungen ohne Prüfer** gespeichert (Messdaten haben keinen
  Prüfschritt), mit ihrem `measured_at`.
- Die Meldung legt ein Lebenszyklus-Ereignis `condition_update` an; dieses Ereignis nennt den Schlüssel (`api_key`)
  und hat die Quelle `api`. Die Werte selbst tragen den Schlüssel nicht. Das Audit-Log nennt den Schlüssel als
  Handelnden.
- Ein veröffentlichter Pass wird so neu veröffentlicht, wie Ihre Organisation es eingestellt hat: sofort
  (`publication.outcome` ist `published` oder `unchanged`) oder durch den täglichen Auftrag (`scheduled`).
  `blocked` heißt, dass jetzt eine Pflichtangabe fehlt; der Pass steht auf `update_needed`, und ein Mitglied muss
  handeln.

## Was nie fließt

Nur die dynamischen Felder der Einheit (Registerabschnitt `condition`). Keine Standort- oder GPS-Daten, keine
Fahrzeug- oder Halterdaten, keine Namen von Bedienpersonal. Eine Notiz ist Freitext, der in der Historie der
Einheit landet: Füllen Sie sie aus einer festen Vorlage.

## Fehlerbehandlung

| Antwort                   | Bedeutung                                                                                                                                                                                                            | Was tun                                                                            |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `400 validation_error`    | Ein Feld, das kein dynamisches Feld der Einheit ist (`unknown_field`), ein Wert außerhalb seines Bereichs, ein Zeitpunkt in der Zukunft oder vor der Herstellung, oder eine fehlschlagende feldübergreifende Prüfung | Messwert korrigieren; die ganze Meldung wurde abgelehnt.                           |
| `401 unauthenticated`     | Schlüssel fehlt, ist unbekannt oder widerrufen                                                                                                                                                                       | Geheimnis prüfen; Alarm auslösen.                                                  |
| `403 forbidden`           | Dem Schlüssel fehlt der Scope `condition`                                                                                                                                                                            | Den richtigen Schlüssel verwenden.                                                 |
| `404 not_found`           | Die Einheit gehört nicht zu Ihrer Organisation                                                                                                                                                                       | Zuordnungstabelle auffrischen.                                                     |
| `409 conflict`            | Das Modell der Einheit ist archiviert                                                                                                                                                                                | Für diese Einheit nicht mehr melden.                                               |
| `429 rate_limited`        | Zu viele Anfragen                                                                                                                                                                                                    | `Retry-After` Sekunden warten; tägliche Meldungen zeitlich verteilen.              |
| `5xx`, Zeitüberschreitung | Ergebnis unbekannt                                                                                                                                                                                                   | Denselben Body mit Backoff wiederholen; wiederholte Messungen werden übersprungen. |
