refactor(api): découpe main.py en routeurs par domaine
main.py comptait 829 lignes et regroupait 9 domaines fonctionnels. Il ne fait plus que l'assemblage de l'application (configuration, middlewares, montage des routeurs), soit 115 lignes. - routers/ : un module par domaine, chacun déclarant sa propre dépendance d'authentification - domain.py : enums UserRole et AuditAction, mapping MONITO_TABLES ; les rôles étaient jusqu'ici répétés en dur à deux endroits - helpers.py : conversion des lignes pyodbc, écriture du journal - rate_limit.py : limiteur partagé, isolé pour éviter un import circulaire entre main.py et le routeur d'authentification Les codes HTTP littéraux (404, 401, 400, 201) passent aux constantes fastapi.status, comme le faisait déjà auth.py. Les actions du journal d'audit passent en paramètre SQL au lieu d'être concaténées. Aucune route modifiée : la comparaison des specs OpenAPI avant/après confirme que les 26 URL existantes sont identiques. conftest patchait main.get_cursor ; chaque routeur important désormais get_cursor dans son propre espace de noms, la fixture remplace le nom dans tous les modules concernés.
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# ============================================================
|
||||
# routers/evolution.py — Évolution globale (VUE_TABLE_FINAL_CONSO)
|
||||
#
|
||||
# Vue qui agrège TABLE_FINAL en 3 niveaux :
|
||||
# GLOBAL → total toutes monitorings confondues (courbe principale)
|
||||
# MONITO → détail par monitoring (courbes individuelles)
|
||||
# SERVICE → regroupement par service (Contrat / Fournisseur)
|
||||
# ============================================================
|
||||
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from auth import get_current_user
|
||||
from config import get_cursor
|
||||
|
||||
router = APIRouter(dependencies=[Depends(get_current_user)], tags=["Évolution globale"])
|
||||
|
||||
|
||||
@router.get("/global")
|
||||
def get_evolution_global(
|
||||
date_debut : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
date_fin : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
):
|
||||
"""
|
||||
Courbe globale : total des erreurs tous monitorings confondus, par jour.
|
||||
Source : VUE_TABLE_FINAL_CONSO WHERE type_agregat = 'GLOBAL'
|
||||
Format optimisé Recharts → [{date, nb_erreurs}]
|
||||
"""
|
||||
query = (
|
||||
"SELECT date_sauvegarde AS date, nb_erreurs "
|
||||
"FROM VUE_TABLE_FINAL_CONSO "
|
||||
"WHERE type_agregat = 'GLOBAL'"
|
||||
)
|
||||
params = []
|
||||
|
||||
if date_debut:
|
||||
query += " AND date_sauvegarde >= ?"
|
||||
params.append(str(date_debut))
|
||||
if date_fin:
|
||||
query += " AND date_sauvegarde <= ?"
|
||||
params.append(str(date_fin))
|
||||
|
||||
query += " ORDER BY date_sauvegarde ASC"
|
||||
|
||||
with get_cursor() as cursor:
|
||||
cursor.execute(query, *params)
|
||||
data = [{"date": r[0], "nb_erreurs": r[1]} for r in cursor.fetchall()]
|
||||
|
||||
return {
|
||||
"type" : "GLOBAL",
|
||||
"points" : len(data),
|
||||
"series" : data,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/par-monitoring")
|
||||
def get_evolution_par_monitoring(
|
||||
date_debut : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
date_fin : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
id_monito : Optional[int] = Query(None, description="Filtrer sur un seul monitoring"),
|
||||
):
|
||||
"""
|
||||
Évolution par monitoring — toutes les courbes individuelles.
|
||||
Source : VUE_TABLE_FINAL_CONSO WHERE type_agregat = 'MONITO'
|
||||
|
||||
Retourne un format pivot par monitoring :
|
||||
{
|
||||
"monitorings": [{"id": 1, "nom": "...", "serie": [{"date","nb_erreurs"}]}]
|
||||
}
|
||||
Utile pour un graphique multi-lignes Recharts (une ligne par monitoring).
|
||||
"""
|
||||
query = (
|
||||
"SELECT date_sauvegarde AS date, id_monito, nom_monito, nb_erreurs "
|
||||
"FROM VUE_TABLE_FINAL_CONSO "
|
||||
"WHERE type_agregat = 'MONITO'"
|
||||
)
|
||||
params = []
|
||||
|
||||
if date_debut:
|
||||
query += " AND date_sauvegarde >= ?"
|
||||
params.append(str(date_debut))
|
||||
if date_fin:
|
||||
query += " AND date_sauvegarde <= ?"
|
||||
params.append(str(date_fin))
|
||||
if id_monito:
|
||||
query += " AND id_monito = ?"
|
||||
params.append(id_monito)
|
||||
|
||||
query += " ORDER BY id_monito ASC, date_sauvegarde ASC"
|
||||
|
||||
with get_cursor() as cursor:
|
||||
cursor.execute(query, *params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# Pivot : regrouper par monitoring
|
||||
pivot: dict[int, dict] = {}
|
||||
for row in rows:
|
||||
date_point, mid, nom, nb = row[0], row[1], row[2], row[3]
|
||||
if mid not in pivot:
|
||||
pivot[mid] = {"id_monito": mid, "nom_monito": nom, "serie": []}
|
||||
pivot[mid]["serie"].append({"date": date_point, "nb_erreurs": nb})
|
||||
|
||||
return {
|
||||
"type" : "MONITO",
|
||||
"nb_series" : len(pivot),
|
||||
"monitorings" : list(pivot.values()),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/par-service")
|
||||
def get_evolution_par_service(
|
||||
date_debut : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
date_fin : Optional[date] = Query(None, description="YYYY-MM-DD"),
|
||||
):
|
||||
"""
|
||||
Évolution par service (Contrat / Fournisseur) — 1 courbe par service.
|
||||
Source : VUE_TABLE_FINAL_CONSO WHERE type_agregat = 'SERVICE'
|
||||
|
||||
Retourne :
|
||||
{
|
||||
"services": [{"service": "Contrat", "serie": [{"date","nb_erreurs"}]}]
|
||||
}
|
||||
"""
|
||||
query = (
|
||||
"SELECT date_sauvegarde AS date, nom_monito AS service, nb_erreurs "
|
||||
"FROM VUE_TABLE_FINAL_CONSO "
|
||||
"WHERE type_agregat = 'SERVICE'"
|
||||
)
|
||||
params = []
|
||||
|
||||
if date_debut:
|
||||
query += " AND date_sauvegarde >= ?"
|
||||
params.append(str(date_debut))
|
||||
if date_fin:
|
||||
query += " AND date_sauvegarde <= ?"
|
||||
params.append(str(date_fin))
|
||||
|
||||
query += " ORDER BY nom_monito ASC, date_sauvegarde ASC"
|
||||
|
||||
with get_cursor() as cursor:
|
||||
cursor.execute(query, *params)
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# Pivot par service
|
||||
pivot: dict[str, list] = {}
|
||||
for row in rows:
|
||||
date_point, service, nb = row[0], row[1], row[2]
|
||||
if service not in pivot:
|
||||
pivot[service] = []
|
||||
pivot[service].append({"date": date_point, "nb_erreurs": nb})
|
||||
|
||||
return {
|
||||
"type" : "SERVICE",
|
||||
"nb_series": len(pivot),
|
||||
"services" : [{"service": s, "serie": v} for s, v in pivot.items()],
|
||||
}
|
||||
Reference in New Issue
Block a user