# Questra Data

Python Client für Questra Dynamic Objects (GraphQL + REST API).

## Features

- **High-Level API**: Vereinfachte Schnittstelle für häufige Operationen
- **Low-Level API**: Direkter Zugriff auf GraphQL und REST Endpoints
- **Typsichere Dataclasses**: IDE-Unterstützung für Inventory-Erstellung ✨ NEU!
- **Zeitreihen-Verwaltung**: Effiziente Verwaltung von TimeSeries-Daten
- **Inventory-Operationen**: CRUD-Operationen für Dynamic Objects
- **Optional: pandas Integration**: DataFrames für Zeitreihen- und Inventory-Daten

## Installation

### Für End-User (mit pip)

```bash
# Basis-Installation
pip install questra-data

# Mit pandas-Unterstützung (empfohlen für Data Science)
pip install questra-data[pandas]
```

### Für Entwickler (mit uv)

```bash
# Basis-Installation
uv sync

# Mit pandas-Unterstützung
uv sync --group pandas

# Mit allen Development-Tools
uv sync --all-groups
```

Siehe [INSTALLATION.md](INSTALLATION.md) für detaillierte Installations-Anleitungen.

## Schnellstart

### High-Level API (empfohlen)

```python
from questra_authentication import QuestraAuthentication
from questra_data import QuestraData
from datetime import datetime

# Authentifizierung
auth_client = QuestraAuthentication(
    url="https://authentik.dev.example.com",
    username="ServiceUser",
    password="secret"
)

# Client initialisieren
client = QuestraData(
    graphql_url="https://dev.example.com/dynamic-objects/graphql",
    auth_client=auth_client
)

# Inventory Items auflisten
items = client.list(
    inventory_name="Stromzaehler",
    namespace_name="Energie",
    properties=["_id", "stromzaehlernummer", "hersteller"],
    limit=10
)

# Zeitreihen-Werte laden (kombiniert automatisch Inventory + TimeSeries!)
result = client.list_timeseries_values(
    inventory_name="Stromzaehler",
    namespace_name="Energie",
    timeseries_property="messwerte_Energie",
    from_time=datetime(2025, 1, 1),
    to_time=datetime(2025, 12, 31)
)

for item_id, data in result.items():
    print(f"Stromzähler: {data['item']['stromzaehlernummer']}")
    print(f"Anzahl Werte: {len(data['values'])}")
```

### Inventory erstellen mit Property-Klassen ✨ NEU!

```python
from questra_data import (
    StringProperty,
    IntProperty,
    InventoryRelation,
    RelationType,
    ConflictAction
)

# Typsichere Property-Definitionen (vereinfachte API)
properties = [
    StringProperty(
        propertyName="Name",
        maxLength=200,
        isRequired=True,
        isUnique=True
    ),
    IntProperty(
        propertyName="Age",
        isRequired=False
    )
]

# Inventory erstellen
client.create_inventory(
    name="TEC",
    properties=properties,
    if_exists=ConflictAction.IGNORE
)

# Mit Relationen
relations = [
    InventoryRelation(
        propertyName="Technologie",
        relationType=RelationType.ONE_TO_MANY,
        parentInventoryName="TEC",
        parentPropertyName="Anlagen"
    )
]

client.create_inventory(
    name="ANL",
    properties=properties,
    relations=relations,
    if_exists=ConflictAction.IGNORE
)
```

**Vorteile der Dataclasses:**
- ✅ IDE-Autovervollständigung und Typprüfung
- ✅ Fehler werden zur Entwicklungszeit erkannt
- ✅ Klare, selbstdokumentierende API
- ✅ Rückwärtskompatibel (Dictionary-API funktioniert weiterhin)

Siehe [docs/INPUT_DATACLASSES.md](docs/INPUT_DATACLASSES.md) für vollständige Dokumentation.

### Mit pandas (Optional)

