Skip to content

Latest commit

 

History

History
708 lines (546 loc) · 25.3 KB

File metadata and controls

708 lines (546 loc) · 25.3 KB

Documentation technique - Reflets BSPP

Jumeau Numérique de la Brigade de Sapeurs-Pompiers de Paris


Table des matières

  1. Vue d'ensemble
  2. Architecture du projet
  3. Installation et lancement
  4. Structure des fichiers
  5. Modele de donnees (classes.py)
  6. Chargement des donnees (data.py)
  7. Boucle de simulation (main.py)
  8. Attribution des vehicules (filters.py)
  9. Prediction des temps de trajet (fonction_calcul_trajet.py)
  10. Module d'optimisation (optimisation/)
  11. Interface web Streamlit (interface.py + pages/)
  12. Pipeline de donnees (datas/)
  13. Scripts d'analyse
  14. Pistes d'amelioration

1. Vue d'ensemble

Reflets est un jumeau numerique de la BSPP developpe dans le cadre d'un PSC (Projet Scientifique Collectif) a l'Ecole Polytechnique. Il simule le fonctionnement operationnel de la brigade a partir des donnees d'interventions reelles de 2023 et permet d'optimiser l'allocation des vehicules entre les 37 centres de secours (CS) parisiens.

Objectifs

  • Simuler le deroulement chronologique des interventions avec attribution de vehicules
  • Optimiser la repartition des engins pour minimiser les temps de reponse
  • Analyser les taux d'utilisation, la couverture operationnelle et la resilience du dispositif
  • Visualiser les resultats via une interface web interactive

Chiffres cles

Donnee Valeur
Interventions (2023) ~500 000
Centres de secours 72
Vehicules 268 (96 POMPE, 167 VSAV, 5 autres)
Types de vehicules POMPE, VSAV, PSE

2. Architecture du projet

main.py              Boucle de simulation
    |
    v
filters.py           Logique d'attribution (plus proche voisin glouton)
    |
    v
classes.py            Objets metier : Engin, Intervention, Secteur
    |
    v
fonction_calcul_trajet.py   Prediction temps de trajet (LightGBM / KNN)
    |
    v
allocation_optimizer.py     Evaluation d'une allocation
    |
    v
optimisation/               Algorithmes d'optimisation avancee

Flux de donnees

datas/ (CSV, Parquet, GeoJSON)
    |
    v
data.py (chargement, nettoyage, mise en cache)
    |
    v
main.py (simulation)  --->  statistiques.py (metriques)
    |                              |
    v                              v
interface.py (Streamlit)    optimisation/ (recuit simule, genetique...)

3. Installation et lancement

Prerequis

  • Python 3.13
  • Conda (recommande)
  • libomp (necessaire pour LightGBM)

Installation

git clone https://github.com/baptisteduperray/Reflets.git
cd Reflets-bspp/
conda create -n reflets_env python=3.13
conda activate reflets_env
pip install -r requirements.txt

Lancement

Interface Streamlit :

streamlit run interface.py

Notebooks Marimo :

marimo edit

Simulation en ligne de commande :

from main import main
from data import get_interventions, get_engins, get_secteurs, get_indisponibilites

result = main(interventions, secteurs, engins, indisponibilites)

Dependances principales

Librairie Usage
pandas, numpy Manipulation de donnees
streamlit Interface web
lightgbm Modele de temps de trajet
scikit-learn KNN (fallback temps de trajet)
geopandas Donnees geographiques
folium Cartes interactives
plotly, matplotlib, seaborn Visualisation
marimo Notebooks interactifs
pyarrow Support Parquet

4. Structure des fichiers

