chore: build

This commit is contained in:
2026-06-14 17:50:35 +02:00
parent 3c92a0371f
commit 3a0f725159
93 changed files with 5311 additions and 4224 deletions
+70 -44
View File
@@ -6,10 +6,10 @@ import (
"fmt"
"gestion/models"
"io"
"log"
"math"
"net/http"
"net/url"
"os"
"strings"
"time"
@@ -28,10 +28,6 @@ const (
LocationTTL = 1 * time.Hour
)
// ============================================
// STRUCTURES
// ============================================
type GeoLocation struct {
Latitude float64 `json:"lat,string"`
Longitude float64 `json:"lon,string"`
@@ -51,43 +47,68 @@ type DeliveryDistance struct {
}
type GeoService struct {
redis *redis.Client
ctx context.Context
httpClient *http.Client
redis *redis.Client
ctx context.Context
httpClient *http.Client
correctionService *AddressCorrectionService
}
// ============================================
// CONSTRUCTEUR
// ============================================
func NewGeoService(redisClient *redis.Client, ctx context.Context) *GeoService {
return &GeoService{
gs := &GeoService{
redis: redisClient,
ctx: ctx,
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
}
// Le correctionService est initialisé après, car il a besoin de gs lui-même
gs.correctionService = NewAddressCorrectionService(gs)
return gs
}
// ============================================
// GÉOCODAGE - API NOMINATIM
// ============================================
func (gs *GeoService) GeocodeAddress(address string) (*GeoLocation, error) {
// 1. Vérifier le cache Redis
location, err := gs.getFromCache(address)
if err == nil {
// 1. Cache Redis (adresse originale)
if location, err := gs.getFromCache(address); err == nil {
return location, nil
}
location, err = gs.fetchFromNominatim(address)
if err != nil {
return nil, err
// 2. Tentative directe via Nominatim
if location, err := gs.fetchFromNominatim(address); err == nil {
gs.saveToCache(address, location)
return location, nil
}
// 3. Sauvegarder en cache
// 3. ── NOUVEAU : correction automatique de l'adresse ──────────────────
// Déclenché uniquement si le géocodage direct a échoué.
log.Printf("🔍 [GEO] Géocodage direct échoué pour '%s', tentative de correction...", address)
suggestion, err := gs.correctionService.ResolveAddress(address)
if err != nil {
log.Printf("❌ [GEO] Correction impossible pour '%s': %v", address, err)
return nil, fmt.Errorf("adresse introuvable : '%s'", address)
}
if suggestion.CorrectionApplied {
log.Printf(
"✅ [GEO] Correction appliquée (confiance %.0f%%) : '%s' → '%s'",
suggestion.Confidence*100,
address,
suggestion.CorrectedAddress,
)
}
location := &GeoLocation{
Latitude: suggestion.Coordinates.Latitude,
Longitude: suggestion.Coordinates.Longitude,
DisplayName: suggestion.CorrectedAddress,
}
// Mettre en cache avec l'adresse originale pour les prochains appels
gs.saveToCache(address, location)
// Mettre en cache aussi avec l'adresse corrigée
if suggestion.CorrectionApplied {
gs.saveToCache(suggestion.CorrectedAddress, location)
}
return location, nil
}
@@ -244,32 +265,37 @@ func CalculateETA(distanceKm float64) int {
// CalculateETAWithTomTom calcule l'ETA via TomTom API (précis avec trafic réel)
// Retourne (etaMinutes, distanceKm, error)
func CalculateETAWithTomTom(from, to Coordinates) (int, float64, error) {
apiKey := os.Getenv("TOMTOM_API_KEY")
if apiKey == "" {
// Fallback sur calcul local si pas de clé API
if len(tomTomKeys.keys) == 0 {
distance := CalculateDistance(from, to)
return CalculateETA(distance), distance, nil
}
// API TomTom Routing: Calculate Route avec trafic
apiURL := fmt.Sprintf(
"https://api.tomtom.com/routing/1/calculateRoute/%f,%f:%f,%f/json?key=%s&traffic=true&travelMode=car",
from.Latitude, from.Longitude, to.Latitude, to.Longitude, apiKey,
)
client := &http.Client{Timeout: 8 * time.Second}
resp, err := client.Get(apiURL)
buildReq := func(key string) (*http.Request, error) {
u := &url.URL{
Scheme: "https",
Host: "api.tomtom.com",
Path: fmt.Sprintf("/routing/1/calculateRoute/%f,%f:%f,%f/json", from.Latitude, from.Longitude, to.Latitude, to.Longitude),
}
q := url.Values{}
q.Set("key", key)
q.Set("traffic", "true")
q.Set("travelMode", "car")
u.RawQuery = q.Encode()
return http.NewRequest(http.MethodGet, u.String(), nil)
}
resp, err := tomTomKeys.Do(client, buildReq)
if err != nil {
// Fallback sur calcul local en cas d'erreur réseau
distance := CalculateDistance(from, to)
eta := CalculateETA(distance)
fmt.Printf("⚠️ TomTom timeout, fallback: %.2f km -> %d min\n", distance, eta)
fmt.Printf("⚠️ TomTom indisponible, fallback: %.2f km -> %d min (%v)\n", distance, eta, err)
return eta, distance, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// Fallback sur calcul local en cas d'erreur API
distance := CalculateDistance(from, to)
eta := CalculateETA(distance)
fmt.Printf("⚠️ TomTom API error %d, fallback: %.2f km -> %d min\n", resp.StatusCode, distance, eta)
@@ -294,18 +320,14 @@ func CalculateETAWithTomTom(from, to Coordinates) (int, float64, error) {
}
summary := routeResponse.Routes[0].Summary
// Calculer ETA en minutes (arrondi supérieur)
etaMinutes := (summary.TravelTimeInSeconds + 59) / 60
distanceKm := float64(summary.LengthInMeters) / 1000.0
// Appliquer minimum
if etaMinutes < MinETA {
etaMinutes = MinETA
}
fmt.Printf("🛣️ TomTom: %.2f km -> %d min (trafic réel)\n", distanceKm, etaMinutes)
return etaMinutes, distanceKm, nil
}
@@ -503,13 +525,13 @@ func (gs *GeoService) GetAllDeliveryDistances(target Coordinates, availableUsern
// ============================================
// GetDeliveryHeatmap retourne toutes les positions des livreurs
func (gs *GeoService) GetDeliveryHeatmap() ([]map[string]interface{}, error) {
func (gs *GeoService) GetDeliveryHeatmap() ([]map[string]any, error) {
keys, err := gs.redis.Keys(gs.ctx, "delivery:location:*").Result()
if err != nil {
return nil, err
}
var heatmap []map[string]interface{}
var heatmap []map[string]any
for _, key := range keys {
data, err := gs.redis.Get(gs.ctx, key).Result()
@@ -517,7 +539,7 @@ func (gs *GeoService) GetDeliveryHeatmap() ([]map[string]interface{}, error) {
continue
}
var location map[string]interface{}
var location map[string]any
json.Unmarshal([]byte(data), &location)
username := key[len("delivery:location:"):]
@@ -528,3 +550,7 @@ func (gs *GeoService) GetDeliveryHeatmap() ([]map[string]interface{}, error) {
return heatmap, nil
}
func (gs *GeoService) CorrectionService() *AddressCorrectionService {
return gs.correctionService
}