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