carpenterbb commited on
Commit
1c6713f
·
verified ·
1 Parent(s): 468d6ee

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +122 -0
main.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ %%writefile main.py
2
+ from fastapi import FastAPI, HTTPException, Security
3
+ from fastapi.security import APIKeyHeader
4
+ from pydantic import BaseModel
5
+ import pandas as pd
6
+ import numpy as np
7
+ import joblib
8
+
9
+ # --- 1. CONFIGURAÇÃO ---
10
+ app = FastAPI(title="EcoHull AI - Transpetro API", description="Previsão de Bioincrustação e Perda de Performance")
11
+
12
+ # Carregar Modelos
13
+ try:
14
+ model = joblib.load("modelo_casco.joblib")
15
+ regua_fisica = joblib.load("regua_fisica.joblib")
16
+ le = joblib.load("label_encoder.joblib")
17
+ print("✅ Modelos carregados no servidor!")
18
+ except Exception as e:
19
+ print(f"❌ Erro ao carregar modelos: {e}")
20
+
21
+ # Segurança (Simples)
22
+ API_KEY = "hackathon_transpetro_2025"
23
+ api_key_header = APIKeyHeader(name="access_token", auto_error=False)
24
+
25
+ async def get_api_key(api_key_header: str = Security(api_key_header)):
26
+ if api_key_header == API_KEY: return api_key_header
27
+ raise HTTPException(status_code=403, detail="Token Inválido")
28
+
29
+ # --- 2. MODELO DE DADOS (O que o Backend deve mandar) ---
30
+ class DadosNavio(BaseModel):
31
+ shipName: str
32
+ speed: float # Nós
33
+ duration: float # Horas
34
+ distance: float # Milhas
35
+ beaufortScale: int
36
+ Area_Molhada: float
37
+ MASSA_TOTAL_TON: float
38
+ TIPO_COMBUSTIVEL_PRINCIPAL: str # "VLSFO", "MGO"
39
+ decLatitude: float
40
+ decLongitude: float
41
+ DiasDesdeUltimaLimpeza: float
42
+
43
+ # --- 3. PIPELINE DE ENGENHARIA (A Mágica) ---
44
+ def processar_dados(data: dict):
45
+ df = pd.DataFrame([data])
46
+
47
+ # A. Geo Risk
48
+ def get_region_risk(row):
49
+ lat, lon = row['decLatitude'], row['decLongitude']
50
+ if (-10 <= lat <= 22) and (95 <= lon <= 145): return 5.0 # Asia
51
+ if (18 <= lat <= 30) and (-98 <= lon <= -80): return 5.0 # Golfo
52
+ if (-18 <= lat <= 5) and (-50 <= lon <= -34): return 5.0 # BR NE
53
+ if (30 <= lat <= 45) and (-6 <= lon <= 36): return 4.5 # Med
54
+ if (-25 <= lat < -18) and (-55 <= lon <= -39): return 4.0 # BR SE
55
+ if (-34 <= lat < -25) and (-55 <= lon <= -47): return 3.5 # BR Sul
56
+ abs_lat = abs(lat)
57
+ return 5.0 if abs_lat <= 23.5 else (3.0 if abs_lat <= 40 else 2.0)
58
+
59
+ df['RISCO_REGIONAL'] = df.apply(get_region_risk, axis=1)
60
+
61
+ # B. Química
62
+ mapa_energia = {'LSHFO': 40.5, 'MGO': 42.7, 'VLSFO': 41.2}
63
+ def get_densidade(tipo):
64
+ for k, v in mapa_energia.items():
65
+ if str(k) in str(tipo): return v
66
+ return 41.0
67
+
68
+ df['fator_energia'] = df['TIPO_COMBUSTIVEL_PRINCIPAL'].apply(get_densidade)
69
+ df['ENERGIA_CALCULADA_MJ'] = df['MASSA_TOTAL_TON'] * 1000 * df['fator_energia']
70
+
71
+ # C. Física (Usando a Régua Carregada)
72
+ df['velocidade_cubo'] = df['speed'] ** 3
73
+ X_regua = (df['velocidade_cubo'] * df['duration']).values.reshape(-1,1)
74
+
75
+ # Previsão da régua
76
+ df['ENERGIA_ESPERADA'] = regua_fisica.predict(X_regua)
77
+ df['PERFORMANCE_LOSS_MJ'] = df['ENERGIA_CALCULADA_MJ'] - df['ENERGIA_ESPERADA']
78
+
79
+ # Seleção Final
80
+ cols = ['speed', 'duration', 'distance', 'beaufortScale',
81
+ 'Area_Molhada', 'PERFORMANCE_LOSS_MJ',
82
+ 'DiasDesdeUltimaLimpeza', 'velocidade_cubo', 'RISCO_REGIONAL']
83
+
84
+ return df[cols].fillna(0)
85
+
86
+ # --- 4. ENDPOINT (Onde o Backend bate) ---
87
+ @app.post("/predict")
88
+ def predict(dados: DadosNavio, token: str = Security(get_api_key)):
89
+ try:
90
+ # 1. Processar
91
+ df_pronto = processar_dados(dados.dict())
92
+
93
+ # 2. Predizer
94
+ pred_encoded = model.predict(df_pronto)[0]
95
+ pred_label = int(pred_encoded)
96
+
97
+ # 3. Decodificar (Label Encoder opcional se soubermos que 0-4 é LOF)
98
+ # Vamos retornar o número e o texto
99
+ texto_lof = ["BOM", "REGULAR", "ATENÇÃO", "RUIM", "CRÍTICO"][pred_label]
100
+
101
+ # 4. Cálculo Financeiro Rápido (Estimativa)
102
+ perda_mj = float(df_pronto['PERFORMANCE_LOSS_MJ'].values[0])
103
+ prejuizo_usd = (perda_mj / 41200.0) * 650.0 if perda_mj > 0 else 0.0
104
+
105
+ return {
106
+ "status": "sucesso",
107
+ "navio": dados.shipName,
108
+ "lof_previsto": pred_label,
109
+ "condicao_texto": texto_lof,
110
+ "dados_tecnicos": {
111
+ "perda_performance_mj": round(perda_mj, 2),
112
+ "risco_regional": float(df_pronto['RISCO_REGIONAL'].values[0]),
113
+ "prejuizo_estimado_viagem_usd": round(prejuizo_usd, 2)
114
+ },
115
+ "recomendacao": "AGENDAR LIMPEZA" if pred_label >= 3 else "MONITORAR"
116
+ }
117
+ except Exception as e:
118
+ raise HTTPException(status_code=500, detail=str(e))
119
+
120
+ @app.get("/")
121
+ def home():
122
+ return {"msg": "API EcoHull Online. Use POST /predict"}