# Feuille KPI — Volume, taux de chargement & temps de rotation

## Contexte

Création d'une nouvelle feuille de KPI (route `/kpi`), destinée à accueillir plusieurs
indicateurs au fil du temps. Filtres communs à toute la feuille : date à date +
centrale (défaut : toutes les centrales).

Neuf cartes KPI livrées, chacune avec détail par camion accessible en cliquant dessus :

1. **Volume moyen transporté** (m3) : volume moyen livré par voyage, tous camions de
   transport confondus sur la période/centrale filtrée.
2. **Taux de chargement** (%) : moyenne, sur l'ensemble des camions, du ratio
   (volume moyen transporté par ce camion / sa capacité maximale).
3. **Temps de rotation moyen par toupie** : durée moyenne d'une rotation (chargement →
   trajet aller → attente & déchargement → trajet retour), avec le détail par phase.
4. **Temps d'attente sur chantier** : durée moyenne d'attente avant déchargement
   (arrivée chantier → début vidange), distincte de l'attente+déchargement combinés de
   l'indicateur 3.
5. **Retour béton** : volume total de béton retourné (m3) et taux par rapport au volume
   total livré (%).
6. **Attente moyenne sur centrale** : durée moyenne d'attente d'un camion sur la
   centrale entre deux chargements, le même jour.
7. **Taux de camions sous-chargés** : % de voyages dont le volume livré est inférieur à
   90% de la capacité du camion.
8. **Rotations / jour travaillé** : nombre moyen de rotations par camion, sur ses jours
   effectivement travaillés (pas sur les jours calendaires de la période).
9. **Volume & voyages (sélection)** : volume total (m3) et nombre de voyages sur la
   période/centrale filtrée — totaux bruts (pas des moyennes), place en tête de la
   section "Volume & chargement". Réutilise les données déjà calculées par
   l'indicateur 1 (voir décisions), pas de nouvel endpoint ni de nouvelle requête.

(Un premier indicateur unique "volume moyen / capacité, en m3/camion" avait été livré
initialement, puis remplacé par les indicateurs 1 et 2 à la demande.)

## Fichiers créés / modifiés

- `routes/web.php` — groupe `Route::prefix('/kpi')->name('kpi.')` (middleware
  `auth:sanctum` + `interne`), routes `kpi.index`, `kpi.volume_moyen`,
  `kpi.taux_chargement`, `kpi.temps_rotation`, `kpi.temps_attente_chantier`,
  `kpi.retour_beton`, `kpi.temps_attente_centrale`.