Reflets-bspp/
|-- main.py                      # Boucle de simulation principale
|-- data.py                      # Chargement et cache des donnees
|-- classes.py                   # Modeles metier (Engin, Intervention, Secteur)
|-- filters.py                   # Logique d'attribution des vehicules
|-- fonction_calcul_trajet.py    # Prediction des temps de trajet (ML)
|-- allocation_optimizer.py      # Evaluation d'une allocation
|-- statistiques.py              # Metriques et comparaisons
|-- interface.py                 # Interface Streamlit principale
|-- web.py                       # Lancement web
|-- build_df_total_cache.py      # Generation du cache de temps de trajet
|
|-- optimisation/
|   |-- optimisation_avancee.py  # Recuit simule, genetique, multi-start
|   |-- standalone_optimization.py  # API d'evaluation autonome
|   |-- interface_helper.py      # Integration Streamlit
|
|-- pages/
|   |-- 2_Optimisation.py        # Page optimisation
|   |-- 3_Secteurs_optimaux.py   # Page analyse des secteurs
|
|-- ui/
|   |-- common.py                # Utilitaires Streamlit (session state, etc.)
|   |-- utilisation.py           # Calculs de taux d'utilisation
|   |-- graph_helpers.py         # Fonctions de visualisation
|
|-- src/
|   |-- app.py                   # Application Marimo complete
|   |-- carte_interactive.py     # Carte de positionnement vehicules
|   |-- reflets_marimo.py        # Simulation Marimo
|
|-- scripts/
|   |-- generate_inter_23_valhalla_input.py  # Preparation requetes routage
|   |-- run_valhalla_on_inter_csv.py         # Routage Valhalla
|   |-- run_graph_times_on_inter_csv.py      # Routage par graphe
|
|-- datas/
|   |-- interventions/           # Donnees d'interventions 2023
|   |-- geo/                     # Donnees geographiques (GeoJSON)
|   |-- utils/                   # Referentiels (engins, CS, indisponibilites)
|   |-- reseau/                  # Donnees du reseau routier
|
|-- docs/                        # Documentation
|-- images/                      # Logos et captures
|-- notebooks/                   # Configuration notebooks

5. Modele de donnees (classes.py)

Intervention

Represente un appel d'urgence.

Attribut Type Description
id str Identifiant unique
x, y float Coordonnees du lieu d'intervention
x_final, y_final float Coordonnees de destination (hopital, etc.)
date datetime Date et heure de l'appel
date_int int Timestamp Unix (pour recherche binaire O(log n))
duree_sur_place float Duree passee sur les lieux (secondes)
temps_trajet float Temps de trajet reel (secondes)
cstc str Centre de secours territorialement competent
cs_vhl str Centre dont le vehicule a ete envoye (donnees reelles)
type_intervention str POMPE ou VSAV
proc str Procedure : R (Rouge, urgent) ou V (Vert)

Engin (classe abstraite)

Represente un vehicule de secours. Sous-classes : POMPE_Engin, VSAV_Engin, PSE_Engin.

Attribut Type Description
id str Identifiant du vehicule
cs str Centre de secours d'affectation
x_cs, y_cs float Coordonnees du centre
interventions list Historique des interventions attribuees
indisponibilites list Periodes d'indisponibilite

Methodes principales :

Methode Description
est_disponible(date) Verifie si le vehicule est libre a une date donnee
temps_trajet(intervention) Calcule le temps de trajet vers l'intervention
attribuer_a(intervention) Attribue l'intervention au vehicule, met a jour l'etat
distance_euclidienne_au_carre(x, y) Distance au carre (evite le sqrt)
x(date), y(date) Position du vehicule a une date donnee

Modularite (PSE / VSAV) : Les VSAV et PSE peuvent etre apparies (modularite). Un couple PSE-VSAV partage un vehicule avec des contraintes horaires :

  • Nuit : 23h - 8h
  • Week-end : 0h - 7h

Quand ils sont apparies, les deux vehicules ne peuvent operer qu'ensemble ou l'un reste en caserne.

Secteur

Represente un centre de secours et sa zone de couverture.

Attribut Type Description
cs str Identifiant du centre
x, y float Coordonnees
engins dict Vehicules par type (POMPE, VSAV, PSE, etc.)

Methode cle : engins_for_type_intervention(type) - Renvoie les vehicules disponibles pour un type d'appel donne.

IndisponibiliteEngin

Gere les periodes d'indisponibilite des vehicules (maintenance, etc.) avec recherche binaire sur les timestamps tries pour des verifications en O(log n). Les intervalles chevauchants sont fusionnes automatiquement.


6. Chargement des donnees (data.py)

Ce module centralise tout le chargement et la mise en cache des donnees.

Fonctions principales

Fonction Description
get_data() Charge le DataFrame brut des interventions (Parquet)
get_interventions() Convertit les lignes en objets Intervention
get_engins() Charge l'inventaire des vehicules et cree les objets Engin
get_secteurs() Construit les objets Secteur avec leurs vehicules
get_indisponibilites() Charge les periodes d'indisponibilite

