Files
ENI-projet-piscine/apps/backend/openapi.json
Johan LEROYandGitHub 297d85a0ca Merge pull request #84 from ineszang/feat/endpoint-sites-current
feat(backend): expose GET /api/v1/sites/{site_id}/current
2026-09-18 10:33:53 +02:00

2979 lines
82 KiB
JSON

{
"openapi": "3.1.0",
"info": {
"title": "EnerVision API",
"summary": "Collecte, analyse et restitution de séries temporelles énergétiques.",
"description": "\nToutes les routes sont préfixées par `/api/v1`.\n\n**Authentification.** Le jeton d'accès se présente dans l'en-tête `Authorization: Bearer ...`.\nLe jeton de rafraîchissement est un cookie `HttpOnly` que le code client ne voit jamais : il\nsuffit d'émettre les requêtes avec les identifiants de session. `POST /auth/refresh` rend un\nnouveau jeton d'accès et fait tourner le cookie.\n\n**Rôles.** `lecteur`, puis `operateur`, puis `admin`. Chaque rôle couvre les droits du\nprécédent.\n\n**Erreurs.** Le corps porte toujours une clé `detail`. Un `403` dont le `detail` vaut\n`password_change_required` n'est pas un refus de droits : il exige le changement du mot de passe\nprovisoire avant toute autre action.\n\nLe parcours de session complet est décrit dans\n`docs/architecture/31-contrat-authentification.md`.\n",
"version": "0.1.0",
"x-logo": {
"url": "/static/logo-icon.png",
"altText": "EnerVision"
}
},
"paths": {
"/api/v1/health/live": {
"get": {
"tags": [
"health"
],
"summary": "Sonde de vivacité",
"operationId": "liveness_api_v1_health_live_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LivenessStatus"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
}
}
}
},
"/api/v1/health/ready": {
"get": {
"tags": [
"health"
],
"summary": "Sonde de disponibilité",
"operationId": "readiness_api_v1_health_ready_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReadinessStatus"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"503": {
"description": "Base injoignable, ou extension TimescaleDB absente de la base.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/auth/login": {
"post": {
"tags": [
"auth"
],
"summary": "Ouvre une session",
"operationId": "login_api_v1_auth_login_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"401": {
"description": "Identifiants faux, compte inconnu ou compte désactivé. Le message est le même dans les trois cas, et n'apprend donc rien sur l'existence du compte.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"429": {
"description": "Trop de tentatives sur cette fenêtre glissante.",
"headers": {
"Retry-After": {
"description": "Secondes à attendre avant une nouvelle tentative.",
"schema": {
"type": "integer"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/auth/refresh": {
"post": {
"tags": [
"auth"
],
"summary": "Fait tourner la session",
"operationId": "refresh_api_v1_auth_refresh_post",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"403": {
"description": "Origine non autorisée (protection CSRF de `require_trusted_origin`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Cookie absent, session expirée, révoquée, ou jeton déjà tourné. Dans ce dernier cas toute la famille de sessions est révoquée et le cookie est effacé avec la réponse.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Cookie de rafraîchissement": []
}
]
}
},
"/api/v1/auth/logout": {
"post": {
"tags": [
"auth"
],
"summary": "Ferme la session courante",
"operationId": "logout_api_v1_auth_logout_post",
"responses": {
"204": {
"description": "Successful Response"
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"403": {
"description": "Origine non autorisée (protection CSRF de `require_trusted_origin`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Cookie de rafraîchissement": []
}
]
}
},
"/api/v1/auth/logout-all": {
"post": {
"tags": [
"auth"
],
"summary": "Ferme toutes les sessions du compte",
"operationId": "logout_all_api_v1_auth_logout_all_post",
"responses": {
"204": {
"description": "Successful Response"
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Origine non autorisée (protection CSRF de `require_trusted_origin`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/auth/me": {
"get": {
"tags": [
"auth"
],
"summary": "Décrit le compte connecté",
"operationId": "me_api_v1_auth_me_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PrincipalResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/auth/password": {
"post": {
"tags": [
"auth"
],
"summary": "Change son propre mot de passe",
"operationId": "change_password_api_v1_auth_password_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasswordChangeRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"403": {
"description": "Origine non autorisée (protection CSRF de `require_trusted_origin`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"401": {
"description": "Jeton d'accès invalide, ou mot de passe courant faux.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/auth/forgot-password": {
"post": {
"tags": [
"auth"
],
"summary": "Demande un lien de réinitialisation par email",
"operationId": "forgot_password_api_v1_auth_forgot_password_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ForgotPasswordRequest"
}
}
},
"required": true
},
"responses": {
"202": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"429": {
"description": "Trop de demandes sur cette fenêtre glissante.",
"headers": {
"Retry-After": {
"description": "Secondes à attendre avant une nouvelle tentative.",
"schema": {
"type": "integer"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/auth/reset-password/validate": {
"get": {
"tags": [
"auth"
],
"summary": "Vérifie sans le consommer si un lien de réinitialisation est encore valide",
"operationId": "validate_reset_token_api_v1_auth_reset_password_validate_get",
"parameters": [
{
"name": "token",
"in": "query",
"required": true,
"schema": {
"type": "string",
"title": "Token"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResetTokenValidationResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
}
}
}
},
"/api/v1/auth/reset-password": {
"post": {
"tags": [
"auth"
],
"summary": "Choisit un nouveau mot de passe depuis un lien reçu par email",
"operationId": "reset_password_api_v1_auth_reset_password_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResetPasswordRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TokenResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"403": {
"description": "Origine non autorisée (protection CSRF de `require_trusted_origin`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"400": {
"description": "Lien invalide, déjà utilisé, ou expiré (durée de vie : 15 minutes).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/users": {
"get": {
"tags": [
"users"
],
"summary": "Liste les comptes",
"operationId": "list_users_api_v1_users_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/UserResponse"
},
"type": "array",
"title": "Response List Users Api V1 Users Get"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
},
"post": {
"tags": [
"users"
],
"summary": "Crée un compte avec un mot de passe provisoire",
"operationId": "create_user_api_v1_users_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreateRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemporaryPasswordResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"409": {
"description": "Adresse déjà portée par un autre compte.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/users/{user_id}": {
"patch": {
"tags": [
"users"
],
"summary": "Change le rôle ou l'activation",
"operationId": "update_user_api_v1_users__user_id__patch",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "user_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "User Id"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserUpdateRequest"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"404": {
"description": "Aucun compte ne porte cet identifiant.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"400": {
"description": "Corps vide, aucune modification demandée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"409": {
"description": "L'opération laisserait la plateforme sans administrateur actif, qu'il s'agisse de rétrograder le dernier ou de le désactiver.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/users/{user_id}/password-reset": {
"post": {
"tags": [
"users"
],
"summary": "Réinitialise le mot de passe et ferme les sessions",
"operationId": "reset_password_api_v1_users__user_id__password_reset_post",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "user_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid",
"title": "User Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TemporaryPasswordResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"404": {
"description": "Aucun compte ne porte cet identifiant.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/sites": {
"get": {
"tags": [
"sites"
],
"summary": "Liste les sites",
"operationId": "list_sites_api_v1_sites_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SiteResponse"
},
"type": "array",
"title": "Response List Sites Api V1 Sites Get"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/sites/{site_id}": {
"get": {
"tags": [
"sites"
],
"summary": "Décrit un site",
"operationId": "get_site_api_v1_sites__site_id__get",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "site_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Site Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SiteResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"404": {
"description": "Aucun site ne porte cet identifiant.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/sites/{site_id}/current": {
"get": {
"tags": [
"sites"
],
"summary": "Dernière mesure d'un site",
"operationId": "get_current_api_v1_sites__site_id__current_get",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "site_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Site Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SiteCurrentResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"404": {
"description": "Aucun site ne porte cet identifiant.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/alerts": {
"get": {
"tags": [
"alerts"
],
"summary": "Liste les alertes",
"operationId": "list_alerts_api_v1_alerts_get",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "site_id",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Site Id"
}
},
{
"name": "severity",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/AlertSeverity"
},
{
"type": "null"
}
],
"title": "Severity"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AlertResponse"
},
"title": "Response List Alerts Api V1 Alerts Get"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
}
}
}
},
"/api/v1/recommendations": {
"get": {
"tags": [
"recommendations"
],
"summary": "Liste les recommandations",
"operationId": "list_recommendations_api_v1_recommendations_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/RecommendationResponse"
},
"type": "array",
"title": "Response List Recommendations Api V1 Recommendations Get"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/recommendations/{recommendation_id}": {
"get": {
"tags": [
"recommendations"
],
"summary": "Décrit une recommandation",
"operationId": "get_recommendation_api_v1_recommendations__recommendation_id__get",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "recommendation_id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"title": "Recommendation Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RecommendationResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"404": {
"description": "Aucune recommandation ne porte cet identifiant.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/stats/summary": {
"get": {
"tags": [
"stats"
],
"summary": "Résume la consommation instantanée du parc",
"operationId": "get_summary_api_v1_stats_summary_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatsSummaryResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
},
"/api/v1/readings": {
"get": {
"tags": [
"readings"
],
"summary": "Liste l'historique des lectures",
"operationId": "list_readings_api_v1_readings_get",
"security": [
{
"Jeton d'accès": []
}
],
"parameters": [
{
"name": "site_id",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Site Id"
}
},
{
"name": "start",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Start"
}
},
{
"name": "end",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "End"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"maximum": 2000,
"minimum": 1,
"default": 500,
"title": "Limit"
}
},
{
"name": "offset",
"in": "query",
"required": false,
"schema": {
"type": "integer",
"minimum": 0,
"default": 0,
"title": "Offset"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ReadingResponse"
},
"title": "Response List Readings Api V1 Readings Get"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Mot de passe provisoire à changer (`detail` vaut `password_change_required`).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"422": {
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationErrorResponse"
}
}
}
},
"400": {
"description": "Fenêtre temporelle invalide : `start` postérieur ou égal à `end`, ou écart entre les deux supérieur à 90 jours.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/sensors/status": {
"get": {
"tags": [
"sensors"
],
"summary": "État de santé des capteurs par site",
"operationId": "get_status_api_v1_sensors_status_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SensorStatusResponse"
}
}
}
},
"500": {
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InternalErrorResponse"
}
}
}
},
"401": {
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"403": {
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"security": [
{
"Jeton d'accès": []
}
]
}
}
},
"components": {
"schemas": {
"AccountKind": {
"type": "string",
"enum": [
"human",
"service"
],
"title": "AccountKind"
},
"AlertResponse": {
"properties": {
"alert_id": {
"type": "integer",
"title": "Alert Id"
},
"site_id": {
"type": "string",
"title": "Site Id"
},
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp"
},
"type": {
"$ref": "#/components/schemas/AlertType"
},
"severity": {
"$ref": "#/components/schemas/AlertSeverity"
},
"message": {
"type": "string",
"title": "Message"
},
"value": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Value"
},
"threshold": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Threshold"
},
"metric": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Metric"
},
"prediction_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Prediction Id"
}
},
"type": "object",
"required": [
"alert_id",
"site_id",
"timestamp",
"type",
"severity",
"message",
"value",
"threshold",
"metric",
"prediction_id"
],
"title": "AlertResponse"
},
"AlertSeverity": {
"type": "string",
"enum": [
"low",
"medium",
"high",
"critical"
],
"title": "AlertSeverity"
},
"AlertType": {
"type": "string",
"enum": [
"spike",
"threshold",
"anomaly",
"outage",
"sensor"
],
"title": "AlertType"
},
"ErrorResponse": {
"properties": {
"detail": {
"type": "string",
"title": "Detail"
}
},
"type": "object",
"required": [
"detail"
],
"title": "ErrorResponse"
},
"FieldError": {
"properties": {
"champ": {
"type": "string",
"title": "Champ"
},
"type": {
"type": "string",
"title": "Type"
}
},
"type": "object",
"required": [
"champ",
"type"
],
"title": "FieldError"
},
"ForgotPasswordRequest": {
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "Email"
}
},
"type": "object",
"required": [
"email"
],
"title": "ForgotPasswordRequest"
},
"InternalErrorResponse": {
"properties": {
"detail": {
"type": "string",
"title": "Detail"
},
"correlation": {
"type": "string",
"title": "Correlation"
}
},
"type": "object",
"required": [
"detail",
"correlation"
],
"title": "InternalErrorResponse"
},
"LivenessStatus": {
"properties": {
"status": {
"type": "string",
"const": "ok",
"title": "Status"
},
"service": {
"type": "string",
"title": "Service"
},
"version": {
"type": "string",
"title": "Version"
},
"environment": {
"type": "string",
"title": "Environment"
}
},
"type": "object",
"required": [
"status",
"service",
"version",
"environment"
],
"title": "LivenessStatus"
},
"LoginRequest": {
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "Email"
},
"password": {
"type": "string",
"maxLength": 128,
"minLength": 1,
"title": "Password"
}
},
"type": "object",
"required": [
"email",
"password"
],
"title": "LoginRequest"
},
"PasswordChangeRequest": {
"properties": {
"current_password": {
"type": "string",
"maxLength": 128,
"minLength": 1,
"title": "Current Password"
},
"new_password": {
"type": "string",
"maxLength": 128,
"minLength": 8,
"title": "New Password"
}
},
"type": "object",
"required": [
"current_password",
"new_password"
],
"title": "PasswordChangeRequest"
},
"PrincipalResponse": {
"properties": {
"id": {
"type": "string",
"format": "uuid",
"title": "Id"
},
"email": {
"type": "string",
"title": "Email"
},
"role": {
"$ref": "#/components/schemas/Role"
},
"kind": {
"$ref": "#/components/schemas/AccountKind"
},
"must_change_password": {
"type": "boolean",
"title": "Must Change Password"
}
},
"type": "object",
"required": [
"id",
"email",
"role",
"kind",
"must_change_password"
],
"title": "PrincipalResponse"
},
"ReadinessStatus": {
"properties": {
"status": {
"type": "string",
"const": "ready",
"title": "Status"
},
"database": {
"type": "string",
"const": "reachable",
"title": "Database"
},
"timescaledb": {
"type": "string",
"const": "loaded",
"title": "Timescaledb"
}
},
"type": "object",
"required": [
"status",
"database",
"timescaledb"
],
"title": "ReadinessStatus"
},
"ReadingDataQuality": {
"type": "string",
"enum": [
"good",
"partial",
"degraded",
"critical"
],
"title": "ReadingDataQuality"
},
"ReadingResponse": {
"properties": {
"reading_id": {
"type": "integer",
"title": "Reading Id"
},
"site_id": {
"type": "string",
"title": "Site Id"
},
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp"
},
"source": {
"$ref": "#/components/schemas/ReadingSource"
},
"consumption_kw": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Consumption Kw"
},
"consumption_kwh": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Consumption Kwh"
},
"consumption_euros": {
"anyOf": [
{
"type": "string",
"pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"
},
{
"type": "null"
}
],
"title": "Consumption Euros"
},
"voltage_v": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Voltage V"
},
"current_a": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Current A"
},
"power_factor": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Power Factor"
},
"temperature_celsius": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Temperature Celsius"
},
"humidity_percent": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Humidity Percent"
},
"solar_irradiance_wm2": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Solar Irradiance Wm2"
},
"is_working_hours": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Is Working Hours"
},
"data_quality": {
"anyOf": [
{
"$ref": "#/components/schemas/ReadingDataQuality"
},
{
"type": "null"
}
]
},
"null_reasons": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Null Reasons"
},
"imputed_values": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Imputed Values"
},
"imputation_method": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Imputation Method"
}
},
"type": "object",
"required": [
"reading_id",
"site_id",
"timestamp",
"source",
"consumption_kw",
"consumption_kwh",
"consumption_euros",
"voltage_v",
"current_a",
"power_factor",
"temperature_celsius",
"humidity_percent",
"solar_irradiance_wm2",
"is_working_hours",
"data_quality",
"null_reasons",
"imputed_values",
"imputation_method"
],
"title": "ReadingResponse"
},
"ReadingSource": {
"type": "string",
"enum": [
"csv",
"api_current",
"api_history"
],
"title": "ReadingSource"
},
"RecommendationResponse": {
"properties": {
"recommendation_id": {
"type": "integer",
"title": "Recommendation Id"
},
"alert_id": {
"type": "integer",
"title": "Alert Id"
},
"action": {
"type": "string",
"title": "Action"
},
"explanation": {
"type": "string",
"title": "Explanation"
},
"rule_reference": {
"type": "string",
"title": "Rule Reference"
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
}
},
"type": "object",
"required": [
"recommendation_id",
"alert_id",
"action",
"explanation",
"rule_reference",
"created_at"
],
"title": "RecommendationResponse"
},
"ResetPasswordRequest": {
"properties": {
"token": {
"type": "string",
"minLength": 1,
"title": "Token"
},
"new_password": {
"type": "string",
"maxLength": 128,
"minLength": 8,
"title": "New Password"
}
},
"type": "object",
"required": [
"token",
"new_password"
],
"title": "ResetPasswordRequest"
},
"ResetTokenValidationResponse": {
"properties": {
"valid": {
"type": "boolean",
"title": "Valid"
}
},
"type": "object",
"required": [
"valid"
],
"title": "ResetTokenValidationResponse"
},
"Role": {
"type": "string",
"enum": [
"lecteur",
"operateur",
"admin"
],
"title": "Role"
},
"SensorDiagnosticResponse": {
"properties": {
"status": {
"type": "string",
"enum": [
"ok",
"failing"
],
"title": "Status"
},
"since": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Since",
"description": "Horodatage de la dernière lecture reçue pour ce site. Ce n'est pas le début de la panne : l'historique ne permet pas de le dater sans requête supplémentaire."
}
},
"type": "object",
"required": [
"status",
"since"
],
"title": "SensorDiagnosticResponse"
},
"SensorStatusResponse": {
"properties": {
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp"
},
"sites": {
"items": {
"$ref": "#/components/schemas/SiteSensorStatusResponse"
},
"type": "array",
"title": "Sites"
}
},
"type": "object",
"required": [
"timestamp",
"sites"
],
"title": "SensorStatusResponse"
},
"SiteCurrentResponse": {
"properties": {
"timestamp": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Timestamp"
},
"site_id": {
"type": "string",
"title": "Site Id"
},
"site_type": {
"type": "string",
"title": "Site Type"
},
"consumption_kw": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Consumption Kw"
},
"consumption_kwh": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Consumption Kwh"
},
"voltage_v": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Voltage V"
},
"current_a": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Current A"
},
"power_factor": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Power Factor"
},
"temperature_celsius": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Temperature Celsius"
},
"humidity_percent": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Humidity Percent"
},
"null_reasons": {
"items": {
"type": "string"
},
"type": "array",
"title": "Null Reasons"
},
"data_quality": {
"type": "string",
"enum": [
"good",
"partial",
"degraded",
"critical"
],
"title": "Data Quality"
}
},
"type": "object",
"required": [
"timestamp",
"site_id",
"site_type",
"consumption_kw",
"consumption_kwh",
"voltage_v",
"current_a",
"power_factor",
"temperature_celsius",
"humidity_percent",
"null_reasons",
"data_quality"
],
"title": "SiteCurrentResponse"
},
"SiteResponse": {
"properties": {
"site_id": {
"type": "string",
"title": "Site Id"
},
"site_name": {
"type": "string",
"title": "Site Name"
},
"site_type": {
"type": "string",
"title": "Site Type"
},
"location": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Location"
},
"capacity_kw": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Capacity Kw"
},
"status": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Status"
}
},
"type": "object",
"required": [
"site_id",
"site_name",
"site_type",
"location",
"capacity_kw",
"status"
],
"title": "SiteResponse"
},
"SiteSensorStatusResponse": {
"properties": {
"site_id": {
"type": "string",
"title": "Site Id"
},
"site_name": {
"type": "string",
"title": "Site Name"
},
"sensors": {
"$ref": "#/components/schemas/SiteSensorsResponse"
},
"overall": {
"type": "string",
"enum": [
"ok",
"degraded",
"critical"
],
"title": "Overall"
}
},
"type": "object",
"required": [
"site_id",
"site_name",
"sensors",
"overall"
],
"title": "SiteSensorStatusResponse"
},
"SiteSensorsResponse": {
"properties": {
"consumption": {
"$ref": "#/components/schemas/SensorDiagnosticResponse"
},
"electrical": {
"$ref": "#/components/schemas/SensorDiagnosticResponse"
},
"temperature": {
"$ref": "#/components/schemas/SensorDiagnosticResponse"
},
"humidity": {
"$ref": "#/components/schemas/SensorDiagnosticResponse"
},
"network": {
"$ref": "#/components/schemas/SensorDiagnosticResponse"
}
},
"type": "object",
"required": [
"consumption",
"electrical",
"temperature",
"humidity",
"network"
],
"title": "SiteSensorsResponse"
},
"SiteSummaryResponse": {
"properties": {
"site_id": {
"type": "string",
"title": "Site Id"
},
"site_name": {
"type": "string",
"title": "Site Name"
},
"current_consumption_kw": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Current Consumption Kw"
},
"capacity_kw": {
"type": "number",
"title": "Capacity Kw"
},
"load_percent": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Load Percent"
},
"data_quality": {
"type": "string",
"enum": [
"good",
"partial",
"degraded",
"critical"
],
"title": "Data Quality"
}
},
"type": "object",
"required": [
"site_id",
"site_name",
"current_consumption_kw",
"capacity_kw",
"load_percent",
"data_quality"
],
"title": "SiteSummaryResponse"
},
"StatsSummaryResponse": {
"properties": {
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp"
},
"total_sites": {
"type": "integer",
"title": "Total Sites"
},
"total_consumption_kw": {
"type": "number",
"title": "Total Consumption Kw"
},
"total_capacity_kw": {
"type": "number",
"title": "Total Capacity Kw"
},
"average_load_percent": {
"type": "number",
"title": "Average Load Percent"
},
"sites": {
"items": {
"$ref": "#/components/schemas/SiteSummaryResponse"
},
"type": "array",
"title": "Sites"
}
},
"type": "object",
"required": [
"timestamp",
"total_sites",
"total_consumption_kw",
"total_capacity_kw",
"average_load_percent",
"sites"
],
"title": "StatsSummaryResponse"
},
"TemporaryPasswordResponse": {
"properties": {
"user": {
"$ref": "#/components/schemas/UserResponse"
},
"temporary_password": {
"type": "string",
"title": "Temporary Password"
}
},
"type": "object",
"required": [
"user",
"temporary_password"
],
"title": "TemporaryPasswordResponse"
},
"TokenResponse": {
"properties": {
"access_token": {
"type": "string",
"title": "Access Token"
},
"token_type": {
"type": "string",
"const": "bearer",
"title": "Token Type",
"default": "bearer"
},
"expires_in": {
"type": "integer",
"title": "Expires In"
},
"principal": {
"$ref": "#/components/schemas/PrincipalResponse"
}
},
"type": "object",
"required": [
"access_token",
"expires_in",
"principal"
],
"title": "TokenResponse"
},
"UserCreateRequest": {
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "Email"
},
"role": {
"$ref": "#/components/schemas/Role"
},
"full_name": {
"anyOf": [
{
"type": "string",
"maxLength": 200
},
{
"type": "null"
}
],
"title": "Full Name"
}
},
"type": "object",
"required": [
"email",
"role"
],
"title": "UserCreateRequest"
},
"UserResponse": {
"properties": {
"id": {
"type": "string",
"format": "uuid",
"title": "Id"
},
"email": {
"type": "string",
"title": "Email"
},
"role": {
"$ref": "#/components/schemas/Role"
},
"kind": {
"$ref": "#/components/schemas/AccountKind"
},
"is_active": {
"type": "boolean",
"title": "Is Active"
},
"must_change_password": {
"type": "boolean",
"title": "Must Change Password"
},
"full_name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Full Name"
},
"last_login_at": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Last Login At"
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
}
},
"type": "object",
"required": [
"id",
"email",
"role",
"kind",
"is_active",
"must_change_password",
"full_name",
"last_login_at",
"created_at"
],
"title": "UserResponse"
},
"UserUpdateRequest": {
"properties": {
"role": {
"anyOf": [
{
"$ref": "#/components/schemas/Role"
},
{
"type": "null"
}
]
},
"is_active": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Is Active"
}
},
"type": "object",
"title": "UserUpdateRequest"
},
"ValidationErrorResponse": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/FieldError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"required": [
"detail"
],
"title": "ValidationErrorResponse"
}
},
"securitySchemes": {
"Cookie de rafraîchissement": {
"type": "apiKey",
"description": "Cookie `HttpOnly` posé par `/auth/login` et tourné par `/auth/refresh`. Il prend le préfixe `__Secure-` dès que l'API tourne derrière TLS, et n'est émis que vers `/api/v1/auth`.",
"in": "cookie",
"name": "ev_refresh"
},
"Jeton d'accès": {
"type": "http",
"scheme": "bearer"
}
}
},
"tags": [
{
"name": "health",
"description": "Sondes d'infrastructure, publiques. `live` prouve que le processus répond, `ready` que la base répond et que l'extension TimescaleDB est chargée."
},
{
"name": "auth",
"description": "Ouverture, rotation et fermeture de session, et changement de son propre mot de passe."
},
{
"name": "users",
"description": "Administration des comptes. Réservé au rôle `admin`."
},
{
"name": "sites",
"description": "Consultation du parc de sites. Accessible à partir du rôle `lecteur`."
},
{
"name": "alerts",
"description": "Consultation des alertes de consommation. Accessible à partir du rôle `lecteur`."
},
{
"name": "recommendations",
"description": "Consultation des recommandations issues des alertes. Accessible à partir du rôle `lecteur`."
},
{
"name": "stats",
"description": "Statistiques agrégées de consommation. Accessible à partir du rôle `lecteur`."
},
{
"name": "readings",
"description": "Historique des lectures de consommation. Fenêtre temporelle plafonnée à 90 jours, 24 dernières heures par défaut si `start`/`end` sont omis. Accessible à partir du rôle `lecteur`."
},
{
"name": "sensors",
"description": "État de santé des capteurs par site. Réservé au rôle `admin`."
}
]
}