diff --git a/backend/routes/clustering.py b/backend/routes/clustering.py index 8918faa..1836900 100644 --- a/backend/routes/clustering.py +++ b/backend/routes/clustering.py @@ -152,13 +152,17 @@ _SQL_COLS = [ def _run_clustering_job(k: int, hours: int, sensitivity: float = 1.0) -> None: """Exécuté dans le thread pool. Met à jour _CACHE. - - sensitivity : multiplicateur de k [0.5 – 3.0]. + + sensitivity : multiplicateur de k [0.5 – 5.0]. + 0.5 = vue très agrégée (k/2 clusters) 1.0 = comportement par défaut 2.0 = deux fois plus de clusters → groupes plus homogènes - 0.5 = moitié → vue très agrégée + 5.0 = granularité maximale (classification la plus fine) + + k_actual est plafonné à 300 pour éviter des temps de calcul excessifs. + n_init est réduit à 1 quand k_actual > 60 pour rester rapide. """ - k_actual = max(4, min(50, round(k * sensitivity))) + k_actual = max(4, min(300, round(k * sensitivity))) t0 = time.time() with _LOCK: _CACHE["status"] = "computing" @@ -189,7 +193,9 @@ def _run_clustering_job(k: int, hours: int, sensitivity: float = 1.0) -> None: X_std, feat_mean, feat_std = standardize(X64) # ── 4. K-means++ sur l'espace standardisé ──────────────────────── - km = kmeans_pp(X_std, k=k_actual, max_iter=80, n_init=3, seed=42) + # n_init réduit à 1 pour k élevé (> 60) afin de limiter le temps de calcul + n_init = 1 if k_actual > 60 else 3 + km = kmeans_pp(X_std, k=k_actual, max_iter=80, n_init=n_init, seed=42) log.info(f"[clustering] K-means: {km.n_iter} iters, inertia={km.inertia:.2f}") # Centroïdes dans l'espace original [0,1] pour affichage radar @@ -411,9 +417,9 @@ async def get_status(): @router.get("/clusters") async def get_clusters( - k: int = Query(14, ge=4, le=30, description="Nombre de clusters de base"), + k: int = Query(20, ge=4, le=100, description="Nombre de clusters de base"), hours: int = Query(24, ge=1, le=168, description="Fenêtre temporelle (heures)"), - sensitivity: float = Query(1.0, ge=0.5, le=3.0, description="Sensibilité : multiplicateur de k"), + sensitivity: float = Query(1.0, ge=0.5, le=5.0, description="Sensibilité : multiplicateur de k (5.0 = granularité maximale)"), force: bool = Query(False, description="Forcer le recalcul"), ): """ diff --git a/frontend/src/components/ClusteringView.tsx b/frontend/src/components/ClusteringView.tsx index b1bd7f4..ccadef6 100644 --- a/frontend/src/components/ClusteringView.tsx +++ b/frontend/src/components/ClusteringView.tsx @@ -94,7 +94,7 @@ function hexToRgba(hex: string, alpha = 255): [number, number, number, number] { // ─── Composant principal ────────────────────────────────────────────────────── export default function ClusteringView() { - const [k, setK] = useState(14); + const [k, setK] = useState(20); const [hours, setHours] = useState(24); const [sensitivity, setSensitivity] = useState(1.0); const [data, setData] = useState(null); @@ -344,15 +344,15 @@ export default function ClusteringView() {
Sensibilité - {sensitivity === 0.5 ? 'Grossière' : sensitivity <= 1.0 ? 'Normale' : sensitivity <= 1.5 ? 'Fine' : sensitivity <= 2.0 ? 'Très fine' : 'Maximum'} - {' '}({Math.round(k * sensitivity)} clusters) + {sensitivity <= 0.5 ? 'Grossière' : sensitivity <= 1.0 ? 'Normale' : sensitivity <= 2.0 ? 'Fine' : sensitivity <= 3.5 ? 'Très fine' : sensitivity <= 4.5 ? 'Maximale' : 'Extrême'} + {' '}({Math.round(k * sensitivity)} clusters effectifs)
- setSensitivity(+e.target.value)} className="w-full accent-accent-primary" />
- GrossièreMaximum + GrossièreFineExtrême
@@ -362,10 +362,10 @@ export default function ClusteringView() {