Sources de donnees

Fichier Contenu
datas/interventions/inter_23_modifie.parquet Interventions 2023 (~200k lignes)
datas/interventions/cma_inter.csv Mapping intervention-CMA
datas/utils/lso.csv Coordonnees des centres de secours
datas/utils/liste_engins_fix.csv Inventaire vehicules (id, cs, type, modularite)
datas/utils/hour_cs_vsav.parquet Indisponibilites par heure
datas/geo/secteurs_cs.geojson Polygones geographiques des secteurs

Nettoyage applique

  • Suppression des interventions STEC
  • Suppression des lignes avec valeurs manquantes
  • Tri chronologique
  • Fusion avec cma_inter.csv sur IdMMASelection

7. Boucle de simulation (main.py)

Algorithme

Pour chaque intervention (ordre chronologique) :
    1. Identifier le CSTC (centre competent)
    2. Appeler filters.attribuer() pour trouver le meilleur vehicule
    3. Si vehicule trouve :
       - engin.attribuer_a(intervention)
       - Mise a jour de l'etat du vehicule (position, disponibilite)
    4. Enregistrer les metriques (temps de trajet, concordance CS, etc.)

Resultat : SimulationResult

La simulation produit un objet SimulationResult (dataclass) contenant :

Champ Description
interventions_simulees Liste des interventions traitees
processing_time Temps d'execution de la simulation
taux_concordance_cs % ou le CS simule = CS reel
utilisation_par_fenetre Taux d'utilisation par fenetre 8h-8h
nb_vehicules_utilises Nombre de vehicules mobilises

Gestion de l'etat des vehicules

Chaque vehicule maintient son etat temporel :

  • _en_caserne : date de retour en caserne
  • _plus_en_inter : date de fin d'intervention sur les lieux
  • _intervention : intervention en cours
  • temps_de_sorties_de_la_caserne : horodatages de depart
  • temps_de_retour_a_la_caserne : horodatages de retour

8. Attribution des vehicules (filters.py)

Algorithme : glouton avec fallback geographique

Etape 1 - Cas nominal (CSTC connu) :

  1. Chercher dans le centre competent (CSTC)
  2. Filtrer les vehicules disponibles du type requis
  3. Trier par distance euclidienne au carre
  4. Retourner le plus proche

Etape 2 - Fallback (CSTC indisponible) :

  1. Parcourir les centres par ordre de proximite (matrice pre-calculee)
  2. Traiter par lots de 5 centres (_BATCH_SIZE = 5)
  3. Retourner le premier vehicule disponible du lot le plus proche

Optimisations

  • Matrice de proximite : _SECTEURS_PROCHES[cs_id] pre-calculee au demarrage
  • Traitement par lots : reduit le nombre de tris (un tri par lot vs. tous les centres)
  • Distance au carre : evite le calcul de racine carree

Correspondance type d'intervention / type de vehicule

Type d'intervention Vehicules eligibles
POMPE POMPE, POMPE+PSE
VSAV VSAV, VSAV+PSE

9. Prediction des temps de trajet (fonction_calcul_trajet.py)

Architecture a 2 niveaux (matrice + KNN)

Requete (x_inter, y_inter, cs_vhl)
    |
    v
[Niveau 1] Matrice Valhalla pre-calculee
    |  Lookup par (cs_name, x_inter_key, y_inter_key)
    |  Hit ~99% (interventions connues)
    |  Miss
    v
[Niveau 2] KNN par CS (3 plus proches voisins)
    |  Cherche les 3 interventions connues les plus proches geographiquement
    |  Retourne la moyenne de leurs temps pour ce CS

Niveau 1 : Matrice Valhalla

  • Source : datas/matrices/travel_matrix.npy (aller) et return_travel_matrix.npy (retour)
  • Cle : (cs_name, float_key(x_inter), float_key(y_inter)) avec precision = 5 decimales
  • Taux de hit : ~99% (toutes les interventions de 2023 sont dans la matrice)
  • Temps corrige empiriquement : t = max(alpha(heure) * t_valhalla, 120s)

