GitHub Actions commited on
Commit
13ddaaa
1 Parent(s): 9da9782

Auto-deploy from GitHub

Browse files
Files changed (2) hide show
  1. app.py +8 -10
  2. municipal_predictor.py +37 -25
app.py CHANGED
@@ -40,7 +40,6 @@ async def startup_event():
40
 
41
  offline_flag = (not ONLINE)
42
  local_city_inf = None
43
- local_state_inf = None
44
 
45
  detector = DengueDetector()
46
  try:
@@ -53,15 +52,7 @@ async def startup_event():
53
  # print full traceback to help debugging (was previously only printing str(e))
54
  traceback.print_exc()
55
  predictor = None
56
- try:
57
- state_predictor = StatePredictor(
58
- offline=offline_flag,
59
- local_inference_path=local_state_inf,
60
- )
61
- except Exception as e:
62
- print("[WARN] StatePredictor n茫o inicializado:", str(e))
63
- traceback.print_exc()
64
- state_predictor = None
65
  print("M贸dulos de IA carregados com sucesso. API pronta. Modo:", "online" if ONLINE else "offline")
66
 
67
 
@@ -108,6 +99,13 @@ async def predict_dengue_route(payload: dict = Body(...)):
108
  ibge_code_str = payload.get("ibge_code")
109
  if ibge_code_str is None:
110
  raise ValueError("O campo 'ibge_code' 茅 obrigat贸rio.")
 
 
 
 
 
 
 
111
 
112
  ibge_code = int(ibge_code_str)
113
  result = predictor.predict(ibge_code)
 
40
 
41
  offline_flag = (not ONLINE)
42
  local_city_inf = None
 
43
 
44
  detector = DengueDetector()
45
  try:
 
52
  # print full traceback to help debugging (was previously only printing str(e))
53
  traceback.print_exc()
54
  predictor = None
55
+ state_predictor = None
 
 
 
 
 
 
 
 
56
  print("M贸dulos de IA carregados com sucesso. API pronta. Modo:", "online" if ONLINE else "offline")
57
 
58
 
 
99
  ibge_code_str = payload.get("ibge_code")
100
  if ibge_code_str is None:
101
  raise ValueError("O campo 'ibge_code' 茅 obrigat贸rio.")
102
+ year = payload.get("year")
103
+ week = payload.get("week")
104
+ if year is not None or week is not None:
105
+ return JSONResponse(
106
+ status_code=400,
107
+ content={"error": "A rota municipal /predict/ prev锚 apenas a partir da 煤ltima semana dispon铆vel e n茫o aceita 'year'/'week'."},
108
+ )
109
 
110
  ibge_code = int(ibge_code_str)
111
  result = predictor.predict(ibge_code)
municipal_predictor.py CHANGED
@@ -33,6 +33,7 @@ class DenguePredictor:
33
  self.local_inference_path = Path(local_inference_path) if local_inference_path else None
34
  self.sequence_length = 12
35
  self.horizon = 6
 
36
  self.year_min_train = 2014
37
  self.year_max_train = 2025
