
# 03 – Spécification API Backend (FastAPI)

Objectif : définir précisément les endpoints backend, les schémas JSON échangés et les règles métier associées.

L’API sera exposée sous `/api`.

---

## 1. Contenu attendu de ce fichier

Ce document doit servir de référence au développeur backend. Il décrit :

1. Le format du payload JSON envoyé par le script de benchmark (`bench.sh`) vers le backend.
2. Les endpoints REST nécessaires :
   - Réception d’un benchmark.
   - Consultation des devices.
   - Consultation de l’historique des benchmarks.
   - Gestion des liens constructeur.
   - Gestion des documents (PDF, images).
3. Les règles d’authentification (token simple).
4. Les codes d’erreur principaux et attentes de validation.

---

## 2. Schéma JSON – payload d’un benchmark

Endpoint cible : `POST /api/benchmark`

Le script client envoie un JSON de la forme :

```json
{
  "device_identifier": "elitedesk-800g3",
  "bench_script_version": "1.0.0",
  "hardware": {
    "cpu": {
      "vendor": "Intel",
      "model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz",
      "microarchitecture": "Skylake",
      "cores": 4,
      "threads": 8,
      "base_freq_ghz": 3.4,
      "max_freq_ghz": 4.0,
      "cache_l1_kb": 256,
      "cache_l2_kb": 1024,
      "cache_l3_kb": 8192,
      "flags": ["avx", "avx2", "aes"],
      "tdp_w": 65
    },
    "ram": {
      "total_mb": 65536,
      "slots_total": 4,
      "slots_used": 4,
      "ecc": false,
      "layout": [
        {
          "slot": "DIMM_A1",
          "size_mb": 16384,
          "type": "DDR4",
          "speed_mhz": 2133,
          "vendor": "Corsair",
          "part_number": "XYZ"
        }
      ]
    },
    "gpu": {
      "vendor": "Intel",
      "model": "Intel HD Graphics 530",
      "driver_version": "xxx",
      "memory_dedicated_mb": null,
      "memory_shared_mb": 512,
      "api_support": ["OpenGL 4.5"]
    },
    "storage": {
      "devices": [
        {
          "name": "/dev/nvme0n1",
          "type": "NVMe",
          "interface": "PCIe 3.0 x4",
          "capacity_gb": 1000,
          "vendor": "Samsung",
          "model": "970 EVO Plus",
          "smart_health": "PASSED",
          "temperature_c": 42
        }
      ],
      "partitions": [
        {
          "name": "/dev/nvme0n1p1",
          "mount_point": "/",
          "fs_type": "ext4",
          "used_gb": 50,
          "total_gb": 100
        }
      ]
    },
    "network": {
      "interfaces": [
        {
          "name": "eth0",
          "type": "ethernet",
          "mac": "aa:bb:cc:dd:ee:ff",
          "ip": "10.0.0.10",
          "speed_mbps": 1000,
          "driver": "e1000e"
        }
      ]
    },
    "motherboard": {
      "vendor": "HP",
      "model": "HP 8054",
      "bios_version": "P01 Ver. 02.48",
      "bios_date": "2023-05-10"
    },
    "os": {
      "name": "Debian",
      "version": "12 (bookworm)",
      "kernel_version": "6.1.0-xx-amd64",
      "architecture": "x86_64",
      "virtualization_type": "none"
    },
    "sensors": {
      "cpu_temp_c": 45,
      "disk_temps_c": {
        "/dev/nvme0n1": 42
      }
    },
    "raw_info": {
      "lscpu": "raw text…",
      "lsblk": "raw text…"
    }
  },
  "results": {
    "cpu": {
      "events_per_sec": 12000,
      "duration_s": 10,
      "score": 90
    },
    "memory": {
      "throughput_mib_s": 20000,
      "score": 95
    },
    "disk": {
      "read_mb_s": 1200,
      "write_mb_s": 1000,
      "iops_read": 50000,
      "iops_write": 45000,
      "latency_ms": 1.2,
      "score": 94
    },
    "network": {
      "upload_mbps": 930,
      "download_mbps": 940,
      "ping_ms": 1.2,
      "jitter_ms": 0.3,
      "packet_loss_percent": 0.0,
      "score": 88
    },
    "gpu": {
      "glmark2_score": null,
      "score": null
    },
    "global_score": 92
  }
}
```

Remarques importantes :

- Certains champs peuvent être `null` ou absents en fonction de la machine (ex: pas de GPU dédié).
- Le backend doit être robuste à l’absence de certains blocs (ex: pas de `network` si aucun test réseau).

