VIJAYARAGUL commited on
Commit
193c990
ยท
1 Parent(s): 6d67e0e

hugging face version 1

Browse files
app.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Request, Response
2
+ from pydantic import BaseModel
3
+ import pandas as pd
4
+ import joblib
5
+ import requests
6
+ from datetime import timedelta
7
+ from math import sin, cos, radians, pi
8
+ import logging
9
+ import gc
10
+ import os
11
+ from huggingface_hub import hf_hub_download
12
+ from contextlib import asynccontextmanager
13
+
14
+ # -------------------------
15
+ # Logger setup
16
+ # -------------------------
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format="%(asctime)s - %(levelname)s - %(message)s"
20
+ )
21
+
22
+ # -------------------------
23
+ # Global variables for lazy loading (memory optimization)
24
+ # -------------------------
25
+ REPO_ID = "Vikctor/Drought_Disaster_Management"
26
+ _occurrence_model = None
27
+ _occurrence_scaler = None
28
+ _severity_model = None
29
+ _severity_scaler = None
30
+
31
+ # -------------------------
32
+ # NASA POWER setup
33
+ # -------------------------
34
+ API_BASE = "https://power.larc.nasa.gov/api/temporal/daily/point"
35
+ PARAMS = "PRECTOT,T2M,T2M_MAX,T2M_MIN,ALLSKY_SFC_SW_DWN,RH2M,WS2M"
36
+
37
+ FEATURE_ORDER = [
38
+ "RH2M", "T2M_MAX", "T2M_MIN", "WS2M", "T2M",
39
+ "ALLSKY_SFC_SW_DWN", "PRECTOTCORR",
40
+ "lat_sin", "lat_cos", "lon_sin", "lon_cos",
41
+ "month_sin", "month_cos"
42
+ ]
43
+
44
+
45
+ # -------------------------
46
+ # Memory management
47
+ # -------------------------
48
+ def cleanup_memory():
49
+ """Force garbage collection to free up memory"""
50
+ gc.collect()
51
+
52
+
53
+ def safe_model_load(filename: str):
54
+ """Load model with comprehensive error handling"""
55
+ try:
56
+ logging.info(f"๐Ÿ”„ Attempting to download {filename}...")
57
+ model_path = hf_hub_download(
58
+ repo_id=REPO_ID,
59
+ filename=filename,
60
+ cache_dir="/tmp/hf_cache",
61
+ resume_download=True
62
+ )
63
+ logging.info(f"๐Ÿ“ Model downloaded to: {model_path}")
64
+
65
+ # Check file exists and has content
66
+ if not os.path.exists(model_path):
67
+ raise FileNotFoundError(f"Downloaded file not found: {model_path}")
68
+
69
+ file_size = os.path.getsize(model_path)
70
+ if file_size == 0:
71
+ raise ValueError(f"Downloaded file is empty: {model_path}")
72
+
73
+ logging.info(f"๐Ÿ“Š File size: {file_size / (1024 * 1024):.1f} MB")
74
+
75
+ # Load the model
76
+ model = joblib.load(model_path)
77
+ logging.info(f"โœ… Successfully loaded {filename}")
78
+ return model
79
+
80
+ except Exception as e:
81
+ logging.error(f"โŒ Failed to load {filename}: {str(e)}")
82
+ logging.error(f"โŒ Error type: {type(e).__name__}")
83
+ raise HTTPException(status_code=500, detail=f"Model loading failed: {filename} - {str(e)}")
84
+
85
+
86
+ # -------------------------
87
+ # Lazy loading functions
88
+ # -------------------------
89
+ def get_occurrence_model_and_scaler():
90
+ global _occurrence_model, _occurrence_scaler
91
+ if _occurrence_model is None or _occurrence_scaler is None:
92
+ logging.info("Loading occurrence model and scaler...")
93
+ _occurrence_model = safe_model_load("drought_occurrence_model.joblib")
94
+ _occurrence_scaler = safe_model_load("drought_occurrence_scaler.joblib")
95
+ cleanup_memory()
96
+ return _occurrence_model, _occurrence_scaler
97
+
98
+
99
+ def get_severity_model_and_scaler():
100
+ global _severity_model, _severity_scaler
101
+ if _severity_model is None or _severity_scaler is None:
102
+ logging.info("Loading severity model and scaler...")
103
+ _severity_model = safe_model_load("drought_severity_model.joblib")
104
+ _severity_scaler = safe_model_load("drought_severity_scaler.joblib")
105
+ cleanup_memory()
106
+ return _severity_model, _severity_scaler
107
+
108
+
109
+ # -------------------------
110
+ # Lifespan event handler (replaces deprecated on_event)
111
+ # -------------------------
112
+ @asynccontextmanager
113
+ async def lifespan(app: FastAPI):
114
+ # Startup
115
+ logging.info("๐Ÿš€ Drought API starting - models will load on first request")
116
+ cleanup_memory()
117
+
118
+ yield
119
+
120
+ # Shutdown
121
+ logging.info("๐Ÿ›‘ Drought API shutting down")
122
+ global _occurrence_model, _occurrence_scaler, _severity_model, _severity_scaler
123
+ _occurrence_model = _occurrence_scaler = _severity_model = _severity_scaler = None
124
+ cleanup_memory()
125
+
126
+
127
+ # -------------------------
128
+ # Request schema
129
+ # -------------------------
130
+ class PredictionRequest(BaseModel):
131
+ lat: float
132
+ lon: float
133
+ time: str # YYYY-MM-DD
134
+
135
+
136
+ # -------------------------
137
+ # FastAPI app with lifespan
138
+ # -------------------------
139
+ app = FastAPI(
140
+ title="๐ŸŒ Drought Prediction API",
141
+ version="2.4",
142
+ description="Memory-optimized drought prediction API",
143
+ lifespan=lifespan
144
+ )
145
+
146
+
147
+ # -------------------------
148
+ # NASA fetcher (memory optimized)
149
+ # -------------------------
150
+ def fetch_features(lat, lon, time_str: str) -> dict:
151
+ end = pd.to_datetime(time_str)
152
+ start = end - pd.Timedelta(days=90)
153
+
154
+ params = {
155
+ "latitude": lat,
156
+ "longitude": lon,
157
+ "start": start.strftime("%Y%m%d"),
158
+ "end": end.strftime("%Y%m%d"),
159
+ "parameters": PARAMS,
160
+ "format": "JSON",
161
+ "community": "AG"
162
+ }
163
+
164
+ try:
165
+ response = requests.get(API_BASE, params=params, timeout=30)
166
+ if response.status_code != 200:
167
+ logging.error(f"NASA API error {response.status_code}")
168
+ raise HTTPException(status_code=502, detail="NASA API error")
169
+
170
+ data = response.json().get("properties", {}).get("parameter", {})
171
+ if not data:
172
+ raise HTTPException(status_code=502, detail="No data from NASA API")
173
+
174
+ features = {}
175
+ for p, values in data.items():
176
+ vals = [v for v in values.values() if v is not None]
177
+ if vals:
178
+ if p == "PRECTOT":
179
+ features["PRECTOTCORR"] = sum(vals)
180
+ else:
181
+ features[p] = sum(vals) / len(vals)
182
+
183
+ # Clear response from memory
184
+ del data, response, vals
185
+ cleanup_memory()
186
+
187
+ # Derived features
188
+ features.update({
189
+ "lat_sin": sin(radians(lat)),
190
+ "lat_cos": cos(radians(lat)),
191
+ "lon_sin": sin(radians(lon)),
192
+ "lon_cos": cos(radians(lon)),
193
+ "month_sin": sin(2 * pi * end.month / 12),
194
+ "month_cos": cos(2 * pi * end.month / 12)
195
+ })
196
+
197
+ missing = [f for f in FEATURE_ORDER if f not in features]
198
+ if missing:
199
+ raise HTTPException(status_code=500, detail=f"Missing features: {missing}")
200
+
201
+ return features
202
+
203
+ except HTTPException:
204
+ raise
205
+ except Exception as e:
206
+ logging.error(f"NASA API fetch error: {e}")
207
+ raise HTTPException(status_code=502, detail="NASA API request failed")
208
+
209
+
210
+ # -------------------------
211
+ # Prediction endpoint (memory optimized with detailed debugging)
212
+ # -------------------------
213
+ @app.post("/predict")
214
+ async def predict(req: PredictionRequest):
215
+ try:
216
+ logging.info(f"๐Ÿ”„ Starting prediction for lat={req.lat}, lon={req.lon}, time={req.time}")
217
+
218
+ # Validate input
219
+ try:
220
+ pd.to_datetime(req.time)
221
+ except Exception as e:
222
+ logging.error(f"Invalid time format: {req.time}")
223
+ raise HTTPException(status_code=400, detail=f"Invalid time format: {req.time}. Use YYYY-MM-DD")
224
+
225
+ # Get features
226
+ logging.info("๐Ÿ“ก Fetching NASA data...")
227
+ features = fetch_features(req.lat, req.lon, req.time)
228
+ logging.info(f"โœ… Features fetched: {len(features)} features")
229
+
230
+ X = pd.DataFrame([[features[col] for col in FEATURE_ORDER]], columns=FEATURE_ORDER)
231
+ logging.info(f"๐Ÿ“Š DataFrame created: {X.shape}")
232
+
233
+ # Occurrence prediction
234
+ logging.info("๐Ÿ”ฎ Loading occurrence model...")
235
+ try:
236
+ occ_model, occ_scaler = get_occurrence_model_and_scaler()
237
+ logging.info("โœ… Occurrence model loaded")
238
+ except Exception as e:
239
+ logging.error(f"โŒ Failed to load occurrence model: {e}")
240
+ raise HTTPException(status_code=500, detail=f"Failed to load occurrence model: {str(e)}")
241
+
242
+ try:
243
+ X_occ = occ_scaler.transform(X)
244
+ occurrence_pred = int(occ_model.predict(X_occ)[0])
245
+ occurrence_proba = occ_model.predict_proba(X_occ)[0].tolist()
246
+ logging.info(f"โœ… Occurrence prediction: {occurrence_pred}")
247
+ except Exception as e:
248
+ logging.error(f"โŒ Occurrence prediction failed: {e}")
249
+ raise HTTPException(status_code=500, detail=f"Occurrence prediction failed: {str(e)}")
250
+
251
+ del X_occ # Free memory
252
+ cleanup_memory()
253
+
254
+ # Severity prediction
255
+ logging.info("๐Ÿ”ฎ Loading severity model...")
256
+ try:
257
+ sev_model, sev_scaler = get_severity_model_and_scaler()
258
+ logging.info("โœ… Severity model loaded")
259
+ except Exception as e:
260
+ logging.error(f"โŒ Failed to load severity model: {e}")
261
+ raise HTTPException(status_code=500, detail=f"Failed to load severity model: {str(e)}")
262
+
263
+ try:
264
+ X_sev = sev_scaler.transform(X)
265
+ severity_pred = int(sev_model.predict(X_sev)[0])
266
+ severity_proba = sev_model.predict_proba(X_sev)[0].tolist()
267
+ logging.info(f"โœ… Severity prediction: {severity_pred}")
268
+ except Exception as e:
269
+ logging.error(f"โŒ Severity prediction failed: {e}")
270
+ raise HTTPException(status_code=500, detail=f"Severity prediction failed: {str(e)}")
271
+
272
+ del X_sev # Free memory
273
+ cleanup_memory()
274
+
275
+ result = {
276
+ "input": {"lat": req.lat, "lon": req.lon, "time": req.time},
277
+ "occurrence": {
278
+ "prediction": occurrence_pred,
279
+ "probabilities": occurrence_proba
280
+ },
281
+ "severity": {
282
+ "prediction": severity_pred,
283
+ "probabilities": severity_proba
284
+ },
285
+ "features_used": {k: round(v, 4) for k, v in zip(FEATURE_ORDER, X.iloc[0].tolist())}
286
+ }
287
+
288
+ # Final cleanup
289
+ del X, features
290
+ cleanup_memory()
291
+
292
+ logging.info(f"โœ… Prediction complete: Occurrence={occurrence_pred}, Severity={severity_pred}")
293
+ return result
294
+
295
+ except HTTPException as http_err:
296
+ logging.error(f"HTTP Error: {http_err.detail}")
297
+ cleanup_memory()
298
+ raise http_err
299
+ except Exception as e:
300
+ logging.error(f"โŒ Unexpected prediction error: {str(e)}")
301
+ logging.error(f"โŒ Error type: {type(e).__name__}")
302
+ import traceback
303
+ logging.error(f"โŒ Traceback: {traceback.format_exc()}")
304
+ cleanup_memory()
305
+ raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")
306
+
307
+
308
+ # -------------------------
309
+ # Debug endpoint to test individual components
310
+ # -------------------------
311
+ @app.get("/debug")
312
+ async def debug_info():
313
+ """Debug endpoint to check system status"""
314
+ try:
315
+ debug_data = {
316
+ "python_version": f"{os.sys.version_info.major}.{os.sys.version_info.minor}.{os.sys.version_info.micro}",
317
+ "feature_order": FEATURE_ORDER,
318
+ "repo_id": REPO_ID,
319
+ "api_base": API_BASE,
320
+ "models_loaded": {
321
+ "occurrence_model": _occurrence_model is not None,
322
+ "occurrence_scaler": _occurrence_scaler is not None,
323
+ "severity_model": _severity_model is not None,
324
+ "severity_scaler": _severity_scaler is not None
325
+ }
326
+ }
327
+
328
+ # Test NASA API with a simple request
329
+ try:
330
+ test_response = requests.get("https://power.larc.nasa.gov", timeout=10)
331
+ debug_data["nasa_api_accessible"] = test_response.status_code == 200
332
+ except:
333
+ debug_data["nasa_api_accessible"] = False
334
+
335
+ # Test HuggingFace Hub access
336
+ try:
337
+ from huggingface_hub import list_repo_files
338
+ files = list_repo_files(REPO_ID)
339
+ debug_data["hf_hub_accessible"] = len(files) > 0
340
+ debug_data["hf_files_found"] = list(files)
341
+ except Exception as e:
342
+ debug_data["hf_hub_accessible"] = False
343
+ debug_data["hf_error"] = str(e)
344
+
345
+ return debug_data
346
+
347
+ except Exception as e:
348
+ return {"debug_error": str(e)}
349
+
350
+
351
+ # -------------------------
352
+ # Test prediction with sample data
353
+ # -------------------------
354
+ @app.get("/test")
355
+ async def test_prediction():
356
+ """Test endpoint with hardcoded values"""
357
+ try:
358
+ # Use a recent date and valid coordinates
359
+ test_request = PredictionRequest(
360
+ lat=40.7128, # New York
361
+ lon=-74.0060,
362
+ time="2024-08-15"
363
+ )
364
+
365
+ result = await predict(test_request)
366
+ return {"test_status": "success", "result": result}
367
+
368
+ except Exception as e:
369
+ return {"test_status": "failed", "error": str(e)}
370
+
371
+
372
+ # -------------------------
373
+ # Health check (lightweight)
374
+ # -------------------------
375
+ @app.api_route("/health", methods=["GET", "HEAD"])
376
+ async def health_check(request: Request):
377
+ if request.method == "HEAD":
378
+ return Response(status_code=200)
379
+
380
+ return {
381
+ "status": "healthy",
382
+ "api_version": "2.4",
383
+ "python_version": f"{os.sys.version_info.major}.{os.sys.version_info.minor}"
384
+ }
385
+
386
+
387
+ # -------------------------
388
+ # Root endpoint
389
+ # -------------------------
390
+ @app.get("/")
391
+ async def root():
392
+ return {
393
+ "message": "๐ŸŒ Drought Prediction API",
394
+ "version": "2.4",
395
+ "endpoints": {
396
+ "predict": "/predict",
397
+ "health": "/health",
398
+ "docs": "/docs"
399
+ }
400
+ }
drought_occurrence_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8cb05be867a6b3268f9d49d8b960f70ecad14eab065a23ec3c362f00806e4942
3
+ size 336273753
drought_occurrence_model_scaler.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c0e56fd359b86b018ad105f0b58b4a3957900331285b01dd4101a29ea9d4b617
3
+ size 1295
drought_severity_model.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5e457728dd7ad92a8c3d15b77d61857a5b69df195f7bded6558c4149db20dae0
3
+ size 288980609
drought_severity_model_scaler.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7929d7ec1460b3849fbba09209616b8c1fb6ee748b982f9035e059a785243117
3
+ size 1295
requirements.txt ADDED
Binary file (384 Bytes). View file