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"}