File size: 12,299 Bytes
5ccd893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
"""
Weather Controller
Handles weather data operations and coordinates between service and API
"""
import logging
from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from services.weather_service import NASAPowerService
from models.weather_model import WeatherRequest, WeatherDataModel
from utils import create_error_response, create_success_response

class WeatherController:
    """Controller for weather data operations"""
    
    def __init__(self, weather_service: NASAPowerService):
        self.weather_service = weather_service
        self.logger = logging.getLogger(__name__)
    
    def get_weather_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Get weather data for specific coordinates and date
        
        Args:
            data: Request data containing coordinates, date, and optional parameters
            
        Returns:
            Weather data response
        """
        try:
            # Validate required parameters
            required_fields = ['latitude', 'longitude', 'disaster_date']
            missing_fields = [field for field in required_fields if field not in data or data[field] is None]
            
            if missing_fields:
                return create_error_response(
                    f"Missing required fields: {', '.join(missing_fields)}",
                    {"missing_fields": missing_fields}
                )
            
            # Extract parameters
            try:
                latitude = float(data['latitude'])
                longitude = float(data['longitude'])
                disaster_date = str(data['disaster_date'])
                days_before = int(data.get('days_before', 60))
            except (ValueError, TypeError) as e:
                return create_error_response(
                    f"Invalid parameter format: {str(e)}",
                    {"validation_error": str(e)}
                )
            
            # Create weather request
            weather_request = WeatherRequest(
                latitude=latitude,
                longitude=longitude,
                disaster_date=disaster_date,
                days_before=days_before
            )
            
            # Validate request
            validation = weather_request.validate()
            if not validation['valid']:
                return create_error_response(
                    "Request validation failed",
                    {"validation_errors": validation['errors']}
                )
            
            self.logger.info(f"Fetching weather data for lat={latitude}, lon={longitude}, "
                           f"disaster_date={disaster_date}, days_before={days_before}")
            
            # Fetch weather data
            success, result = self.weather_service.fetch_weather_data(weather_request)
            
            if success:
                return create_success_response(result)
            else:
                return create_error_response(
                    "Failed to fetch weather data",
                    result
                )
                
        except Exception as e:
            self.logger.error(f"Weather data error: {str(e)}")
            return create_error_response(
                f"Failed to get weather data: {str(e)}"
            )
    
    def get_weather_time_series(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Get weather data as time series DataFrame
        
        Args:
            data: Request data containing coordinates, date, and optional parameters
            
        Returns:
            Time series weather data response
        """
        try:
            # Get weather data first
            weather_result = self.get_weather_data(data)
            
            if weather_result.get('status') != 'success':
                return weather_result
            
            # Extract weather data
            weather_data = weather_result['data']['weather_data']
            disaster_date = data['disaster_date']
            days_before = int(data.get('days_before', 60))
            
            # Create time series DataFrame
            df = WeatherDataModel.create_time_series_dataframe(
                weather_data, disaster_date, days_before
            )
            
            # Convert DataFrame to dict for JSON response
            time_series_data = {
                'dates': df['date'].tolist(),
                'weather_data': {
                    col: df[col].tolist() 
                    for col in df.columns if col != 'date'
                }
            }
            
            return create_success_response({
                'time_series': time_series_data,
                'metadata': weather_result['data']['metadata'],
                'validation': weather_result['data']['validation']
            })
            
        except Exception as e:
            self.logger.error(f"Time series error: {str(e)}")
            return create_error_response(
                f"Failed to create time series: {str(e)}"
            )
    
    def batch_get_weather_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Get weather data for multiple locations
        
        Args:
            data: Request data containing list of location/date combinations
            
        Returns:
            Batch weather data response
        """
        try:
            # Validate batch request
            if 'locations' not in data or not isinstance(data['locations'], list):
                return create_error_response(
                    "Invalid batch request: 'locations' array required"
                )
            
            locations = data['locations']
            if len(locations) > 100:  # Limit batch size
                return create_error_response(
                    "Batch size too large: maximum 100 locations allowed",
                    {"max_allowed": 100, "requested": len(locations)}
                )
            
            # Create weather requests
            weather_requests = []
            for i, location in enumerate(locations):
                try:
                    request = WeatherRequest(
                        latitude=float(location['latitude']),
                        longitude=float(location['longitude']),
                        disaster_date=str(location['disaster_date']),
                        days_before=int(location.get('days_before', 60))
                    )
                    weather_requests.append(request)
                except Exception as e:
                    return create_error_response(
                        f"Invalid location at index {i}: {str(e)}",
                        {"location_index": i, "error": str(e)}
                    )
            
            self.logger.info(f"Starting batch weather fetch for {len(weather_requests)} locations")
            
            # Batch fetch weather data
            batch_result = self.weather_service.batch_fetch_weather_data(weather_requests)
            
            return create_success_response(batch_result)
            
        except Exception as e:
            self.logger.error(f"Batch weather error: {str(e)}")
            return create_error_response(
                f"Failed to process batch weather request: {str(e)}"
            )
    
    def get_weather_summary(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Get weather data summary statistics
        
        Args:
            data: Request data containing coordinates, date, and optional parameters
            
        Returns:
            Weather summary response with statistics
        """
        try:
            # Get weather data first
            weather_result = self.get_weather_data(data)
            
            if weather_result.get('status') != 'success':
                return weather_result
            
            weather_data = weather_result['data']['weather_data']
            
            # Calculate summary statistics
            summary_stats = {}
            for field_name, values in weather_data.items():
                valid_values = [v for v in values if v is not None]
                
                if valid_values:
                    summary_stats[field_name] = {
                        'mean': sum(valid_values) / len(valid_values),
                        'min': min(valid_values),
                        'max': max(valid_values),
                        'count': len(valid_values),
                        'missing': len([v for v in values if v is None]),
                        'completeness': len(valid_values) / len(values) * 100
                    }
                else:
                    summary_stats[field_name] = {
                        'mean': None, 'min': None, 'max': None,
                        'count': 0, 'missing': len(values),
                        'completeness': 0.0
                    }
            
            return create_success_response({
                'summary_statistics': summary_stats,
                'metadata': weather_result['data']['metadata'],
                'data_quality': weather_result['data']['validation']['data_quality']
            })
            
        except Exception as e:
            self.logger.error(f"Weather summary error: {str(e)}")
            return create_error_response(
                f"Failed to create weather summary: {str(e)}"
            )
    
    def get_available_fields(self) -> Dict[str, Any]:
        """Get available weather fields and their descriptions"""
        try:
            field_descriptions = {
                'temperature_C': 'Temperature at 2 meters (°C)',
                'humidity_perc': 'Relative humidity at 2 meters (%)',
                'wind_speed_mps': 'Wind speed at 2 meters (m/s)',
                'precipitation_mm': 'Precipitation corrected (mm)',
                'surface_pressure_hPa': 'Surface pressure (hPa)',
                'solar_radiation_wm2': 'Solar radiation (W/m²)',
                'temperature_max_C': 'Maximum temperature (°C)',
                'temperature_min_C': 'Minimum temperature (°C)',
                'specific_humidity_g_kg': 'Specific humidity at 2m (g/kg)',
                'dew_point_C': 'Dew point temperature at 2m (°C)',
                'wind_speed_10m_mps': 'Wind speed at 10 meters (m/s)',
                'cloud_amount_perc': 'Cloud amount (%)',
                'sea_level_pressure_hPa': 'Sea level pressure (hPa)',
                'surface_soil_wetness_perc': 'Surface soil wetness (%)',
                'wind_direction_10m_degrees': 'Wind direction at 10m (degrees)',
                'evapotranspiration_wm2': 'Evapotranspiration energy flux (W/m²)',
                'root_zone_soil_moisture_perc': 'Root zone soil moisture (%)'
            }
            
            return create_success_response({
                'available_fields': field_descriptions,
                'field_count': len(field_descriptions),
                'nasa_power_fields': WeatherDataModel.WEATHER_FIELDS,
                'service_info': {
                    'data_source': 'NASA POWER API',
                    'temporal_resolution': 'daily',
                    'spatial_resolution': '0.5° x 0.625°',
                    'coverage': 'global',
                    'data_lag': '~7 days'
                }
            })
            
        except Exception as e:
            self.logger.error(f"Available fields error: {str(e)}")
            return create_error_response(
                f"Failed to get available fields: {str(e)}"
            )
    
    def get_service_status(self) -> Dict[str, Any]:
        """Get weather service status and health"""
        try:
            service_status = self.weather_service.get_service_status()
            
            return create_success_response({
                'controller': 'Weather Controller',
                'service': service_status,
                'health': 'healthy' if service_status.get('initialized') else 'unhealthy'
            })
            
        except Exception as e:
            self.logger.error(f"Service status error: {str(e)}")
            return create_error_response(
                f"Failed to get service status: {str(e)}"
            )