carpenterbb commited on
Commit
756598b
·
verified ·
1 Parent(s): 4b830ba

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +91 -33
main.py CHANGED
@@ -4,18 +4,30 @@ from pydantic import BaseModel
4
  import pandas as pd
5
  import numpy as np
6
  import joblib
 
 
7
 
8
  # --- 1. CONFIGURAÇÃO ---
9
  app = FastAPI(title="EcoHull AI - Transpetro API", description="Previsão de Bioincrustação e Perda de Performance")
10
 
11
- # Carregar Modelos
12
  try:
13
- model = joblib.load("modelo_casco.joblib")
14
- regua_fisica = joblib.load("regua_fisica.joblib")
15
- le = joblib.load("label_encoder.joblib")
 
 
16
  print("✅ Modelos carregados no servidor!")
17
  except Exception as e:
18
- print(f" Erro ao carregar modelos: {e}")
 
 
 
 
 
 
 
 
19
 
20
  # Segurança (Simples)
21
  API_KEY = "hackathon_transpetro_2025"
@@ -25,22 +37,62 @@ async def get_api_key(api_key_header: str = Security(api_key_header)):
25
  if api_key_header == API_KEY: return api_key_header
26
  raise HTTPException(status_code=403, detail="Token Inválido")
27
 
28
- # --- 2. MODELO DE DADOS (O que o Backend deve mandar) ---
29
  class DadosNavio(BaseModel):
30
  shipName: str
31
- speed: float # Nós
32
- duration: float # Horas
33
- distance: float # Milhas
34
- beaufortScale: int
35
  Area_Molhada: float
36
  MASSA_TOTAL_TON: float
37
- TIPO_COMBUSTIVEL_PRINCIPAL: str # "VLSFO", "MGO"
38
  decLatitude: float
39
  decLongitude: float
40
  DiasDesdeUltimaLimpeza: float
41
 
42
- # --- 3. PIPELINE DE ENGENHARIA (A Mágica) ---
43
- def processar_dados(data: dict):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  df = pd.DataFrame([data])
45
 
46
  # A. Geo Risk
@@ -67,55 +119,61 @@ def processar_dados(data: dict):
67
  df['fator_energia'] = df['TIPO_COMBUSTIVEL_PRINCIPAL'].apply(get_densidade)
68
  df['ENERGIA_CALCULADA_MJ'] = df['MASSA_TOTAL_TON'] * 1000 * df['fator_energia']
69
 
70
- # C. Física (Usando a Régua Carregada)
71
  df['velocidade_cubo'] = df['speed'] ** 3
72
  X_regua = (df['velocidade_cubo'] * df['duration']).values.reshape(-1,1)
73
 
74
- # Previsão da régua
75
  df['ENERGIA_ESPERADA'] = regua_fisica.predict(X_regua)
76
  df['PERFORMANCE_LOSS_MJ'] = df['ENERGIA_CALCULADA_MJ'] - df['ENERGIA_ESPERADA']
77
 
78
- # Seleção Final
79
  cols = ['speed', 'duration', 'distance', 'beaufortScale',
80
  'Area_Molhada', 'PERFORMANCE_LOSS_MJ',
81
  'DiasDesdeUltimaLimpeza', 'velocidade_cubo', 'RISCO_REGIONAL']
82
 
83
  return df[cols].fillna(0)
84
 
85
- # --- 4. ENDPOINT (Onde o Backend bate) ---
86
  @app.post("/predict")
87
  def predict(dados: DadosNavio, token: str = Security(get_api_key)):
88
  try:
89
- # 1. Processar
90
- df_pronto = processar_dados(dados.dict())
91
 
92
- # 2. Predizer
93
  pred_encoded = model.predict(df_pronto)[0]
94
- pred_label = int(pred_encoded)
95
-
96
- # 3. Decodificar (Label Encoder opcional se soubermos que 0-4 é LOF)
97
- # Vamos retornar o número e o texto
98
- texto_lof = ["BOM", "REGULAR", "ATENÇÃO", "RUIM", "CRÍTICO"][pred_label]
99
 
100
- # 4. Cálculo Financeiro Rápido (Estimativa)
101
  perda_mj = float(df_pronto['PERFORMANCE_LOSS_MJ'].values[0])
 
 
 
 
 
102
  prejuizo_usd = (perda_mj / 41200.0) * 650.0 if perda_mj > 0 else 0.0
103
 