---

## 3. Logique de traitement côté backend

1. **Auth**  
   - Lecture du header `Authorization: Bearer <TOKEN>`.
   - Comparaison avec une valeur stockée dans la config/ENV (`API_TOKEN`).
   - Si token invalide ou absent : réponse 401.

2. **Résolution du device**  
   - Chercher un enregistrement `devices` avec `hostname == device_identifier`.
   - Si trouvé :
     - Utiliser `device_id` existant.
   - Sinon :
     - Créer un nouveau `device` minimal avec :
       - `hostname = device_identifier`
       - `created_at`, `updated_at` = maintenant.

3. **Création d’un hardware_snapshot**  
   - Mapper le contenu `hardware` vers la table `hardware_snapshots` telle que définie dans `02_model_donnees.md`.
   - Exemples :
     - `hardware.cpu.vendor` -> `cpu_vendor`
     - `hardware.ram.total_mb` -> `ram_total_mb`
     - `hardware.storage.devices` -> JSON dans `storage_devices_json`
     - etc.
   - `captured_at` = `now()` (ou `run_at` du benchmark si fourni).

4. **Création d’un benchmark**  
   - `run_at` = `now()` (ou fourni explicitement plus tard).
   - `bench_script_version` = champ du payload.
   - Scores :
     - `cpu_score` = `results.cpu.score` (si présent).
     - `memory_score` = `results.memory.score`
     - `disk_score` = `results.disk.score`
     - `network_score` = `results.network.score`
     - `gpu_score` = `results.gpu.score`
     - `global_score` = `results.global_score`
   - `details_json` = JSON complet de `results` (ou éventuellement de `results` + métriques brutes).

5. **Réponse**  
   - En cas de succès, renvoyer :
     ```json
     {
       "status": "ok",
       "device_id": 1,
       "benchmark_id": 42
     }
     ```

---

## 4. Endpoints détaillés

### 4.1. POST /api/benchmark

- Rôle : point d’entrée unique des résultats du script client.
- Auth : header `Authorization: Bearer <TOKEN>`.
- Body : JSON (voir schéma ci-dessus).
- Codes de réponse :
  - `200` – succès.
  - `400` – JSON invalide / validation Pydantic échouée.
  - `401` – token invalide ou manquant.
  - `500` – erreur interne.

---

### 4.2. GET /api/devices

Objectif : lister les devices avec un résumé de leur dernier benchmark.

Paramètres query :
- `page` (int, optionnel, défaut 1)
- `page_size` (int, optionnel, défaut 20)
- `search` (string, optionnel – filtre sur hostname/description/tags)

Réponse (exemple simplifié) :

```json
{
  "items": [
    {
      "id": 1,
      "hostname": "elitedesk-800g3",
      "description": "HP EliteDesk 800 G3 SFF",
      "location": "Bureau",
      "tags": ["lab", "dev"],
      "last_benchmark": {
        "id": 42,
        "run_at": "2025-12-07T10:32:00Z",
        "global_score": 92,
        "cpu_score": 90,
        "memory_score": 95,
        "disk_score": 94,
        "network_score": 88,
        "gpu_score": null
      }
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 20
}
```

---

### 4.3. GET /api/devices/{device_id}

Objectif : récupérer le détail d’un device.

Réponse (exemple) :

```json
{
  "id": 1,
  "hostname": "elitedesk-800g3",
  "fqdn": "elitedesk-800g3.maison43",
  "description": "HP EliteDesk 800 G3 SFF (serveur dev)",
  "asset_tag": "LAB-001",
  "location": "Bureau",
  "owner": "Gilles",
  "tags": ["lab", "proxmox"],
  "created_at": "2025-12-01T10:00:00Z",
  "updated_at": "2025-12-07T10:32:00Z",
  "last_hardware_snapshot": {
    "captured_at": "2025-12-07T10:32:00Z",
    "cpu_vendor": "Intel",
    "cpu_model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz",
    "cpu_cores": 4,
    "cpu_threads": 8,
    "ram_total_mb": 65536,
    "storage_summary": "NVMe 1TB + HDD 2TB",
    "gpu_summary": "Intel HD Graphics 530",
    "os_name": "Debian",
    "os_version": "12 (bookworm)",
    "kernel_version": "6.1.0-xx-amd64"
  },
  "last_benchmark": {
    "id": 42,
    "run_at": "2025-12-07T10:32:00Z",
    "global_score": 92,
    "cpu_score": 90,
    "memory_score": 95,
    "disk_score": 94,
    "network_score": 88,
    "gpu_score": null
  }
}
```