Niveau 2 : KNN fallback

  • Un KDTree par CS, construit sur un echantillon de 10% des interventions de la matrice
  • Espace de recherche : coordonnees (x_inter, y_inter) uniquement
  • 3 plus proches voisins, moyenne des temps
  • Utilise pour les interventions dont les coordonnees ne sont pas dans la matrice (ex: interventions simulees, nouvelles localisations)

9.2 Pre-calcul des matrices de trajet

Les matrices de temps de trajet sont utilisees a la fois par le simulateur sequentiel (fonction_calcul_trajet.py) et par le simulateur GPU (Reflets_gpu/).

Fichiers produits

Reflets_gpu/cache/
  travel_matrix.npy         [N_cs, N_inter]  float32  (~130 MB)
  return_travel_matrix.npy  [N_cs, N_inter]  float32  (~130 MB)
  • travel_matrix[c, i] = temps en secondes pour aller du CS c vers le lieu de l'intervention i
  • return_travel_matrix[c, i] = temps en secondes pour revenir du lieu final de l'intervention i vers le CS c

Matrice aller : Valhalla time-aware

On utilise le serveur de routage Valhalla (deploye localement via Docker avec les donnees OSM Ile-de-France) pour obtenir des temps de trajet dependants de l'heure grace au parametre date_time.

Bucketing temporel (48 buckets)

Les ~478k interventions sont regroupees en 48 buckets : 24 tranches horaires x 2 types de jour (semaine / weekend). Toutes les interventions d'un meme bucket partagent une date_time de reference dans la requete Valhalla :

Type de jour Date de reference
Semaine 2023-01-04 (mercredi)
Weekend 2023-01-07 (samedi)

Cela permet de reduire le nombre de requetes Valhalla distinctes : au lieu de 478k requetes individuelles, on envoie des requetes matricielles groupees par bucket.

Requete Valhalla

POST /sources_to_targets
{
  "sources": [{"lat": 48.86, "lon": 2.35}, ...],
  "targets": [{"lat": 48.85, "lon": 2.34}, ...],
  "costing": "auto",
  "date_time": {"type": 1, "value": "2023-01-04T18:00"}
}
  • type: 1 = depart a l'heure indiquee (Valhalla applique les profils de vitesse OSM pour cette heure)
  • Les sources sont les coordonnees des CS, les targets sont les lieux d'intervention du bucket
  • Batches de ~20 sources x ~100 targets par requete (limite Valhalla ~120 locations par requete)
  • 8 threads paralleles pour le debit

KNN fallback : les paires non remplies par Valhalla (erreurs reseau, coordonnees hors zone) sont completees par KNN (meme logique que fonction_calcul_trajet.py).

Chiffres : ~19 000 requetes Valhalla, debit ~40 req/min, duree totale ~5h, 99% de paires remplies, 1% KNN fallback.

Matrice retour : strategie hybride

