File size: 6,869 Bytes
1c6713f 756598b 1c6713f 756598b 1c6713f 9d40c06 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b 1c6713f 756598b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | 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"} |