Calcul de trajet auto après scrape + recalcul local intelligent
- trajet.py : fonction couts() pure (calcul des coûts sans appel réseau) - app.py : mémorisation distance/durée/péage + adresses dans le CSV ; auto-calcul best-effort après ajout (scrape simple et en lot) ; api_distance recalcule en local si adresses inchangées, complet sinon - index.html : affichage auto du trajet en cache à l'ouverture du prospect
This commit is contained in:
93
app.py
93
app.py
@@ -26,6 +26,8 @@ COLONNES = [
|
||||
"Nom du prospect", "Statut", "Département", "Ville", "Code Postal", "Adresse",
|
||||
"Date d'ajout", "Date de contact", "Nom de contact",
|
||||
"Téléphone", "Email", "Infos du lieu", "Type", "Lien Facebook", "Notes",
|
||||
"Trajet distance km", "Trajet durée min", "Trajet km péage",
|
||||
"Trajet adresse départ", "Trajet adresse arrivée",
|
||||
]
|
||||
STATUT_DEFAUT = "À contacter"
|
||||
|
||||
@@ -49,6 +51,8 @@ CONFIG_DEFAUT = {
|
||||
app = Flask(__name__, static_folder="static")
|
||||
verrou_scrape = threading.Lock()
|
||||
verrou_csv = threading.Lock()
|
||||
# Sérialise les calculs de trajet (un appel Nominatim/OSRM à la fois, ~1 req/s).
|
||||
verrou_trajet = threading.Lock()
|
||||
|
||||
|
||||
def lire_prospects():
|
||||
@@ -121,6 +125,56 @@ def ecrire_config(config):
|
||||
os.replace(tmp, CONFIG_PATH)
|
||||
|
||||
|
||||
def _adresse_arrivee(p):
|
||||
"""Reconstruit l'adresse d'arrivée d'un prospect (Adresse, Code Postal, Ville)."""
|
||||
return ", ".join(
|
||||
x.strip() for x in (p.get("Adresse"), p.get("Code Postal"), p.get("Ville")) if x and x.strip())
|
||||
|
||||
|
||||
def _tarifs(config):
|
||||
"""(conso L/100km, prix €/L, péage €/km) depuis la config, avec replis."""
|
||||
return (
|
||||
float(config.get("conso_l_100km") or 0) or CONFIG_DEFAUT["conso_l_100km"],
|
||||
float(config.get("prix_carburant") or 0) or CONFIG_DEFAUT["prix_carburant"],
|
||||
float(config.get("cout_peage_km") or 0), # 0 = pas de péage compté
|
||||
)
|
||||
|
||||
|
||||
def _ecrire_colonnes_trajet(ligne, depart, arrivee, res):
|
||||
"""Mémorise dans une ligne les données géo + adresses utilisées pour le calcul."""
|
||||
ligne["Trajet distance km"] = str(res["distance_km"])
|
||||
ligne["Trajet durée min"] = str(res["duree_min"])
|
||||
ligne["Trajet km péage"] = str(res["km_peage"])
|
||||
ligne["Trajet adresse départ"] = depart
|
||||
ligne["Trajet adresse arrivée"] = arrivee
|
||||
|
||||
|
||||
def _auto_trajet(ligne, config):
|
||||
"""Calcule et mémorise le trajet d'un prospect fraîchement ajouté (best-effort).
|
||||
|
||||
Conçu pour tourner dans un thread daemon : un échec réseau ne doit jamais
|
||||
affecter le scrape. `verrou_trajet` sérialise les appels Nominatim/OSRM.
|
||||
"""
|
||||
try:
|
||||
depart = (config.get("adresse_depart") or "").strip()
|
||||
arrivee = _adresse_arrivee(ligne)
|
||||
lien = (ligne.get("Lien Facebook") or "").strip()
|
||||
if not depart or not arrivee:
|
||||
return
|
||||
with verrou_trajet:
|
||||
res = trajet.calculer(depart, arrivee, *_tarifs(config))
|
||||
with verrou_csv:
|
||||
lignes = lire_prospects()
|
||||
cible = next(
|
||||
(l for l in lignes if (l.get("Lien Facebook") or "").strip() == lien), None
|
||||
) if lien else None
|
||||
if cible is not None:
|
||||
_ecrire_colonnes_trajet(cible, depart, arrivee, res)
|
||||
ecrire_prospects(lignes)
|
||||
except Exception:
|
||||
pass # best-effort : le trajet sera calculé au clic si besoin
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def accueil():
|
||||
return app.send_static_file("index.html")
|
||||
@@ -186,6 +240,7 @@ def api_ajouter():
|
||||
lignes = lire_prospects()
|
||||
lignes.append(ligne)
|
||||
ecrire_prospects(lignes)
|
||||
threading.Thread(target=_auto_trajet, args=(ligne, lire_config()), daemon=True).start()
|
||||
return jsonify({"ok": True, "index": len(lignes) - 1})
|
||||
|
||||
|
||||
@@ -274,9 +329,11 @@ def _ajouter_resultat_lot(resultat):
|
||||
journaliser(url, "doublon", champs["Nom du prospect"])
|
||||
return {"url": url, "statut": "doublon", "nom": champs["Nom du prospect"]}
|
||||
champs["Statut"] = STATUT_DEFAUT
|
||||
lignes.append({col: champs.get(col, "") for col in COLONNES})
|
||||
ligne = {col: champs.get(col, "") for col in COLONNES}
|
||||
lignes.append(ligne)
|
||||
ecrire_prospects(lignes)
|
||||
journaliser(url, "ajoute", champs["Nom du prospect"])
|
||||
threading.Thread(target=_auto_trajet, args=(ligne, lire_config()), daemon=True).start()
|
||||
return {"url": url, "statut": "ajoute", "nom": champs["Nom du prospect"]}
|
||||
|
||||
|
||||
@@ -377,24 +434,42 @@ def api_distance(idx):
|
||||
"error": "pas_de_depart",
|
||||
"message": "Renseignez d'abord votre adresse de départ dans les Paramètres.",
|
||||
}), 400
|
||||
arrivee = ", ".join(x.strip() for x in (p.get("Adresse"), p.get("Code Postal"), p.get("Ville")) if x and x.strip())
|
||||
arrivee = _adresse_arrivee(p)
|
||||
if not arrivee:
|
||||
return jsonify({
|
||||
"error": "pas_d_adresse",
|
||||
"message": "Ce prospect n'a pas d'adresse ou de ville exploitable.",
|
||||
}), 400
|
||||
conso, prix, peage = _tarifs(config)
|
||||
|
||||
# Recalcul local : les données géo en cache restent valides tant que les deux
|
||||
# adresses n'ont pas changé. On réapplique simplement les prix courants.
|
||||
if (p.get("Trajet distance km")
|
||||
and p.get("Trajet adresse départ") == depart
|
||||
and p.get("Trajet adresse arrivée") == arrivee):
|
||||
try:
|
||||
return jsonify({**trajet.couts(
|
||||
float(p["Trajet distance km"]),
|
||||
float(p.get("Trajet durée min") or 0),
|
||||
float(p.get("Trajet km péage") or 0),
|
||||
conso, prix, peage,
|
||||
), "source": "local"})
|
||||
except (TypeError, ValueError):
|
||||
pass # données en cache illisibles : on bascule sur un recalcul complet
|
||||
|
||||
# Recalcul complet : appels Nominatim/OSRM, puis mémorisation des données géo.
|
||||
try:
|
||||
resultat = trajet.calculer(
|
||||
depart, arrivee,
|
||||
float(config.get("conso_l_100km") or 0) or CONFIG_DEFAUT["conso_l_100km"],
|
||||
float(config.get("prix_carburant") or 0) or CONFIG_DEFAUT["prix_carburant"],
|
||||
float(config.get("cout_peage_km") or 0), # 0 = pas de péage compté
|
||||
)
|
||||
return jsonify(resultat)
|
||||
resultat = trajet.calculer(depart, arrivee, conso, prix, peage)
|
||||
except trajet.ErreurTrajet as e:
|
||||
return jsonify({"error": "trajet", "message": str(e)}), 502
|
||||
except Exception as e:
|
||||
return jsonify({"error": "erreur", "message": f"Échec du calcul : {e}"}), 500
|
||||
with verrou_csv:
|
||||
lignes = lire_prospects()
|
||||
if 0 <= idx < len(lignes):
|
||||
_ecrire_colonnes_trajet(lignes[idx], depart, arrivee, resultat)
|
||||
ecrire_prospects(lignes)
|
||||
return jsonify({**resultat, "source": "api"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user