| | 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 |
| |
|
| | |
| | app = FastAPI(title="EcoHull AI - Transpetro API", description="Previsão de Bioincrustação e Perda de Performance") |
| |
|
| | |
| | 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: |
| | |
| | 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.") |
| |
|
| | |
| | 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") |
| |
|
| | |
| | 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 |
| |
|
| | |
| |
|
| | def calcular_detalhes_biofouling(pred_lof, perda_performance, risco_regional): |
| | """ |
| | Função V3.1: Calcula % precisa e Tipo de Incrustação |
| | """ |
| | |
| | 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)) |
| | |
| | |
| | random.seed(int(abs(perda_performance))) |
| | ajuste = random.uniform(-0.9, 0.9) |
| | pct_final = round(pct_base + ajuste, 1) |
| | |
| | |
| | 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) |
| |
|
| | |
| | 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]) |
| | |
| | |
| | def get_region_risk(row): |
| | lat, lon = row['decLatitude'], row['decLongitude'] |
| | if (-10 <= lat <= 22) and (95 <= lon <= 145): return 5.0 |
| | if (18 <= lat <= 30) and (-98 <= lon <= -80): return 5.0 |
| | if (-18 <= lat <= 5) and (-50 <= lon <= -34): return 5.0 |
| | if (30 <= lat <= 45) and (-6 <= lon <= 36): return 4.5 |
| | if (-25 <= lat < -18) and (-55 <= lon <= -39): return 4.0 |
| | if (-34 <= lat < -25) and (-55 <= lon <= -47): return 3.5 |
| | 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) |
| |
|
| | |
| | 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'] |
| |
|
| | |
| | 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'] |
| |
|
| | |
| | cols = ['speed', 'duration', 'distance', 'beaufortScale', |
| | 'Area_Molhada', 'PERFORMANCE_LOSS_MJ', |
| | 'DiasDesdeUltimaLimpeza', 'velocidade_cubo', 'RISCO_REGIONAL'] |
| | |
| | return df[cols].fillna(0) |
| |
|
| | |
| | @app.post("/predict") |
| | def predict(dados: DadosNavio, token: str = Security(get_api_key)): |
| | try: |
| | |
| | df_pronto = processar_dataframe(dados.dict()) |
| | |
| | |
| | pred_encoded = model.predict(df_pronto)[0] |
| | try: |
| | pred_lof = int(le.inverse_transform([int(pred_encoded)])[0]) |
| | except: |
| | pred_lof = int(pred_encoded) |
| | |
| | |
| | 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) |
| |
|
| | |
| | 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"} |