adema5051 commited on
Commit
5904297
·
verified ·
1 Parent(s): adeb781

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +11 -184
main.py CHANGED
@@ -2,15 +2,18 @@
2
  from fastapi import FastAPI, File, UploadFile, HTTPException, Request
3
  from fastapi.responses import StreamingResponse, HTMLResponse
4
  from fastapi.templating import Jinja2Templates
5
- from pydantic import BaseModel, field_validator
6
- from typing import Optional, Dict
7
  import pandas as pd
8
  import io
9
  import asyncio
 
10
  from concurrent.futures import ThreadPoolExecutor
11
 
 
 
12
  from spatial_queries import get_terrain_metrics, distance_to_water
13
- from vulnerability import calculate_vulnerability_index
14
  from gee_auth import initialize_gee
15
  from height_predictor.inference import get_predictor
16
  from height_predictor.get_height_gba import GlobalBuildingAtlasHeight
@@ -34,6 +37,9 @@ except Exception as e:
34
  # APP INITIALIZATION
35
  app = FastAPI(title="Flood Vulnerability Assessment API", version="1.0")
36
 
 
 
 
37
  # Frontend templates setup
38
  templates = Jinja2Templates(directory="templates")
39
 
@@ -43,35 +49,6 @@ executor = ThreadPoolExecutor(max_workers=10)
43
  gba_getter = GlobalBuildingAtlasHeight()
44
 
45
 
46
- # DATA MODEL
47
- class SingleAssessment(BaseModel):
48
- latitude: float
49
- longitude: float
50
- height: Optional[float] = 0.0
51
- basement: Optional[float] = 0.0
52
-
53
- @field_validator('latitude')
54
- @classmethod
55
- def check_lat(cls, v: float) -> float:
56
- if not -90 <= v <= 90:
57
- raise ValueError('Latitude must be between -90 and 90')
58
- return v
59
-
60
- @field_validator('longitude')
61
- @classmethod
62
- def check_lon(cls, v: float) -> float:
63
- if not -180 <= v <= 180:
64
- raise ValueError('Longitude must be between -180 and 180')
65
- return v
66
-
67
- @field_validator('basement')
68
- @classmethod
69
- def check_basement(cls, v: float) -> float:
70
- if v > 0:
71
- raise ValueError('Basement height must be 0 or negative (e.g., -1, -2, -3)')
72
- return v
73
-
74
-
75
  # FRONTEND ROUTE
76
  @app.get("/", response_class=HTMLResponse)
77
  async def home(request: Request):
@@ -143,81 +120,7 @@ async def predict_height(data: SingleAssessment) -> Dict:
143
  raise HTTPException(status_code=500, detail=str(e))
144
 
145
 
146
- def process_single_row(row, use_predicted_height=False, use_gba_height=False):
147
- """Process a single row from CSV - used for parallel processing."""
148
- try:
149
- lat = row['latitude']
150
- lon = row['longitude']
151
- height = row.get('height', 0.0)
152
- basement = row.get('basement', 0.0)
153
- if use_gba_height:
154
- try:
155
- result = gba_getter.get_height_m(lat, lon, buffer_m=5.0)
156
- if result.get('status') == 'success' and result.get('predicted_height') is not None:
157
- h = result['predicted_height']
158
- if h >= 0: # Only use valid positive heights
159
- height = h
160
- except Exception as e:
161
- print(f"GBA height failed for {lat},{lon}: {e}")
162
- elif use_predicted_height:
163
- try:
164
- predictor = get_predictor()
165
- pred = predictor.predict_from_coordinates(lat, lon)
166
- if pred['status'] == 'success' and pred['predicted_height'] is not None:
167
- height = pred['predicted_height']
168
- except Exception as e:
169
- print(f"Height prediction failed for {lat},{lon}: {e}")
170
-
171
- terrain = get_terrain_metrics(lat, lon)
172
- water_dist = distance_to_water(lat, lon)
173
-
174
- result = calculate_vulnerability_index(
175
- lat=lat,
176
- lon=lon,
177
- height=height,
178
- basement=basement,
179
- terrain_metrics=terrain,
180
- water_distance=water_dist
181
- )
182
-
183
- # CSV output - essential columns
184
- return {
185
- 'latitude': lat,
186
- 'longitude': lon,
187
- 'height': height,
188
- 'basement': basement,
189
- 'vulnerability_index': result['vulnerability_index'],
190
- 'ci_lower_95': result['confidence_interval']['lower_bound_95'],
191
- 'ci_upper_95': result['confidence_interval']['upper_bound_95'],
192
- 'vulnerability_level': result['risk_level'],
193
- 'confidence': result['uncertainty_analysis']['confidence'],
194
- 'confidence_interpretation': result['uncertainty_analysis']['interpretation'],
195
- 'elevation_m': result['elevation_m'],
196
- 'tpi_m': result['relative_elevation_m'],
197
- 'slope_degrees': result['slope_degrees'],
198
- 'distance_to_water_m': result['distance_to_water_m'],
199
- 'quality_flags': ','.join(result['uncertainty_analysis']['data_quality_flags']) if result['uncertainty_analysis']['data_quality_flags'] else ''
200
- }
201
-
202
- except Exception as e:
203
- return {
204
- 'latitude': row.get('latitude'),
205
- 'longitude': row.get('longitude'),
206
- 'height': row.get('height', 0.0),
207
- 'basement': row.get('basement', 0.0),
208
- 'error': str(e),
209
- 'vulnerability_index': None,
210
- 'ci_lower_95': None,
211
- 'ci_upper_95': None,
212
- 'risk_level': None,
213
- 'confidence': None,
214
- 'confidence_interpretation': None,
215
- 'elevation_m': None,
216
- 'tpi_m': None,
217
- 'slope_degrees': None,
218
- 'distance_to_water_m': None,
219
- 'quality_flags': ''
220
- }
221
 