Le temps de retour (lieu final de l'intervention -> CS) est moins critique pour le score d'optimisation que le temps d'aller. On utilise une strategie en 3 niveaux :

Analyse des lieux finaux (x_final, y_final)

Categorie Part des inter Methode
Top 50 hopitaux ~65% Valhalla (hopital -> CS x 48 buckets)
x_final proche de x_inter (< 11m) ~34% Reutilisation de travel_matrix[c, i]
Autres ~1% KNN fallback
  • Hopitaux via Valhalla : les 50 destinations les plus frequentes (grands hopitaux parisiens) concentrent 65% des interventions. On pre-calcule hopital -> CS pour chaque bucket horaire, soit ~2 288 requetes Valhalla, 21.7M paires mises a jour (~25 min).
  • x_final = x_inter : dans 34% des cas le vehicule repart du lieu meme de l'intervention (pas de transport patient). Le temps de retour est approxime par le temps d'aller travel_matrix[c, i] (meme distance, heure proche).
  • KNN fallback : pour le ~1% restant, interpolation par les 3 plus proches voisins dans l'espace (position).

Correction empirique : modele affine

Les temps Valhalla bruts ne tiennent pas compte du trafic reel (pas de donnees de trafic dans l'instance OSM). Une analyse des temps de trajet reels BSPP (trajet dans inter_23_modifie) vs les temps theoriques Valhalla montre que :

  1. Il existe un temps fixe incompressible de ~189s (3.1 min) correspondant a la sortie de caserne (alerte, equipement, demarrage), independant de la distance.
  2. Le temps de trajet routier est proportionnel a Valhalla avec un facteur alpha(heure) qui capte l'effet du trafic.

Modele : t_reel = max(alpha(heure) * t_valhalla, 120s)

  • alpha(heure) >= 1 — facteur multiplicatif pur sur le temps Valhalla. alpha > 1 car les temps Valhalla sont des temps de trajet routier ideaux sans prise en compte du trafic reel ni des contraintes operationnelles (sortie caserne, feux rouges, etc.).
  • Plancher a 120s (2 min) — temps minimum pour tout trajet (sortie caserne + demarrage).

Valeurs de alpha par heure (fittees sur ~435k paires, trimmed 5-95%, RMSE ~170-205s) :

Heure Weekday Weekend Heure Weekday Weekend
0h 1.517 1.535 12h 1.551 1.464
1h 1.566 1.566 13h 1.573 1.477
2h 1.557 1.562 14h 1.563 1.487
3h 1.572 1.555 15h 1.593 1.478
4h 1.572 1.549 16h 1.614 1.523
5h 1.595 1.564 17h 1.653 1.533
6h 1.568 1.513 18h 1.680 1.566
7h 1.531 1.468 19h 1.584 1.553
8h 1.602 1.394 20h 1.500 1.469
9h 1.647 1.447 21h 1.448 1.447
10h 1.619 1.465 22h 1.457 1.448
11h 1.612 1.470 23h 1.475 1.463

Tendances :

  • Weekday : pics a 9h (1.65) et 17-18h (1.65-1.68), creux en soiree 21-22h (1.45)
  • Weekend : plus plat (1.39-1.57), pas de pic de pointe marque
  • La variation de alpha capte a la fois le trafic (heures de pointe) et les effets operationnels (temps de sortie caserne absorbe dans le facteur)

Les facteurs sont appliques directement aux matrices .npy avant deploiement sur le serveur.

Pipeline de pre-calcul

1. Deployer Valhalla (Docker + PBF Ile-de-France)
2. Telecharger les donnees depuis le serveur
3. Lancer run_precompute.py --valhalla_url http://localhost:8002
4. Lancer precompute_hopitaux.py (retour hopitaux via Valhalla)
5. Appliquer les facteurs de correction (apply_factors.py)
6. Copier les .npy sur le serveur GPU

Scripts : Reflets_gpu/run_precompute.py, Reflets_gpu/precompute.py, Reflets_gpu/data_loader.py


10. Module d'optimisation (optimisation/)

Algorithmes disponibles

Recuit simule (Simulated Annealing)

SimulatedAnnealingOptimizer.optimize(
    temp_init=1.0,       # Temperature initiale
    cooling_rate=0.995,  # Taux de refroidissement
    max_iter=1000        # Iterations max
)

Explore l'espace des allocations en acceptant des solutions degradees avec une probabilite decroissante.

Algorithme genetique

GeneticOptimizer.optimize(
    population_size=50,
    mutation_rate=0.1,
    generations=100
)

Fait evoluer une population d'allocations par selection, croisement et mutation.

Multi-start Local Search

Lance plusieurs recherches locales depuis des points de depart aleatoires et conserve la meilleure solution.

Metriques d'evaluation

Metrique Description
temps_moyen Temps de reponse moyen (metrique principale)
temps_p95 95e percentile du temps de reponse
taux_couverture % d'interventions avec reponse < 8 min (480s)
manque_local_ratio % ou le vehicule vient d'un autre CS que le CSTC
std_q95_utilisation Ecart-type du Q95 d'utilisation entre CS
couverture_0_total_heures Heures totales avec 0 vehicule disponible par CS

Profils de parametrage

Profil Description Vitesse
ultra_rapide Tests rapides Tres rapide
rapide Exploration initiale Rapide
equilibre Compromis qualite/temps Moyen
qualite Resultats de reference Lent

Evaluation (allocation_optimizer.py)

  • simuler_window_df_engins() : lance la simulation sur une fenetre d'interventions avec une allocation donnee
  • eval_window() : calcule les metriques a partir des resultats de simulation

11. Interface web Streamlit (interface.py + pages/)

Page principale (interface.py)

  • En-tete : titre "Jumeau numerique de la BSPP" + logo
  • Barre laterale :
    • Selecteur de plage de dates (debut/fin de simulation)
    • Gestion des vehicules : boutons +/- par type, compteurs
    • Bouton de reinitialisation de l'allocation
  • Corps principal (2 colonnes) :
    • Gauche : upload CSV d'interventions, tableau recapitulatif
    • Droite : tableau detaille des vehicules (id, cs, type)

Pages supplementaires

Page Fonction
2_Optimisation.py Lancement d'optimisation avec choix d'algorithme et profil
3_Secteurs_optimaux.py Analyse des affectations optimales par secteur

Gestion de l'etat (session_state)

Cle Contenu
engins Allocation courante (liste de dicts)
engins_initial Allocation initiale
allocation_dirty Flag : l'utilisateur a modifie l'allocation
interventions_data Interventions pre-chargees
progress Progression de la simulation

Modules utilitaires (ui/)

  • common.py : initialisation du session_state, chargement geo, configuration GDAL
  • utilisation.py : calculs de taux d'utilisation par fenetre 8h-8h (shift BSPP)
  • graph_helpers.py : fonctions de visualisation (Plotly, Folium)

12. Pipeline de donnees (datas/)

Structure

datas/
|-- interventions/
|   |-- inter_23_modifie.parquet       # Interventions 2023 (~500MB)
|   |-- cma_inter.csv                  # Mapping intervention-CMA
|   +-- graph_times*.csv               # Temps de trajet par graphe
|
|-- geo/
|   +-- secteurs_cs.geojson            # Polygones des secteurs + metadata
|
|-- utils/
|   |-- lso.csv                        # Coordonnees des CS (cs, x, y)
|   |-- liste_engins_fix.csv           # Inventaire vehicules
|   |-- modifications_engins.csv       # Reaffectations manuelles
|   +-- hour_cs_vsav.parquet           # Indisponibilites par heure
|
|-- reseau/
|   |-- experiment_osm.csv             # Donnees graphe OSM
|   +-- nodes.csv                      # Noeuds du reseau
|
+-- sql/
    +-- *.sql                          # Schemas de base de donnees

Fichiers cles

Fichier Colonnes principales
inter_23_modifie.parquet id, date, x, y, x_final, y_final, cstc, cs, fem_mma (POMPE/VSAV), proc (R/V)
lso.csv cs, x (longitude), y (latitude)
liste_engins_fix.csv id, cs, Type_VHL, modularite
datas/matrices/travel_matrix.npy Matrice aller [71 CS, 478k inter] int16, temps en secondes
datas/matrices/return_travel_matrix.npy Matrice retour [71 CS, 478k inter] int16, temps en secondes

13. Scripts d'analyse

Script Description
statistiques.py Metriques de simulation : temps moyen, concordance CS, taux d'utilisation
analyser_resultats.py Analyse post-simulation
analyse_concordance_cs.py Comparaison CS simule vs. CS reel
analyse_inter_cs_reel.py Analyse des interventions par CS
test_simulation.py Tests unitaires de la simulation
test_optimisation.py Tests unitaires de l'optimisation
test_approximation_cout.py Validation des modeles de temps de trajet
nb_configurations.py Calcul du nombre de configurations possibles

14. Pistes d'amelioration

Haute priorite

Fichier Probleme Solution Impact
optimisation_avancee.py Double simulation par voisin intelligent Passer current_metrics pour eviter la re-evaluation Fort
allocation_optimizer.py deepcopy(secteurs) a chaque evaluation Structure legere ou pool d'objets Fort

Moyenne priorite

Fichier Probleme Solution Impact
fonction_calcul_trajet.py Cache misses sur KNN Ecrire les predictions en cache secondaire Fort si taux de miss eleve
filters.py Tri de tous les vehicules par attribution Utiliser min() au lieu de sort Modere
allocation_optimizer.py Re-creation de tous les Engin par fenetre Utiliser dataclasses/slots ou pool Modere

Travaux futurs (roadmap)

  • COVA differenciee en fonction des periodes de l'annee
  • Regles d'engagement
  • Gestion des statuts
  • Indicateurs COUVOPS
  • Connexion avec SIGTAO
  • Calques de scenarios (terrorisme, crues, etc.)