bteodoru commited on
Commit
ff3cd00
·
verified ·
1 Parent(s): 122c156

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -196
app.py CHANGED
@@ -1,230 +1,241 @@
 
 
 
 
 
 
1
  import os
2
- import sys
3
- import traceback
4
- from pathlib import Path
5
- import pickle
6
- from datetime import datetime
7
 
8
- # Funcție de logging foarte verbos pentru debugging
9
- def debug_log(message, level="INFO"):
10
- """
11
- Funcție de logging care afișează informații detaliate despre starea aplicației.
12
- Aceasta ne ajută să vedem exact unde se oprește procesul de inițializare.
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
- # Începem logging-ul imediat la încărcarea modulului
21
- debug_log("🚀 PORNIREA APLICAȚIEI UCS - Început încărcare modul app.py")
 
 
 
 
 
 
 
 
 
22
 
 
 
 
 
 
23
  try:
24
- # Importurile necesare cu logging pentru fiecare pas major
25
- debug_log("📚 Încărcare biblioteci Python...")
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
- debug_log("📊 Verificare directoare și fișiere disponibile...")
40
-
41
- # Verificăm ce fișiere sunt disponibile în directorul de lucru
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
- debug_log("❌ PROBLEMĂ: Directorul models_for_deployment nu există!")
55
-
56
- # Verificăm și modelele vechi pentru compatibilitate
57
- old_model_files = ["rf_model.joblib", "rf_model_optim.joblib"]
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
- debug_log(f"❌ EROARE la importuri inițiale: {str(e)}", "ERROR")
64
- debug_log(f" Traceback complet: {traceback.format_exc()}", "ERROR")
65
- # Nu facem exit aici - lăsăm aplicația să încerce să continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- # Variabile globale pentru sistemul de modele
68
- sistem_incertitudine = None
69
- system_loaded = False
 
 
 
 
 
70
 
71
- def load_uncertainty_system_with_detailed_logging():
72
- """
73
- Funcție de încărcare a sistemului de incertitudine cu logging foarte detaliat.
74
- Aceasta ne va arăta exact unde se oprește procesul dacă apar probleme.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  """
76
- global sistem_incertitudine, system_loaded
77
 
78
- debug_log("🔍 ÎNCEPEREA ÎNCĂRCĂRII SISTEMULUI DE INCERTITUDINE")
 
 
79
 
 
 
80
  try:
81
- # Strategia de încărcare cu mai multe opțiuni de fallback
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
- sistem_loaded_successfully = False
 
 
89
 
90
- for filename in possible_files:
91
- file_path = models_dir / filename
92
- debug_log(f"🔍 Verific fișierul: {file_path}")
93
-
94
- if file_path.exists():
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
- debug_log(f" Fișierul nu există: {filename}")
123
 
124
- if not sistem_loaded_successfully:
125
- debug_log("🔄 Încerc încărcarea modelelor individuale ca fallback...")
126
- return load_individual_models_fallback()
127
 
128
- system_loaded = True
129
- debug_log("🎉 SISTEM DE INCERTITUDINE ÎNCĂRCAT CU SUCCES!")
130
- return True
131
 
 
 
 
 
 
 
 
132
  except Exception as e:
133
- debug_log(f"❌ EROARE GENERALĂ la încărcarea sistemului: {str(e)}", "ERROR")
134
- debug_log(f"❌ Traceback complet: {traceback.format_exc()}", "ERROR")
135
- return False
136
 
137
- def load_individual_models_fallback():
 
 
138
  """
139
- Funcție de fallback care încearcă încarce modelele vechi dacă sistemul nou nu funcționează.
140
  """
141
- debug_log("🔄 ÎNCĂRCARE FALLBACK - modele individuale")
142
-
143
- try:
144
- # Încercăm încărcăm modelele vechi pentru compatibilitate
145
- model_files = ["/code/rf_model_optim.joblib", "/code/rf_model.joblib"]
146
-
147
- for model_file in model_files:
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
- # Endpoint simplu de debugging pentru testarea stării sistemului
200
- @app.get("/debug-status")
201
- async def debug_status():
202
  """
203
- Endpoint special pentru debugging care returnează informații complete despre starea sistemului.
204
  """
205
- debug_log("🔍 Cerere pentru debug-status")
206
-
207
  try:
 
 
 
208
  return {
209
- "status": "API is running",
210
- "timestamp": datetime.now().isoformat(),
211
- "system_loaded": system_loaded,
212
- "models_directory_exists": Path("/code/models_for_deployment").exists(),
213
- "files_in_models_dir": list(Path("/code/models_for_deployment").iterdir()) if Path("/code/models_for_deployment").exists() else [],
214
- "python_version": sys.version,
215
- "current_directory": str(Path.cwd()),
216
- "environment_variables": dict(os.environ),
 
 
 
 
 
 
217
  }
218
  except Exception as e:
219
- debug_log(f" EROARE în debug-status: {str(e)}", "ERROR")
220
- return {"error": str(e), "traceback": traceback.format_exc()}
221
 
222
- # Endpoint simplu pentru verificarea că aplicația răspunde
223
- @app.get("/")
224
  async def root():
225
- debug_log("🏠 Cerere pentru endpoint root")
226
- return {"message": "UCS API Debug Version - System Running", "system_loaded": system_loaded}
 
 
 
 
 
 
 
 
 
 
 
227
 
228
- # La sfârșit, confirmăm că modulul s-a încărcat complet
229
- debug_log("🎉 MODULUL app.py S-A ÎNCĂRCAT COMPLET!")
230
- debug_log(f"📊 Starea finală: system_loaded = {system_loaded}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ )