Update app.py
Browse files
app.py
CHANGED
|
@@ -1,230 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
-
import
|
| 3 |
-
import
|
| 4 |
-
from
|
| 5 |
-
import
|
| 6 |
-
from datetime import datetime
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
|
| 10 |
-
""
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 15 |
-
print(f"[{timestamp}] [{level}] {message}", flush=True)
|
| 16 |
-
|
| 17 |
-
# Forțăm flush-ul pentru ca mesajele să apară imediat în loguri
|
| 18 |
-
sys.stdout.flush()
|
| 19 |
|
| 20 |
-
#
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
try:
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
from fastapi import FastAPI, HTTPException, Request, Depends, Query
|
| 27 |
-
from fastapi.responses import JSONResponse, HTMLResponse
|
| 28 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 29 |
-
debug_log("✅ FastAPI și dependențele încărcate cu succes")
|
| 30 |
-
|
| 31 |
-
import pandas as pd
|
| 32 |
-
import joblib
|
| 33 |
-
import numpy as np
|
| 34 |
-
debug_log("✅ Biblioteci științifice (pandas, numpy, joblib) încărcate")
|
| 35 |
-
|
| 36 |
-
from pydantic import BaseModel, ValidationError, Field, field_validator, model_validator
|
| 37 |
-
debug_log("✅ Pydantic încărcat pentru validarea datelor")
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
current_dir = Path("/code")
|
| 43 |
-
debug_log(f"📁 Directorul de lucru: {current_dir}")
|
| 44 |
-
debug_log(f"📁 Conținutul directorului /code: {list(current_dir.iterdir())}")
|
| 45 |
-
|
| 46 |
-
# Verificăm specific directorul cu modelele
|
| 47 |
-
models_dir = current_dir / "models_for_deployment"
|
| 48 |
-
debug_log(f"📁 Calea către modele: {models_dir}")
|
| 49 |
-
debug_log(f"📁 Directorul modele există: {models_dir.exists()}")
|
| 50 |
-
|
| 51 |
-
if models_dir.exists():
|
| 52 |
-
debug_log(f"📁 Conținutul directorului modele: {list(models_dir.iterdir())}")
|
| 53 |
else:
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
for filename in old_model_files:
|
| 59 |
-
file_path = current_dir / filename
|
| 60 |
-
debug_log(f"📄 {filename} există: {file_path.exists()}")
|
| 61 |
-
|
| 62 |
except Exception as e:
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
"""
|
| 76 |
-
|
| 77 |
|
| 78 |
-
|
|
|
|
|
|
|
| 79 |
|
|
|
|
|
|
|
| 80 |
try:
|
| 81 |
-
|
| 82 |
-
models_dir = Path("/code/models_for_deployment")
|
| 83 |
-
possible_files = [
|
| 84 |
-
"sistem_incertitudine_final_sistem_complet.pkl",
|
| 85 |
-
"sistem_final_ncv_sistem_complet.pkl", # Numele posibil diferit
|
| 86 |
-
]
|
| 87 |
|
| 88 |
-
|
|
|
|
|
|
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
debug_log(f"✅ Fișierul găsit: {filename}")
|
| 96 |
-
debug_log(f"📏 Dimensiunea fișierului: {file_path.stat().st_size / (1024*1024):.2f} MB")
|
| 97 |
-
|
| 98 |
-
try:
|
| 99 |
-
debug_log(f"📖 Încep încărcarea fișierului: {filename}")
|
| 100 |
-
|
| 101 |
-
with open(file_path, 'rb') as f:
|
| 102 |
-
sistem_incertitudine = pickle.load(f)
|
| 103 |
-
|
| 104 |
-
debug_log(f"✅ Fișierul {filename} încărcat cu succes!")
|
| 105 |
-
|
| 106 |
-
# Verificăm că sistemul are componentele necesare
|
| 107 |
-
required_attrs = ['model_principal', 'model_incertitudine', 'predict_cu_incertitudine']
|
| 108 |
-
missing_attrs = [attr for attr in required_attrs if not hasattr(sistem_incertitudine, attr)]
|
| 109 |
-
|
| 110 |
-
if missing_attrs:
|
| 111 |
-
debug_log(f"⚠️ Sistemul nu are atributele: {missing_attrs}", "WARNING")
|
| 112 |
-
else:
|
| 113 |
-
debug_log("✅ Sistemul are toate atributele necesare")
|
| 114 |
-
sistem_loaded_successfully = True
|
| 115 |
-
break
|
| 116 |
-
|
| 117 |
-
except Exception as load_error:
|
| 118 |
-
debug_log(f"❌ Eroare la încărcarea {filename}: {str(load_error)}", "ERROR")
|
| 119 |
-
debug_log(f"❌ Traceback: {traceback.format_exc()}", "ERROR")
|
| 120 |
-
continue
|
| 121 |
else:
|
| 122 |
-
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
return load_individual_models_fallback()
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
return True
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
except Exception as e:
|
| 133 |
-
|
| 134 |
-
debug_log(f"❌ Traceback complet: {traceback.format_exc()}", "ERROR")
|
| 135 |
-
return False
|
| 136 |
|
| 137 |
-
|
|
|
|
|
|
|
| 138 |
"""
|
| 139 |
-
|
| 140 |
"""
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
debug_log(f"🔍 Verific modelul: {model_file}")
|
| 149 |
-
if Path(model_file).exists():
|
| 150 |
-
debug_log(f"✅ Modelul {model_file} există")
|
| 151 |
-
try:
|
| 152 |
-
model = joblib.load(model_file)
|
| 153 |
-
debug_log(f"✅ Modelul {model_file} încărcat cu succes")
|
| 154 |
-
# Pentru moment, doar confirmăm că modelul se încarcă
|
| 155 |
-
# Puteți implementa logica de compatibilitate aici
|
| 156 |
-
except Exception as e:
|
| 157 |
-
debug_log(f"❌ Eroare la încărcarea {model_file}: {str(e)}", "ERROR")
|
| 158 |
-
else:
|
| 159 |
-
debug_log(f"❌ Modelul {model_file} nu există")
|
| 160 |
-
|
| 161 |
-
debug_log("⚠️ Sistem funcționând în modul de compatibilitate limitată", "WARNING")
|
| 162 |
-
return False
|
| 163 |
-
|
| 164 |
-
except Exception as e:
|
| 165 |
-
debug_log(f"❌ EROARE în funcția de fallback: {str(e)}", "ERROR")
|
| 166 |
-
return False
|
| 167 |
-
|
| 168 |
-
# Încărcăm sistemul imediat după definirea funcțiilor
|
| 169 |
-
debug_log("🎯 Pornesc încărcarea sistemului de modele...")
|
| 170 |
-
model_loading_success = load_uncertainty_system_with_detailed_logging()
|
| 171 |
-
debug_log(f"📊 Rezultatul încărcării modelelor: {model_loading_success}")
|
| 172 |
-
|
| 173 |
-
# Creăm aplicația FastAPI cu logging
|
| 174 |
-
debug_log("🌐 Creez aplicația FastAPI...")
|
| 175 |
-
|
| 176 |
-
try:
|
| 177 |
-
app = FastAPI(
|
| 178 |
-
title="UCS Prediction API with Uncertainty Quantification - DEBUG VERSION",
|
| 179 |
-
description="Versiune de debugging pentru diagnoticarea problemelor de pornire",
|
| 180 |
-
version="2.0-debug"
|
| 181 |
-
)
|
| 182 |
-
debug_log("✅ Aplicația FastAPI creată cu succes")
|
| 183 |
-
|
| 184 |
-
# Adăugăm middleware CORS cu logging
|
| 185 |
-
debug_log("🔧 Configurez CORS middleware...")
|
| 186 |
-
app.add_middleware(
|
| 187 |
-
CORSMiddleware,
|
| 188 |
-
allow_origins=["*"], # Pentru debugging, permittem toate originile
|
| 189 |
-
allow_credentials=True,
|
| 190 |
-
allow_methods=["GET", "POST", "OPTIONS"],
|
| 191 |
-
allow_headers=["*"],
|
| 192 |
-
)
|
| 193 |
-
debug_log("✅ CORS middleware configurat")
|
| 194 |
-
|
| 195 |
-
except Exception as e:
|
| 196 |
-
debug_log(f"❌ EROARE la crearea aplicației FastAPI: {str(e)}", "ERROR")
|
| 197 |
-
debug_log(f"❌ Traceback: {traceback.format_exc()}", "ERROR")
|
| 198 |
|
| 199 |
-
#
|
| 200 |
-
@app.get("/
|
| 201 |
-
async def
|
| 202 |
"""
|
| 203 |
-
|
| 204 |
"""
|
| 205 |
-
debug_log("🔍 Cerere pentru debug-status")
|
| 206 |
-
|
| 207 |
try:
|
|
|
|
|
|
|
|
|
|
| 208 |
return {
|
| 209 |
-
"
|
| 210 |
-
"
|
| 211 |
-
"
|
| 212 |
-
"
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
}
|
| 218 |
except Exception as e:
|
| 219 |
-
|
| 220 |
-
return {"error": str(e), "traceback": traceback.format_exc()}
|
| 221 |
|
| 222 |
-
#
|
| 223 |
-
@app.get("/")
|
| 224 |
async def root():
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
-
#
|
| 229 |
-
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Request, Depends
|
| 2 |
+
from fastapi.responses import JSONResponse
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import joblib
|
| 6 |
+
import numpy as np
|
| 7 |
import os
|
| 8 |
+
import time
|
| 9 |
+
from sklearn.ensemble import RandomForestRegressor # Important for model deserialization
|
| 10 |
+
from pydantic import BaseModel, ValidationError, Field, field_validator, model_validator
|
| 11 |
+
from typing import Any, Dict, List, Optional
|
|
|
|
| 12 |
|
| 13 |
+
# Create FastAPI application
|
| 14 |
+
app = FastAPI(
|
| 15 |
+
title="Soil UCS Prediction API",
|
| 16 |
+
description="API for predicting the unconfined compressive strength (UCS) of cement-stabilized soils",
|
| 17 |
+
version="1.0.0"
|
| 18 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
# Add CORS middleware to allow requests only from your interface server
|
| 21 |
+
app.add_middleware(
|
| 22 |
+
CORSMiddleware,
|
| 23 |
+
allow_origins=[
|
| 24 |
+
"http://www.bi4e-at.tuiasi.ro/ucs-prediction/",
|
| 25 |
+
"https://www.bi4e-at.tuiasi.ro/ucs-prediction/"
|
| 26 |
+
],
|
| 27 |
+
allow_credentials=True,
|
| 28 |
+
allow_methods=["GET", "POST"], # Restrict to only necessary methods
|
| 29 |
+
allow_headers=["*"],
|
| 30 |
+
)
|
| 31 |
|
| 32 |
+
# Global variables
|
| 33 |
+
MODEL_PATH = 'rf_model_optim.joblib' # Path to the model
|
| 34 |
+
DEFAULT_FEATURE_ORDER = ['cement_perecent', 'curing_period', 'compaction_rate'] # Default feature order
|
| 35 |
+
|
| 36 |
+
# Load the model
|
| 37 |
try:
|
| 38 |
+
start_time = time.time()
|
| 39 |
+
model = joblib.load(MODEL_PATH)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
# Determine feature order from model or use default
|
| 42 |
+
if hasattr(model, 'feature_names_in_'):
|
| 43 |
+
FEATURE_ORDER = model.feature_names_in_
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
else:
|
| 45 |
+
FEATURE_ORDER = np.array(DEFAULT_FEATURE_ORDER)
|
| 46 |
+
|
| 47 |
+
load_time = time.time() - start_time
|
| 48 |
+
print(f"Model loaded successfully in {load_time:.2f} seconds! Feature Order: {FEATURE_ORDER}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
except Exception as e:
|
| 50 |
+
import traceback
|
| 51 |
+
print(f"Error loading model: {str(e)}")
|
| 52 |
+
print(traceback.format_exc())
|
| 53 |
+
model = None
|
| 54 |
+
FEATURE_ORDER = np.array(DEFAULT_FEATURE_ORDER)
|
| 55 |
+
|
| 56 |
+
# Input data model definition
|
| 57 |
+
class SoilInput(BaseModel):
|
| 58 |
+
cement_perecent: float = Field(
|
| 59 |
+
...,
|
| 60 |
+
description="Cement percentage in the mixture (0-15%)"
|
| 61 |
+
)
|
| 62 |
+
curing_period: float = Field(
|
| 63 |
+
...,
|
| 64 |
+
description="Curing period in days (1-90 days)"
|
| 65 |
+
)
|
| 66 |
+
compaction_rate: float = Field(
|
| 67 |
+
...,
|
| 68 |
+
description="Compaction rate in mm/min (0.5-2.0 mm/min)"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
@model_validator(mode="after")
|
| 72 |
+
def check_cement_and_curing(self):
|
| 73 |
+
if self.cement_perecent == 0:
|
| 74 |
+
self.curing_period = 0
|
| 75 |
+
else:
|
| 76 |
+
if not (1 <= self.curing_period <= 90):
|
| 77 |
+
raise ValueError("Curing period must be between 1 and 90 days")
|
| 78 |
+
return self
|
| 79 |
+
|
| 80 |
+
@field_validator('cement_perecent')
|
| 81 |
+
@classmethod
|
| 82 |
+
def validate_cement(cls, v: float) -> float:
|
| 83 |
+
if not 0 <= v <= 15:
|
| 84 |
+
raise ValueError("Cement percentage must be between 0% and 15%")
|
| 85 |
+
return v
|
| 86 |
+
|
| 87 |
+
@field_validator('compaction_rate')
|
| 88 |
+
@classmethod
|
| 89 |
+
def validate_compaction(cls, v: float) -> float:
|
| 90 |
+
if not 0.5 <= v <= 2:
|
| 91 |
+
raise ValueError("Compaction rate must be between 0.5 and 2 mm/min")
|
| 92 |
+
return v
|
| 93 |
|
| 94 |
+
class Config:
|
| 95 |
+
schema_extra = {
|
| 96 |
+
"example": {
|
| 97 |
+
"cement_perecent": 5,
|
| 98 |
+
"curing_period": 28,
|
| 99 |
+
"compaction_rate": 1.0
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
|
| 103 |
+
# Prediction response model
|
| 104 |
+
class PredictionResponse(BaseModel):
|
| 105 |
+
success: bool
|
| 106 |
+
prediction: float
|
| 107 |
+
units: str
|
| 108 |
+
input_parameters: Dict[str, float]
|
| 109 |
+
prediction_time_ms: Optional[float] = None
|
| 110 |
+
|
| 111 |
+
# Function to check if model is loaded
|
| 112 |
+
async def get_model():
|
| 113 |
+
if model is None:
|
| 114 |
+
raise HTTPException(
|
| 115 |
+
status_code=503,
|
| 116 |
+
detail="Model was not loaded correctly. Please try again later."
|
| 117 |
+
)
|
| 118 |
+
return model
|
| 119 |
+
|
| 120 |
+
# Prediction endpoint
|
| 121 |
+
@app.post("/predict", response_model=PredictionResponse, summary="UCS Prediction")
|
| 122 |
+
async def predict(soil_data: SoilInput, model=Depends(get_model)):
|
| 123 |
"""
|
| 124 |
+
Predicts the UCS (unconfined compressive strength) of soil.
|
| 125 |
|
| 126 |
+
- **cement_perecent**: Cement percentage in the mixture (0-15%)
|
| 127 |
+
- **curing_period**: Curing period in days (1-90 days)
|
| 128 |
+
- **compaction_rate**: Compaction rate in mm/min (0.5-2 mm/min)
|
| 129 |
|
| 130 |
+
Returns the UCS strength in kPa.
|
| 131 |
+
"""
|
| 132 |
try:
|
| 133 |
+
start_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
# Build DataFrame for prediction
|
| 136 |
+
input_data = soil_data.dict()
|
| 137 |
+
input_df = pd.DataFrame([input_data])
|
| 138 |
|
| 139 |
+
# Ensure correct feature order
|
| 140 |
+
prediction_df = pd.DataFrame()
|
| 141 |
+
for feature in FEATURE_ORDER:
|
| 142 |
+
if feature in input_df.columns:
|
| 143 |
+
prediction_df[feature] = input_df[feature]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
else:
|
| 145 |
+
raise ValueError(f"Feature '{feature}' is missing from input data")
|
| 146 |
|
| 147 |
+
# Make prediction
|
| 148 |
+
prediction = model.predict(prediction_df)
|
|
|
|
| 149 |
|
| 150 |
+
# Calculate prediction time
|
| 151 |
+
prediction_time_ms = (time.time() - start_time) * 1000
|
|
|
|
| 152 |
|
| 153 |
+
return {
|
| 154 |
+
"success": True,
|
| 155 |
+
"prediction": float(prediction[0]),
|
| 156 |
+
"units": "kPa",
|
| 157 |
+
"input_parameters": input_data,
|
| 158 |
+
"prediction_time_ms": prediction_time_ms
|
| 159 |
+
}
|
| 160 |
except Exception as e:
|
| 161 |
+
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
| 162 |
|
| 163 |
+
# Status endpoint
|
| 164 |
+
@app.get("/status", summary="API Status")
|
| 165 |
+
async def status():
|
| 166 |
"""
|
| 167 |
+
Returns the API status and model availability.
|
| 168 |
"""
|
| 169 |
+
return {
|
| 170 |
+
"status": "API is running",
|
| 171 |
+
"model_loaded": model is not None,
|
| 172 |
+
"model_type": "Random Forest Regressor" if model is not None else None,
|
| 173 |
+
"feature_order": FEATURE_ORDER.tolist() if model is not None else [],
|
| 174 |
+
"server_time": time.time()
|
| 175 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
+
# Model info endpoint
|
| 178 |
+
@app.get("/model-info", summary="Model Information")
|
| 179 |
+
async def model_info(model=Depends(get_model)):
|
| 180 |
"""
|
| 181 |
+
Returns detailed information about the model being used.
|
| 182 |
"""
|
|
|
|
|
|
|
| 183 |
try:
|
| 184 |
+
# Extract model parameters
|
| 185 |
+
model_params = model.get_params()
|
| 186 |
+
|
| 187 |
return {
|
| 188 |
+
"model_type": type(model).__name__,
|
| 189 |
+
"features": FEATURE_ORDER.tolist(),
|
| 190 |
+
"target": "UCS (kPa)",
|
| 191 |
+
"valid_ranges": {
|
| 192 |
+
"cement_perecent": {"min": 0, "max": 15, "units": "%"},
|
| 193 |
+
"curing_period": {"min": 0, "max": 90, "units": "days"},
|
| 194 |
+
"compaction_rate": {"min": 0.5, "max": 2, "units": "mm/min"}
|
| 195 |
+
},
|
| 196 |
+
"model_parameters": {
|
| 197 |
+
"n_estimators": model_params.get("n_estimators", None),
|
| 198 |
+
"max_depth": model_params.get("max_depth", None),
|
| 199 |
+
"min_samples_split": model_params.get("min_samples_split", None),
|
| 200 |
+
"min_samples_leaf": model_params.get("min_samples_leaf", None)
|
| 201 |
+
}
|
| 202 |
}
|
| 203 |
except Exception as e:
|
| 204 |
+
raise HTTPException(status_code=500, detail=f"Error retrieving model information: {str(e)}")
|
|
|
|
| 205 |
|
| 206 |
+
# Documentation endpoint
|
| 207 |
+
@app.get("/", summary="API Documentation")
|
| 208 |
async def root():
|
| 209 |
+
"""
|
| 210 |
+
Returns information about the API.
|
| 211 |
+
"""
|
| 212 |
+
return {
|
| 213 |
+
"message": "Welcome to the UCS prediction API for cement-stabilized soils",
|
| 214 |
+
"endpoints": {
|
| 215 |
+
"predict": "/predict - Make UCS predictions",
|
| 216 |
+
"status": "/status - Check API status",
|
| 217 |
+
"model-info": "/model-info - Get model information"
|
| 218 |
+
},
|
| 219 |
+
"docs": "/docs - Swagger documentation",
|
| 220 |
+
"redoc": "/redoc - ReDoc documentation"
|
| 221 |
+
}
|
| 222 |
|
| 223 |
+
# ValidationError exception handler
|
| 224 |
+
@app.exception_handler(ValidationError)
|
| 225 |
+
async def validation_exception_handler(request: Request, exc: ValidationError):
|
| 226 |
+
errors = []
|
| 227 |
+
for error in exc.errors():
|
| 228 |
+
message = error.get('msg', '')
|
| 229 |
+
if message.startswith("Value error, "):
|
| 230 |
+
message = message[12:]
|
| 231 |
+
|
| 232 |
+
errors.append({
|
| 233 |
+
"loc": error.get('loc', []),
|
| 234 |
+
"msg": message,
|
| 235 |
+
"type": error.get('type', '')
|
| 236 |
+
})
|
| 237 |
+
|
| 238 |
+
return JSONResponse(
|
| 239 |
+
status_code=422,
|
| 240 |
+
content={"detail": errors}
|
| 241 |
+
)
|