// frontend/src/api/services/external.service.ts /** * External Data Service * Handles weather and traffic data operations for the external microservice */ import { apiClient } from '../client'; import { RequestTimeouts } from '../client/config'; // Align with backend WeatherDataResponse schema export interface WeatherData { date: string; temperature?: number; precipitation?: number; humidity?: number; wind_speed?: number; pressure?: number; description?: string; source: string; } // Align with backend TrafficDataResponse schema export interface TrafficData { date: string; traffic_volume?: number; pedestrian_count?: number; congestion_level?: string; average_speed?: number; source: string; } export interface WeatherForecast { date: string; temperature_min: number; temperature_max: number; temperature_avg: number; precipitation: number; description: string; humidity?: number; wind_speed?: number; } export interface HourlyForecast { forecast_datetime: string; generated_at: string; temperature: number; precipitation: number; humidity: number; wind_speed: number; description: string; source: string; hour: number; } export class ExternalService { /** * Get Current Weather Data */ async getCurrentWeather( tenantId: string, lat: number, lon: number ): Promise { try { // ✅ FIX 1: Correct endpoint path with tenant ID const endpoint = `/tenants/${tenantId}/weather/current`; // ✅ FIX 2: Correct parameter names (latitude/longitude, not lat/lon) const response = await apiClient.get(endpoint, { params: { latitude: lat, // Backend expects 'latitude' longitude: lon // Backend expects 'longitude' } }); console.log('Weather API response:', response); // Return backend response directly (matches WeatherData interface) return response; } catch (error) { console.error('Failed to fetch weather from AEMET API via backend:', error); throw new Error(`Weather data unavailable: ${error instanceof Error ? error.message : 'AEMET API connection failed'}`); } } /** * Get Weather Forecast */ async getWeatherForecast( tenantId: string, lat: number, lon: number, days: number = 7 ): Promise { try { // Fix: Use POST with JSON body as expected by backend const response = await apiClient.post(`/tenants/${tenantId}/weather/forecast`, { latitude: lat, longitude: lon, days: days }); // Handle response format if (Array.isArray(response)) { return response; } else if (response && response.forecasts) { return response.forecasts; } else { console.warn('Unexpected weather forecast response format:', response); return []; } } catch (error) { console.error('Failed to fetch weather forecast from AEMET API:', error); throw new Error(`Weather forecast unavailable: ${error instanceof Error ? error.message : 'AEMET API connection failed'}`); } } /** * Get Hourly Weather Forecast (NEW) */ async getHourlyWeatherForecast( tenantId: string, lat: number, lon: number, hours: number = 48 ): Promise { try { console.log(`🕒 Fetching hourly weather forecast from AEMET API for tenant ${tenantId}`, { latitude: lat, longitude: lon, hours: hours }); const response = await apiClient.post(`/tenants/${tenantId}/weather/hourly-forecast`, { latitude: lat, longitude: lon, hours: hours }); // Handle response format if (Array.isArray(response)) { return response; } else if (response && response.data) { return response.data; } else { console.warn('Unexpected hourly forecast response format:', response); return []; } } catch (error) { console.error('Failed to fetch hourly forecast from AEMET API:', error); throw new Error(`Hourly forecast unavailable: ${error instanceof Error ? error.message : 'AEMET API connection failed'}`); } } /** * Get Historical Weather Data */ async getHistoricalWeather( tenantId: string, lat: number, lon: number, startDate: string, endDate: string ): Promise { try { // Fix: Use POST with JSON body as expected by backend const response = await apiClient.post(`/tenants/${tenantId}/weather/historical`, { latitude: lat, longitude: lon, start_date: startDate, end_date: endDate }); // Return backend response directly (matches WeatherData interface) return Array.isArray(response) ? response : response.data || []; } catch (error) { console.error('Failed to fetch historical weather from AEMET API:', error); throw new Error(`Historical weather data unavailable: ${error instanceof Error ? error.message : 'AEMET API connection failed'}`); } } /** * Get Current Traffic Data */ async getCurrentTraffic( tenantId: string, lat: number, lon: number ): Promise { try { const response = await apiClient.get(`/tenants/${tenantId}/traffic/current`, { params: { latitude: lat, longitude: lon } }); // Return backend response directly (matches TrafficData interface) return response; } catch (error) { console.error('Failed to fetch traffic data from external API:', error); throw new Error(`Traffic data unavailable: ${error instanceof Error ? error.message : 'External API connection failed'}`); } } /** * Get Traffic Forecast */ async getTrafficForecast( tenantId: string, lat: number, lon: number, hours: number = 24 ): Promise { try { // Fix: Use POST with JSON body as expected by backend const response = await apiClient.post(`/tenants/${tenantId}/traffic/forecast`, { latitude: lat, longitude: lon, hours: hours }); // Return backend response directly (matches TrafficData interface) return Array.isArray(response) ? response : response.data || []; } catch (error) { console.error('Failed to fetch traffic forecast from external API:', error); throw new Error(`Traffic forecast unavailable: ${error instanceof Error ? error.message : 'External API connection failed'}`); } } /** * Get Historical Traffic Data */ async getHistoricalTraffic( tenantId: string, lat: number, lon: number, startDate: string, endDate: string ): Promise { try { // Fix: Use POST with JSON body as expected by backend const response = await apiClient.post(`/tenants/${tenantId}/traffic/historical`, { latitude: lat, longitude: lon, start_date: startDate, end_date: endDate }); // Return backend response directly (matches TrafficData interface) return Array.isArray(response) ? response : response.data || []; } catch (error) { console.error('Failed to fetch historical traffic from external API:', error); throw new Error(`Historical traffic data unavailable: ${error instanceof Error ? error.message : 'External API connection failed'}`); } } /** * Test External Service Connectivity */ async testConnectivity(tenantId: string): Promise<{ weather: boolean; traffic: boolean; overall: boolean; }> { const results = { weather: false, traffic: false, overall: false }; try { // Test weather service (AEMET API) await this.getCurrentWeather(tenantId, 40.4168, -3.7038); // Madrid coordinates results.weather = true; } catch (error) { console.warn('AEMET weather service connectivity test failed:', error); results.weather = false; } try { // Test traffic service await this.getCurrentTraffic(tenantId, 40.4168, -3.7038); // Madrid coordinates results.traffic = true; } catch (error) { console.warn('Traffic service connectivity test failed:', error); results.traffic = false; } results.overall = results.weather && results.traffic; return results; } } export const externalService = new ExternalService();