{"openapi":"3.0.1","info":{"title":"spring-org API","description":"API **100 % locale** (aucune dependance a une API externe a l'execution) reunissant trois briques :\n\n1. **Routing** (`/routing`) — calcul d'itineraire et de matrices de temps via GraphHopper embarque, a partir d'un extrait OpenStreetMap de la France.\n2. **Optimisation** (`/optimization`) — resolution de tournees (VRP/TSP) via Timefold : ordre de passage optimal de N points, sous contrainte de capacite, en minimisant le temps de conduite.\n3. **Geocoding** (`/geocoding`) — recherche / autocompletion d'adresse via un index Lucene alimente par la Base Adresse Nationale (BAN).\n\n### Donnees\nLes fichiers (OSM ~5 Go, BAN ~900 Mo) sont **telecharges automatiquement** au demarrage s'ils manquent, et mis a jour **tous les dimanches a 8h** (heure de Paris). Pendant qu'une brique n'a pas ses donnees, ses endpoints renvoient **503** (corps `application/problem+json`) ; consulter `GET /status` de chaque brique.\n\n### Codes de reponse transverses\n- `200` succes\n- `400` requete invalide (validation des champs)\n- `503` sous-systeme indisponible (donnees absentes / en cours de (re)construction)\n","contact":{"name":"StackBZH"},"license":{"name":"Proprietaire"},"version":"0.1.0"},"servers":[{"url":"http://ors.stack.bzh","description":"Generated server url"}],"tags":[{"name":"Routing","description":"Calcul d'itineraires et de matrices de temps via GraphHopper embarque (extrait OpenStreetMap de la France). Necessite que le graphe routier soit charge (voir GET /routing/status). Sinon : 503."},{"name":"Statut","description":"Etat de l'application : sous-systemes, telechargements, memoire JVM, donnees."},{"name":"Optimisation","description":"Optimisation de tournees (VRP/TSP) via Timefold. Determine l'ordre de passage optimal des points (et leur repartition entre vehicules) en minimisant le temps de conduite total, sous contrainte de capacite. S'appuie sur le moteur de routing pour la matrice de temps : si le routing est indisponible, renvoie 503."},{"name":"Geocoding","description":"Recherche / autocompletion d'adresse via un index Lucene alimente par la Base Adresse Nationale (BAN). Tolerant aux accents (normalisation NFD) ; le dernier mot saisi est traite en prefixe pour l'autocompletion type GPS. Necessite l'index charge (voir GET /geocoding/status). Sinon : 503."}],"paths":{"/routing/route":{"post":{"tags":["Routing"],"summary":"Itineraire point a point ou multi-points ordonne","description":"Calcule un itineraire routier. Deux modes (cf. corps de requete) :\n\n- **point a point** : fournir `from` et `to` ;\n- **multi-points ordonne** : fournir `points` (>= 2 elements). L'itineraire passe par tous les points DANS L'ORDRE FOURNI (aucune reoptimisation : pour reordonner, utiliser POST /optimization/optimize). Chaque paire consecutive est routee, puis les troncons sont concatenes en une trace continue (sans point en double aux jonctions).\n\nRetour : `distanceMeters` et `durationSeconds` cumules sur tout le parcours, plus la geometrie. `geometry` (liste [lat,lon]) est renseigne si `geometryFormat=POINTS` ; `geometryPolyline` (encodage Google/OSRM, precision 5, ordre lat/lon) est renseigne pour POINTS comme POLYLINE (null seulement si NONE).","operationId":"route","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteRequest"}}},"required":true},"responses":{"200":{"description":"Itineraire calcule","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}},"400":{"description":"Corps invalide : ni (`from`+`to`) ni `points` (>= 2) fournis, ou coordonnees invalides"},"503":{"description":"Moteur de routing indisponible (graphe non charge)"}}}},"/routing/matrix":{"post":{"tags":["Routing"],"summary":"Matrice des temps de trajet","description":"Calcule la matrice NxN des temps de trajet (secondes) entre tous les points fournis.\n`durationsSeconds[i][j]` = temps pour aller du point i au point j (peut etre asymetrique a cause des sens uniques ; la diagonale vaut 0).\nUtilise en amont d'une optimisation. Cout : N*N routages (rapide grace aux Contraction Hierarchies).","operationId":"matrix","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixRequest"}}},"required":true},"responses":{"200":{"description":"Matrice calculee","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MatrixResponse"}}}},"503":{"description":"Moteur de routing indisponible"},"400":{"description":"Liste de points vide ou invalide"}}}},"/optimization/optimize":{"post":{"tags":["Optimisation"],"summary":"Optimiser une tournee","description":"Optimise l'ordre de passage d'une liste de points depuis/vers un depot commun.\n\nDeroulement interne :\n1. construction des points (depot + visites) ;\n2. calcul de la matrice des temps reels via le routing local (GraphHopper) ;\n3. resolution du VRP avec Timefold (duree allouee = `timefold.solver.termination.spent-limit`, 5 s par defaut) ;\n4. renvoi des tournees ordonnees par vehicule.\n\nCas d'usage typiques :\n- **1 vehicule, capacite illimitee** : simple optimisation d'ordre (TSP). Omettre `vehicleCapacity` (= illimite, aucune contrainte de charge) et `demand` (= 0). L'ordre optimal de passage est `routes[0].stops[].visitId` dans l'ordre du tableau.\n- **N vehicules + `vehicleCapacity` + `demand`** : repartition capacitaire (CVRP).\n\nSemantique des valeurs par defaut : `vehicleCount` omis = 1 ; `vehicleCapacity` omis = illimite ; `demand` omis = 0 ; `serviceDurationSeconds` omis = 0 ; `departureTime` omis = heure courante du serveur. L'ordre des arrets retourne EST l'ordre de passage optimal.\n\nNote : avec capacite illimitee et plusieurs vehicules, le solveur tend a n'en utiliser qu'un (chaque vehicule ajoute un aller-retour au depot).\n\nLa reponse est enrichie pour l'affichage : pour chaque segment (point precedent -> point), distance (m), duree (s) et **geometrie** ; pour chaque arret, distance/temps cumules et **heures d'arrivee et de depart** (a partir de `departureTime` + `serviceDurationSeconds`). Chaque `routes[]` expose aussi **`geometry`/`geometryPolyline`** : la trace COMPLETE de la tournee (depot -> arrets -> depot), a utiliser pour afficher le trace global d'un seul trait (les polylignes par segment ne se concatenent pas). Mettre `geometryFormat=NONE` (ou `includeGeometry=false`) pour alleger la reponse.\n\nPoints non rattachables au reseau routier (tolerance) : avant l'optimisation, chaque point est teste contre le reseau routier. Une visite dont les coordonnees ne peuvent PAS etre rattachees a une route (en mer, hors zone OSM couverte, reseau deconnecte) ou dont la route la plus proche depasse le seuil `app.routing.max-snap-distance-meters` (1000 m par defaut) est **ECARTEE** de l'optimisation : elle n'apparait dans aucune tournee et est listee dans **`skippedVisits[]`** (avec `reason` = `UNROUTABLE` ou `TOO_FAR` et la distance de rattachement). Une seule visite invalide ne fait donc PLUS echouer toute la requete (plus de 503 pour ce motif) : la tournee est calculee avec les points valides restants. Le client DOIT inspecter `skippedVisits` et signaler/corriger ces points. Si TOUTES les visites sont ecartees, la reponse est un 200 avec `routes` vide et tous les points dans `skippedVisits`. En revanche, si c'est le **depot** qui n'est pas rattachable, l'optimisation est impossible -> **400**.","operationId":"optimize","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OptimizeRequest"}}},"required":true},"responses":{"400":{"description":"Depot manquant/non rattachable au reseau routier, ou liste de visites vide"},"200":{"description":"Tournee(s) optimisee(s). Peut inclure `skippedVisits` (points ecartes car non rattachables au reseau routier ou trop eloignes).","content":{"*/*":{"schema":{"$ref":"#/components/schemas/OptimizeResponse"}}}},"503":{"description":"Routing indisponible (matrice non calculable)"}}}},"/status":{"get":{"tags":["Statut"],"summary":"Etat complet de l'API","description":"Statut global, etat de chaque sous-systeme, telechargements en cours, memoire JVM, presence/taille des fichiers de donnees. Le tableau de bord '/' interroge cet endpoint.","operationId":"status","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}}}},"/routing/status":{"get":{"tags":["Routing"],"summary":"Etat du moteur de routing","description":"Indique si le graphe routier est charge (`ready=true`) et le profil utilise (`car`). A interroger avant tout appel de routing : si `ready=false`, les autres endpoints renvoient 503.","operationId":"status_1","responses":{"200":{"description":"Etat retourne","content":{"*/*":{"schema":{"type":"string","example":{"ready":true,"profile":"car"}}}}}}}},"/geocoding/status":{"get":{"tags":["Geocoding"],"summary":"Etat de l'index d'adresses","description":"Indique si l'index Lucene est charge (`ready=true`). Si `false`, `/search` renvoie 503.","operationId":"status_2","responses":{"200":{"description":"Etat retourne","content":{"*/*":{"schema":{"type":"string","example":{"ready":true}}}}}}}},"/geocoding/search":{"get":{"tags":["Geocoding"],"summary":"Rechercher une adresse","description":"Recherche plein texte avec autocompletion. Renvoie les resultats les plus pertinents (tries par score decroissant), avec leurs coordonnees GPS.\n\n**Niveau de resultat (voie vs adresse precise)** — automatique selon la requete :\n- Si la requete ne contient **aucun numero de voie** (ex : `rue du bocage`), les resultats sont **agreges au niveau de la voie** : une seule entree par rue distincte (commune + code postal), `type=street`, `houseNumber` vide, libelle sans numero. On ne renvoie donc plus une longue liste de numeros d'une meme rue, mais la liste des rues qui existent.\n- Si la requete contient un numero (ex : `12 rue du bocage`), les resultats sont des **adresses precises** (`type=housenumber`), comme auparavant. Un numero est un jeton de 1 a 4 chiffres (eventuellement suivi d'une lettre : `12b`) ; un code postal a 5 chiffres n'est pas considere comme un numero.\n\n**Pertinence** — la voie dont le nom correspond exactement au texte saisi (numero ignore) est privilegiee, devant les correspondances partielles (ex : `rue du bocage` fait remonter les `Rue du Bocage` avant `Rue du Parc du Bocage`).\n\n**Biais de proximite (optionnel)** — si `lat` et `lon` sont fournis, les resultats sont classes en favorisant les plus proches de cette position (distance reelle ponderant le score). Chaque resultat expose alors `distanceMeters`. La position **n'est pas obligatoire** ; sans elle, le classement reste purement textuel et `distanceMeters` vaut `null`. Fournir `lat` sans `lon` (ou l'inverse), ou des coordonnees hors bornes WGS84, renvoie **400**.\n\nExemples :\n- `q=rue du bocage` -> liste des rues du Bocage (une par commune), sans numero\n- `q=rue du bocage&lat=48.11&lon=-1.68` -> les rues du Bocage les plus proches d'abord\n- `q=12 rue de la paix par` -> autocomplete adresse sur le dernier mot (`par`...)\n\nChaque mot complet doit matcher (AND) ; le dernier mot est traite en prefixe (pas de tolerance aux fautes de frappe : `bocaje` ne trouve pas `bocage`).","operationId":"search","parameters":[{"name":"q","in":"query","description":"Texte recherche (adresse, rue, ville...). L'autocompletion s'applique au dernier mot.","required":true,"schema":{"type":"string"},"example":"rue du bocage"},{"name":"limit","in":"query","description":"Nombre maximum de resultats (1 a 50).","required":false,"schema":{"type":"integer","format":"int32","default":10},"example":10},{"name":"lat","in":"query","description":"Latitude WGS84 de la position de reference (optionnel). Si fournie, `lon` doit l'etre aussi.","required":false,"schema":{"type":"number","format":"double"},"example":48.1147},{"name":"lon","in":"query","description":"Longitude WGS84 de la position de reference (optionnel). Si fournie, `lat` doit l'etre aussi.","required":false,"schema":{"type":"number","format":"double"},"example":-1.6794}],"responses":{"200":{"description":"Liste de resultats (peut etre vide)","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AddressResult"}}}}},"503":{"description":"Index d'adresses indisponible"},"400":{"description":"Parametres invalides (ex : lat sans lon, hors bornes)"}}}}},"components":{"schemas":{"Coordinate":{"required":["lat","lon"],"type":"object","properties":{"lat":{"type":"number","description":"Latitude en degres decimaux.","format":"double","example":48.1173},"lon":{"type":"number","description":"Longitude en degres decimaux.","format":"double","example":-1.6778}},"description":"Point geographique en coordonnees WGS84.","nullable":true},"RouteRequest":{"type":"object","properties":{"from":{"$ref":"#/components/schemas/Coordinate"},"to":{"$ref":"#/components/schemas/Coordinate"},"points":{"type":"array","description":"Liste ordonnee des points de passage (mode multi-points). >= 2 elements. L'ordre est respecte tel quel ; distance/duree sont cumulees et la geometrie est une trace continue de bout en bout. Prioritaire sur `from`/`to` si fournie.","nullable":true,"items":{"$ref":"#/components/schemas/Coordinate"}},"geometryFormat":{"type":"string","description":"Format de la geometrie :\n- POINTS   : liste de points [lat,lon] (champ `geometry`). Lisible mais volumineux.\n- POLYLINE : chaine encodee (champ `geometryPolyline`), algorithme Google/OSRM precision 5 (~1 m), bien plus compacte pour les longs trajets. Decodable avec @mapbox/polyline, etc.\n- NONE     : pas de geometrie (reponse la plus legere).","nullable":true,"default":"POINTS","enum":["POINTS","POLYLINE","NONE"]}},"description":"Demande d'itineraire. Deux modes mutuellement exclusifs :\n- point a point : renseigner `from` et `to` ;\n- multi-points ordonne : renseigner `points` (>= 2 elements). L'itineraire passe par tous les points DANS L'ORDRE FOURNI (aucune reoptimisation ; pour reordonner, utiliser /optimization/optimize). Si `points` est present, il prime sur `from`/`to`."},"RouteResponse":{"type":"object","properties":{"distanceMeters":{"type":"number","description":"Distance totale en metres.","format":"double","example":104532.0},"durationSeconds":{"type":"integer","description":"Duree totale en secondes.","format":"int64","example":4380},"geometryFormat":{"type":"string","description":"Format de geometrie demande : POINTS, POLYLINE ou NONE. Rappel : `geometryPolyline` est renseigne pour POINTS et POLYLINE.","example":"POINTS"},"geometry":{"type":"array","description":"Trace en liste de points [latitude, longitude]. Non nul uniquement si format = POINTS. Sur un trajet multi-points, c'est la trace continue de bout en bout (sans doublon aux jonctions).","nullable":true,"example":[[48.1173,-1.6778],[48.0,-1.7],[47.2184,-1.5536]],"items":{"type":"array","description":"Trace en liste de points [latitude, longitude]. Non nul uniquement si format = POINTS. Sur un trajet multi-points, c'est la trace continue de bout en bout (sans doublon aux jonctions).","nullable":true,"example":[[48.1173,-1.6778],[48.0,-1.7],[47.2184,-1.5536]],"items":{"type":"number","description":"Trace en liste de points [latitude, longitude]. Non nul uniquement si format = POINTS. Sur un trajet multi-points, c'est la trace continue de bout en bout (sans doublon aux jonctions).","format":"double","nullable":true}}},"geometryPolyline":{"type":"string","description":"Trace en polyligne encodee. Renseigne (non nul) pour les formats POINTS et POLYLINE ; null uniquement si format = NONE. Encodage : algorithme Google/OSRM, precision 5 (facteur 1e5), deltas dans l'ordre (latitude, longitude). Decodable par @mapbox/polyline. Sur un trajet multi-points, couvre tout le parcours.","nullable":true,"example":"ydlrHnwfA~A_@dGsT"}},"description":"Itineraire calcule entre deux points."},"MatrixRequest":{"required":["points"],"type":"object","properties":{"points":{"type":"array","description":"Liste des points (au moins 1). L'ordre est conserve dans la matrice resultat.","items":{"$ref":"#/components/schemas/Coordinate"}}},"description":"Demande de matrice de temps entre N points."},"MatrixResponse":{"type":"object","properties":{"size":{"type":"integer","description":"Nombre de points (N).","format":"int32","example":3},"durationsSeconds":{"type":"array","description":"Temps de trajet en secondes : durationsSeconds[i][j] = i -> j. Diagonale = 0.","example":[[0,4380,9200],[4350,0,8100],[9150,8050,0]],"items":{"type":"array","description":"Temps de trajet en secondes : durationsSeconds[i][j] = i -> j. Diagonale = 0.","example":[[0,4380,9200],[4350,0,8100],[9150,8050,0]],"items":{"type":"integer","description":"Temps de trajet en secondes : durationsSeconds[i][j] = i -> j. Diagonale = 0.","format":"int64"}}}},"description":"Matrice NxN des temps de trajet."},"OptimizeRequest":{"required":["depot","visits"],"type":"object","properties":{"depot":{"$ref":"#/components/schemas/Coordinate"},"vehicleCount":{"type":"integer","description":"Nombre de vehicules. 1 = optimisation d'ordre simple (TSP). Defaut : 1.","format":"int32","example":1,"default":1},"vehicleCapacity":{"type":"integer","description":"Capacite par vehicule (memes unites que `demand`). Null = illimite.","format":"int32","nullable":true,"example":10},"departureTime":{"type":"string","description":"Heure de depart du depot (ISO-8601). Sert a calculer les heures d'arrivee/depart a chaque point. Si absent : heure courante du serveur.","format":"date-time","nullable":true},"includeGeometry":{"type":"boolean","description":"[Deprecie : preferer geometryFormat] Inclure la geometrie de chaque segment. Ignore si geometryFormat est fourni. true -> POINTS, false -> NONE.","nullable":true,"example":true},"geometryFormat":{"type":"string","description":"Format de la geometrie :\n- POINTS   : liste de points [lat,lon] (champ `geometry`). Lisible mais volumineux.\n- POLYLINE : chaine encodee (champ `geometryPolyline`), algorithme Google/OSRM precision 5 (~1 m), bien plus compacte pour les longs trajets. Decodable avec @mapbox/polyline, etc.\n- NONE     : pas de geometrie (reponse la plus legere).","nullable":true,"default":"POINTS","enum":["POINTS","POLYLINE","NONE"]},"visits":{"type":"array","description":"Points a visiter (au moins 1).","items":{"$ref":"#/components/schemas/VisitDto"}}},"description":"Demande d'optimisation de tournee : depot commun, vehicules, points a visiter."},"VisitDto":{"required":["lat","lon"],"type":"object","properties":{"id":{"type":"string","description":"Identifiant du point (optionnel ; auto-genere si absent).","example":"A"},"name":{"type":"string","description":"Libelle lisible (optionnel).","example":"Client A"},"lat":{"type":"number","description":"Latitude WGS84.","format":"double","example":47.2184},"lon":{"type":"number","description":"Longitude WGS84.","format":"double","example":-1.5536},"demand":{"type":"integer","description":"Demande / charge consommee a ce point (optionnel ; defaut 0). Comparee a la capacite du vehicule.","format":"int32","example":1},"serviceDurationSeconds":{"type":"integer","description":"Duree d'arret / de service a ce point en secondes (optionnel ; defaut 0). Decale les heures d'arrivee des points suivants.","format":"int32","example":300}},"description":"Point a visiter dans une tournee."},"LegDto":{"type":"object","properties":{"distanceMeters":{"type":"number","description":"Distance du segment, en metres.","format":"double","example":52300.0},"durationSeconds":{"type":"integer","description":"Duree de conduite du segment, en secondes.","format":"int64","example":4380},"geometry":{"type":"array","description":"Trace en points [lat,lon]. Non nul uniquement si geometryFormat = POINTS.","nullable":true,"example":[[48.1173,-1.6778],[48.0,-1.7],[47.2184,-1.5536]],"items":{"type":"array","description":"Trace en points [lat,lon]. Non nul uniquement si geometryFormat = POINTS.","nullable":true,"example":[[48.1173,-1.6778],[48.0,-1.7],[47.2184,-1.5536]],"items":{"type":"number","description":"Trace en points [lat,lon]. Non nul uniquement si geometryFormat = POINTS.","format":"double","nullable":true}}},"geometryPolyline":{"type":"string","description":"Trace en polyligne encodee (precision 5). Non nul uniquement si geometryFormat = POLYLINE.","nullable":true,"example":"ydlrHnwfA~A_@dGsT"}},"description":"Un segment routier : distance, duree et geometrie (selon geometryFormat)."},"OptimizeResponse":{"type":"object","properties":{"score":{"type":"string","description":"Score Timefold (`<dur>hard/<soft>soft`). `0hard` = contraintes dures respectees.","example":"0hard/-12450soft"},"totalDrivingTimeSeconds":{"type":"integer","description":"Temps de conduite total cumule sur tous les vehicules, en secondes.","format":"int64","example":12450},"totalDistanceMeters":{"type":"number","description":"Distance totale parcourue par tous les vehicules, en metres.","format":"double","example":184300.0},"routes":{"type":"array","description":"Une entree par vehicule.","items":{"$ref":"#/components/schemas/RouteDto"}},"skippedVisits":{"type":"array","description":"Visites EXCLUES de l'optimisation car non rattachables au reseau routier (coordonnees en mer, hors de la zone OSM couverte, ou trop eloignees de toute route selon `app.routing.max-snap-distance-meters`). Ces points ne figurent dans AUCUNE tournee : ils sont ignores pour ne pas faire echouer toute la requete. Liste vide si tous les points ont ete pris en compte. Le client DOIT verifier ce tableau et, le cas echeant, signaler/corriger ces points.","items":{"$ref":"#/components/schemas/SkippedVisitDto"}}},"description":"Resultat d'optimisation : tournees ordonnees par vehicule, avec distances, heures d'arrivee et geometrie du trace."},"RouteDto":{"type":"object","properties":{"vehicleId":{"type":"string","description":"Identifiant du vehicule.","example":"vehicle-0"},"departureTime":{"type":"string","description":"Heure de depart du depot.","format":"date-time"},"returnTime":{"type":"string","description":"Heure de retour au depot (fin de tournee).","format":"date-time"},"drivingTimeSeconds":{"type":"integer","description":"Temps de conduite total de la tournee, en secondes.","format":"int64","example":12450},"serviceTimeSeconds":{"type":"integer","description":"Temps de service total (arrets) de la tournee, en secondes.","format":"int64","example":1500},"distanceMeters":{"type":"number","description":"Distance totale de la tournee, en metres.","format":"double","example":184300.0},"totalDemand":{"type":"integer","description":"Somme des demandes des visites de la tournee.","format":"int32","example":2},"stops":{"type":"array","description":"Arrets dans l'ordre optimal de passage.","items":{"$ref":"#/components/schemas/StopDto"}},"returnLeg":{"$ref":"#/components/schemas/LegDto"},"geometry":{"type":"array","description":"Trace COMPLETE de la tournee (depot -> arrets dans l'ordre -> depot) en liste de points [lat,lon], continue et sans doublon aux jonctions. Non nul uniquement si geometryFormat = POINTS. A privilegier pour tracer toute la tournee d'un seul trait : les polylignes par segment ne sont pas concatenables.","nullable":true,"example":[[48.1173,-1.6778],[48.05,-1.7],[47.2184,-1.5536]],"items":{"type":"array","description":"Trace COMPLETE de la tournee (depot -> arrets dans l'ordre -> depot) en liste de points [lat,lon], continue et sans doublon aux jonctions. Non nul uniquement si geometryFormat = POINTS. A privilegier pour tracer toute la tournee d'un seul trait : les polylignes par segment ne sont pas concatenables.","nullable":true,"example":[[48.1173,-1.6778],[48.05,-1.7],[47.2184,-1.5536]],"items":{"type":"number","description":"Trace COMPLETE de la tournee (depot -> arrets dans l'ordre -> depot) en liste de points [lat,lon], continue et sans doublon aux jonctions. Non nul uniquement si geometryFormat = POINTS. A privilegier pour tracer toute la tournee d'un seul trait : les polylignes par segment ne sont pas concatenables.","format":"double","nullable":true}}},"geometryPolyline":{"type":"string","description":"Trace COMPLETE de la tournee (depot -> arrets -> depot) en polyligne encodee (Google/OSRM, precision 5, ordre lat/lon). Renseignee (non nul) pour POINTS et POLYLINE ; null uniquement si geometryFormat = NONE. Champ a utiliser pour afficher le trace global de la tournee (les `legFromPrevious.geometryPolyline` ne se concatenent pas entre eux).","nullable":true,"example":"ydlrHnwfA~A_@dGsT"}},"description":"Tournee d'un vehicule : visites dans l'ordre, depot -> points -> depot."},"SkippedVisitDto":{"type":"object","properties":{"visitId":{"type":"string","description":"Identifiant de la visite (celui fourni, ou auto-genere `v<index>`).","example":"A"},"name":{"type":"string","description":"Libelle de la visite (tel que fourni).","nullable":true,"example":"Client A"},"lat":{"type":"number","description":"Latitude fournie.","format":"double","example":47.2184},"lon":{"type":"number","description":"Longitude fournie.","format":"double","example":-1.5536},"reason":{"type":"string","description":"Motif d'exclusion : `UNROUTABLE` (aucune route trouvable a proximite : point en mer, hors zone couverte, ou reseau deconnecte) ou `TOO_FAR` (route la plus proche au-dela du seuil `app.routing.max-snap-distance-meters`).","example":"TOO_FAR","enum":["UNROUTABLE","TOO_FAR"]},"snapDistanceMeters":{"type":"number","description":"Distance (m) jusqu'a la route la plus proche. Renseignee pour `TOO_FAR` ; null pour `UNROUTABLE` (aucune route trouvee).","format":"double","nullable":true,"example":1830.0}},"description":"Visite ecartee de l'optimisation, avec le motif. N'apparait dans aucune tournee."},"StopDto":{"type":"object","properties":{"visitId":{"type":"string","description":"Identifiant du point visite.","example":"A"},"name":{"type":"string","description":"Libelle du point.","example":"Client A"},"lat":{"type":"number","description":"Latitude.","format":"double","example":47.2184},"lon":{"type":"number","description":"Longitude.","format":"double","example":-1.5536},"legFromPrevious":{"$ref":"#/components/schemas/LegDto"},"cumulativeDistanceMeters":{"type":"number","description":"Distance cumulee depuis le depot jusqu'a ce point, en metres.","format":"double","example":52300.0},"cumulativeDrivingSeconds":{"type":"integer","description":"Temps de conduite cumule depuis le depot jusqu'a ce point, en secondes.","format":"int64","example":4380},"arrivalTime":{"type":"string","description":"Heure d'arrivee a ce point.","format":"date-time"},"departureTime":{"type":"string","description":"Heure de depart de ce point (arrivee + duree de service).","format":"date-time"},"demand":{"type":"integer","description":"Demande consommee a ce point.","format":"int32","example":1}},"description":"Un arret de la tournee, avec le segment depuis le point precedent et les cumuls."},"ComponentDto":{"type":"object","properties":{"name":{"type":"string"},"state":{"type":"string"},"detail":{"type":"string"},"ready":{"type":"boolean"}}},"DataFileDto":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"},"present":{"type":"boolean"},"sizeBytes":{"type":"integer","format":"int64"}}},"DownloadDto":{"type":"object","properties":{"name":{"type":"string"},"downloadedBytes":{"type":"integer","format":"int64"},"totalBytes":{"type":"integer","format":"int64"},"percent":{"type":"integer","format":"int32"},"done":{"type":"boolean"}}},"JvmDto":{"type":"object","properties":{"javaVersion":{"type":"string"},"pid":{"type":"integer","format":"int64"},"cpuCores":{"type":"integer","format":"int32"},"memUsedBytes":{"type":"integer","format":"int64"},"memMaxBytes":{"type":"integer","format":"int64"},"memUsedPercent":{"type":"integer","format":"int32"}}},"LinksDto":{"type":"object","properties":{"swaggerUi":{"type":"string"},"openApi":{"type":"string"}}},"StatusResponse":{"type":"object","properties":{"application":{"type":"string"},"status":{"type":"string"},"uptimeSeconds":{"type":"integer","format":"int64"},"startedAtMillis":{"type":"integer","format":"int64"},"routingProfile":{"type":"string"},"addressCount":{"type":"integer","format":"int32"},"jvm":{"$ref":"#/components/schemas/JvmDto"},"components":{"type":"array","items":{"$ref":"#/components/schemas/ComponentDto"}},"downloads":{"type":"array","items":{"$ref":"#/components/schemas/DownloadDto"}},"data":{"type":"array","items":{"$ref":"#/components/schemas/DataFileDto"}},"links":{"$ref":"#/components/schemas/LinksDto"}}},"AddressResult":{"type":"object","properties":{"label":{"type":"string","description":"Libelle complet et lisible de l'adresse. En resultat de type `street`, le numero est omis (ex : `Rue du Bocage, 35000 Rennes`).","example":"12 Rue du Bocage, 35000 Rennes"},"houseNumber":{"type":"string","description":"Numero de voie. Vide pour un resultat de type `street` (agrege au niveau de la voie).","example":"12"},"street":{"type":"string","description":"Nom de la voie.","example":"Rue du Bocage"},"postcode":{"type":"string","description":"Code postal.","example":"35000"},"city":{"type":"string","description":"Commune.","example":"Rennes"},"lat":{"type":"number","description":"Latitude WGS84. Pour un resultat `street`, coordonnee du point representatif (le plus proche de la position fournie si `lat`/`lon` sont renseignes).","format":"double","example":48.1147},"lon":{"type":"number","description":"Longitude WGS84.","format":"double","example":-1.6794},"type":{"type":"string","description":"Type de resultat : `street` (voie agregee, sans numero — renvoye quand la requete ne contient aucun numero de voie) ou `housenumber` (adresse precise avec numero).","example":"street","enum":["street","housenumber"]},"distanceMeters":{"type":"number","description":"Distance en metres entre la position fournie (`lat`/`lon`) et ce resultat. `null` si aucune position n'a ete fournie dans la requete.","format":"double","nullable":true,"example":742.0},"score":{"type":"number","description":"Score de pertinence (plus eleve = plus pertinent). Sans position : score textuel Lucene. Avec position : score textuel pondere par la proximite (decroissance avec la distance).","format":"float","example":8.42}},"description":"Adresse trouvee, avec ses coordonnees et son score de pertinence."}}}}