|
|
""" |
|
|
FastAPI Main Application - REMB Optimization Engine |
|
|
""" |
|
|
from fastapi import FastAPI, UploadFile, File, HTTPException |
|
|
from fastapi.middleware.cors import CORSMiddleware |
|
|
from pydantic import BaseModel |
|
|
from typing import Optional, List |
|
|
import logging |
|
|
|
|
|
from config.settings import settings |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
app = FastAPI( |
|
|
title=settings.PROJECT_NAME, |
|
|
version=settings.VERSION, |
|
|
description="AI-Powered Industrial Estate Master Planning Optimization Engine" |
|
|
) |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
class OptimizationRequest(BaseModel): |
|
|
"""Request model for optimization""" |
|
|
site_id: str |
|
|
population_size: int = 100 |
|
|
n_generations: int = 200 |
|
|
n_plots: int = 20 |
|
|
|
|
|
|
|
|
class OptimizationResponse(BaseModel): |
|
|
"""Response model for optimization""" |
|
|
optimization_id: str |
|
|
status: str |
|
|
n_solutions: int |
|
|
generation_time_seconds: float |
|
|
|
|
|
|
|
|
class LayoutMetricsResponse(BaseModel): |
|
|
"""Layout metrics response""" |
|
|
layout_id: str |
|
|
total_area_sqm: float |
|
|
sellable_area_sqm: float |
|
|
green_space_area_sqm: float |
|
|
sellable_ratio: float |
|
|
green_space_ratio: float |
|
|
is_compliant: bool |
|
|
|
|
|
|
|
|
@app.get("/") |
|
|
async def root(): |
|
|
"""Root endpoint""" |
|
|
return { |
|
|
"message": "REMB Industrial Estate Master Planning Optimization Engine", |
|
|
"version": settings.VERSION, |
|
|
"status": "operational" |
|
|
} |
|
|
|
|
|
|
|
|
@app.get("/api/v1/health") |
|
|
async def health_check(): |
|
|
"""Health check endpoint""" |
|
|
return { |
|
|
"status": "healthy", |
|
|
"version": settings.VERSION |
|
|
} |
|
|
|
|
|
|
|
|
@app.post("/api/v1/sites/upload") |
|
|
async def upload_site_boundary(file: UploadFile = File(...)): |
|
|
""" |
|
|
Upload site boundary file (Shapefile or DXF) |
|
|
|
|
|
Args: |
|
|
file: Shapefile (.shp) or DXF file |
|
|
|
|
|
Returns: |
|
|
Site ID and metadata |
|
|
""" |
|
|
if not file.filename: |
|
|
raise HTTPException(status_code=400, detail="No file provided") |
|
|
|
|
|
|
|
|
extension = file.filename.split('.')[-1].lower() |
|
|
if f".{extension}" not in settings.ALLOWED_EXTENSIONS: |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail=f"File type .{extension} not allowed. Allowed: {settings.ALLOWED_EXTENSIONS}" |
|
|
) |
|
|
|
|
|
|
|
|
site_id = "site_123" |
|
|
|
|
|
return { |
|
|
"site_id": site_id, |
|
|
"filename": file.filename, |
|
|
"status": "uploaded", |
|
|
"message": "Site boundary uploaded successfully" |
|
|
} |
|
|
|
|
|
|
|
|
@app.post("/api/v1/sites/{site_id}/optimize", response_model=OptimizationResponse) |
|
|
async def optimize_site(site_id: str, request: OptimizationRequest): |
|
|
""" |
|
|
Run optimization for a site |
|
|
|
|
|
Args: |
|
|
site_id: Site identifier |
|
|
request: Optimization parameters |
|
|
|
|
|
Returns: |
|
|
Optimization results with Pareto front |
|
|
""" |
|
|
logger.info(f"Starting optimization for site {site_id}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return OptimizationResponse( |
|
|
optimization_id="opt_123", |
|
|
status="completed", |
|
|
n_solutions=8, |
|
|
generation_time_seconds=120.5 |
|
|
) |
|
|
|
|
|
|
|
|
@app.get("/api/v1/layouts/{layout_id}/metrics", response_model=LayoutMetricsResponse) |
|
|
async def get_layout_metrics(layout_id: str): |
|
|
""" |
|
|
Get metrics for a specific layout |
|
|
|
|
|
Args: |
|
|
layout_id: Layout identifier |
|
|
|
|
|
Returns: |
|
|
Layout metrics |
|
|
""" |
|
|
|
|
|
return LayoutMetricsResponse( |
|
|
layout_id=layout_id, |
|
|
total_area_sqm=250000.0, |
|
|
sellable_area_sqm=162500.0, |
|
|
green_space_area_sqm=37500.0, |
|
|
sellable_ratio=0.65, |
|
|
green_space_ratio=0.15, |
|
|
is_compliant=True |
|
|
) |
|
|
|
|
|
|
|
|
@app.get("/api/v1/layouts/{layout_id}/export") |
|
|
async def export_layout_dxf(layout_id: str): |
|
|
""" |
|
|
Export layout as DXF file |
|
|
|
|
|
Args: |
|
|
layout_id: Layout identifier |
|
|
|
|
|
Returns: |
|
|
DXF file download |
|
|
""" |
|
|
|
|
|
return { |
|
|
"layout_id": layout_id, |
|
|
"status": "exported", |
|
|
"download_url": f"/downloads/{layout_id}.dxf" |
|
|
} |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
import uvicorn |
|
|
uvicorn.run(app, host="0.0.0.0", port=8000) |
|
|
|