- Vue d'ensemble
- Architecture du projet
- Installation et lancement
- Structure des fichiers
- Modele de donnees (classes.py)
- Chargement des donnees (data.py)
- Boucle de simulation (main.py)
- Attribution des vehicules (filters.py)
- Prediction des temps de trajet (fonction_calcul_trajet.py)
- Module d'optimisation (optimisation/)
- Interface web Streamlit (interface.py + pages/)
- Pipeline de donnees (datas/)
- Scripts d'analyse
- Pistes d'amelioration
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.
- 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
| Donnee | Valeur |
|---|---|
| Interventions (2023) | ~500 000 |
| Centres de secours | 72 |
| Vehicules | 268 (96 POMPE, 167 VSAV, 5 autres) |
| Types de vehicules | POMPE, VSAV, PSE |
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
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...)
- Python 3.13
- Conda (recommande)
- libomp (necessaire pour LightGBM)
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.txtInterface Streamlit :
streamlit run interface.pyNotebooks Marimo :
marimo editSimulation 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)| 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 |
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
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) |
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.
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.
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.
Ce module centralise tout le chargement et la mise en cache des donnees.
| 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 |
| 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 |
- Suppression des interventions STEC
- Suppression des lignes avec valeurs manquantes
- Tri chronologique
- Fusion avec
cma_inter.csvsurIdMMASelection
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.)
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 |
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 courstemps_de_sorties_de_la_caserne: horodatages de departtemps_de_retour_a_la_caserne: horodatages de retour
Etape 1 - Cas nominal (CSTC connu) :
- Chercher dans le centre competent (CSTC)
- Filtrer les vehicules disponibles du type requis
- Trier par distance euclidienne au carre
- Retourner le plus proche
Etape 2 - Fallback (CSTC indisponible) :
- Parcourir les centres par ordre de proximite (matrice pre-calculee)
- Traiter par lots de 5 centres (
_BATCH_SIZE = 5) - Retourner le premier vehicule disponible du lot le plus proche
- 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
| Type d'intervention | Vehicules eligibles |
|---|---|
| POMPE | POMPE, POMPE+PSE |
| VSAV | VSAV, VSAV+PSE |
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
- Source :
datas/matrices/travel_matrix.npy(aller) etreturn_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)
- 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)
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/).
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 ireturn_travel_matrix[c, i]= temps en secondes pour revenir du lieu final de l'intervention i vers le CS c
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.
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).
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 :
- Il existe un temps fixe incompressible de ~189s (3.1 min) correspondant a la sortie de caserne (alerte, equipement, demarrage), independant de la distance.
- 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.
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
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.
GeneticOptimizer.optimize(
population_size=50,
mutation_rate=0.1,
generations=100
)Fait evoluer une population d'allocations par selection, croisement et mutation.
Lance plusieurs recherches locales depuis des points de depart aleatoires et conserve la meilleure solution.
| 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 |
| Profil | Description | Vitesse |
|---|---|---|
ultra_rapide |
Tests rapides | Tres rapide |
rapide |
Exploration initiale | Rapide |
equilibre |
Compromis qualite/temps | Moyen |
qualite |
Resultats de reference | Lent |
simuler_window_df_engins(): lance la simulation sur une fenetre d'interventions avec une allocation donneeeval_window(): calcule les metriques a partir des resultats de simulation
- 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)
| Page | Fonction |
|---|---|
2_Optimisation.py |
Lancement d'optimisation avec choix d'algorithme et profil |
3_Secteurs_optimaux.py |
Analyse des affectations optimales par secteur |
| 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 |
- 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)
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
| 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 |
| 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 |
| 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 |
| 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 |
- 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.)