- `app/Http/Controllers/kpi_controller.php` :
  - `statsParVehicule()` — requête commune groupée par camion (capacité, nb voyages,
    volume total/moyen, taux de chargement), réutilisée par les indicateurs 1 et 2.
  - `volume_moyen()` / `taux_chargement()` — JSON indicateur global + détail par camion.
    `volume_moyen()` expose aussi `volume_total` (somme brute), utilisé par la carte
    "Volume & voyages (sélection)" en plus de la moyenne.
  - `baseRotationQuery()` — requête de base par BL de toupie avec la durée (secondes)
    de chaque segment de rotation (dont l'attente pure sur chantier), réutilisée par
    `temps_rotation()` et `temps_attente_chantier()`.
  - `temps_rotation()` — JSON indicateur global (durée totale moyenne, en minutes) +
    détail par camion avec le détail par phase.
  - `temps_attente_chantier()` — JSON indicateur global (attente moyenne avant
    déchargement) + détail par camion.
  - `statsRetourBetonParVehicule()` / `retour_beton()` — volume de retour béton et
    taux (retour / volume livré, en %), globalement et par camion.
  - `baseAttenteCentraleQuery()` / `temps_attente_centrale()` — écarts (fenêtre SQL
    `LAG()`) entre le retour centrale d'un voyage et le chargement du voyage suivant du
    même camion le même jour, moyennés globalement et par camion.
  - `nbRotationsParVehicule()` — nombre total de rotations par camion (helper utilisé
    dans le détail de `temps_attente_centrale()`, à côté du nombre d'écarts mesurés).
  - `statsSousChargeParVehicule()` / `taux_sous_charge()` — % de voyages sous le seuil
    `SEUIL_SOUS_CHARGE` (constante de classe), globalement et par camion.
  - `statsRotationsParJourVehicule()` / `rotations_par_jour()` — nombre de rotations,
    de jours travaillés (distincts) et rotations/jour par camion.
- `resources/views/kpi/index.blade.php` — filtres date/centrale, neuf cartes KPI
  cliquables regroupées en 4 sections, une modale de détail (tableau DataTables) par
  indicateur (la carte "Volume & voyages" réutilise la modale de "Volume moyen
  transporté").
- `public/inc/haut.inc.php` — lien "KPI" dans le sous-menu "Statistique" existant
  (même garde `profil_statistique`).

## Décisions prises

### Communes

- **BL annulés** (`bl_annule = 1`) exclus, comme dans les autres rapports BL existants.
- **Véhicules "sous centrale"** (`t_vehicule.Vehicule_CSC = 1`) exclus, ainsi que leurs
  BL — ce ne sont pas des camions de transport réel (13 véhicules sur 106 en base de
  dev).

### Indicateurs 1 & 2 (volume, taux de chargement)

- **Champ "volume transporté"** : `t_bl.bl_qte_livree`, par cohérence avec le rapport
  existant `app/Reports/vehicule_bl` (rapport par camion le plus proche). Le dashboard
  existant (`dashboard_controller`) utilise de son côté `bl_qte_livraison` pour ses
  propres agrégats — **à confirmer avec le métier** si `bl_qte_livree` est la bonne
  définition pour ces KPI.
- **Capacité camion** : `t_vehicule.Vehicule_Capacite`. Camions sans capacité
  renseignée (NULL ou 0) exclus (impossible de calculer un taux de chargement).
- **Volume moyen transporté** : moyenne pondérée par voyage (= somme des volumes /
  somme des voyages, tous camions confondus). Chaque voyage compte pour un.
- **Volume & voyages (sélection)** : simple affichage des totaux bruts (somme des
  volumes, nombre de voyages) déjà calculés pour l'indicateur "Volume moyen
  transporté" — même endpoint `kpi.volume_moyen`, pas de requête supplémentaire.
  Placée en premier dans la section "Volume & chargement" (donne le contexte de
  volumétrie avant les ratios/moyennes qui suivent).
- **Taux de chargement** : moyenne **non pondérée par camion** (= moyenne simple des
  taux de chargement de chaque camion). Chaque camion compte pour un, quel que soit son
  nombre de voyages — pour éviter qu'un camion à très fort volume de voyages (ex.
  observé en dev : un camion client à 50 000+ voyages de faible volume) n'écrase la
  moyenne du reste de la flotte. **À confirmer avec le métier** : si une pondération
  par nombre de voyages est préférée, il suffit de pondérer la somme dans
  `taux_chargement()` par `nb_voyages`.

### Indicateur 3 (temps de rotation)

- **"Toupie"** : tout véhicule de transport, c'est-à-dire tous les véhicules hors
  "sous centrale" (`Vehicule_CSC`), comme pour les indicateurs 1 et 2 — pas de filtre
  sur le libellé/type de véhicule (un filtre `Vehicule_Libelle LIKE '%MALAXEUR%'` avait
  été utilisé initialement puis retiré à la demande : "toupie" = tous les véhicules non
  sous centrale).
- **Segments mesurés**, à partir des colonnes horaires (type `TIME`, sans date) de
  `t_bl` :
  - Chargement : `bl_heure_fab` → `bl_heure_dep_centrale`
  - Trajet aller : `bl_heure_dep_centrale` → `bl_heure_arr_chantier`
  - Attente + déchargement : `bl_heure_arr_chantier` → `bl_heure_fin_vidange`
  - Trajet retour : `bl_heure_dep_chantier` → `bl_heure_arr_centrale`
  - Durée totale : `bl_heure_fab` → `bl_heure_arr_centrale`
- **Lavage non mesurable** : aucune colonne horodatée n'existe dans le schéma pour le
  lavage (vérifié : seules des tables de paramétrage du type de lavage existent, pas de
  timestamp par BL). Le lavage n'est donc **pas inclus** dans la durée totale — indiqué
  explicitement sur la carte KPI ("hors lavage, non mesuré").
- **Fiabilité des horaires** : seuls les BL dont un horaire vaut la valeur sentinelle
  `'00:00:00'` (non renseigné) sont exclus. Les colonnes `*_type` associées (qui
  indiquent si l'horaire est confirmé — saisie manuelle/GPS — ou "théorique/calculé"
  à 0) avaient été utilisées dans une première version comme filtre supplémentaire
  (`<> 0`), puis retirées à la demande : un horaire théorique mais renseigné est
  désormais pris en compte comme les autres.
- **Anomalies exclues** : rotations dont un segment dépasse 2h ou dont le total dépasse
  4h (erreurs de saisie plutôt que rotations réelles).
- Suit le pattern déjà en place pour `dashboard_controller` (page Blade + endpoints
  JSON Ajax + jQuery), plutôt que le pattern KoolReport de `statistique_laravel`, car
  la page est pensée comme un tableau de bord de cartes KPI amené à grandir plutôt
  qu'un rapport tabulaire classique.

### Indicateur 4 (temps d'attente sur chantier)

- **Segment mesuré** : `bl_heure_arr_chantier` → `bl_heure_dep_vidange` (attente pure
  avant le début du déchargement), distinct de "attente + déchargement" de l'indicateur
  3 qui va jusqu'à `bl_heure_fin_vidange` (fin du déchargement).
- Réutilise `baseRotationQuery()` : mêmes filtres de fiabilité (horaires renseignés,
  y compris théoriques) et mêmes exclusions d'anomalies que l'indicateur 3 — un ajout a
  été fait pour exiger que `bl_heure_dep_vidange` soit aussi renseigné (`<> '00:00:00'`),
  ce qui ne change quasiment rien au périmètre des indicateurs 3 (1 seule ligne sur
  96 505 en base de dev avait ce champ manquant alors que les autres étaient présents).

### Indicateur 5 (retour béton)

- **Champ** : `t_bl.bl_qte_retour_beton` (volume de béton retourné par le camion, m3),
  rapporté à `t_bl.bl_qte_livree` (même champ "volume transporté" que les indicateurs
  1 et 2) pour le taux.
- Requête indépendante de `statsParVehicule()` (pas de filtre sur la capacité, non
  pertinent ici) mais mêmes exclusions communes (BL annulés, véhicules sous centrale).
- Le détail par camion **n'affiche que les camions ayant eu au moins un retour**
  (`volume_retour > 0`) — la grande majorité des camions n'en ont aucun sur une période
  donnée (502 BL avec retour sur 33 345 en base de dev), les lister tous aurait noyé
  l'information utile dans une table à 90% de zéros.
- Taux global = volume total de retour / volume total livré (pas une moyenne des taux
  par camion), cohérent avec un indicateur "combien de béton perdu sur combien
  produit" plutôt qu'une moyenne de taux individuels.

### Indicateur 6 (attente moyenne sur centrale)

- **Définition** : pour un camion donné, un jour donné, on trie ses BL par heure de
  chargement (`bl_heure_fab`) du premier au dernier de la journée ; pour chaque BL
  (sauf le premier de la journée, qui n'a pas de "précédent" à comparer), l'attente sur
  centrale = `bl_heure_fab` (chargement du BL courant) − `bl_heure_arr_centrale` (retour
  centrale du BL précédent du même camion, même jour). C'est le temps passé par le
  camion à la centrale entre la fin d'une rotation et le début de la suivante.
- Implémentation via fenêtre SQL `LAG(...) OVER (PARTITION BY vehicule, date ORDER BY
  heure_chargement)` (MySQL 8.3, confirmé compatible). Mêmes exclusions communes que
  les autres indicateurs (BL annulés, véhicules sous centrale, horaires non renseignés).
- **Anomalies exclues** : écarts hors de l'intervalle 0-4h. **Point de vigilance data** :
  en base de dev, certains couples de BL du même camion le même jour ont des horaires
  incohérents (un BL "suivant" dont le chargement démarre avant le retour centrale du BL
  "précédent" trié par heure de chargement — écart négatif), probablement dû à des
  horaires saisis a posteriori ou de façon désynchronisée plutôt qu'à un vrai
  chevauchement de rotations. Ces écarts négatifs sont mécaniquement exclus par le
  filtre 0-4h, donc ne polluent pas la moyenne, mais réduisent le nombre d'écarts
  effectivement mesurés par rapport au nombre théorique de paires consécutives.

### Indicateur 7 (taux de camions sous-chargés)

- **Granularité et seuil confirmés avec l'utilisateur** (AskUserQuestion) : évaluation
  **par voyage** (chaque BL individuellement, pas une moyenne par camion), seuil à
  **90%** de la capacité — un voyage est "sous-chargé" si
  `bl_qte_livree / Vehicule_Capacite < 0.9`. Seuil exposé comme constante de classe
  `kpi_controller::SEUIL_SOUS_CHARGE` (facile à ajuster si le métier change d'avis).
- Taux global = nombre total de voyages sous-chargés / nombre total de voyages (pas une
  moyenne des taux par camion) — cohérent avec le choix "par voyage" plutôt que "par
  camion".
- Mêmes exclusions communes que les indicateurs 1 et 2 (BL annulés, véhicules sous
  centrale, camions sans capacité renseignée).

### Indicateur 8 (rotations / jour travaillé)

- **Définition** : pour chaque camion, "jour travaillé" = jour distinct
  (`bl_date_livraison`) où il a eu au moins une rotation valide sur la période/centrale
  filtrée — pas un jour calendaire de la période entière (un camion inactif pendant une
  semaine n'est pas pénalisé). Rotations/jour = nb total de rotations du camion / nb de
  jours travaillés du camion.
- Moyenne globale **non pondérée par camion** (même convention que "Taux de
  chargement" et "Taux de camions sous-chargés" côté camion) : chaque camion compte
  pour un, quelle que soit son activité — cohérent avec la formulation "en moyenne"
  (moyenne des ratios par camion, pas ratio des sommes). **À confirmer avec le métier**
  si une pondération par nb de jours travaillés (= somme rotations / somme jours,
  favorisant les camions les plus actifs) serait préférée.
- Mêmes exclusions communes (BL annulés, véhicules sous centrale) ; pas de filtre sur
  la capacité (non pertinent ici).

## Vérifications effectuées

- Lint PHP (`php -l`) sur le contrôleur et `routes/web.php`.
- Exécution directe des trois actions du contrôleur (via PHP 8.1 CLI de WAMP, en
  dehors d'artisan/tinker à cause de la CLI par défaut en 7.4) contre la base de dev :
  - Indicateurs 1 & 2 : 77 camions de transport hors sous-centrale, volume moyen
    ≈ 4,31 m3/voyage, taux de chargement moyen ≈ 85,1 %.
  - Indicateur 3 : 47 véhicules, 17 467 rotations exploitables sur 2025-2026 (après
    suppression du filtre sur les horaires "théoriques"), durée totale moyenne
    ≈ 107,9 min (détail par phase cohérent : ~15,7 min chargement, ~21,8 min trajet
    aller, ~46,3 min attente/déchargement, ~23,2 min trajet retour).
  - Indicateur 4 : 47 véhicules, 17 424 rotations exploitables, attente moyenne sur
    chantier ≈ 11,5 min avant déchargement.
  - Indicateur 5 : 1 138,82 m3 de retour béton sur 154 578,23 m3 livrés (2025-2026),
    soit un taux ≈ 0,74 %, sur 40 camions concernés (52 camions au total sur la
    période).
  - Indicateur 6 : 45 véhicules, 11 226 écarts mesurés, attente moyenne sur centrale
    ≈ 48,6 min. Vérification manuelle d'un camion/jour concordante avec la logique de
    fenêtre SQL (et confirmant la présence d'écarts négatifs correctement exclus par le
    filtre d'anomalies).
  - Indicateur 7 : 33 345 voyages (2025-2026), 18 427 sous-chargés (< 90% de capacité),
    soit un taux ≈ 55,26 %. Cohérent avec l'indicateur 2 : le camion client à très faible
    taux de chargement moyen (≈ 14,8 %) ressort à 98,98 % de voyages sous-chargés.
  - Indicateur 8 : 52 véhicules, 33 345 rotations, moyenne ≈ 3,24 rotations/jour
    travaillé par camion. Le camion client (fort volume de petites livraisons)
    ressort à 39 rotations/jour, ce qui tire la moyenne non pondérée vers le haut par
    rapport aux malaxeurs classiques (~1 à 2 rotations/jour) — cohérent avec les
    observations des indicateurs précédents sur ce même véhicule.
- Non testé dans le navigateur en tant qu'utilisateur connecté (page derrière
  `auth:sanctum` + `interne` — nécessite une session utilisateur réelle).

## Organisation visuelle de la page

Les 9 cartes sont regroupées en 4 sections (titres au-dessus de chaque rangée),
toutes exposées via la même page/mêmes routes/mêmes endpoints — pur regroupement
visuel, aucun changement côté contrôleur :

- **Volume & chargement** : Volume & voyages (sélection), Volume moyen transporté,
  Taux de chargement, Taux de camions sous-chargés.
- **Temps de cycle** : Temps de rotation moyen, Temps d'attente sur chantier, Attente
  moyenne sur centrale.
- **Qualité** : Retour béton.
- **Productivité** : Rotations / jour travaillé.

## Suite possible

- Ajouter d'autres indicateurs sur la même feuille (même structure de filtres).
- Ajouter export Excel/PDF du détail si besoin.
