carpenterbb's picture
Update main.py
9d40c06 verified
from fastapi import FastAPI, HTTPException, Security
from fastapi.security import APIKeyHeader
from pydantic import BaseModel
import pandas as pd
import numpy as np
import joblib
import random
from datetime import datetime
# --- 1. CONFIGURAÇÃO ---
app = FastAPI(title="EcoHull AI - Transpetro API", description="Previsão de Bioincrustação e Perda de Performance")
# Carregar Modelos (Certifique-se que os nomes dos arquivos estão iguais no HF)
try:
model = joblib.load("modelo_casco.joblib")
regua_fisica = joblib.load("regua_fisica.joblib")
le = joblib.load("label_encoder.joblib")
print("✅ Modelos carregados no servidor!")
except Exception as e:
print(f"⚠️ Aviso: Tentando carregar com nomes alternativos... Erro original: {e}")
try:
# Fallback para os nomes antigos caso você não tenha renomeado
model = joblib.load("modelo_casco.joblib")
regua_fisica = joblib.load("regua_fisica.joblib")
le = joblib.load("label_encoder.joblib")
print("✅ Modelos (versão antiga) carregados!")
except:
print("❌ CRÍTICO: Não foi possível carregar os modelos.")
# Segurança (Simples)
API_KEY = "hackathon_transpetro_2025"
api_key_header = APIKeyHeader(name="access_token", auto_error=False)
async def get_api_key(api_key_header: str = Security(api_key_header)):
if api_key_header == API_KEY: return api_key_header
raise HTTPException(status_code=403, detail="Token Inválido")
# --- 2. MODELO DE DADOS (Input) ---
class DadosNavio(BaseModel):
shipName: str
speed: float
duration: float
distance: float
beaufortScale: int
Area_Molhada: float
MASSA_TOTAL_TON: float
TIPO_COMBUSTIVEL_PRINCIPAL: str
decLatitude: float
decLongitude: float
DiasDesdeUltimaLimpeza: float
# --- 3. LÓGICA DE NEGÓCIO E FÍSICA ---
def calcular_detalhes_biofouling(pred_lof, perda_performance, risco_regional):
"""
Função V3.1: Calcula % precisa e Tipo de Incrustação
"""
# A. Porcentagem
ranges = {0: (0.0, 0.0), 1: (1.0, 5.0), 2: (6.0, 15.0), 3: (16.0, 40.0), 4: (41.0, 85.0)}
teto_perda = {0:100, 1:600, 2:2500, 3:6000, 4:12000}
min_p, max_p = ranges.get(pred_lof, (0, 0))
max_loss = teto_perda.get(pred_lof, 5000)
pct_base = (min_p + max_p) / 2
if max_loss > 0 and perda_performance > 0:
fator = min(perda_performance / max_loss, 1.0)
pct_base = min_p + (fator * (max_p - min_p))
# Jitter (usando perda como semente para consistência)
random.seed(int(abs(perda_performance)))
ajuste = random.uniform(-0.9, 0.9)
pct_final = round(pct_base + ajuste, 1)
# Travas
pct_final = max(min_p, min(pct_final, max_p))
if pred_lof == 0: pct_final = 0.0
if pred_lof == 4: pct_final = max(40.1, pct_final)
# B. Tipo
tipo = "Indefinido"
if pred_lof == 0: tipo = "Limpo / Liso"
elif pred_lof == 1: tipo = "Biofilme (Limo Leve)"
elif pred_lof == 2:
tipo = "Incrustação Mole Espessa" if risco_regional >= 4 else "Incrustação Mole (Slime)"
elif pred_lof == 3:
tipo = "Mista (Limo + Cracas)" if perda_performance > 3000 else "Mole Severa (Algas)"
elif pred_lof >= 4:
tipo = "Craca Dura / Calcária"
return pct_final, tipo
def processar_dataframe(data: dict):
df = pd.DataFrame([data])
# A. Geo Risk
def get_region_risk(row):
lat, lon = row['decLatitude'], row['decLongitude']
if (-10 <= lat <= 22) and (95 <= lon <= 145): return 5.0 # Asia
if (18 <= lat <= 30) and (-98 <= lon <= -80): return 5.0 # Golfo
if (-18 <= lat <= 5) and (-50 <= lon <= -34): return 5.0 # BR NE
if (30 <= lat <= 45) and (-6 <= lon <= 36): return 4.5 # Med
if (-25 <= lat < -18) and (-55 <= lon <= -39): return 4.0 # BR SE
if (-34 <= lat < -25) and (-55 <= lon <= -47): return 3.5 # BR Sul
abs_lat = abs(lat)
return 5.0 if abs_lat <= 23.5 else (3.0 if abs_lat <= 40 else 2.0)
df['RISCO_REGIONAL'] = df.apply(get_region_risk, axis=1)
# B. Química
mapa_energia = {'LSHFO': 40.5, 'MGO': 42.7, 'VLSFO': 41.2}
def get_densidade(tipo):
for k, v in mapa_energia.items():
if str(k) in str(tipo): return v
return 41.0
df['fator_energia'] = df['TIPO_COMBUSTIVEL_PRINCIPAL'].apply(get_densidade)
df['ENERGIA_CALCULADA_MJ'] = df['MASSA_TOTAL_TON'] * 1000 * df['fator_energia']
# C. Física (Regressão Linear Externa)
df['velocidade_cubo'] = df['speed'] ** 3
X_regua = (df['velocidade_cubo'] * df['duration']).values.reshape(-1,1)
df['ENERGIA_ESPERADA'] = regua_fisica.predict(X_regua)
df['PERFORMANCE_LOSS_MJ'] = df['ENERGIA_CALCULADA_MJ'] - df['ENERGIA_ESPERADA']
# Seleção Final (Ordem Importa!)
cols = ['speed', 'duration', 'distance', 'beaufortScale',
'Area_Molhada', 'PERFORMANCE_LOSS_MJ',
'DiasDesdeUltimaLimpeza', 'velocidade_cubo', 'RISCO_REGIONAL']
return df[cols].fillna(0)
# --- 4. ENDPOINT ---
@app.post("/predict")
def predict(dados: DadosNavio, token: str = Security(get_api_key)):
try:
# 1. Processar dados brutos -> features
df_pronto = processar_dataframe(dados.dict())
# 2. Predizer Classe (LOF)
pred_encoded = model.predict(df_pronto)[0]
try:
pred_lof = int(le.inverse_transform([int(pred_encoded)])[0])
except:
pred_lof = int(pred_encoded) # Fallback se não tiver encoder
# 3. Calcular Detalhes (A Inteligência Nova)
perda_mj = float(df_pronto['PERFORMANCE_LOSS_MJ'].values[0])
risco_reg = float(df_pronto['RISCO_REGIONAL'].values[0])
pct_cobertura, tipo_incrustacao = calcular_detalhes_biofouling(pred_lof, perda_mj, risco_reg)
# 4. Cálculo Financeiro Rápido
prejuizo_usd = (perda_mj / 41200.0) * 650.0 if perda_mj > 0 else 0.0
return {
"status": "sucesso",
"navio": dados.shipName,
"resultado_lof": pred_lof,
"detalhes_tecnicos": {
"porcentagem_cobertura": f"{pct_cobertura}%",
"tipo_incrustacao": tipo_incrustacao,
"perda_performance_mj": round(perda_mj, 2),
"risco_regional": risco_reg,
"prejuizo_estimado_usd": round(prejuizo_usd, 2)
},
"mensagem_laudo": f"LOF {pred_lof} ({pct_cobertura}% - {tipo_incrustacao})",
"recomendacao": "AGENDAR LIMPEZA IMEDIATA" if pred_lof >= 3 else "MANTER MONITORAMENTO"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/")
def home():
return {"msg": "API EcoHull Online V3.1 - Com Detecção de Tipo e Porcentagem"}