# ADR-007 Addendum: `_bulk` vs `_many` Suffix bei Zeitreihen-Operationen

**Status:** Akzeptiert
**Datum:** 2025-01-07
**Kontext:** Ergänzung zu ADR-007 (Naming Conventions für CRUD-Operationen)

## Problemstellung

Die ADR-007 definiert das `_many` Suffix für Bulk-Operationen (z.B. `create_many()`, `delete_many()`).

Bei Zeitreihen-Operationen existiert jedoch `save_timeseries_values_bulk()` statt `save_timeseries_values_many()`.

**Frage:** Warum weicht die Namensgebung vom Standard-Pattern ab?

## Entscheidung

Bei **mehrdimensionalen Stapelverarbeitungen** verwenden wir das `_bulk` Suffix anstelle von `_many`.

### Verwendung von `_many`

Das `_many` Suffix ist reserviert für **Standard-CRUD-Operationen auf mehreren Ressourcen desselben Typs**:

```python
# ✅ Standard-Pattern: _many für einfache Listen
create_many(items=[{...}, {...}, {...}])      # n Items
update_many(items=[{...}, {...}])             # n Items
delete_many(item_ids=[1, 2, 3])               # n Items
```

**Charakteristika von `_many`:**

- Flache Datenstruktur (Liste von Objekten)
- Gleiche Operation auf mehrere Ressourcen
- Standard-CRUD (Create, Read, Update, Delete)
- Eindimensionale Stapelverarbeitung

### Verwendung von `_bulk`

Das `_bulk` Suffix kennzeichnet **komplexe, mehrdimensionale Stapelverarbeitungen**:

```python
# ✅ Bulk-Pattern: Verschachtelte Strukturen
save_timeseries_values_bulk(
    timeseries_properties=["temperature", "humidity"],  # m Properties
    item_values={                                       # n Items
        "item_1": {
            "temperature": [value1, value2, ...],       # k Values
            "humidity": [value1, value2, ...]
        },
        "item_2": {
            "temperature": [value1, value2, ...],
            "humidity": [value1, value2, ...]
        }
    }
)
```

**Charakteristika von `_bulk`:**

- Verschachtelte, mehrdimensionale Datenstruktur
- Mehrere Dimensionen: n Items × m Properties × k Values
- Keine reine CRUD-Operation (kombiniert mehrere Schritte)
- Performanceorientierte Batch-Verarbeitung
- Etablierter Begriff aus SQL (`BULK INSERT`) und ETL-Prozessen

## Begründung

### Semantischer Unterschied

| Aspekt | `_many` | `_bulk` |
|--------|---------|---------|
| **Struktur** | Flach (Liste) | Verschachtelt (Multi-Level Dict) |
| **Dimensionen** | 1D (n Items) | nD (n Items × m Properties × k Values) |
| **CRUD-Operation** | Ja (Create/Update/Delete) | Nein (UPSERT + Lookup) |
| **Datenvolumen** | Moderat | Groß (Batch-optimiert) |
| **Verwendung** | Standard-CRUD | ETL, Bulk-Loads, Datenimport |

### Warum `save_timeseries_values_bulk` richtig ist

1. **Multi-dimensionale Struktur:**

   ```python
   # ❌ _many würde suggerieren:
   save_timeseries_values_many([value1, value2, ...])  # Flache Liste

   # ✅ _bulk signalisiert Komplexität:
   save_timeseries_values_bulk({                        # Verschachtelt
       "item_1": {"temp": [...], "humidity": [...]},
       "item_2": {"temp": [...], "humidity": [...]}
   })
   ```

2. **Keine reine CRUD-Operation:**
   - `save` ist bereits ein UPSERT (ADR-007, Zeile 30)
   - Die Bulk-Variante kombiniert zusätzlich:
     1. Inventory Items abfragen
     2. TimeSeries-IDs extrahieren
     3. Werte speichern
   - Das ist komplexer als ein einfaches `create_many()`

3. **Performance-Kontext:**
   - Bulk-Operationen sind für große Datenmengen optimiert
   - Analogie zu SQL `BULK INSERT` (nicht `INSERT MANY`)
   - ETL-Prozesse nutzen "Bulk-Load", nicht "Load Many"

4. **Konsistenz mit etablierten Begriffen:**

   ```python
   # Etablierte Patterns aus anderen Frameworks:
   pandas.DataFrame.to_sql(..., method='multi')   # multi = _many
   elasticsearch.helpers.bulk(...)                # bulk = komplexe Struktur
   sqlalchemy.session.bulk_insert_mappings(...)   # bulk = Performance-Batch
   ```

### Vergleich mit Standard-CRUD

