| Pseudo | Prénom | Inscrit le | Langue · site | Newsletter | Offres partenaires | Droits | ||
|---|---|---|---|---|---|---|---|---|
| Chargement... | ||||||||
Diffusés dans un ordre aléatoire pendant qu'un calcul tourne ("LE SAVIEZ-VOUS ?", "KARGIT COMPARE"...). Visibles par tous les visiteurs, pas seulement les admins. Les textes marqués « Prioritaire » sont favorisés (sans être exclusifs) une fois le temps estimé dépassé -- utile pour expliquer un calcul plus long que prévu.
deploy/rollback.sh).copie.log) ; les sauvegardes ne sont pas chiffrées ni copiées hors de chez toi ; un incident chez toi (vol, incendie) les toucherait toutes.deploy/LISEZ-MOI-SAUVEGARDES.md. Les conversations et les mots de passe qu'elles contiennent ne doivent jamais être partagés.Facteurs de consommation météo : ils multiplient la consommation de référence du véhicule (la vitesse est modélisée à part). Pris en compte tout de suite par tous les calculs et par l'affichage « Consommation retenue » des options. Détails et repères dans « Méthode de calcul » (section 3).
Service qui transforme une adresse tapée en coordonnées au moment du calcul. « Local » = notre propre serveur Photon (gratuit, sans quota) ; « LocationIQ » = service externe payant au-delà du quota gratuit. Le choix s'applique en moins de 30 secondes, sans redémarrage ; l'autre service reste en repli automatique (panne, quota, adresse inconnue). Détails dans « Méthode de calcul » (section « Géocodage »).
tesla.com refuse toute requête venant d'un serveur : les prix sont lus depuis ton navigateur, puis déposés ici. Détails dans « Méthode de calcul » (section « Sources des tarifs »).
Chargement…
Zone de construction séparée (base locale) : rien ici n'est utilisé par le calcul ni la carte tant que tu n'as pas donné ton feu vert. La case « Vérifié par Jérémy » ne se coche qu'à la main, pendant la revue commune.
Chargement…
Chaque demande qui touche un écran : son état sur chaque plateforme, puis la double vérification (le Mac vérifie l'appli Android, le PC vérifie l'appli iOS). Tenu à jour par les sessions Claude via le dépôt Git partagé (docs/parite.json) ; rien ne se modifie ici.
Chargement…
Messages envoyés depuis le formulaire « Contact » du site (les 200 plus récents), aussi transmis à contact@kargit.fr par email quand le serveur d'envoi (SMTP) est configuré -- la colonne « Email envoyé » l'indique. Cliquer une ligne pour lire le message en entier. Anti-spam : champ piège, délai minimal, limites par IP / adresse mail / jour.
| Reçu le | Nom | Adresse mail | Message | Email envoyé |
|---|---|---|---|---|
| Chargement... | ||||
Les emails automatiques (bienvenue, mot de passe oublié) et les campagnes ponctuelles envoyées aux utilisateurs qui ont accepté de les recevoir.
Le bouton et le pied de page (liens, mentions légales) ne sont pas modifiables ici : ils sont communs à tous les emails et garantissent que les liens fonctionnent toujours.
| Date | Type | Destinataire | Objet | Statut |
|---|---|---|---|---|
| Chargement... | ||||
Historique des calculs de trajet lancés depuis le site et l'appli (les 200 plus récents) -- cliquer une ligne pour voir la configuration complète. Origine connue depuis le 25/09 ; « Test interne » = appel direct au serveur (tests de performance), exclu du classement « Trajets les plus recherchés » des Statistiques.
| Jour / heure | Type | Origine | Lieux | |
|---|---|---|---|---|
| Chargement... | ||||
| Véhicule | Batterie (kWh) | Utile (kWh) | Connecteur | Puissance DC max (kW) | Conso (kWh/100km) | Masse à vide (kg) | Courbe de charge | Notes | Vérifié | |
|---|---|---|---|---|---|---|---|---|---|---|
| Chargement... | ||||||||||
| Opérateur | Bornes réelles | Tarifs | Abonnements | Prix min (€/kWh) | Prix max (€/kWh) | Provenance des prix | Vérifié par Jérémy | Commentaires Jérémy |
|---|---|---|---|---|---|---|---|---|
| Chargement... | ||||||||
| Nom | Opérateur | Enseigne | Puissance (kW) | Connecteurs | Points de charge | Accès | Source |
|---|---|---|---|---|---|---|---|
| Chargement... | |||||||
| Nom | Opérateur d'origine | Coût mensuel (€) | Opérateurs couverts (tarifs) | Conditions | Lien d'inscription (bouton "S'abonner") | Dernière vérification | Clics « S'abonner » (total) | Clics (7 derniers jours) | Vérifié par Jérémy | Commentaires Jérémy | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chargement... | |||||||||||
| Date | Abonnement | Ce qui a été vérifié ou modifié |
|---|---|---|
| Chargement... | ||
| Opérateur | Moyen de paiement | Abonnement requis | Prix (€/kWh) | Frais session (€) | Plage horaire | Exemple ? | Cashback | |
|---|---|---|---|---|---|---|---|---|
| Chargement... | ||||||||
| Opérateur | Identifiant | Type | Valeur | Plafond (€) | Utilisé par | Conditions | |
|---|---|---|---|---|---|---|---|
| Chargement... | |||||||
Principes de calcul, hypothèses retenues et techniques d'accélération, avec l'impact de chacune sur le résultat. Les valeurs chiffrées sont celles du code au moment de la rédaction (référence entre parenthèses) : si une constante change, cette page doit être mise à jour.
Un calcul se déroule toujours dans le même ordre :
waycategory) ; GraphHopper : classe de route et vitesse limite/moyenne par tronçon (path details). OSRM n'en fournit pas. Un itinéraire sans profil retombe sur une vitesse constante déduite de sa vitesse moyenne.La consommation dépend de la vitesse réelle de chaque tronçon : énergie d'un tronçon = distance × consommation de référence (fiche véhicule, kWh/100 km, proche du WLTP à ~90 km/h) × facteur météo × facteur vitesse du tronçon. Elle est ramenée en points de batterie (SOC) par rapport à la capacité utile du véhicule. Les énergies cumulées le long du trajet sont précalculées : l'énergie d'un arc borne → borne est une simple différence, sans ralentir la recherche.
Vitesse de croisière d'un tronçon : 130 km/h sur autoroute (limite légale ; plafonnée par « vitesse maximale sur autoroute », qui ajuste aussi la durée, voir section 2) ; ailleurs, la vitesse moyenne du tronçon ramenée à la limite légale immédiatement supérieure (30, 50, 70, 80, 90, 110) ou, avec GraphHopper, la vitesse limite OSM ; toujours plafonnée par le réglage « vitesse maximale sur autoroute » (90, 110 ou 130 km/h, défaut 130). Les tronçons à 80-90 km/h réduisent donc la consommation par rapport à la référence.
| Paramètre | Valeur | Explication et impact |
|---|---|---|
| Facteur météo « standard » | × 1,05 (réglable) | Modifiable dans l'onglet « Réglages du calcul » (table app_settings, prise en compte au calcul suivant, valeurs entre 0,5 et 2, « mauvaises » ≥ « standard » ; les valeurs affichées ici sont les défauts). Temps doux à frais (environ 10 à 25 °C). Valeur par défaut. Ne porte que sur la température / le vent / la pluie : l'effet de la vitesse est modélisé à part (1,15 avant la modélisation de la vitesse, puis 1,08 ; plusieurs essais en septembre 2026 ; 1,05 retenu par l'utilisateur, ABRP annonçant 23 % d'arrivée sur Rennes ↔ Angers en Mégane 60). Repère indépendant : Mégane E-Tech 60 par 14-18 °C = 23,2 kWh/100 km à 130 km/h et 20,9 à 120 km/h (Caradisiac) ; avec × 1,05 et le facteur vitesse ci-dessous, le calcul donne 20,9 et 19,8 (volontairement en dessous, voir la ligne « facteur vitesse »). L'ancienne option « bonnes » est fusionnée dans « standard » ; l'API accepte toujours good et le traite comme « standard ». |
| Facteur météo « mauvaises » | × 1,15 | Froid, pluie, vent ou forte chaleur (chauffage ou clim + résistance à l'avancement). Un vrai froid vif (≈ −5 °C) dépasserait +25 %. Était × 1,25 jusqu'en septembre 2026. |
| Facteur vitesse d'un tronçon | 1,00 à 90 km/h | 0,78 + 0,22 × (v / 90)², v = vitesse de croisière plafonnée : 22 % de la consommation de référence est supposée aérodynamique (40 % jusqu'en septembre 2026, puis 32 %). Calé sur ABRP : Mégane E-Tech 60 partant de 28 rue Marthe Simard à Rennes à 100 % arrive à 12 % chez Atlante Chambray-lès-Tours (258 km) ; le calcul donne 11,5 % (contre −1 % avec 40 %, 5 % avec 32 %), et 6 arrêts sur Rennes → Nice comme ABRP. 130 km/h → × 1,24 ; 120 → × 1,17 ; 110 → × 1,11 ; 90 → × 1,00 ; 80 → × 0,95. Compromis assumé : cela donne ≈ 20,9 kWh/100 km à 130 km/h pour la Mégane 60, en dessous des 23,2 mesurés par Caradisiac par 14-18 °C, donc optimiste pour quelqu'un qui roule réellement à 130 (plafond « vitesse max » du profil ou facteur météo pour compenser). Plancher à 0,75 (jamais atteint avec ce réglage). Approximation, pas une simulation physique. Part aérodynamique propre à chaque véhicule (26/09/2026) : les 22 % valent pour la Mégane E-Tech 60 (SCx 0,648 m² pour 16,1 kWh/100 km) ; pour les autres, la part suit la traînée rapportée à la consommation de référence, 0,22 × (SCx / conso) / (0,648 / 16,1), bornée entre 0,14 et 0,32 (planner/energy.py, aero_fraction). SCx = Cx × surface frontale, colonne vehicles.aero_cda_m2 (sources dans aero_source) : SCx publié, sinon Cx × surface publiée, sinon Cx × 0,84 × largeur × hauteur ; 194 véhicules sur 211, les autres gardent 0,22. Exemples à 130 km/h : Model 3 GA × 1,21, BMW i4 × 1,20, Mégane × 1,24, Kona 64 × 1,28, Dacia Spring × 1,32. Sur les 20 trajets comparés à ABRP (départ 90 %), l’écart absolu moyen de durée passe de 4,1 à 3,8 %. Calculé une fois par itinéraire : sans effet sur le temps de recherche. Affichage (30/09/2026) : « Consommation retenue » des Options avancées (site et appli, vehicleCruiseFactor) et la consommation autoroute du profil utilisent la même part propre au véhicule ; avant, les Options prenaient 0,22 pour tous (Ioniq 5 84 kWh à 130 km/h, météo standard : 21,6 affiché contre 22,0 calculé). |
| Vitesse max sur autoroute | 110 / 120 / 130 km/h (défaut 130) | Plafond de toute vitesse de croisière, réglage de « Mon profil › Mon véhicule et ma conduite » (comptes identifiés ; un invité reste à 130 ; l'ancienne valeur 90 est devenue 110). Rennes → Lille (Audi Q4) : 130 → 88,7 kWh rechargés, 110 → 66,7 kWh (−25 %). Allonge aussi la durée de conduite (voir section 2 : + 45 min sur Rennes → Lille à 110 km/h). |
| Dégradation batterie | 0-30 %, curseur réglé par l'utilisateur | Capacité utile × (1 − dégradation %). Bornée à 30 % depuis le 24/09/2026 (90 % avant) : une valeur plus haute déjà enregistrée est ramenée à 30 % à l'affichage et au calcul, sans être réécrite sur le compte tant que l'utilisateur n'y touche pas ; l'enregistrement du profil refuse désormais plus de 30. Appliquée à la copie du véhicule utilisée pour le calcul, jamais en base. Saisie dans « Mon profil › Mon véhicule et ma conduite » (comptes identifiés seulement ; un invité calcule avec les valeurs d'origine et ne voit pas de véhicule « - Personnalisée »). |
| Consommation personnalisée | 3-80 kWh/100 km, saisie par l'utilisateur | Remplace la consommation de la fiche pour ce calcul (c'est la valeur de référence : les facteurs météo et vitesse s'appliquent ensuite par-dessus, comme pour la valeur d'origine). |
| Véhicule « - Personnalisée » | 1 réglage par véhicule de base | Dès qu'une dégradation ou une consommation est saisie, le véhicule s'affiche « nom - Personnalisée ». Changer de véhicule repart des valeurs d'origine, mais le réglage est mémorisé (sur l'appareil et, si connecté, sur le compte) et proposé en tête du sélecteur de véhicule pour le réutiliser ; il peut y être oublié. Le réglage courant est aussi enregistré dans le profil et envoyé à chaque calcul. |
| Batterie mini en roulant | 10 % | SOC minimum toléré avant une borne (option « batterie mini »). |
| Batterie mini à l'arrivée | 10 % | SOC minimum à la destination finale. |
Dénivelé (septembre 2026) : ORS renvoie l'altitude du tracé (données SRTM, environ 5 km d'échantillonnage, lissée sur 3 échantillons pour ne garder que le relief de fond : le relief ondulé est déjà compris dans la consommation de référence, calée sur ABRP). Par intervalle, énergie = énergie à plat + montée × masse × g / 0,85 (rendement batterie → roues) − descente × masse × g × 0,5 (la régénération ne rend que la moitié de l'énergie potentielle), sans jamais descendre sous zéro. Masse = masse à vide de la fiche technique + 100 kg (passager et bagages) ; les masses des 210 véhicules ont été relevées sur fiches-auto.fr, ev-database.org, meilleurelectrique.fr, auto-data.net et les fiches constructeur (colonne « Masse à vide » de l'onglet Véhicules, source en infobulle), avec repli 1150 kg + 7 kg par kWh de batterie (véhicule générique). Les itinéraires GraphHopper/OSRM, sans altitude, l'empruntent à l'itinéraire ORS voisin (au moins 35 % du tracé en commun, sinon relief ignoré). Ordres de grandeur (Mégane 60, météo standard) : Grenoble → Briançon +12,7 points de batterie (+6,6 kWh/100 km), Briançon → Grenoble −1,1, Chambéry → Modane +7,2, Modane → Chambéry −2,8, Nice → Barcelonnette +17,3, Rennes → Nice +11,1 sur 1204 km, Rennes → Tours +1,5. Calculé une fois par itinéraire : aucun impact mesurable sur les temps de calcul (ORS 116 ms sans altitude, 117 avec). Le détour vers une borne n'inclut pas le relief.
Hypothèses non modélisées : température réelle, vent réel, préconditionnement de la batterie. Les détours vers une borne sont comptés au taux de consommation moyen du trajet, à plat (leur vitesse et leur relief sont inconnus). La vitesse moyenne d'une étape du routeur est une approximation de la vitesse de croisière (ronds-points et virages la tirent vers le bas, d'où l'arrondi à la limite légale).
scripts/recalibrate_charging_curves.py) : chaque courbe garde sa forme mais est mise à l'échelle (plafonnée à la puissance DC max) pour donner un temps 10 → 80 % réaliste, tiré des mesures du Supertest 2024 d'Automobile Propre quand le modèle y figure (ID.7 Pro 27 min, Cupra Born 28, Volvo EX30 28, Scénic 38, Peugeot e-208 29, ID.3 Pro 32, Renault 5 30) ou du temps constructeur + 10 à 15 % de marge (Ioniq 5 / EV6 : 20 min, Model 3 GA 30, Niro EV 47, Leaf e+ 50, Dacia Spring 52…). Mégane E-Tech 60 : 10 → 80 % en 35 min (34 à 38 min mesurés, 30 annoncés), 10 → 100 % en environ 1 h ; la charge 80 → 100 % est longue pour tous les véhicules (10 → 100 % : 43 min pour une Ioniq 5, 55 à 75 min pour la plupart des autres). Les formes elles-mêmes restent des approximations par famille de plateforme documentées dans db/seed_charging_curves.sql. À défaut de courbe : 2 paliers (100 % de la puissance DC max jusqu'à 70 % de SOC, puis 40 % au-delà). 22/09/2026 : 41 véhicules ajoutés (recherche sourcée par famille de plateforme -- Stellantis eCMP/eCMP2/STLA, VW MEB, Hyundai/Kia E-GMP 400V/800V, BYD LFP, Renault-Nissan AmpR Small, BMW FAAR/Neue Klasse, Mercedes MFA2/EVA2, Toyota e-TNGA, Xpeng SEPA 2.0, MG MSP, Honda, Ford, Smart SEA -- voir scripts/recalibrate_charging_curves.py pour le détail par véhicule et les sources). Pour une plateforme encore absente de la base, la forme est reprise d'un véhicule de la plateforme la plus proche déjà calibrée puis remise à l'échelle sur la cible 10 → 80 % propre à ce véhicule (mesurée indépendamment quand trouvée, sinon constructeur + marge comme les autres). 25/09/2026 : 130 véhicules ajoutés, dont la Dacia Spring 2e génération (commandes ouvertes le 08/09/2026), les modèles électriques vendus neufs qui manquaient encore et les modèles d’occasion courants (Renault Zoé ZE50 R110/R135, Nissan Leaf 40 kWh en CHAdeMO, Kona et e-Niro 64 kWh de 1re génération, BMW i3, e-Golf, e-208/e-2008/Corsa-e 50 kWh, anciennes Model 3/S/X, e-tron, I-Pace, EQC…), avec les années dans le libellé. Même méthode (une source par champ, forme de courbe de la plateforme la plus proche, recalage sur la cible 10 → 80 % du véhicule) ; les cibles sont lues dans scripts/vehicles_batch_20260925.json (statut par véhicule : mesure indépendante, équivalence, constructeur + marge, ou estimation EV Database quand rien d’autre n’a été trouvé). Le catalogue ne couvre que les versions capables de recharge rapide DC : Zoé ZE40 ou ZE50 sans option CCS, Twingo Electric, Smart EQ, Kangoo Z.E. n’y figurent pas (aucune borne de la base ne pourrait les recharger). Famille e-3008 73 kWh (e-3008, ë-C5 Aircross, Grandland, Compass) : 10 → 80 % commun de 40 min. Les fiches qui décrivent une ancienne version portent désormais leurs années (Spring 2021-2025, Mini Cooper SE F56, EQA 250, EV6 77,4 kWh, RZ 300e, XC40 Recharge), la version actuelle ayant sa propre fiche.stations_query.py, 25/09/2026) : pour une voiture en CHAdeMO (Nissan Leaf), c’est la puissance de la prise CHAdeMO de la fiche borne, et 50 kW quand elle est inconnue (CHAdeMO porté par le même point qu’un CCS) ou pas crédible (au-delà de 100 kW : Ionity déclare 350 kW pour les CHAdeMO de ses « Multicharger », limités à 50 kW d’après son support ; les CHAdeMO déclarés chez Ionity et Electra existent bien, une prise par station environ) -- avant, elle héritait de celle du CCS (« 300 kW » chez Allego) ; Leaf e+ Paris → Lyon : 83 → 96 min de recharge. Voitures en CCS : inchangé, sans coût ajouté à la recherche. Correction de septembre 2026 : la version précédente faisait la moyenne arithmétique des puissances, ce qui surestimait la puissance dès qu'on traverse des paliers lents (Mégane E-Tech 60, 28 → 100 % : 34 min annoncées, 43 min avec le bon calcul, + 4 min d'arrêt). Effet sur les plans : temps de recharge cumulé Rennes → Lille 75 → 89 min, Paris → Marseille 105 → 132 min.station_access.py, appelé par candidates_for_route). Verdict mémorisé par borne et par sens (secteur de 45°), en mémoire et en base (table station_access_checks) : une aire n'est vérifiée qu'une fois par sens. Sur les 20 trajets de test : 230 bornes vérifiées, 90 écartées (ex. « A7 - Aire de la Bouterne (direction Lyon) » en descendant : +55 km réels pour 0,6 km estimé ; IONITY Corbières Nord en allant vers le sud) ; 2 trajets sur 20 changent d'arrêt. Temps de calcul : inchangé une fois les verdicts mémorisés ; première vérification limitée à 2,5 s et 40 bornes par itinéraire (≈ +0,5 s par trajet la première fois). Carte : sur le trajet sélectionné, un trait fin montre le chemin réel vers chaque borne (sortie, accès, retour), demandé après l'affichage (POST /api/stop-access, GraphHopper, sans effet sur le temps de recherche). Depuis le 26/09/2026, ce temps de détour compte aussi dans le choix des bornes (graph.py et _search_native.pyx, arête de conduite) : en « le plus rapide » 1 min de détour pèse comme 1 min de charge, en équilibré au même prix de la minute, en « le moins cher » il ne sert qu'à départager deux coûts égaux. Avant, il n'était compté que dans la durée affichée : Model Y Propulsion Lille → Lyon choisissait un Superchargeur à 7 km de l'axe pour une minute de charge gagnée (7 h 36 → 7 h 27 avec la correction). Parité moteur compilé / Python : 432 recherches identiques ; temps de calcul inchangé. « Le plus rapide » tient compte du prix depuis le 29/09/2026 (retour d'un utilisateur, accord de Jérémy) : classement = coût + durée × 1 €/min (FAST_VALUE_OF_TIME_EUR_PER_MIN), au lieu de la durée seule. Le temps reste très prioritaire (un arrêt qui fait gagner 10 min pour 5 € de plus est toujours choisi), mais Kargit ne paie plus 12 € pour une minute : Limoges → Kembs (Model Y) prenait un Allego à 0,59 €/kWh pour gagner 1 min de détour sur le Superchargeur de Limoges à 0,32 €/kWh. Aller-retour Kembs ↔ Limoges : 922 min / 128,29 € → 931 min / 89,03 € ; Marseille → Toulouse (Model Y) : même durée, 33,39 → 24,33 € (le départage entre options aussi rapides était arbitraire). Le moteur compilé utilise sa formule « équilibré » avec cette valeur du temps (aucune recompilation). Temps de calcul : 0 à 0,1 s de plus selon le trajet (mesures sur 4 recherches).Règles de confort (ajoutées car le plan mathématiquement le moins cher proposait des arrêts absurdes, par exemple +1 point de batterie pour 0,22 €) :
Une limite optionnelle du nombre d'arrêts est aussi disponible (« m'arrêter moins souvent, quitte à payer plus »).
| Filtre | Valeur par défaut | Remarque |
|---|---|---|
| Couloir autour du tracé | 8 km | Recherche spatiale exacte (PostGIS) sur toute la géométrie du trajet. |
| Puissance DC minimale | 50 kW | Modifiable dans le profil / les options. |
| Connecteur | celui du véhicule | CCS, CHAdeMO ou Type 2. |
| Points de charge minimum | 1 | Évite les bornes uniques si l'utilisateur le demande. |
| Opérateurs exclus | choix utilisateur | En plus des opérateurs sans prix vérifiable (Freshmile, Greenspot), gardés en base mais jamais proposés. |
| Regroupement | tranche de 3 km × opérateur | Voir « Vitesse de calcul » : on ne garde qu'une borne par tranche et par opérateur (détour le plus court, puissance la plus élevée en départage). |
L'ordre des bornes le long du trajet est obtenu en projetant chaque borne sur un sous-échantillon d'environ 300 points du tracé : imprécision de quelques centaines de mètres, sans effet sur le résultat.
Fiche borne (survol sur la carte). Elle ne change rien au calcul : elle est demandée à la volée au survol d'un arrêt (jamais pendant la recherche, donc sans effet sur sa durée) et rejoue le tarif de la borne pour cette session (énergie, durée et heure d'arrivée de l'arrêt). Les connecteurs viennent de la base IRVE : nombre de prises et puissance max par type (CCS, Type 2, CHAdeMO), recalculés à chaque import. L’IRVE ne donne la puissance que par point de charge : sur un point rapide qui porte aussi une prise Type 2 (courant alternatif) ou un CHAdeMO à côté du câble CCS, cette puissance ne vaut que pour le CCS ; ces prises-là sont affichées sans puissance, et un point Type 2 seul est plafonné à 43 kW (corrigé le 25/09/2026, la fiche affichait « Type 2 150 kW »). Position des bornes : l’import quotidien (scripts/import_irve.py) ne garde que les coordonnées situées en France métropolitaine, Corse comprise (colonnes consolidées puis coordonneesXY, une paire latitude/longitude inversée étant remise dans l’ordre) ; une borne sans position plausible est écartée, donc retirée de la base au passage suivant, outre-mer compris pour l’instant (25/09/2026 : Allego Saint-Clément-de-Rivière placée dans le golfe de Guinée, Tesla Val-de-Meuse au Kazakhstan -- cette dernière retrouve sa vraie position, 16 bornes d’outre-mer retirées). Depuis le 28/09/2026, chaque position est aussi soumise au géocodeur du serveur (Photon, recherche inverse dans un rayon de 2 km, 4 requêtes en parallèle, environ 3 s pour toutes les bornes) : une borne qui ne tombe pas en France est écartée (Allego Anglet placée dans la Manche, une borne Powerdot réellement en Belgique rattachée au Pas-de-Calais). Géocodeur injoignable : contrôle sauté, toutes les bornes gardées. Site en plusieurs fiches (28/09/2026) : un réseau qui republie ses bornes sous de nouveaux identifiants laisse l’ancienne déclaration dans le fichier (Allego : FRALLPEVCARS… de mars puis FREVCP… de septembre ; Ionity : fiches de 2023 à une prise sous les sites de 2026). Même opérateur et (a) à moins de 20 m, ou (b) jusqu’à 500 m avec au moins la moitié des prises en commun, sur des numéros d’au moins 6 chiffres (les numéros courts coïncident entre bornes différentes). Prises en commun (sans le préfixe : FRALLEGO9000571 = FREVCE9000571) ou fiche plus vieille de 6 mois : on ne garde que la déclaration la plus récente, sans additionner les prises. Prises différentes et même période (deux bornes Lidl d’un parking) : une seule fiche, prises additionnées. Les tarifs Electra régénérés chaque jour suivent la fiche gardée, les exclusions des utilisateurs aussi ; deux fiches portant chacune un tarif saisi à la main restent séparées. Sur la prod : environ 750 fiches en trop retirées (Electra, Allego, TotalEnergies, Ionity…). Les tarifs sont toujours au plus quatre lignes, chacune au prix de l'heure d'arrivée : le tarif retenu par le calcul (quel qu'il soit), la carte bancaire, l'appli de l'opérateur de la borne sans abonnement (seulement si elle est moins chère que la carte bancaire, ou toujours quand la borne n'a pas de prix par carte, comme Tesla) et le prix avec l'abonnement de cet opérateur (le moins cher) quand le calcul ne l'a pas retenu ; les abonnements d'un autre réseau ne sont affichés que s'ils sont le tarif retenu. Une ligne dont le prix varie selon l'heure (au moins deux plages de prix différents) porte « variable selon l'horaire » : un clic déplie sa grille horaire (une seule dépliée à la fois), montant de la session par plage. Heure d'arrivée connue : la plage cochée est celle de l'arrivée ; inconnue : la plus chère, comme dans le calcul. Sur ordinateur, la fiche reste ouverte quand on y amène la souris et pendant un déplacement de la carte (elle suit alors sa borne) ; elle se ferme quand la souris la quitte ou repart ailleurs sur la carte, et à chaque zoom ou dézoom (même règle que l'onglet Bornes). Sur la carte du trajet, une horloge dans la bulle de prix d'un arrêt signale que son tarif retenu varie selon l'heure (champ hourly des arrêts de /api/plan et /api/trip-plan, même règle que l'onglet Bornes : tariffs.option_varies_by_hour, au moins deux plages horaires à des prix différents pour ce moyen de paiement / abonnement ; calculé après la recherche sur les seuls arrêts retenus, lignes de tarif déjà en cache).
Disponibilité des prises en direct (26/09/2026). Source : la consolidation nationale des données IRVE dynamiques du Point d’accès national (transport.data.gouv.fr, schéma etalab/schema-irve-dynamique, un point de charge par ligne : état en service / hors service, libre / occupé, état de chaque type de prise). Le serveur la relit toutes les 2 minutes (backend/app/live_status.py, environ 1,6 Mo compressé, calcul d’environ 0,5 s dans un fil d’arrière-plan) et la croise avec les points de charge de chaque borne, enregistrés par l’import IRVE (colonne stations.pdc_types). Affichée dans la fiche de borne (onglet Bornes et détail d’un arrêt) : « libres / total » par type de prise, prises et borne hors service, heure de la donnée avec un point vert qui pulse (maquette pack_disponibilite_D2). Règles, pour ne jamais afficher un chiffre faux : seulement les opérateurs dont le flux est vraiment en direct (mesuré le 26/09/2026 : Tesla, Ionity, Fastned, Izivia, Powerdot, Lidl, TotalEnergies, Engie Vianeo, Driveco ; Electra, Allego, Atlante, Freshmile et Leclerc publient avec des heures ou des mois de retard, rien n’est affiché pour eux) ; un opérateur dont le flux ne bouge plus depuis 1 h est ignoré automatiquement ; au moins la moitié des points de la borne doivent avoir un état connu, et le compte ne porte que sur ceux-là (un point absent du flux est le plus souvent un ancien identifiant resté dans le fichier des bornes) ; plus rien n’est affiché si la dernière relecture a plus de 10 minutes. La fiche relit la disponibilité chaque minute tant qu’elle reste ouverte. Effet sur le calcul : une borne dont tous les points sont connus et hors service est écartée des trajets (au moment de lister les bornes le long de l’itinéraire, api/_shared.py::candidates_for_route ; vérification en mémoire, sans effet mesurable sur la durée de recherche) ; rien n’est écarté quand la donnée a plus de 10 minutes. Ajout du 28/09/2026 : Allego suivi par le flux national borne par borne (seulement les bornes dont un point a changé d’état depuis moins de 24 h : 250 sur 402 le 28/09, les autres étant figées depuis des semaines, PER_STATION_OPERATORS) ; Electra lu à la demande dans l’API publique de leur carte (backend/app/electra_live.py) : une requête pour la station à l’ouverture de sa fiche, gardée 1 min, car leur flux national arrive par paquets avec plusieurs heures de retard ; Electra n’intervient pas dans le calcul des trajets (bornes hors service non écartées). Atlante : rien de fiable en direct (flux national figé, fichier de leur carte vieux d’environ 10 h). Ajout du 29/09/2026 : R3 suivi de la même façon qu’Allego, borne par borne (mesuré le 29/09 : dernier changement d’état il y a 1 min, 150 stations sur 165 avec un changement depuis moins de 24 h, 4 figées, 11 avec moins de la moitié de leurs points dans le flux). Pays voisins (29/09/2026, backend/app/eu_live.py), mêmes règles d’affichage : Suisse, flux officiel OFEN (data.geo.admin.ch, OICP), relu toutes les 2 min ; sans horodatage par point, Kargit note lui-même les changements d’état, et un opérateur n’est suivi que si l’un de ses points a changé depuis moins d’1 h : 503 stations sur 637 le 29/09, 7 min après le démarrage (GOFAST et Tesla absents ou figés) ; Pays-Bas, flux officiel NDW (OCPI, 17 Mo compressés), relu toutes les 4 min dans un processus à part (eu_live_nl.py : le décodage prend environ 5 s et 1 Go, il bloquerait sinon les recherches) ; une station n’est suivie que si l’un de ses points a changé depuis moins de 24 h (Tesla, E.ON, Valk… figés, écartés d’eux-mêmes) : 691 stations sur 791 le 29/09 ; Portugal, flux officiel DATEX II de MOBI.E (point d’accès national, fiche n° 149, libre accès, republié toutes les 5 min, ~36 Mo de XML) : un HEAD par minute, relu seulement quand son ETag change, dans un processus à part (eu_live_pt.py, ~23 s) ; fichier tronqué rejeté (balise de fin absente ou moins de 12 000 points), identifiant de point présent sur plusieurs sites ignoré ; même règle que la Suisse (changements notés par Kargit, opérateur suivi seulement si l’un de ses points a changé depuis moins d’1 h) : 2 077 stations sur 2 229 lors du test du 29/09 ; Royaume-Uni, flux « open data » imposés par les Public Charge Point Regulations 2023, seuls ceux ouverts sans inscription : MFG (OCPI, 3,6 Mo, relu toutes les 5 min, au-delà il répond 429) : 577 stations sur 591 ; « INOPERATIVE » chez MFG = prise sœur occupée (puissance partagée), comptée occupée ; GeniePoint publie aussi mais son flux refuse l’adresse du serveur (403, Cloudflare) ; InstaVolt, Osprey, Shell, IONITY, GRIDSERVE, Fastned : formulaire ou contrat exigé, rien; Italie (sources officielles sans compte ; ni la plateforme nationale PUN ni le compte « invité » d’Enel) : IONITY par le serveur de l’appli IONITY Direct (une fiche par station, état de chaque prise, 42 appels toutes les 5 min) et Free To X par sa page carte officielle (un appel toutes les 5 min, servie par un cache d’environ 5 min, retiré de l’heure affichée) : même règle que la Suisse (changements notés par Kargit, opérateur suivi seulement si l’une de ses prises a changé depuis moins d’1 h), 42 et 109 stations sur 42 et 109 ; Duferco à la demande, à l’ouverture de la fiche (jeton anonyme de sa carte publique, gardé 2 min), codes 1 et 9 libres, 2 et 5 occupés, 3, 4, 7 et 8 hors service ; Ewiva, Tesla, Fastned : aucun direct public ; Allego : données figées (jamais une prise occupée), non affichées; Electra dans tous les pays par leur API (BE, CH, DE, ES, LU, NL). Mesure du 29/09 (deux relevés à 3 min d’intervalle) : 166 changements d’état en Suisse, 1 014 aux Pays-Bas. Allemagne, Belgique (hors Electra), Espagne : pas de flux ouvert sans inscription (Mobilithek, clés à demander). Le flux national français ne sert plus qu’aux bornes françaises.
Fiche borne de l'onglet Bornes. Même présentation que la fiche des trajets, sans trajet donc sans « tarif retenu par le calcul » : elle utilise une session type de 30 kWh (25 min), comme les prix des repères, et affiche le prix effectif au kWh (frais de session et de temps répartis sur l'énergie), la puissance max, le nombre de prises, les connecteurs et les services à moins de 300 m. Les tarifs suivent la colonne « Abonnements à considérer » de droite (au départ : les abonnements déjà possédés, sans rien enregistrer sur le compte) : rien de coché = le tarif le moins cher avec abonnement (tous réseaux), l'appli de l'opérateur sans abonnement, la carte bancaire ; des abonnements cochés = une ligne par abonnement coché applicable à la borne, puis l'appli sans abonnement et la carte bancaire en repère, la moins chère étant cochée. Un abonnement réservé à ceux qui le possèdent déjà (Plug Inn Charge Pass) n'est jamais proposé tant qu'il n'est pas coché. Appel à la demande (/api/tariffs-map/station/{id}/card?map_view=true), sans effet sur la recherche de trajet. La fiche suit sa borne quand on déplace la carte ; un zoom ou un dézoom la ferme. La carte des tarifs n'affiche que des bornes chiffrées : les opérateurs sans prix vérifiable (Freshmile, Greenspot, voir pricing_policy.py) et les bornes sans aucun tarif connu ne sont ni dessinés, ni comptés, ni proposés dans le filtre des opérateurs.
VEHICLE_BRAND et BRAND_INCLUDED_SUBSCRIPTIONS dans tariffs.py ; même mécanisme pour le Charge Pass Basic sur les bornes Plug Inn Fast Charge (véhicules Renault, Dacia, Alpine) ; marque lue sur le véhicule du calcul, y compris dans les processus parallèles et pour la fiche borne).owned_only_subscription_ids, appliqué en entrée de /plan et /trip, aucun coût dans la boucle de recherche). La variante « sans nouvel abonnement » garde les abonnements déjà possédés (avant, elle les excluait aussi : un abonnement possédé était ignoré dès que le calcul de base en préférait un autre, non possédé).hour de /tariffs-map/stations, arrival_hour de la fiche), plus le pire cas. La bulle de la borne, la ligne cochée de sa fiche et la plage cochée de sa grille horaire (dépliable par « variable selon l'horaire » sur chaque ligne dont le prix varie, une seule à la fois) donnent toujours le même prix ; une horloge sur la bulle signale un prix variable selon l'heure. Sans heure envoyée (ancienne version de la page), retour au pire cas horaire.PlugFilter, app/api/tariffs_map.py). Onglet Bornes seulement : les trajets gardent la prise du véhicule. Les bornes italiennes Electra et Free To X ont reçu leur type de prise le même jour (collecteurs prix_electra_it.py, prix_freetox_it.py), sans quoi ce filtre les aurait masquées.POST /api/tariffs-map/availability-batch, après les prix ; la bulle reçoit « ● libres/total » (pastille verte s'il reste une prise libre, rouge sinon), comptés sur les prises du filtre, sinon CCS et CHAdeMO (un point qui porte les deux compte une fois : type le mieux fourni). Dispo inconnue : bulle inchangée. Flux en mémoire lus directement ; Electra et Duferco (demandés à l'opérateur) : au plus 40 par appel, 8 à la fois, gardés 1 à 2 min. Aucun effet sur la recherche de trajets. Présenté comme un sélecteur « Affichage » (Prix seuls / Prix + dispo en direct) ; le choix est retenu pour un utilisateur connecté (profil, colonne users.default_bornes_live, migration 20260929_bornes_live_pref.sql).consider_cashback, mémorisée sur l'appareil et dans le profil) : si elle est cochée, 50 % du cashback (fixe, sans pondération par type de récompense) est retranché du coût utilisé uniquement pour comparer les tarifs et les plans (Dijkstra, choix de l'abonnement, « sans abonnement », top 3, abonnement manqué), car un cashback ne sert qu'une fois : recharge à 50 € avec 10 € de cashback comparée comme 45 €. Le moteur tient donc deux coûts : le coût de classement (rank_cost_eur) et le coût payé (total_cost_eur), qui ne déduit jamais le cashback ; les totaux affichés restent le coût payé. Seule une réduction immédiate (remise appliquée à la borne) est un vrai prix et se déduit du coût net ; la base n'en contient aujourd'hui aucune (les trois récompenses connues — Atlante, Engie Vianeo — sont des crédits futurs).Trois modes de recherche existent côté calcul, et l'interface en présente quatre :
| Libellé dans l'appli | Mode de calcul | Critère minimisé |
|---|---|---|
| TOTAL LE MOINS CHER | balanced | coût net (recharges, hors cashback) + abonnement + 0,12 € par minute (soit 7,20 € de l'heure : valeur du temps supposée) |
| LE PLUS RAPIDE | fast | durée totale seule (le coût est ignoré) |
| SANS ABONNEMENT | balanced sans aucun abonnement/carte | même critère, abonnements exclus |
| PÉAGE INVERSÉ | balanced avec le réglage péage inversé | même critère, sur le tracé avec (ou sans) péage |
Le mode cost pur (coût seul, avec une durée infime en départage) existe dans le moteur mais n'est plus utilisé par l'interface : il ignore le temps et donnait par exemple un « sans abonnement » moins cher que « le moins cher », ce qui était déroutant.
scripts/import_irve.py) : un même site déclaré plusieurs fois est réduit à une fiche, la déclaration la plus récente : fiches à moins de 20 m, ou à moins de 500 m avec des prises en commun, ou (depuis le 29/09/2026) du même nom à moins de 60 m (Allego « Rouillé-Pamproux ASF » et « Abbeville », déclarés en 2023 puis en 2024). Une déclaration plus vieille de 6 mois est écartée sans additionner ses prises ; sinon les prises des bornes voisines sont réunies. Seules les fiches qui portent un tarif saisi à la main sont protégées d'une fusion : les tarifs repris automatiquement par position (Electra, IONITY, Driveco, Tesla, pages de paiement Powerdot, Lidl, Izivia) ne bloquent plus la fusion (29/09/2026 : ils l'empêchaient pour les doublons IONITY, Lidl et Powerdot), la fiche gardée les reçoit à la relecture suivante. Puis les corrections de la table station_overrides sont appliquées (fiches IONITY masquées ou renommées d'après l'API officielle, voir IONITY). Audit en lecture seule : scripts/audit_bornes.sql.scripts/etranger/publier_prod.py, depuis la zone de travail etranger (onglet « Pays voisins ») : seulement les tarifs d'un opérateur au statut « sûr » ou lus dans l'API officielle de l'opérateur, jamais un statut « incertain » ni un tarif expiré ; seulement les stations qui ont un de ces tarifs ; pas les stations à horaires limités (même règle que Driveco) ; une station à moins de 30 m d'une borne française du même opérateur est écartée (doublon de l'IRVE) ; un même site déclaré plusieurs fois dans un registre (même opérateur à moins de 50 m : Aral pulse, EnBW, E-Flux…) ne garde qu'une fiche, celle qui a un prix propre, puis la plus puissante (157 fiches écartées le 29/09/2026). Chaque borne et chaque ligne de tarif porte son pays (stations.country, tariffs.country, « FR » pour tout ce qui existait) : une ligne ne s'applique qu'aux bornes de son pays (Allego, Lidl, Electra… n'ont pas les mêmes prix en France et en Belgique). Les abonnements portent la liste des pays où ils valent (subscriptions.countries) ; une offre valable en France et ailleurs reste une seule ligne. Tout est enregistré en euros : les prix en francs suisses sont convertis au taux de référence BCE du jour de la publication (le prix d'origine et le taux restent dans la source de la ligne) ; seul le prix en euros est affiché. Trajets : seulement les bornes des pays couverts par le graphe routier, réglage TRIP_COUNTRIES_CSV (config.trip_countries, « FR » tant que le graphe ne couvre que la France ; filtre dans stations_query.py, et abonnements pertinents lus sur les tarifs de ces pays). Le graphe France + pays voisins est fabriqué à part par deploy/server/osm-europe-build.sh (extraits Geofabrik fusionnés, filtrés sur les routes, ferries et navettes ; moteurs en service inchangés tant qu'on ne bascule pas). Onglet Bornes : la carte montre toujours toutes les bornes (France et pays voisins) ; la bascule « France | Europe » (France par défaut) ne filtre que la liste des opérateurs et celle des abonnements (paramètre zone des points d'accès, « fr » par défaut pour les applis qui ne l'envoient pas encore). Les scripts de prix français (Electra, IONITY, Driveco, Tesla, Powerdot, Lidl, Izivia, grille Allego) ne lisent et n'écrivent que les bornes et tarifs français. Les lieux à proximité des bornes étrangères (moins de 300 m) sont ajoutés à osm_amenities (country), que l'import des lieux français ne remplace plus.scripts/update_electra_tariffs.py, relancé toutes les 24 h par le conteneur electra_tariffs_updater) : l'API publique du site d'Electra donne, pour chaque borne, ses paliers horaires ; le rapprochement avec nos bornes se fait par coordonnées (≈ 50 m) et les lignes « carte bancaire » de la borne sont entièrement remplacées à chaque passage. Le supplément de congestion (borne très occupée) n'est pas modélisé.regenerate_electra_plus). Les lignes génériques à 0,39 € / 0,29 € restent en repli pour les bornes sans relevé ; les tarifs Electra+ Smart à 0,49 € sur Atlante, Fastned et Ionity (réseau ChargeLeague) sont saisis à part et ne sont pas touchés.scripts/update_driveco_tariffs.py, relancé toutes les 24 h par le conteneur driveco_tariffs_updater) : Driveco ne publie pas de grille, chaque borne a son prix. Son appli officielle l'affiche point de charge par point de charge, même sans compte ; le script lit la même API que l'appli (exploitation.driveco.com/icpm, clé fournie par l'appli) : la carte, découpée en zones de 100 stations au plus, puis la fiche de chaque station (puissance, prix au kWh, frais fixes par session, gratuité). Prix vérifiés à la main dans l'appli le 28/09/2026 (Carrefour Market Cazouls-lès-Béziers : 0,51 €/kWh sur les points 50 kW, 0,55 €/kWh sur les points 200 kW). Une station qui mélange des puissances a une ligne par tranche de puissance (power_band_min_kw / power_band_max_kw, seuls les points de 43 kW et plus) ; à puissance égale, le prix le plus cher. Rapprochement par coordonnées (80 m) : les 356 bornes Driveco de l'IRVE retrouvées. 29/09/2026 : Driveco est aussi reconnu par son nom d'exploitant dans l'import IRVE (« DRIVECO », « DRIVECO Partner Network », surtout des concessionnaires) : 671 stations au lieu de 356 ; les stations qui ne sont pas ouvertes 24 h/24 d'après l'API (horaires d'atelier, quelques Carrefour Market fermés le soir) n'ont aucun prix et ne sont jamais proposées (décision de Jérémy : Kargit ne gère pas encore les horaires d'ouverture) ; environ 430 stations chiffrées. Aucun prix selon l'heure. Un point marqué gratuit dans l'API n'est pas repris (29/09/2026 : concession Hyundai de Villeneuve-d'Ascq, en pratique réservée aux clients ; à 0 € elle aurait attiré tous les trajets du coin), ni un prix nul. Les frais de stationnement (0,30 €/min, 15 min après la fin de la charge) ne sont pas dans l'API et ne sont pas modélisés, comme la congestion d'Electra. L'ancienne ligne générique (prix d'agrégateur) est supprimée : une borne sans relevé n'est pas chiffrée. Enregistré comme paiement « carte bancaire / sans compte ».scripts/update_ionity_tariffs.py, relancé toutes les 24 h par le conteneur ionity_tariffs_updater) : la page tarifs d'IONITY n'affiche que des prix minimums (« les prix varient selon la borne »). Le script lit l'API de l'appli officielle (adhoc-bff.ionity.cloud, sans compte, avec les en-têtes de l'appli) : pour chaque point, le prix « IONITY DIRECT » (sans compte ni abonnement), rangé en carte bancaire, une ligne par tranche de puissance ; points haute puissance (150 kW et plus) d'un même site à des prix différents : le plus cher. Constaté le 28/09/2026 : 0,62 ou 0,55 €/kWh selon le site (l'ancien découpage « autoroute / hors autoroute » se trompait sur 22 bornes), anciens points 50 kW et 43 kW AC à 0,39 ou 0,35 €/kWh. Appli sans abonnement (« Go ») : l'API ne le donne qu'à un compte connecté ; seuls les paliers vus dans l'appli sont écrits (DIRECT 0,62 → 0,59 ; 0,55 → 0,52), sur la tranche haute puissance. Motion / Power : montants inchangés (0,39 / 0,31 au palier bas, lignes génériques 0,44 / 0,35 sinon), désormais rattachés au palier réel de chaque borne. Rapprochement par coordonnées (250 m). Fiches corrigées d'après l'API (29/09/2026) : le fichier national garde les déclarations IONITY de mars 2023 (FRIONE…) à côté de celles de septembre 2026 (FRIOYP…) ; 28 sites étaient en double et 32 fiches de 2023 portaient le nom et l'adresse d'un site avec les coordonnées exactes d'un autre (« Troyes Sud » dessiné à Tours). Chaque jour, avant les prix, chaque fiche est comparée au site de l'API le plus proche (250 m, même nom) : sur un site, la fiche la plus récente au bon nom est gardée, les autres sont masquées ; un site sans fiche au bon nom garde sa fiche la plus récente, renommée avec le nom et l'adresse de l'API ; une fiche loin de tout site de l'API est masquée (ex. « Val Gelon » près de Troyes). Corrections écrites dans la table station_overrides, que l'import IRVE quotidien applique aussi. Garde-fous : aucune correction si l'API renvoie moins de 500 sites, ou s'il faudrait masquer plus de 40 % des fiches.scripts/charger_prix_fr.py : rapprochement par coordonnées, une ligne par tranche de puissance, points de 150 kW et plus d'un même site au prix le plus cher, frais au temps « après N minutes » non modélisés ; la ligne générique de l'opérateur reste en repli pour les bornes non retrouvées) : Powerdot (page de paiement par QR code, scripts/prix_powerdot_fr.py : 1 117 bornes sur 1 136, surtout 0,59 / 0,56 / 0,60 €/kWh au lieu de 0,54 partout), Izivia (page de paiement par carte PayNow, scripts/prix_izivia_fr.py : 872 bornes sur 1 043 ; IZIVIA FAST à 0,35 €, 0,30 de 9 h à 11 h et de 15 h à 17 h, on garde le plus cher), Lidl (page de paiement direct ChargePoint, scripts/prix_lidl_fr.py : 874 bornes sur 880, 0,39 € presque partout). Rangés en carte bancaire (paiement sans compte).scripts/maj_grille_allego.py) : grille officielle par puissance (allego.eu/pricing, France) : jusqu'à 50 kW 0,49 carte / 0,47 Smart / 0,35 Plus ; au-delà 0,59 / 0,56 / 0,39.POST /api/admin/tesla-prices. Le rapprochement se fait par coordonnées : le site Tesla le plus proche de la borne, à moins de ≈ 400 m. L'onglet indique la date du dernier relevé et signale un relevé de plus de 14 jours. Chaque import ajoute une entrée à l'historique de l'abonnement Tesla.BRAND_INCLUDED_SUBSCRIPTIONS), jamais aux autres marques ; il s'affiche comme un prix « appli, sans abo » et n'est pas listé dans les abonnements du conducteur (HIDDEN_SUBSCRIPTION_IDS), rien à cocher. Les frais de stationnement (0,30 €/min au-delà d'1 h) ne sont pas modélisés. Script : scripts/add_plug_inn_tariffs.py.nom_operateur exactement « R3 » : EXACT_OPERATOR_RULES dans import_irve.py) : carte bancaire ou QR code 0,55 €/kWh, sans frais supplémentaires, tarif unique sur toutes les stations publiques (dbt.fr/comprendre-sa-recharge, relevé le 29/09/2026, concordant avec le champ tarification IRVE). La recharge normale (jusqu’à 22 kW, 0,36 €/kWh) n’est pas chargée, car Kargit ne garde que les bornes rapides. R3 n’a ni application ni abonnement : une seule ligne. Script : scripts/add_r3_tariffs.py.Les états du graphe sont des (borne, niveau de batterie). Deux types d'arêtes : conduire jusqu'à une borne suivante (ou la destination) sans coût, en consommant de la batterie ; recharger à la borne courante jusqu'à un palier de 5 % plus haut, au coût du tarif retenu. Une recherche de plus court chemin (Dijkstra) trouve la combinaison la moins coûteuse selon le mode.
feasible faux, raison par trajet) et porte une erreur « Calcul impossible… » dans warnings de /plan et /trip-plan, et dans errors[i] de /multi-plan (le résultat reste renvoyé) ; si la batterie de départ est au plancher (ex. départ à 10 % avec 10 % minimum à chaque arrêt), le message le dit ; la batterie théorique à l'arrivée sans recharge est affichée à titre indicatif.Dijkstra minimise le coût par session : le frais mensuel d'un abonnement n'apparaît qu'une fois, au total. Pour ne pas rater un abonnement rentable sur l'ensemble du trajet, on compare aussi des recherches « avec cet abonnement gratuit » et « sans aucun abonnement », puis on garde le meilleur total (frais compris, cashback ignoré par défaut : un abonnement n'est recommandé que si l'économie sur le prix des recharges dépasse son frais, par exemple Atlante Go n'est plus retenu sur un trajet isolé quand seul son crédit futur le rendait rentable ; avec l'option « Prise en compte du cashback » cochée, le total de classement — dont 50 % du cashback est déduit — sert à ces comparaisons, mais le total affiché reste le coût réellement payé : Atlante Go redevient alors choisi sur Paris → Marseille et Lille → Nice). Pour limiter le nombre de recherches complètes :
MAX_TESTABLE_SUBSCRIPTIONS). Compromis explicite : un abonnement au-delà du top 5 pourrait, très rarement, être le vrai gagnant sans être testé. Effet constaté : sur 70 voyages tests, 2 résultats changent, tous deux en mieux (Paris ↔ Strasbourg : 55,80 € → 51,26 € avec Electra+ Smart, jamais testé dans le top 3).Quand l'interface calcule au moins deux trajets en mode « le moins cher » (POST /api/multi-plan), le forfait d'un abonnement est compté une seule fois par mois pour l'ensemble des trajets, et non en entier à chaque trajet : Atlante Go (9,99 €/mois) perd sur chaque trajet pris seul mais peut gagner sur la somme. La fréquence de chaque trajet (« N fois par mois ») et le cashback pondéré (coût de classement, voir section 6) sont déjà dans le coût mensuel de chaque calcul ; le choix minimise le coût de classement total, les montants affichés restent ce que l'on paie.
subscriptions_required garde le vrai forfait, comme un calcul seul, à dédoublonner par identifiant) ; pooled_subscriptions (forfaits comptés une fois) ; totaux mutualisés et « trajets seuls » pour mesurer l'apport. Une erreur sur un trajet (adresse introuvable...) ne fait pas échouer les autres (results[i] vide, errors[i] renseigné).Vitesse (serveur OVH, aller-retour de ≈ 500 km par sens) : 2 trajets 4,5 s, 5 trajets 7,5 s (24 à 65 calculs en parallèle) ; c'est ce que l'interface mettait déjà en calculant les trajets l'un après l'autre. Cinq utilisateurs lançant en même temps 2 trajets : 9,7 à 11,5 s au pire.
/api/plan) : si aucun plan n'est possible sur le tracé le plus rapide, les autres tracés du routeur sont essayés dans l'ordre et le premier faisable est retenu (coût : une recherche par tracé essayé, seulement quand la réponse serait sinon « impossible »). Aller-retour et étapes (/api/trip-plan) : si le tracé le plus rapide a plus de 150 km sans borne candidate (MAX_STATION_GAP_KM), le premier autre tracé sans un tel trou est pris (jamais en France, où les bornes sont bien plus rapprochées). Pas de changement quand l'utilisateur a choisi lui-même un tracé.toll_tariffs : prix pour une paire de gares.Tollways ajouté à ors-config.yml ; GraphHopper : détail « toll »). Le graphe ORS a été reconstruit le 20 septembre 2026 (environ 40 min ; une première tentative le 19 avait été interrompue par la mise en veille du PC : la reconstruction doit se faire PC éveillé). L'ancien graphe est conservé dans J:\kargit-ors-data\graphs\car.bak-20260920 (retour arrière : le remettre sous le nom car et restaurer ors-config.yml.bak-20260919). Un trou estimé n'est facturé que sur ses kilomètres payants : l'A47 gratuite entre Saint-Étienne et Givors était comptée ≈ 8,5 € sur Rennes → Nice (131,99 € → 124,10 € ; Mappy annonce 115 €). Sans profil (routeur ne le fournissant pas), la distance entière est estimée comme avant. Aucun coût de calcul mesurable (ORS ≈ 70 ms avec ou sans) ; l'évitement des péages fonctionne toujours (0 km payant sur l'itinéraire « sans péage »)._pair_plausible : distance trop longue tolérée si l’une des gares est une « BARRIÈRE ») ; la portion ainsi payée d’avance (du côté de la barrière, jusqu’à la distance tarifaire moins 5 km) ne se paie pas une seconde fois : ni les barrières fixes qui s’y trouvent (Dordives à 300 m de la sortie, Le Tourneau sur l’A77), ni une estimation (l’A77 près de Gondreville). Myennes, au-delà, reste due. Sur les 20 trajets de test, seul Annecy → Grenoble change : 13,02 € estimés → 12,30 € exacts (Annecy Centre → Chambéry Nord 5,60 € + Chignin barrière → Crolles barrière 6,70 €).toll_gares.cap_payant (degrés, 0 = nord ; NULL = les deux sens). La gare n’est retenue que si le trajet la traverse à ±90° de ce cap (tolls._heading_matches, direction du tracé entre 200 m avant et après la gare).toll_mandatory, affiché « péage obl. ») au lieu d’une erreur 400 (routing._mandatory_toll_routes).pack_exclusion) : menu « ⋯ » sur chaque ligne de borne et de péage du détail, confirmation, puis recalcul du seul trajet concerné. Borne exclue : excluded_station_ids (déjà existant). Tronçon exclu : excluded_toll_zones (/api/plan, /api/trip-plan, chaque trajet de /api/multi-plan) = 4 points pris par le site sur le tracé réel du tronçon (à 20, 40, 60 et 80 % entre ses gares, 1 point pour une barrière) ; le routeur (ORS) évite un petit carré de 300 m autour de chacun (geo.points_avoid_polygon, au plus 24 carrés), donc l’autoroute à ces endroits sans couper les routes voisines, contrairement à l’ancien couloir en ligne droite entre les gares. Dans ce cas ORS quitte son mode précalculé : 2 à 3 s par itinéraire en prod (13 à 21 s sur le PC le 29/09 : Rennes → Paris, Paris → Lyon), GraphHopper n’est pas interrogé (il ne sait pas éviter une zone ; essayé avec des zones interdites : aussi lent et contournement incorrect). Depuis le 29/09 au soir, les itinéraires habituels (précalculés, instantanés) sont essayés d’abord : si l’un d’eux ne traverse aucune zone, il est retenu sans calcul lent (routing._routes_avoiding) ; sinon ORS calcule avec un délai porté de 30 à 120 s (avant : Rennes → Nice et Lille → Marseille finissaient en erreur 400 « timed out » sur le PC). Mesures du calcul lent avec 4 zones au milieu du trajet : prod 24,6 s Rennes → Nice, 18,1 s Lille → Marseille ; PC 30 s et plus. Avec de simples carrés, GraphHopper les contournait de justesse (+1,2 km sur Paris → Lyon, en restant sur le tronçon). Depuis le 29/09 au soir (accord de Jérémy), étape intermédiaire GraphHopper (routing._gh_route_excluding) : sur le tracé habituel qui passe par le tronçon, un couloir de 250 m de part et d’autre (un rectangle par km) va d’un bout à l’autre du tronçon ; la requête « car » y interdit les routes à péage et les autoroutes (in_excl && (toll == ALL || road_class == MOTORWAY), priorité 0), les routes locales qui le traversent restent permises. Préparation « landmarks » ajoutée au profil car (profiles_lm de gh-config.yml, 22 min sur le PC pour la France ; étape activée seulement si GRAPHHOPPER_LM_EXCLUSION=true ; en prod depuis le 30/09/2026 : landmarks du graphe Europe préparés en 31 min sur une copie puis bascule, GraphHopper à 22 Go de mémoire ; exclusion mesurée en prod 1,5 s Paris → Lyon, 2,9 s Lille → Marseille, contre 18 à 25 s avec ORS sur les longs trajets) pour que cette requête sans raccourcis CH reste rapide ; les recherches habituelles restent en CH, inchangées. ORS n’est plus qu’un dernier recours (GraphHopper absent ou sans résultat, ou mode sans péage). Un seul calcul lent par trajet (le retour reprend l’aller à l’envers) ; depuis le 29/09 ces itinéraires sont gardés en mémoire (200 au plus, routing._avoid_cache) et deux requêtes identiques simultanées n’en lancent qu’un (les modes « le moins cher » et « le plus rapide » du même recalcul, un changement de mode ensuite). Exemple : Paris → Lyon sans le tronçon Fleury-en-Bière → Villefranche-Limas passe par l’A77 et l’A79 (499 km au lieu de 461, péage 28,67 € au lieu de 41,30 €). Exclusions propres au trajet, gardées dans l’adresse de la page et dans les favoris, oubliées après « Modifier mes trajets ou options » + nouveau calcul. Au passage, les gares d’un tronçon exact sont affichées dans le sens du trajet (la grille étant symétrique, Paris → Lyon affichait « Villefranche-Limas → Fleury-en-Bière »).scripts/fix_aprr_gare_names.py (simulation par défaut, --vraiment ; --gares --vraiment pour les gares). Restent sans gare : Amboise, Châlons La Veuve / Mourmelon, Fontenay-sur-Loing, Gidy, Gondreville A77, « La Folie-B/Paris », Reims Sud, Savigny, Saint-Germain-les-Vergne, Saint-Martin-Bellevue, Saint-Michel-de-Maurienne, Isle-d'Abeau Centre, Tours-Monnaie. A79 (ALIAE, Montmarault → Digoin, flux libre) importée le 29/09/2026 : la grille officielle (data/tolls/ALIAE-A79-Tarifs-2026.pdf, page 3, tableaux en image relevés à la main) donne les prix classe 1 entre ses 9 points (extrémités, échangeurs du Montet, de Montbeugny et de Molinet, bords des deux sections gratuites), modélisés comme un système fermé : 9 gares aux positions OpenStreetMap des portiques et échangeurs, 21 paires. Traversée complète 4,20 € (= portiques « Transit » 1,00 + 1,90 + 1,30 de la page 2). Tarif de base ; le tarif réduit des véhicules très faible émission (Crit'air 0, 3,20 € la traversée) n'est pas appliqué, comme ailleurs. Script : scripts/import_toll_a79.py. Jonction entre réseaux : un trou de moins de 5 km entre gares d'exploitants différents sans paire commune (fin de l'A79 à 2 km de la barrière APRR de Deux-Chaises) n'est pas estimé. Kembs → Limoges : 86,20 € avant ces corrections, 31,30 € en tarifs exacts (Fontaine-Larivière 3,10 + Saint-Maurice → Chalon Sud 20,70 + A79 4,20 + Deux-Chaises → Montluçon 3,30).estimated_price_eur) et n'affiche « ≈ » que si cette part atteint 10 % du montant (Lyon → Marseille : 0,42 € estimés sur 28,52 € s'affiche donc comme un tarif exact). Groupes : Vinci Autoroutes (ASF, Cofiroute, Escota, Arcour, Atlandes, Arcos, Albea — leurs grilles se recouvrent et l'import étiquette « ASF » des gares Cofiroute), APRR / AREA, Sanef / SAPN, les autres à part. Un tronçon estimé prend le groupe de sa gare de sortie (ou d'entrée) si elle a des tarifs, sinon celui du tronçon suivant. Exemple Rennes → Nice : 23 petits tronçons avant, 3 après (Vinci, APRR, Vinci), pour le même total (132,03 €) ; Paris → Marseille : 2 tronçons.| Technique | Gain observé | Impact sur le résultat |
|---|---|---|
| Regroupement des bornes par tranche de 3 km × opérateur | ≈ 4-5× sur la recherche de base (Lille → Aurillac : 577 → 271 bornes, 28,3 s → 6,7 s) | Heuristique : coût et abonnements identiques sur 11 trajets tests, mais une borne moins chère du même groupe peut être écartée. Premier suspect si un résultat semble étrange. |
| Étiquettes de Pareto + tolérances de dominance | évite l'explosion combinatoire (recherches de plus de 120 s ramenées à quelques secondes) | Résultat vérifié identique à une recherche quasi exacte tant que les tolérances (0,1 ct / 0,5 point) ne sont pas élargies. |
| Coupe des arêtes hors de portée | de dizaines de secondes à quelques secondes sur les longs trajets | Aucun : borne physique. |
| Consommation par tronçon : énergie cumulée précalculée à chaque borne (l'énergie d'un arc = une soustraction) ; profil de vitesse lu dans la réponse du routeur déjà demandée | aucun surcoût mesuré : recherche de plan Rennes → Lille 443 ms avec et sans profil ; requête ORS 24 ms contre 26 ms ; construction du profil ≈ 6 ms par itinéraire (mis en cache ensuite) | C'est le modèle de consommation lui-même qui change les résultats (section 3), pas l'optimisation. |
Optimisations de code (étiquettes à slots, comparaison par identité, tables précalculées, recherche binaire dans les courbes de charge, module natif Cython pour la sélection des tarifs) | quelques dizaines de % cumulés | Aucun : résultats vérifiés identiques (parité testée sur 200 000 cas pour les courbes). |
| Calculs en parallèle (processus séparés, jusqu'à 8 à la fois) | un aller-retour prenait ×6,7 le temps d'un aller simple avant ce changement ; nettement réduit depuis (mesure à refaire à chaque évolution) | Aucun : mêmes recherches, lancées ensemble (abonnements testés, variantes « sans abonnement », alternatives, nuit…). |
| Calculs d'un voyage hors du processus de l'appli (aller-retour, étapes ; cas de l'interface, sans alternatives de coût) : le calcul de base et le calcul « sans nouvel abonnement » partent en même temps dans deux processus, puis les abonnements testés, puis les comparaisons « sans abonnement / sans carte » (les calculs déjà faits, à jeu d'exclusions équivalent, ne sont pas refaits). Le processus de l'appli ne fait plus qu'orchestrer. | une recherche est du Python pur, tenue par le verrou global (GIL) : plusieurs requêtes simultanées faisaient la queue dans un seul processus alors que les autres cœurs restaient libres. Serveur OVH RISE-S (Ryzen 7 9700X), aller-retour de ≈ 500 km par sens : 5 requêtes simultanées 24 s → 6,6 s au pire, 10 simultanées 20 s → 3,4 s, un aller-retour seul 4,5 s → 2,2 s | Aucun : mêmes calculs, mêmes résultats (comparés sur 70 trajets : coûts, arrêts, abonnements, péages, durées identiques). Seul le calendrier change ; le calcul « sans nouvel abonnement » est lancé sans attendre le résultat de base (quelques secondes de processeur en plus quand il n'est finalement pas retenu). Avec les alternatives de coût demandées (compute_alternatives), l'ancien chemin séquentiel est conservé. |
Boucle de la recherche de plan en Cython (app/_search_native.pyx, chargée par planner/graph.py ; l'ancienne boucle Python reste comme repli et pour la recherche multi-abonnements) : exploration des arêtes de conduite, détection de dominance, tas et puissance moyenne de charge en C ; la sélection du tarif reste en Python, appelée une fois par recharge envisagée, et le TariffQuote n'est construit que pour les arrêts du plan final | recherche ×7 à ×8 plus rapide (aller-retour de 1 000 km : 2,7 s → 0,4 s sur le PC de développement). Serveur OVH, ~500 km par sens : aller simple 0,8 s → 0,4 s, aller-retour seul 2,3 s → 1,1 s, 5 allers-retours simultanés 5-7 s → 3,1 s au pire, 10 simultanés 3,5 s → 1,8 s (avant le passage à 5 abonnements testés) | Aucun : mêmes opérations flottantes dans le même ordre (compilation sans contraction FMA, sans -march=native), même ordre de sortie du tas, mêmes tolérances de dominance. 432 recherches complètes (3 véhicules, 8 trajets, 3 modes, exclusions/possessions d'abonnements, heure de départ, nombre d'arrêts) : 432 identiques, et 70 réponses d'API identiques. Le module compilé se reconstruit avec python setup_native.py build_ext --inplace (dossier app, paquet libc6-dev requis). |
| Appariement des gares de péage mémorisé par tracé (et projection précalculée) | ≈ 0,4 s de moins par calcul d'un aller-retour (10 appariements identiques par calcul ramenés à 2) | Aucun : même résultat, mémo de 2 minutes au plus. |
| Arrêt anticipé de la recherche du meilleur plan dès qu'aucun label restant ne peut arriver moins cher que la meilleure arrivée déjà trouvée | ≈ −40 % de temps et de processeur par recherche (aller simple Paris → Strasbourg : 3,0 s → 1,8 s sur le PC de développement) | Aucun : les labels sont traités par coût croissant et un coût ne fait que croître le long d'un chemin ; un label à coût égal reste exploré (départage par le niveau de batterie). Avant, la recherche continuait jusqu'à vider la file. |
| Recherche combinée mémoïsée par requête (mêmes exclusions d'abonnements, une fois normalisées) | une recherche complète de moins (≈ 2 s) quand le « sans abonnement » gagne et est recalculé pour l'affichage | Aucun : même clé = même résultat. |
| Gagnant de la recherche d'abonnements manqués : recherche réutilisée (le job qui a trouvé l'abonnement rentable transmet sa recherche ; seul l'assemblage affichable — péages, détails — est refait) | ≈ 1 s de moins sur les voyages où un abonnement « manqué » gagne (Lyon ↔ Toulouse sur le serveur : 5,1 s → 4,2 s), autant de processeur économisé | Aucun : même recherche, mêmes résultats (42 voyages comparés avec et sans réutilisation : 0 différence dans la réponse complète). |
Projection des bornes sur le tracé précalculée (geo.RouteProjector) | × 3,7 sur cette étape (≈ 0,25 s de moins par itinéraire) | Aucun : le point le plus proche est retrouvé par un classement approché puis départagé avec le vrai calcul de distance ; 0 différence sur 4 985 bornes testées. |
| Démarrage des workers par un serveur de forks préchargé (forkserver) au lieu d'un interpréteur neuf par worker (spawn) | chaque lot repayait ≈ 1 s de réimport de l'appli : 3 jobs d'un voyage Rennes → Lille passent de 2,8-3,4 s à 1,4 s | Aucun : mêmes calculs. Le serveur de forks ne garde aucune connexion base et s'arrête avec l'appli ; le pool reste créé et refermé par lot. Repli sur spawn si indisponible. |
| Itinéraires, bornes candidates et cache de tarifs transmis aux workers (un voyage calcule ses itinéraires une fois, pas une fois par abonnement testé) | ≈ 0,7 s de routage + requête spatiale évitées par job | Aucun : ces données ne dépendent pas de l'abonnement testé. |
| Péage non calculé pour les calculs « de test » (comparaisons de coût jetées) | ≈ 0,1 s par calcul | Aucun : le péage n'entre dans aucune de ces comparaisons ; il est calculé pour le résultat affiché. |
| Recherche fusionnée des trajets enchaînés | 1 recherche au lieu de N | Peut améliorer le résultat (optimum global), jamais le dégrader. |
| Retour = aller inversé | pas de nouvelle recherche d'itinéraire ni de couloir | Le retour ne peut plus choisir un autre corridor (voulu). |
| Alternatives sans coût | ≈ −35 % sur un aller-retour avec 4 itinéraires | Le coût des alternatives n'est plus affiché. |
| Caches : géocodage, itinéraires, lignes de tarifs par borne, résultats d'un même voyage | évite requêtes réseau et SQL répétées | Aucun, sauf tarifs modifiés en cours de journée (le cache est vidé à chaque redémarrage). |
| Bornes sûre / heuristique / top 5 pour les abonnements | de 15-20 recherches en série (plusieurs minutes) à un lot parallèle de quelques secondes | Bornes sûres : aucun. Borne heuristique et top 5 : rares abonnements manqués (voir section 8). |
| Variantes calculées à la demande côté interface | seules les variantes consultées sont calculées | Aucun. |
Le temps d'attente annoncé à l'écran est une estimation calibrée sur la durée des calculs précédents et sur la complexité du voyage (un aller-retour ou une étape allonge le calcul), pas une prédiction exacte.
Module app/ratelimit.py. Tout est en mémoire (un seul processus en production : compteurs partagés par construction, remis à zéro à chaque redémarrage ou mise en service). Coût par requête ≈ 20 µs, aucun effet mesurable sur la vitesse (serveur : aller simple 0,5 s, aller-retour 1,5 s, 5 allers-retours simultanés 3,7 s avant comme après). Un refus renvoie 429 (limite de débit) ou 503 (plafond de calculs simultanés) avec le corps {"detail", "retry_after"} et l'en-tête Retry-After (secondes).
X-Forwarded-For n'est cru que si la connexion directe vient d'un proxy de confiance (Caddy, réseau Docker) ; Caddy y met l'adresse du visiteur, on lit la dernière entrée. Un client qui l'envoie lui-même ne peut pas se faire passer pour une autre adresse. Les adresses de RATELIMIT_EXEMPT_CIDRS (boucle locale par défaut : tests de charge, sondes) ne sont jamais limitées ; en développement, y ajouter le réseau Docker./plan, /trip-plan, /multi-plan) : budget en jetons pondérés par le coût réel (1 par /plan, le nombre de trajets pour /trip-plan, la somme pour /multi-plan ; un aller-retour en 4 variantes = 8 jetons). Visiteur anonyme : 60 jetons/min et 600/h par adresse ; connecté : 180/min et 1 800/h par compte ; au plus 300/min par adresse dans tous les cas (plusieurs comptes depuis une même connexion).MAX_CONCURRENT_CALCULATIONS) ; au-delà, attente de 10 s au plus, puis 503. Sous charge normale (5 allers-retours simultanés) il n'intervient jamais./tariffs-map/station/{id}/tariffs, inutilisé par le site) est réservé à l'administrateur./multi-plan, pseudo 40, prénom 60, e-mail 254, mot de passe 128, nom d'un favori 120, nom du formulaire de contact 120, message 5 000 ; la configuration d'un favori et celle enregistrée dans le journal des recherches sont bornées à 20 000 caractères.Mesure d'audience sans cookie ni identifiant conservé (donc sans bandeau de consentement : conditions d'exemption de la CNIL). Code : app/stats.py (collecte, catégories, purge) et app/api/stats.py (POST /api/collect public, GET /api/admin/stats réservé à l'administrateur). Le script du site envoie un signe par changement de vue (Trajets / Bornes), un signe de vie toutes les 15 à 30 s et quelques événements (calcul lancé, résultats affichés, clic « S'abonner », favori enregistré, inscription, borne ouverte, lien partagé) ; il ne stocke rien sur l'appareil. L'appli Android envoie les mêmes signes (AppStats.kt) : le serveur la reconnaît à son identifiant « KargitApp-Android/… » et range ses visites à part (colonne platform = app, navigateur « Appli Android ») ; le bouton Site / Appli filtre toute la page (sauf inscriptions, favoris et trajets les plus calculés, dont l'origine n'est pas connue). L'opposition faite depuis les mentions légales ouvertes dans l'appli y arrête aussi la mesure.
a-z 0-9 _ — et des compteurs sont gardés). Par visite : appareil (mobile / tablette / ordinateur), navigateur, système, langue, domaine du referrer (jamais le chemin), vue d'entrée et vues consultées, pages vues, temps actif cumulé (au plus 30 s par signe de vie), compteurs d'événements.stats_salts) et supprimé après 48 h : deux jours ne peuvent plus être reliés, même avec la base. Le « visiteur » des totaux est donc la somme des visiteurs uniques par jour. Visite = même visiteur, coupée après 30 min sans signe.DNT: 1 ou Sec-GPC: 1 ; robots connus (user-agent) ou absence d'Accept-Language ; corps de plus de 1 Ko ou illisible ; plus de 120 signes/min pour une même adresse (limite propre, hors budget des calculs). L'opposition explicite passe par l'indicateur local kargit_no_stats=1 lu par le script du site.stats_sessions) 13 mois ; à la purge quotidienne (retention.py), les jours plus vieux sont cumulés dans stats_daily (chiffres par jour et répartitions, sans limite de durée) puis supprimés. Au-delà de 13 mois, la médiane de durée est approchée d'après les tranches de durée et il n'y a plus de détail par visite.POST /api/subscribe-click, qui incrémente un simple compteur par jour et par abonnement (subscribe_clicks) — aucune donnée sur la personne (ni IP, ni empreinte, ni visite). Mêmes filtres que la collecte (DNT / GPC, robots, opposition locale, corps de plus de 1 Ko) et au plus 20 clics/min comptés par adresse ; un identifiant inconnu est ignoré. La liste de l'onglet affiche, pour chaque abonnement, deux colonnes : le total et les 7 derniers jours (aujourd'hui compris, fuseau Europe/Paris) via GET /api/admin/subscribe-clicks. Compteurs démarrés le jour de la mise en ligne (pas de reprise des clics passés, qui n'étaient pas rattachés à un abonnement).Le géocodage transforme l'adresse tapée en coordonnées, une fois par adresse (résultat gardé en mémoire puis dans la table geocode_cache, donc une adresse déjà vue ne rappelle aucun service). Code : app/routing.py (geocode, geocoder_primary, _geocode_local).
kargit_photon ; garde-fou (29/09/2026) : si l'adresse finit par un pays étranger (« Genève, Suisse », « Salou, Tarragonais, Espagne ») et que Photon répond dans un autre pays, on passe au géocodeur suivant, LocationIQ, qui couvre le monde (routing._requested_foreign_country ; avec l'index France seule, « München, Bayern, Deutschland » tombait dans le Pas-de-Calais) ; à l'inverse, sans pays nommé, si la première réponse est étrangère et qu'une ville française du même nom figure parmi les 5 premières, elle l'emporte (« Valence » → Drôme et non Valencia ; un simple village homonyme ne suffit pas : « Cologne » reste Köln, « Mons » la ville belge), gratuit, sans quota). Le choix est enregistré en base (app_settings, clé geocoder) et pris en compte en moins de 30 s par tous les processus (relecture périodique) : repasser d'un service à l'autre = un clic.GEOCODER_DEFAULT_PRIMARY). L'admin refuse de choisir « Local » si Photon ne répond pas (/status) ou si GEOCODER_LOCAL_URL est absent.deploy/server/photon-install.sh (téléchargement du dump OpenStreetMap publié par GraphHopper, vérification md5, import France + les 10 pays voisins (COUNTRY_CODES, noms en français, anglais, allemand, italien, espagnol et néerlandais ; France seule jusqu'au 29/09/2026 : « Bruxelles », « Munich », « Londres » donnaient un hameau français homonyme) avec un nouvel index construit à côté, puis vidage du cache des adresses (table geocode_cache et mémoire du backend, redémarré), qui gardait les réponses de l'ancien index, puis bascule sans coupure). Le service se déclare dans docker-compose.prod.yml (profil geocoding, ajouté à COMPOSE_PROFILES dans /opt/kargit/.env). Automatique tous les 3 mois (cron posé par deploy/release.sh, 1er janvier/avril/juillet/octobre à 1h15) ; visible avec sa date, sa durée et le nombre de lieux dans l'onglet « Tâches planifiées » de l'admin (état écrit par le script, jamais déclenché par le backend -- même principe que les sauvegardes). Priorité volontairement basse (site en production sur le même serveur) : téléchargement bridé en débit, décompactage et import à priorité CPU/disque minimale -- s'effacent automatiquement dès qu'une recherche a besoin de la machine, la tâche prend juste plus longtemps (1h30 à 2h30 environ) plutôt que de ralentir un visiteur.scripts/compare_geocoders.py) : 42 résultats identiques au mètre près, la plupart des autres à moins de 1 km ; Photon répond en 16 ms en médiane contre 200 ms pour LocationIQ (jusqu'à 1,2 s pour ce dernier). Photon est meilleur sur les fautes de frappe (« Marseile », « Bordeau », « Strasbourgh » trouvés, LocationIQ se trompe ou échoue), les codes postaux seuls (« 35000 » → Rennes ; LocationIQ répondait en Algérie) et « Gare de Rennes » (10 km d'écart, Photon est sur la gare). Points faibles de Photon : une adresse dont le numéro n'existe pas dans OpenStreetMap tombe sur la rue ou sur un commerce voisin (1 à 5 km d'écart sur les gros numéros de rues très longues : « 20 rue Nationale, Lille », « lyon 3eme » → une agence). Index France : 19,5 millions de lieux, 5,8 Go sur disque, environ 4 Go de mémoire pour le service.scripts/compare_geocoders.py compare les deux services sur une soixantaine d'adresses.GET /api/suggest, GET /api/suggest/reverse -- app/api/suggest.py) : Géoplateforme + notre géocodeur local en parallèle pour la France (même charge sur ces deux services qu'avant, juste depuis notre IP) ; Photon PUBLIC (reste de l'Europe) en plus, en parallèle, mais en meilleur effort non bloquant (sauté sans attendre si sollicité il y a moins de 300 ms) pour ne jamais multiplier par le nombre de visiteurs le trafic vu par ce service public gratuit depuis une seule IP serveur ; Nominatim en tout dernier recours. Limite dédiée (SUGGEST_RULES, app/ratelimit.py) : 12 requêtes/10 s puis 60/min par IP. Pas encore branché côté site (le sélecteur de suggestions appelle encore les services tiers directement) -- CSP (connect-src) à resserrer une fois que ce sera fait.Kargit en anglais, allemand, espagnol, italien et néerlandais (kargit-ev.com, kargit.uk, kargit.de, kargit.es, kargit.it, kargit.nl, kargit.be/fr|nl, kargit.ch/de|fr|it) -- détails dans docs/LOCALISATION.md. Le calcul est le même partout : seul l'affichage change.
GET /api/fx, app/api/fx.py, gardé 6 h, taux de secours si la BCE ne répond pas). Un montant converti est donc arrondi à l'affichage ; il n'influence jamais le choix des bornes.assets/i18n.js) ne tourne que sur les sites étrangers ; kargit.fr ne fait rien de plus.