38
  self.dynamic_features = [
@@ -52,7 +53,11 @@ class DenguePredictor:
52
  def load_assets(self):
53
  models_dir = self.project_root / "models"
54
  scalers_dir = models_dir / "scalers"
55
- model_path = models_dir / "model.keras"
 
 
 
 
56
  city_map_path = models_dir / "city_to_idx.json"
57
 
58
  if not scalers_dir.exists():
@@ -106,7 +111,7 @@ class DenguePredictor:
106
  except Exception:
107
  df["date"] = pd.NaT
108
 
109
- df = df.sort_values(by=["codigo_ibge", "date"]).reset_index(drop=True)
110
  df["week_sin"] = np.sin(2 * np.pi * df["semana"] / 52)
111
  df["week_cos"] = np.cos(2 * np.pi * df["semana"] / 52)
112
  df["year_norm"] = (df["ano"] - self.year_min_train) / (self.year_max_train - self.year_min_train)
@@ -115,8 +120,11 @@ class DenguePredictor:
115
  self.df_master = df
116
  self.municipios = df[["codigo_ibge", "municipio"]].drop_duplicates().sort_values("codigo_ibge")
117
 
118
- if not model_path.exists():
119
- raise FileNotFoundError(str(model_path) + " not found")
 
 
 
120
 
121
  self.model = tf.keras.models.load_model(model_path, custom_objects={"asymmetric_mse": asymmetric_mse}, compile=False)
122
  self._loaded = True
@@ -129,19 +137,21 @@ class DenguePredictor:
129
  plt.close(fig)
130
  return img_str
131
 
132
- def _prepare_sequence(self, df_mun):
133
- df_seq = df_mun.tail(self.sequence_length).copy()
134
- df_seq["casos_velocidade"] = df_seq["numero_casos"].diff().fillna(0)
135
- df_seq["casos_aceleracao"] = df_seq["casos_velocidade"].diff().fillna(0)
136
- df_seq["casos_mm_4_semanas"] = df_seq["numero_casos"].rolling(4, min_periods=1).mean()
137
- df_seq["week_sin"] = np.sin(2 * np.pi * df_seq["semana"] / 52)
138
- df_seq["week_cos"] = np.cos(2 * np.pi * df_seq["semana"] / 52)
139
- df_seq["year_norm"] = (df_seq["ano"] - self.year_min_train) / (self.year_max_train - self.year_min_train)
140
- if "notificacao" not in df_seq.columns:
141
- df_seq["notificacao"] = df_seq["ano"].isin([2021, 2022]).astype(float)
142
- else:
143
- df_seq["notificacao"] = df_seq["notificacao"].astype(float)
144
- return df_seq
 
 
145
 
146
  def predict(self, ibge_code: int, show_plot=False, display_history_weeks=None):
147
  if not self._loaded:
@@ -154,16 +164,17 @@ class DenguePredictor:
154
  municipio_row = self.municipios[self.municipios["codigo_ibge"] == int(ibge_code)]
155
  municipality_name = municipio_row.iloc[0]["municipio"] if not municipio_row.empty else str(ibge_code)
156
 
157
- df_mun_clean = df_mun.dropna(subset=["numero_casos"]).reset_index(drop=True)
158
- if len(df_mun_clean) < self.sequence_length:
159
- raise ValueError(f"Insufficient known-case history for {ibge_code}")
 
160
 
161
- seq_df = self._prepare_sequence(df_mun_clean)
162
  if len(seq_df) < self.sequence_length:
163
  raise ValueError(f"Insufficient sequence length for {ibge_code}")
164
 
165
  dynamic_raw = seq_df[self.dynamic_features].values
166
- static_raw = seq_df[self.static_features].iloc[-1].values.reshape(1, -1)
167
 
168
  missing_feats = [c for c in self.dynamic_features if c not in seq_df.columns]
169
  if missing_feats:
@@ -198,10 +209,11 @@ class DenguePredictor:
198
  predicted_data.append({"date": pred_date, "predicted_cases": int(round(float(val)))})
199
 
200
  # Hist贸rico: por padr茫o retorna tudo; se display_history_weeks > 0, limita a janela
 
201
  if display_history_weeks is None or (isinstance(display_history_weeks, (int, float)) and display_history_weeks <= 0):
202
- hist_tail = df_mun.copy()
203
  else:
204
- hist_tail = df_mun.tail(min(len(df_mun), int(display_history_weeks))).copy()
205
  historic_data = []
206
  for _, row in hist_tail.iterrows():
207
  historic_data.append({
@@ -220,7 +232,7 @@ class DenguePredictor:
220
  return {
221
  "municipality_name": municipality_name,
222
  "ibge": int(ibge_code),
223
- "last_known_index": int(df_mun.index[-1]),
224
  "historic_data": historic_data,
225
  "predicted_data": predicted_data,
226
  "insights": insights,
 
33
  self.local_inference_path = Path(local_inference_path) if local_inference_path else None
34
  self.sequence_length = 12
35
  self.horizon = 6
36
+ self.anchor_lag_weeks = 2
37
  self.year_min_train = 2014
38
  self.year_max_train = 2025
39
  self.dynamic_features = [
 
53
  def load_assets(self):
54
  models_dir = self.project_root / "models"
55
  scalers_dir = models_dir / "scalers"
56
+ candidate_model_paths = [
57
+ models_dir / "model_checkpoint_best_city.keras",
58
+ models_dir / "model.keras",
59
+ ]
60
+ model_path = next((p for p in candidate_model_paths if p.exists()), None)
61
  city_map_path = models_dir / "city_to_idx.json"
62
 
63
  if not scalers_dir.exists():
 
111
  except Exception:
112
  df["date"] = pd.NaT
113
 
114
+ df = df.sort_values(by=["codigo_ibge", "ano", "semana"]).reset_index(drop=True)
115
  df["week_sin"] = np.sin(2 * np.pi * df["semana"] / 52)
116
  df["week_cos"] = np.cos(2 * np.pi * df["semana"] / 52)
117
  df["year_norm"] = (df["ano"] - self.year_min_train) / (self.year_max_train - self.year_min_train)
 
120
  self.df_master = df
121
  self.municipios = df[["codigo_ibge", "municipio"]].drop_duplicates().sort_values("codigo_ibge")
122
 
123
+ if model_path is None:
124
+ raise FileNotFoundError(
125
+ "No municipal model checkpoint found. Expected one of: "
126
+ + ", ".join(str(p) for p in candidate_model_paths)
127
+ )
128
 
129
  self.model = tf.keras.models.load_model(model_path, custom_objects={"asymmetric_mse": asymmetric_mse}, compile=False)
130
  self._loaded = True
 
137
  plt.close(fig)
138
  return img_str
139
 
140
+ def _prepare_sequence(self, df_mun, end_idx=None):
141
+ df_all = df_mun.copy()
142
+ df_all["notificacao"] = df_all["ano"].isin([2021, 2022]).astype(float)
143
+ df_all["week_sin"] = np.sin(2 * np.pi * df_all["semana"] / 52)
144
+ df_all["week_cos"] = np.cos(2 * np.pi * df_all["semana"] / 52)
145
+ df_all["year_norm"] = (df_all["ano"] - self.year_min_train) / (self.year_max_train - self.year_min_train)
146
+ df_all["casos_velocidade"] = df_all["numero_casos"].diff().fillna(0)
147
+ df_all["casos_aceleracao"] = df_all["casos_velocidade"].diff().fillna(0)
148
+ df_all["casos_mm_4_semanas"] = df_all["numero_casos"].rolling(4, min_periods=1).mean()
149
+ if end_idx is None:
150
+ end_idx = len(df_all) - 1
151
+ start_idx = end_idx - self.sequence_length + 1
152
+ if start_idx < 0:
153
+ return df_all.iloc[0:0].copy()
154
+ return df_all.iloc[start_idx:end_idx + 1].copy()
155
 
156
  def predict(self, ibge_code: int, show_plot=False, display_history_weeks=None):
157
  if not self._loaded:
 
164
  municipio_row = self.municipios[self.municipios["codigo_ibge"] == int(ibge_code)]
165
  municipality_name = municipio_row.iloc[0]["municipio"] if not municipio_row.empty else str(ibge_code)
166
 
167
+ pred_point_idx = len(df_mun) - self.anchor_lag_weeks
168
+ last_known_idx = pred_point_idx - 1
169
+ if last_known_idx < self.sequence_length - 1:
170
+ raise ValueError(f"Insufficient sequence window before forecast point for {ibge_code}")
171
 
172
+ seq_df = self._prepare_sequence(df_mun, end_idx=last_known_idx)
173
  if len(seq_df) < self.sequence_length:
174
  raise ValueError(f"Insufficient sequence length for {ibge_code}")
175
 
176
  dynamic_raw = seq_df[self.dynamic_features].values
177
+ static_raw = seq_df[self.static_features].iloc[0].values.reshape(1, -1)
178
 
179
  missing_feats = [c for c in self.dynamic_features if c not in seq_df.columns]
180
  if missing_feats:
 
209
  predicted_data.append({"date": pred_date, "predicted_cases": int(round(float(val)))})
210
 
211
  # Hist贸rico: por padr茫o retorna tudo; se display_history_weeks > 0, limita a janela
212
+ hist_base = df_mun.iloc[:last_known_idx + 1].copy()
213
  if display_history_weeks is None or (isinstance(display_history_weeks, (int, float)) and display_history_weeks <= 0):
214
+ hist_tail = hist_base
215
  else:
216
+ hist_tail = hist_base.tail(min(len(hist_base), int(display_history_weeks))).copy()
217
  historic_data = []
218
  for _, row in hist_tail.iterrows():
219
  historic_data.append({
 
232
  return {
233
  "municipality_name": municipality_name,
234
  "ibge": int(ibge_code),
235
+ "last_known_index": int(last_known_idx),
236
  "historic_data": historic_data,
237
  "predicted_data": predicted_data,
238
  "insights": insights,