```python
# Standard-CRUD mit _many:
items = [
    {"name": "User1", "age": 25},
    {"name": "User2", "age": 30}
]
create_many(items)  # Einfache Liste → _many

# Zeitreihen mit _bulk:
data = {
    "sensor_1": {
        "temperature": [
            TimeSeriesValue(time=..., value=22.5),
            TimeSeriesValue(time=..., value=23.0)
        ],
        "humidity": [...]
    },
    "sensor_2": {...}
}
save_timeseries_values_bulk(data)  # Verschachtelte Struktur → _bulk
```

## Konsequenzen

### Wann `_many` verwenden?

✅ Verwende `_many` wenn:

- Flache Liste von Ressourcen
- Standard-CRUD-Operation (Create, Update, Delete)
- Eindimensionale Stapelverarbeitung
- Einfache Datenstruktur

```python
create_many(items=[{...}, {...}])
update_many(items=[{...}, {...}])
delete_many(item_ids=[1, 2, 3])
```

### Wann `_bulk` verwenden?

✅ Verwende `_bulk` wenn:

- Verschachtelte, mehrdimensionale Struktur
- Komplexe Operation (nicht reines CRUD)
- Performance-kritische Batch-Verarbeitung
- Große Datenmengen (ETL, Import, Export)

```python
save_timeseries_values_bulk(item_values={...})
bulk_import_inventory_data(data={...})
bulk_export_to_format(config={...})
```

## Anti-Patterns

### ❌ Falsche Verwendung von `_many`

```python
# Falsch: _many für komplexe Struktur
def save_timeseries_values_many(
    item_values: dict[str, dict[str, list[TimeSeriesValue]]]
):
    # Verschachtelte Struktur passt nicht zu "_many"
    pass
```

**Problem:** `_many` suggeriert eine flache Liste, aber die Signatur erwartet eine verschachtelte Dictionary-Struktur.

### ❌ Falsche Verwendung von `_bulk`

```python
# Falsch: _bulk für einfache Liste
def create_items_bulk(items: list[dict]):
    # Einfache Liste → sollte create_many() sein
    pass
```

**Problem:** `_bulk` übertreibt die Komplexität für eine Standard-CRUD-Operation mit flacher Liste.

## Implementation-Beispiele

### Korrekte Verwendung

```python
# ✅ _many für Standard-CRUD
def create_items_many(
    inventory_name: str,
    items: list[dict]  # Flache Liste
) -> list[dict]:
    """Erstellt mehrere Items."""
    pass

# ✅ _bulk für mehrdimensionale Operationen
def save_timeseries_values_bulk(
    inventory_name: str,
    timeseries_properties: str | list[str],
    item_values: dict[str, dict[str, list[TimeSeriesValue]]]  # Verschachtelt
) -> None:
    """Speichert Zeitreihen-Werte für mehrere Items und Properties."""
    pass
```

### Hierarchie der Komplexität

```python
# Level 1: Singular (1 Item, 1 Property)
save_timeseries_values(
    item_id="123",
    timeseries_property="temperature",
    values=[...]
)

# Level 2: _many (n Items, 1 Property, flache Struktur)
# → Hypothetisch, nicht implementiert (wird direkt zu _bulk)

# Level 3: _bulk (n Items, m Properties, verschachtelt)
save_timeseries_values_bulk(
    timeseries_properties=["temperature", "humidity"],
    item_values={
        "123": {"temperature": [...], "humidity": [...]},
        "456": {"temperature": [...], "humidity": [...]}
    }
)
```

## Zusammenfassung

| Kriterium | `_many` | `_bulk` |
|-----------|---------|---------|
| **Datenstruktur** | Flache Liste | Verschachtelt (Multi-Level) |
| **Anwendungsfall** | Standard-CRUD | Komplexe Batch-Operationen |
| **Beispiele** | `create_many()`, `delete_many()` | `save_timeseries_values_bulk()` |
| **Inspiration** | Python ORM-Patterns | SQL BULK INSERT, ETL-Prozesse |

**Faustregel:**

- Kannst du die Daten als `list[dict]` darstellen? → Verwende `_many`
- Brauchst du `dict[str, dict[str, list[...]]]`? → Verwende `_bulk`

## Referenzen

- ADR-007: Naming Conventions für CRUD-Operationen (Zeile 29, 46)
- SQL: `BULK INSERT` für große Datenmengen
- Elasticsearch: `bulk()` API für verschachtelte Multi-Index-Operationen
- SQLAlchemy: `bulk_insert_mappings()` für Performance-Batches

## Notizen

Diese Unterscheidung gilt speziell für das questra-data Package. Bei zukünftigen API-Erweiterungen:

- Prüfe die Datenstruktur (flach vs. verschachtelt)
- Prüfe die Komplexität der Operation (CRUD vs. kombinierte Schritte)
- Wähle das passende Suffix basierend auf diesen Kriterien