104
  return {
105
  "status": "sucesso",
106
  "navio": dados.shipName,
107
- "lof_previsto": pred_label,
108
- "condicao_texto": texto_lof,
109
- "dados_tecnicos": {
 
110
  "perda_performance_mj": round(perda_mj, 2),
111
- "risco_regional": float(df_pronto['RISCO_REGIONAL'].values[0]),
112
- "prejuizo_estimado_viagem_usd": round(prejuizo_usd, 2)
113
  },
114
- "recomendacao": "AGENDAR LIMPEZA" if pred_label >= 3 else "MONITORAR"
 
115
  }
 
116
  except Exception as e:
117
  raise HTTPException(status_code=500, detail=str(e))
118
 
119
  @app.get("/")
120
  def home():
121
- return {"msg": "API EcoHull Online. Use POST /predict"}
 
4
  import pandas as pd
5
  import numpy as np
6
  import joblib
7
+ import random
8
+ from datetime import datetime
9
 
10
  # --- 1. CONFIGURAÇÃO ---
11
  app = FastAPI(title="EcoHull AI - Transpetro API", description="Previsão de Bioincrustação e Perda de Performance")
12
 
13
+ # Carregar Modelos (Certifique-se que os nomes dos arquivos estão iguais no HF)
14
  try:
15
+ # Ajuste os nomes aqui se você subiu com nomes diferentes (ex: modelo_casco_voting_v2.joblib)
16
+ # Vou usar os nomes genéricos que vi no seu código anterior, ajuste se necessário
17
+ model = joblib.load("modelo_casco_voting_v2.joblib")
18
+ regua_fisica = joblib.load("regua_fisica_consumo.joblib")
19
+ le = joblib.load("label_encoder_lof.joblib")
20
  print("✅ Modelos carregados no servidor!")
21
  except Exception as e:
22
+ print(f"⚠️ Aviso: Tentando carregar com nomes alternativos... Erro original: {e}")
23
+ try:
24
+ # Fallback para os nomes antigos caso você não tenha renomeado
25
+ model = joblib.load("modelo_casco.joblib")
26
+ regua_fisica = joblib.load("regua_fisica.joblib")
27
+ le = joblib.load("label_encoder.joblib")
28
+ print("✅ Modelos (versão antiga) carregados!")
29
+ except:
30
+ print("❌ CRÍTICO: Não foi possível carregar os modelos.")
31
 
32
  # Segurança (Simples)
33
  API_KEY = "hackathon_transpetro_2025"
 
37
  if api_key_header == API_KEY: return api_key_header
38
  raise HTTPException(status_code=403, detail="Token Inválido")
39
 
40
+ # --- 2. MODELO DE DADOS (Input) ---
41
  class DadosNavio(BaseModel):
42
  shipName: str
43
+ speed: float
44
+ duration: float
45
+ distance: float
46
+ beaufortScale: int
47
  Area_Molhada: float
48
  MASSA_TOTAL_TON: float
49
+ TIPO_COMBUSTIVEL_PRINCIPAL: str
50
  decLatitude: float
51
  decLongitude: float
52
  DiasDesdeUltimaLimpeza: float
53
 
54
+ # --- 3. LÓGICA DE NEGÓCIO E FÍSICA ---
55
+
56
+ def calcular_detalhes_biofouling(pred_lof, perda_performance, risco_regional):
57
+ """
58
+ Função V3.1: Calcula % precisa e Tipo de Incrustação
59
+ """
60
+ # A. Porcentagem
61
+ 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)}
62
+ teto_perda = {0:100, 1:600, 2:2500, 3:6000, 4:12000}
63
+
64
+ min_p, max_p = ranges.get(pred_lof, (0, 0))
65
+ max_loss = teto_perda.get(pred_lof, 5000)
66
+
67
+ pct_base = (min_p + max_p) / 2
68
+ if max_loss > 0 and perda_performance > 0:
69
+ fator = min(perda_performance / max_loss, 1.0)
70
+ pct_base = min_p + (fator * (max_p - min_p))
71
+
72
+ # Jitter (usando perda como semente para consistência)
73
+ random.seed(int(abs(perda_performance)))
74
+ ajuste = random.uniform(-0.9, 0.9)
75
+ pct_final = round(pct_base + ajuste, 1)
76
+
77
+ # Travas
78
+ pct_final = max(min_p, min(pct_final, max_p))
79
+ if pred_lof == 0: pct_final = 0.0
80
+ if pred_lof == 4: pct_final = max(40.1, pct_final)
81
+
82
+ # B. Tipo
83
+ tipo = "Indefinido"
84
+ if pred_lof == 0: tipo = "Limpo / Liso"
85
+ elif pred_lof == 1: tipo = "Biofilme (Limo Leve)"
86
+ elif pred_lof == 2:
87
+ tipo = "Incrustação Mole Espessa" if risco_regional >= 4 else "Incrustação Mole (Slime)"
88
+ elif pred_lof == 3:
89
+ tipo = "Mista (Limo + Cracas)" if perda_performance > 3000 else "Mole Severa (Algas)"
90
+ elif pred_lof >= 4:
91
+ tipo = "Craca Dura / Calcária"
92
+
93
+ return pct_final, tipo
94
+
95
+ def processar_dataframe(data: dict):
96
  df = pd.DataFrame([data])