222
 
223
  @app.post("/assess_batch")
@@ -232,8 +135,6 @@ async def assess_batch(file: UploadFile = File(...), use_predicted_height:bool=F
232
  status_code=400,
233
  detail="CSV must contain 'latitude' and 'longitude' columns"
234
  )
235
-
236
- import numpy as np
237
  df = df[(np.abs(df['latitude']) <= 90) & (np.abs(df['longitude']) <= 180)]
238
  if len(df) == 0:
239
  raise HTTPException(status_code=400, detail="No valid coordinates in CSV (lat -90..90, lon -180..180)")
@@ -280,7 +181,6 @@ async def assess_batch_multihazard(file: UploadFile = File(...), use_predicted_h
280
  )
281
 
282
  loop = asyncio.get_event_loop()
283
- from vulnerability import calculate_multi_hazard_vulnerability
284
  results = await loop.run_in_executor(
285
  executor,
286
  lambda: [process_single_row_multihazard(row, use_predicted_height, use_gba_height) for _, row in df.iterrows()]
@@ -345,79 +245,7 @@ async def explain_assessment(data: SingleAssessment) -> Dict:
345
  raise HTTPException(status_code=500, detail=f"Assessment failed: {e}")
346
 
347
 
348
- def process_single_row_multihazard(row, use_predicted_height=False, use_gba_height=False):
349
- """Process a single row with multi-hazard assessment."""
350
- try:
351
- from vulnerability import calculate_multi_hazard_vulnerability
352
-
353
- lat = row['latitude']
354
- lon = row['longitude']
355
- height = row.get('height', 0.0)
356
- basement = row.get('basement', 0.0)
357
-
358
- if use_gba_height:
359
- try:
360
- result = gba_getter.get_height_m(lat, lon, buffer_m=5.0)
361
- if result.get('status') == 'success' and result.get('predicted_height') is not None:
362
- h = result['predicted_height']
363
- if h >= 0: # Only use valid positive heights
364
- height = h
365
- except Exception as e:
366
- print(f"GBA height failed for {lat},{lon}: {e}")
367
- elif use_predicted_height:
368
- try:
369
- predictor = get_predictor()
370
- pred = predictor.predict_from_coordinates(lat, lon)
371
- if pred['status'] == 'success' and pred['predicted_height'] is not None:
372
- height = pred['predicted_height']
373
- except Exception as e:
374
- print(f"Height prediction failed for {lat},{lon}: {e}")
375
-
376
- terrain = get_terrain_metrics(lat, lon)
377
- water_dist = distance_to_water(lat, lon)
378
-
379
- result = calculate_multi_hazard_vulnerability(
380
- lat=lat,
381
- lon=lon,
382
- height=height,
383
- basement=basement,
384
- terrain_metrics=terrain,
385
- water_distance=water_dist
386
- )
387
-
388
- return {
389
- 'latitude': lat,
390
- 'longitude': lon,
391
- 'height': height,
392
- 'basement': basement,
393
- 'vulnerability_index': result['vulnerability_index'],
394
- 'ci_lower_95': result['confidence_interval']['lower_bound_95'],
395
- 'ci_upper_95': result['confidence_interval']['upper_bound_95'],
396
- 'vulnerability_level': result['risk_level'],
397
- 'confidence': result['uncertainty_analysis']['confidence'],
398
- 'confidence_interpretation': result['uncertainty_analysis']['interpretation'],
399
- 'elevation_m': result['elevation_m'],
400
- 'tpi_m': result['relative_elevation_m'],
401
- 'slope_degrees': result['slope_degrees'],
402
- 'distance_to_water_m': result['distance_to_water_m'],
403
- 'dominant_hazard': result['dominant_hazard'],
404
- 'fluvial_risk': result['hazard_breakdown']['fluvial_riverine'],
405
- 'coastal_risk': result['hazard_breakdown']['coastal_surge'],
406
- 'pluvial_risk': result['hazard_breakdown']['pluvial_drainage'],
407
- 'combined_risk': result['hazard_breakdown']['combined_index'],
408
- 'quality_flags': ','.join(result['uncertainty_analysis']['data_quality_flags'])
409
- if result['uncertainty_analysis']['data_quality_flags'] else ''
410
- }
411
-
412
- except Exception as e:
413
- return {
414
- 'latitude': row.get('latitude'),
415
- 'longitude': row.get('longitude'),
416
- 'height': row.get('height', 0.0),
417
- 'basement': row.get('basement', 0.0),
418
- 'error': str(e),
419
- 'vulnerability_index': None
420
- }
421
 
422
 
423
  @app.post("/assess_multihazard")
@@ -426,7 +254,6 @@ async def assess_multihazard(data: SingleAssessment) -> Dict:
426
  loop = asyncio.get_event_loop()
427
 
428
  try:
429
- from vulnerability import calculate_multi_hazard_vulnerability
430
 
431
  # Run slow terrain + water queries in a background thread
432
  terrain, water_dist = await loop.run_in_executor(
 
2
  from fastapi import FastAPI, File, UploadFile, HTTPException, Request
3
  from fastapi.responses import StreamingResponse, HTMLResponse
4
  from fastapi.templating import Jinja2Templates
5
+ from fastapi.staticfiles import StaticFiles
6
+ from typing import Dict
7
  import pandas as pd
8
  import io
9
  import asyncio
10
+ import numpy as np
11
  from concurrent.futures import ThreadPoolExecutor
12
 
13
+ from api.models import SingleAssessment
14
+ from api.batch import process_single_row, process_single_row_multihazard
15
  from spatial_queries import get_terrain_metrics, distance_to_water
16
+ from vulnerability import calculate_vulnerability_index, calculate_multi_hazard_vulnerability
17
  from gee_auth import initialize_gee
18
  from height_predictor.inference import get_predictor
19
  from height_predictor.get_height_gba import GlobalBuildingAtlasHeight
 
37
  # APP INITIALIZATION
38
  app = FastAPI(title="Flood Vulnerability Assessment API", version="1.0")
39
 
40
+ # Mount static files directory
41
+ app.mount("/static", StaticFiles(directory="static"), name="static")
42
+
43
  # Frontend templates setup
44
  templates = Jinja2Templates(directory="templates")
45
 
 
49
  gba_getter = GlobalBuildingAtlasHeight()
50
 
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  # FRONTEND ROUTE
53
  @app.get("/", response_class=HTMLResponse)
54
  async def home(request: Request):
 
120
  raise HTTPException(status_code=500, detail=str(e))
121
 
122
 
123
+ # Batch processing functions moved to api/batch.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
 
126
  @app.post("/assess_batch")
 
135
  status_code=400,
136
  detail="CSV must contain 'latitude' and 'longitude' columns"
137
  )
 
 
138
  df = df[(np.abs(df['latitude']) <= 90) & (np.abs(df['longitude']) <= 180)]
139
  if len(df) == 0:
140
  raise HTTPException(status_code=400, detail="No valid coordinates in CSV (lat -90..90, lon -180..180)")
 
181
  )
182
 
183
  loop = asyncio.get_event_loop()
 
184
  results = await loop.run_in_executor(
185
  executor,
186
  lambda: [process_single_row_multihazard(row, use_predicted_height, use_gba_height) for _, row in df.iterrows()]
 
245
  raise HTTPException(status_code=500, detail=f"Assessment failed: {e}")
246
 
247
 
248
+ # Multi-hazard batch processing moved to api/batch.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
 
251
  @app.post("/assess_multihazard")
 
254
  loop = asyncio.get_event_loop()
255
 
256
  try:
 
257
 
258
  # Run slow terrain + water queries in a background thread
259
  terrain, water_dist = await loop.run_in_executor(