2955 lines
82 KiB
JSON
2955 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/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": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/predictions": {
|
|
"get": {
|
|
"tags": [
|
|
"predictions"
|
|
],
|
|
"summary": "Dernière prédiction de consommation par site",
|
|
"operationId": "get_predictions_api_v1_predictions_get",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PredictionSummaryResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"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": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"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"
|
|
},
|
|
"PredictionStatus": {
|
|
"type": "string",
|
|
"enum": [
|
|
"available",
|
|
"insufficient_data",
|
|
"error"
|
|
],
|
|
"title": "PredictionStatus"
|
|
},
|
|
"PredictionSummaryResponse": {
|
|
"properties": {
|
|
"timestamp": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"title": "Timestamp"
|
|
},
|
|
"sites": {
|
|
"items": {
|
|
"$ref": "#/components/schemas/SitePredictionSummaryResponse"
|
|
},
|
|
"type": "array",
|
|
"title": "Sites"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"timestamp",
|
|
"sites"
|
|
],
|
|
"title": "PredictionSummaryResponse"
|
|
},
|
|
"PredictionTargetMetric": {
|
|
"type": "string",
|
|
"enum": [
|
|
"consumption_kwh",
|
|
"consumption_kw"
|
|
],
|
|
"title": "PredictionTargetMetric"
|
|
},
|
|
"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"
|
|
},
|
|
"SitePredictionResponse": {
|
|
"properties": {
|
|
"target_at": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"title": "Target At"
|
|
},
|
|
"target_metric": {
|
|
"$ref": "#/components/schemas/PredictionTargetMetric"
|
|
},
|
|
"period_minutes": {
|
|
"anyOf": [
|
|
{
|
|
"type": "integer"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Period Minutes"
|
|
},
|
|
"predicted_value": {
|
|
"anyOf": [
|
|
{
|
|
"type": "number"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Predicted Value"
|
|
},
|
|
"status": {
|
|
"$ref": "#/components/schemas/PredictionStatus"
|
|
},
|
|
"failure_reason": {
|
|
"anyOf": [
|
|
{
|
|
"type": "string"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Failure Reason"
|
|
},
|
|
"model_reference": {
|
|
"type": "string",
|
|
"title": "Model Reference"
|
|
},
|
|
"created_at": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"title": "Created At"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"target_at",
|
|
"target_metric",
|
|
"period_minutes",
|
|
"predicted_value",
|
|
"status",
|
|
"failure_reason",
|
|
"model_reference",
|
|
"created_at"
|
|
],
|
|
"title": "SitePredictionResponse"
|
|
},
|
|
"SitePredictionSummaryResponse": {
|
|
"properties": {
|
|
"site_id": {
|
|
"type": "string",
|
|
"title": "Site Id"
|
|
},
|
|
"site_name": {
|
|
"type": "string",
|
|
"title": "Site Name"
|
|
},
|
|
"prediction": {
|
|
"anyOf": [
|
|
{
|
|
"$ref": "#/components/schemas/SitePredictionResponse"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"site_id",
|
|
"site_name",
|
|
"prediction"
|
|
],
|
|
"title": "SitePredictionSummaryResponse"
|
|
},
|
|
"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`."
|
|
},
|
|
{
|
|
"name": "predictions",
|
|
"description": "Dernière prévision de consommation par site, calculée hors ligne par le pipeline de scoring (`ml/`) et simplement lue ici. Accessible à partir du rôle `lecteur`."
|
|
}
|
|
]
|
|
}
|