$latitude, 'longitude' => $longitude, 'zip_code' => $this->normalize($zipCode), ]); return compact('latitude', 'longitude'); } Log::info('ZipCodeCoordinates fallback to zip code lookup', [ 'zip_code' => $this->normalize($zipCode), 'has_latitude' => $latitude !== null, 'has_longitude' => $longitude !== null, 'reason' => 'missing_address_coordinates', ]); return $this->findByZipCode($zipCode); } public function calculateDistance( ?float $originLatitude, ?float $originLongitude, ?string $originZipCode, ?float $targetLatitude, ?float $targetLongitude, ?string $targetZipCode, ): ?float { $origin = $this->resolve($originLatitude, $originLongitude, $originZipCode); $target = $this->resolve($targetLatitude, $targetLongitude, $targetZipCode); return DistanceService::calculate( $origin['latitude'] ?? null, $origin['longitude'] ?? null, $target['latitude'] ?? null, $target['longitude'] ?? null, ); } public function findByZipCode(?string $zipCode): ?array { $zipCode = $this->normalize($zipCode); if ($zipCode === null) { Log::info('ZipCodeCoordinates skipped lookup: invalid zip code'); return null; } if (array_key_exists($zipCode, $this->resolvedZipCodes)) { Log::debug('ZipCodeCoordinates memory cache hit', [ 'zip_code' => $zipCode, 'found' => $this->resolvedZipCodes[$zipCode] !== null, ]); return $this->resolvedZipCodes[$zipCode]; } if ($this->restoreFromCache($zipCode)) { Log::debug('ZipCodeCoordinates persistent cache hit', [ 'zip_code' => $zipCode, 'found' => $this->resolvedZipCodes[$zipCode] !== null, ]); return $this->resolvedZipCodes[$zipCode]; } Log::info('ZipCodeCoordinates Brasil API lookup required', [ 'zip_code' => $zipCode, 'mode' => 'single', ]); $coordinates = $this->requestCoordinates($zipCode); $this->remember($zipCode, $coordinates); return $coordinates; } public function preload(iterable $zipCodes): void { $pending = []; foreach ($zipCodes as $zipCode) { $zipCode = $this->normalize($zipCode); if ( $zipCode === null || array_key_exists($zipCode, $this->resolvedZipCodes) || $this->restoreFromCache($zipCode) ) { continue; } $pending[$zipCode] = $zipCode; } if ($pending === []) { Log::debug('ZipCodeCoordinates preload skipped: no pending zip codes'); return; } Log::info('ZipCodeCoordinates Brasil API lookup required', [ 'mode' => 'preload', 'pending_count' => count($pending), 'zip_codes_sample' => array_slice(array_values($pending), 0, 20), ]); try { $baseUrl = rtrim((string) config('services.brasil_api.base_url'), '/'); $responses = Http::pool(fn (Pool $pool) => array_map( fn (string $zipCode) => $pool ->as($zipCode) ->acceptJson() ->connectTimeout((int) config('services.brasil_api.connect_timeout', 2)) ->timeout((int) config('services.brasil_api.timeout', 3)) ->get("{$baseUrl}/api/cep/v2/{$zipCode}"), $pending, )); } catch (Throwable $exception) { Log::warning('ZipCodeCoordinates Brasil API preload failed', [ 'pending_count' => count($pending), 'error' => $exception->getMessage(), ]); $responses = []; } foreach ($pending as $zipCode) { $response = $responses[$zipCode] ?? null; $coordinates = $response instanceof Response ? $this->coordinatesFromResponse($response) : null; Log::info('ZipCodeCoordinates Brasil API preload response', [ 'zip_code' => $zipCode, 'has_response' => $response instanceof Response, 'status' => $response instanceof Response ? $response->status() : null, 'found' => $coordinates !== null, 'coordinates' => $coordinates, ]); $this->remember($zipCode, $coordinates); } } private function normalize(?string $zipCode): ?string { $zipCode = preg_replace('/\D/', '', $zipCode ?? ''); return strlen($zipCode) === 8 ? $zipCode : null; } private function restoreFromCache(string $zipCode): bool { $cached = Cache::get("zip-code-coordinates:{$zipCode}"); if (! is_array($cached) || ! array_key_exists('found', $cached)) { return false; } $this->resolvedZipCodes[$zipCode] = $cached['found'] ? $cached['coordinates'] : null; return true; } private function remember(string $zipCode, ?array $coordinates): void { $this->resolvedZipCodes[$zipCode] = $coordinates; Cache::put( "zip-code-coordinates:{$zipCode}", [ 'found' => $coordinates !== null, 'coordinates' => $coordinates, ], now()->addSeconds( $coordinates !== null ? (int) config('services.brasil_api.cep_cache_ttl', 2592000) : (int) config('services.brasil_api.cep_failure_cache_ttl', 600) ) ); } private function requestCoordinates(string $zipCode): ?array { try { Log::info('ZipCodeCoordinates calling Brasil API', [ 'zip_code' => $zipCode, 'base_url' => rtrim((string) config('services.brasil_api.base_url'), '/'), ]); $response = Http::baseUrl(rtrim((string) config('services.brasil_api.base_url'), '/')) ->acceptJson() ->connectTimeout((int) config('services.brasil_api.connect_timeout', 2)) ->timeout((int) config('services.brasil_api.timeout', 3)) ->get("api/cep/v2/{$zipCode}"); $coordinates = $this->coordinatesFromResponse($response); Log::info('ZipCodeCoordinates Brasil API response', [ 'zip_code' => $zipCode, 'status' => $response->status(), 'found' => $coordinates !== null, 'coordinates' => $coordinates, ]); return $coordinates; } catch (Throwable $exception) { Log::warning('ZipCodeCoordinates Brasil API request failed', [ 'zip_code' => $zipCode, 'error' => $exception->getMessage(), ]); return null; } } private function coordinatesFromResponse(Response $response): ?array { if (! $response->successful()) { return null; } $latitude = $response->json('location.coordinates.latitude'); $longitude = $response->json('location.coordinates.longitude'); if (! is_numeric($latitude) || ! is_numeric($longitude)) { return null; } $latitude = (float) $latitude; $longitude = (float) $longitude; if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) { return null; } return compact('latitude', 'longitude'); } }