Ajout suivi prospection : statut, import en masse, message type, trajet+péage

- Statut de prospection (colonne CSV) avec badge coloré et filtre
- Import en masse de liens Facebook (streaming, dédoublonnage)
- Modèle de message de contact configurable + copie en un clic
- Estimation distance/carburant/péage via OpenStreetMap (Nominatim + OSRM)
- Section Paramètres + config.json (non versionné)
This commit is contained in:
jerem
2026-06-13 15:28:25 +02:00
parent 1e57e56643
commit 1cf427a0f2
6 changed files with 671 additions and 120 deletions

108
trajet.py Normal file
View File

@@ -0,0 +1,108 @@
"""Distance routière et coût carburant entre deux adresses (OpenStreetMap : Nominatim + OSRM).
Aucune dépendance ni clé d'API : on utilise les services publics gratuits d'OpenStreetMap
via la bibliothèque standard. Usage personnel et ponctuel (un prospect à la fois).
"""
import json
import re
import urllib.parse
import urllib.request
NOMINATIM = "https://nominatim.openstreetmap.org/search"
OSRM = "https://router.project-osrm.org/route/v1/driving"
# Nominatim impose un User-Agent identifiant l'application
ENTETES = {"User-Agent": "AutoMood/1.0 (outil local de prospection)"}
# Les coordonnées d'une adresse ne changent pas : on évite de re-géocoder (et on
# reste poli envers Nominatim, dont l'usage est limité à ~1 requête/seconde).
_cache_geo = {}
class ErreurTrajet(Exception):
pass
def _http_json(url):
requete = urllib.request.Request(url, headers=ENTETES)
try:
with urllib.request.urlopen(requete, timeout=20) as reponse:
return json.load(reponse)
except Exception as e:
raise ErreurTrajet(f"Service de cartographie injoignable : {e}")
def geocoder(adresse):
"""Adresse texte -> (longitude, latitude). Lève ErreurTrajet si introuvable."""
adresse = (adresse or "").strip()
if not adresse:
raise ErreurTrajet("Adresse vide.")
if adresse in _cache_geo:
return _cache_geo[adresse]
params = urllib.parse.urlencode({
"q": adresse, "format": "json", "limit": 1, "countrycodes": "fr",
})
donnees = _http_json(f"{NOMINATIM}?{params}")
if not donnees:
raise ErreurTrajet(f"Adresse introuvable : « {adresse} ».")
coord = (float(donnees[0]["lon"]), float(donnees[0]["lat"]))
_cache_geo[adresse] = coord
return coord
def _est_autoroute(ref):
"""Vrai si le numéro de route est une autoroute française (« A 11 », « A8;E60 »...).
Heuristique : la plupart des autoroutes « A » sont à péage. Certaines sont
gratuites (rocades, sections urbaines) : l'estimation reste donc approximative.
"""
return any(re.match(r"A\s?\d", t.strip(), re.I) for t in re.split(r"[;,]", ref or ""))
def itineraire(depart, arrivee):
"""(lon, lat) x2 -> (distance_km, duree_min, km_autoroute) du trajet le plus court."""
(lon1, lat1), (lon2, lat2) = depart, arrivee
donnees = _http_json(f"{OSRM}/{lon1},{lat1};{lon2},{lat2}?overview=false&steps=true")
if donnees.get("code") != "Ok" or not donnees.get("routes"):
raise ErreurTrajet("Aucun itinéraire routier trouvé entre ces deux adresses.")
route = donnees["routes"][0]
metres_autoroute = sum(
etape.get("distance", 0)
for jambe in route.get("legs", [])
for etape in jambe.get("steps", [])
if _est_autoroute(etape.get("ref"))
)
return route["distance"] / 1000.0, route["duration"] / 60.0, metres_autoroute / 1000.0
def calculer(adresse_depart, adresse_arrivee, conso_l_100km, prix_carburant, cout_peage_km=0.0):
"""Estime distance, durée, coût carburant et péage (aller simple et aller-retour).
Le péage est estimé : km d'autoroute du trajet × `cout_peage_km` (tarif moyen
paramétrable, en €/km). Mettre 0 pour ne pas compter de péage.
"""
distance_km, duree_min, km_peage = itineraire(
geocoder(adresse_depart), geocoder(adresse_arrivee))
carburant = distance_km / 100.0 * conso_l_100km * prix_carburant
peage = km_peage * cout_peage_km
cout_aller = carburant + peage
return {
"distance_km": round(distance_km, 1),
"distance_aller_retour_km": round(distance_km * 2, 1),
"duree_min": round(duree_min),
"km_peage": round(km_peage, 1),
"carburant_aller": round(carburant, 2),
"carburant_aller_retour": round(carburant * 2, 2),
"peage_aller": round(peage, 2),
"peage_aller_retour": round(peage * 2, 2),
"cout_aller": round(cout_aller, 2),
"cout_aller_retour": round(cout_aller * 2, 2),
}
if __name__ == "__main__":
import sys
if len(sys.argv) != 3:
print("Usage : python trajet.py <adresse-depart> <adresse-arrivee>")
sys.exit(1)
print(json.dumps(calculer(sys.argv[1], sys.argv[2], 6.5, 1.90), ensure_ascii=False, indent=2))