```python
# Zeitreihen als DataFrame
df = client.list_timeseries_values_df(
    inventory_name="Stromzaehler",
    namespace_name="Energie",
    timeseries_property="messwerte_Energie",
    from_time=datetime(2025, 1, 1),
    to_time=datetime(2025, 12, 31),
    include_metadata=True  # Item-Felder als Spalten
)

# DataFrame:
#                        value  quality  item_id  item_stromzaehlernummer
# time
# 2025-01-01 12:00:00   100.5    VALID   630...   SZ-12345
# 2025-01-02 12:00:00   105.2    VALID   630...   SZ-12345

# Direkt weiterverarbeiten
df.resample('1D').mean()  # Tägliche Mittelwerte
df.plot()  # Plotten
df.groupby('item_id')['value'].agg(['mean', 'std'])  # Statistiken

# Inventory Items als DataFrame
df_items = client.list_df(
    inventory_name="Stromzaehler",
    properties=["_id", "stromzaehlernummer", "hersteller"]
)
```

### Low-Level API (für fortgeschrittene Operationen)

```python
# Zugriff auf Low-Level Client
lowlevel = client.lowlevel

# Direkte GraphQL Query
result = lowlevel.execute_raw("""
    query {
        _timeZones(first: 5) {
            name
            baseUtcOffset
        }
    }
""")
```

## API-Übersicht

### High-Level API (`QuestraData`)

#### Zeitreihen

- `list_timeseries_values()` - Zeitreihen-Werte als Dict
- `list_timeseries_values_df()` - Zeitreihen-Werte als DataFrame (benötigt pandas)
- `save_timeseries_values()` - Zeitreihen-Werte speichern

#### Inventory

- `list()` - Items auflisten als List[Dict]
- `list_df()` - Items auflisten als DataFrame (benötigt pandas)
- `create()` - Items erstellen
- `update()` - Items aktualisieren
- `delete()` - Items löschen

#### Verwaltung

- `create_namespace()` - Namespace erstellen → Gibt `NamedItemResult` zurück
- `create_inventory()` - Inventory erstellen mit typisierten Dataclasses → Gibt `NamedItemResult` zurück
- `list_namespaces()` - Namespaces auflisten → Gibt `list[Namespace]` zurück
- `list_inventories()` - Inventories auflisten → Gibt `list[Inventory]` zurück
- `get_system_info()` - System-Informationen → Gibt `SystemInfo` zurück

### Low-Level API (`QuestraDataCore`)

Zugriff über `client.lowlevel`:

- `inventory.*` - Inventory-Operationen (GraphQL)
- `timeseries.*` - Zeitreihen-Operationen (REST)
- `queries.*` - GraphQL Queries
- `mutations.*` - GraphQL Mutations
- `execute_raw()` - Rohe GraphQL Query

## Beispiele

Siehe:
- [example_highlevel_usage.py](example_highlevel_usage.py) - High-Level API mit Dataclasses
- [example_usage.py](example_usage.py) - Low-Level API mit Dataclasses und Legacy-API
- [example_rest_usage.py](example_rest_usage.py) - REST API Beispiele
- [example_nested_fields.py](example_nested_fields.py) - Verschachtelte Felder

## Dokumentation

- [docs/INPUT_DATACLASSES.md](docs/INPUT_DATACLASSES.md) - Vollständige Dataclasses-Dokumentation
- [PANDAS_DEPENDENCY.md](PANDAS_DEPENDENCY.md) - Warum pandas als optionale Dependency?
- [INSTALLATION.md](INSTALLATION.md) - Detaillierte Installations-Anleitungen

## Entwicklung

### Setup

```bash
# Repository klonen
git clone <repo-url>
cd questra-data

# Dependencies installieren (nutzt .python-version für Python 3.10)
uv sync --all-groups
```

**Hinweis:** `uv` muss installiert sein. Siehe [INSTALLATION.md](INSTALLATION.md) für Details.

### Tests

```bash
# Alle Tests
uv run pytest

# Mit Coverage
uv run pytest --cov=questra_data

# Nur pandas Tests
uv run pytest tests/test_pandas_integration.py
```

## Requirements

- Python >= 3.10
- gql >= 3.5.0
- requests >= 2.31.0
- loguru >= 0.7.0
- questra-authentication >= 0.1.4

### Optional

- pandas >= 2.0.0 (für DataFrame-Unterstützung)

## License

Proprietär - Seven2one GmbH