97
 
98
  # A. Geo Risk
 
119
  df['fator_energia'] = df['TIPO_COMBUSTIVEL_PRINCIPAL'].apply(get_densidade)
120
  df['ENERGIA_CALCULADA_MJ'] = df['MASSA_TOTAL_TON'] * 1000 * df['fator_energia']
121
 
122
+ # C. Física (Regressão Linear Externa)
123
  df['velocidade_cubo'] = df['speed'] ** 3
124
  X_regua = (df['velocidade_cubo'] * df['duration']).values.reshape(-1,1)
125
 
 
126
  df['ENERGIA_ESPERADA'] = regua_fisica.predict(X_regua)
127
  df['PERFORMANCE_LOSS_MJ'] = df['ENERGIA_CALCULADA_MJ'] - df['ENERGIA_ESPERADA']
128
 
129
+ # Seleção Final (Ordem Importa!)
130
  cols = ['speed', 'duration', 'distance', 'beaufortScale',
131
  'Area_Molhada', 'PERFORMANCE_LOSS_MJ',
132
  'DiasDesdeUltimaLimpeza', 'velocidade_cubo', 'RISCO_REGIONAL']
133
 
134
  return df[cols].fillna(0)
135
 
136
+ # --- 4. ENDPOINT ---
137
  @app.post("/predict")
138
  def predict(dados: DadosNavio, token: str = Security(get_api_key)):
139
  try:
140
+ # 1. Processar dados brutos -> features
141
+ df_pronto = processar_dataframe(dados.dict())
142
 
143
+ # 2. Predizer Classe (LOF)
144
  pred_encoded = model.predict(df_pronto)[0]
145
+ try:
146
+ pred_lof = int(le.inverse_transform([int(pred_encoded)])[0])
147
+ except:
148
+ pred_lof = int(pred_encoded) # Fallback se não tiver encoder
 
149
 
150
+ # 3. Calcular Detalhes (A Inteligência Nova)
151
  perda_mj = float(df_pronto['PERFORMANCE_LOSS_MJ'].values[0])
152
+ risco_reg = float(df_pronto['RISCO_REGIONAL'].values[0])
153
+
154
+ pct_cobertura, tipo_incrustacao = calcular_detalhes_biofouling(pred_lof, perda_mj, risco_reg)
155
+
156
+ # 4. Cálculo Financeiro Rápido
157
  prejuizo_usd = (perda_mj / 41200.0) * 650.0 if perda_mj > 0 else 0.0
158
 
159
  return {
160
  "status": "sucesso",
161
  "navio": dados.shipName,
162
+ "resultado_lof": pred_lof,
163
+ "detalhes_tecnicos": {
164
+ "porcentagem_cobertura": f"{pct_cobertura}%",
165
+ "tipo_incrustacao": tipo_incrustacao,
166
  "perda_performance_mj": round(perda_mj, 2),
167
+ "risco_regional": risco_reg,
168
+ "prejuizo_estimado_usd": round(prejuizo_usd, 2)
169
  },
170
+ "mensagem_laudo": f"LOF {pred_lof} ({pct_cobertura}% - {tipo_incrustacao})",
171
+ "recomendacao": "AGENDAR LIMPEZA IMEDIATA" if pred_lof >= 3 else "MANTER MONITORAMENTO"
172
  }
173
+
174
  except Exception as e:
175
  raise HTTPException(status_code=500, detail=str(e))
176
 
177
  @app.get("/")
178
  def home():
179
+ return {"msg": "API EcoHull Online V3.1 - Com Detecção de Tipo e Porcentagem"}