---

### 4.4. GET /api/devices/{device_id}/benchmarks

Objectif : récupérer l’historique de benchmarks d’un device.

Paramètres :
- `limit` (int, défaut 20)
- `offset` (int, défaut 0)

Réponse :

```json
{
  "items": [
    {
      "id": 42,
      "run_at": "2025-12-07T10:32:00Z",
      "global_score": 92,
      "cpu_score": 90,
      "memory_score": 95,
      "disk_score": 94,
      "network_score": 88,
      "gpu_score": null
    },
    {
      "id": 35,
      "run_at": "2025-12-05T09:10:00Z",
      "global_score": 89,
      "cpu_score": 88,
      "memory_score": 92,
      "disk_score": 90,
      "network_score": 85,
      "gpu_score": null
    }
  ],
  "total": 2,
  "limit": 20,
  "offset": 0
}
```

---

### 4.5. GET /api/benchmarks/{benchmark_id}

Objectif : récupérer les détails complets d’un benchmark.

Réponse :

```json
{
  "id": 42,
  "device_id": 1,
  "hardware_snapshot_id": 10,
  "run_at": "2025-12-07T10:32:00Z",
  "bench_script_version": "1.0.0",
  "global_score": 92,
  "cpu_score": 90,
  "memory_score": 95,
  "disk_score": 94,
  "network_score": 88,
  "gpu_score": null,
  "details": {
    "cpu": {
      "events_per_sec": 12000,
      "duration_s": 10,
      "score": 90
    },
    "memory": {
      "throughput_mib_s": 20000,
      "score": 95
    },
    "disk": {
      "read_mb_s": 1200,
      "write_mb_s": 1000,
      "iops_read": 50000,
      "iops_write": 45000,
      "latency_ms": 1.2,
      "score": 94
    },
    "network": {
      "upload_mbps": 930,
      "download_mbps": 940,
      "ping_ms": 1.2,
      "jitter_ms": 0.3,
      "packet_loss_percent": 0,
      "score": 88
    },
    "gpu": {
      "glmark2_score": null,
      "score": null
    },
    "global_score": 92
  }
}
```

Note : `details` reflète `details_json` de la base.

---

### 4.6. Liens constructeur

#### GET /api/devices/{device_id}/links

Réponse :

```json
[
  {
    "id": 1,
    "label": "Support HP",
    "url": "https://support.hp.com/..."
  },
  {
    "id": 2,
    "label": "Page produit",
    "url": "https://www.hp.com/..."
  }
]
```

#### POST /api/devices/{device_id}/links

Body :

```json
{
  "label": "Support HP",
  "url": "https://support.hp.com/..."
}
```

Réponse : l’objet créé avec `id`.

#### PUT /api/links/{id}

Body idem POST (label + url).

#### DELETE /api/links/{id}

- Réponse : `204 No Content` en cas de succès.

---

### 4.7. Documents

#### GET /api/devices/{device_id}/docs

Réponse :

```json
[
  {
    "id": 1,
    "doc_type": "manual",
    "filename": "manual_elitedesk.pdf",
    "mime_type": "application/pdf",
    "size_bytes": 3456789,
    "uploaded_at": "2025-05-01T10:00:00Z"
  }
]
```

#### POST /api/devices/{device_id}/docs

- Type : `multipart/form-data`
- Champs :
  - `file` (fichier binaire)
  - `doc_type` (string)

Réponse : métadonnées du document créé.

#### GET /api/docs/{doc_id}/download

- Sert le fichier binaire avec le bon `Content-Type`.

---

## 5. Authentification

MVP : token simple.

- Variable d’env `API_TOKEN` côté backend.
- Chaque requête `POST /api/benchmark` doit inclure :

```http
Authorization: Bearer <API_TOKEN>
```

- Les endpoints de lecture (GET) peuvent être :
  - soit publics sur le LAN,
  - soit protégés par un autre mécanisme (reverse proxy) – hors scope de cette spec.

---

## 6. Gestion des erreurs et validations

- Utiliser des modèles Pydantic pour :
  - valider la structure de `hardware` et `results` autant que possible sans être bloquant.
  - autoriser des champs optionnels (None / manquants).
- Codes d’erreur génériques :
  - 400 : données invalides (schema).
  - 401 : auth.
  - 404 : ressources inexistantes (device, benchmark, link, doc).
  - 413 : payload trop volumineux (facultatif selon config).
  - 500 : erreur interne.

Logs :
- Logguer les erreurs de parsing JSON.
- Logguer l’origine (IP) des appels `POST /api/benchmark`.

---
