GitHub Actions commited on
Commit
50c4f62
1 Parent(s): 2368d87

Auto-deploy from GitHub

Browse files
Files changed (5) hide show
  1. app.py +55 -27
  2. detect.py +0 -1
  3. models/detect.pt +2 -2
  4. municipal_predictor.py +35 -10
  5. state_predictor.py +49 -20
app.py CHANGED
@@ -1,17 +1,18 @@
1
  # uvicorn app:app --reload
2
-
3
  import uvicorn
4
- from fastapi import Body, FastAPI, UploadFile, File, Response
5
  from fastapi.responses import JSONResponse
6
  from fastapi.middleware.cors import CORSMiddleware
7
  import traceback
8
- import numpy as np
9
- import json
10
 
11
  from detect import DengueDetector
12
  from municipal_predictor import DenguePredictor
13
  from state_predictor import StatePredictor
14
 
 
15
  def default_json_serializer(obj):
16
  if isinstance(obj, np.integer):
17
  return int(obj)
@@ -21,26 +22,45 @@ def default_json_serializer(obj):
21
  return obj.tolist()
22
  raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
23
 
24
- detector: DengueDetector = None
25
- predictor: DenguePredictor = None
26
- state_predictor: StatePredictor = None
 
 
 
 
27
 
28
  app = FastAPI()
29
 
30
- # --- evento de startup para carregar os modelos ---
31
  @app.on_event("startup")
32
  async def startup_event():
33
  global detector, predictor, state_predictor
34
  print("Executando evento de startup: Carregando os m贸dulos de IA...")
 
 
 
 
 
35
  detector = DengueDetector()
36
- predictor = DenguePredictor()
37
  try:
38
- state_predictor = StatePredictor()
 
 
 
 
 
 
 
 
 
 
 
39
  except Exception as e:
40
- # N茫o bloqueia a API se o modelo estadual faltar; a rota retornar谩 503
41
  print("[WARN] StatePredictor n茫o inicializado:", str(e))
42
  state_predictor = None
43
- print("M贸dulos de IA carregados com sucesso. API pronta.")
 
44
 
45
  # --- CORS ---
46
  origins = ["https://previdengue.vercel.app", "http://localhost:3000", "*"]
@@ -49,13 +69,19 @@ app.add_middleware(
49
  allow_origins=origins,
50
  allow_credentials=True,
51
  allow_methods=["*"],
52
- allow_headers=["*"]
53
  )
54
 
55
- # --- Rotas ---
56
  @app.get("/")
57
  def health_check():
58
- return {"status": "ok", "message": "API de Dengue rodando!"}
 
 
 
 
 
 
59
 
60
  @app.post("/detect/")
61
  async def detect(file: UploadFile = File(...)):
@@ -63,9 +89,11 @@ async def detect(file: UploadFile = File(...)):
63
  return JSONResponse(status_code=503, content={"error": "Detector ainda n茫o foi inicializado."})
64
  try:
65
  content = await file.read()
66
- result = detector.detect_image(content)
67
  return JSONResponse(content=result)
68
  except Exception as e:
 
 
69
  return JSONResponse(status_code=500, content={"error": str(e)})
70
 
71
 
@@ -77,21 +105,20 @@ async def predict_dengue_route(payload: dict = Body(...)):
77
  ibge_code_str = payload.get("ibge_code")
78
  if ibge_code_str is None:
79
  raise ValueError("O campo 'ibge_code' 茅 obrigat贸rio.")
80
-
81
  ibge_code = int(ibge_code_str)
82
- # Sempre retorna hist贸rico completo; frontend controla a janela vis铆vel
83
  result = predictor.predict(ibge_code)
84
-
85
  json_content = json.dumps(result, default=default_json_serializer)
86
-
87
  return Response(content=json_content, media_type="application/json")
88
 
89
  except Exception as e:
90
  tb_str = traceback.format_exc()
91
- print(tb_str)
92
  return JSONResponse(status_code=500, content={
93
  "error": str(e),
94
- "traceback": tb_str
95
  })
96
 
97
 
@@ -99,9 +126,12 @@ async def predict_dengue_route(payload: dict = Body(...)):
99
  async def predict_dengue_state_route(payload: dict = Body(...)):
100
  global state_predictor
101
  if state_predictor is None:
102
- # Tenta inicializar pregui莽osamente no primeiro uso
103
  try:
104
- state_predictor = StatePredictor()
 
 
 
 
105
  except Exception as e:
106
  return JSONResponse(status_code=503, content={"error": f"Preditor estadual ainda n茫o foi inicializado: {str(e)}"})
107
  try:
@@ -111,8 +141,6 @@ async def predict_dengue_state_route(payload: dict = Body(...)):
111
  if not state_sigla:
112
  raise ValueError("O campo 'state' (sigla) 茅 obrigat贸rio.")
113
 
114
- # year/week s茫o opcionais; se omitidos, prev锚 ap贸s o 煤ltimo ponto conhecido
115
- # Sempre retorna hist贸rico completo; frontend controla a janela vis铆vel
116
  result = state_predictor.predict(
117
  str(state_sigla).upper(),
118
  year=int(year) if year is not None else None,
@@ -127,5 +155,5 @@ async def predict_dengue_state_route(payload: dict = Body(...)):
127
  print(tb_str)
128
  return JSONResponse(status_code=500, content={
129
  "error": str(e),
130
- "traceback": tb_str
131
  })
 
1
  # uvicorn app:app --reload
2
+ import os
3
  import uvicorn
4
+ from fastapi import Body, FastAPI, UploadFile, File, Response
5
  from fastapi.responses import JSONResponse
6
  from fastapi.middleware.cors import CORSMiddleware
7
  import traceback
8
+ import numpy as np
9
+ import json
10
 
11
  from detect import DengueDetector
12
  from municipal_predictor import DenguePredictor
13
  from state_predictor import StatePredictor
14
 
15
+
16
  def default_json_serializer(obj):
17
  if isinstance(obj, np.integer):
18
  return int(obj)
 
22
  return obj.tolist()
23
  raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
24
 
25
+
26
+ detector: DengueDetector | None = None
27
+ predictor: DenguePredictor | None = None
28
+ state_predictor: StatePredictor | None = None
29
+
30
+ # Se api ir谩 utilizar datasets baixados do hugging face ou os locais
31
+ ONLINE: bool = True
32
 
33
  app = FastAPI()
34
 
35
+
36
  @app.on_event("startup")
37
  async def startup_event():
38
  global detector, predictor, state_predictor
39
  print("Executando evento de startup: Carregando os m贸dulos de IA...")
40
+
41
+ offline_flag = (not ONLINE)
42
+ local_city_inf = None
43
+ local_state_inf = None
44
+
45
  detector = DengueDetector()
 
46
  try:
47
+ predictor = DenguePredictor(
48
+ offline=offline_flag,
49
+ local_inference_path=local_city_inf,
50
+ )
51
+ except Exception as e:
52
+ print("[WARN] DenguePredictor (municipal) n茫o inicializado:", str(e))
53
+ predictor = None
54
+ try:
55
+ state_predictor = StatePredictor(
56
+ offline=offline_flag,
57
+ local_inference_path=local_state_inf,
58
+ )
59
  except Exception as e:
 
60
  print("[WARN] StatePredictor n茫o inicializado:", str(e))
61
  state_predictor = None
62
+ print("M贸dulos de IA carregados com sucesso. API pronta. Modo:", "online" if ONLINE else "offline")
63
+
64
 
65
  # --- CORS ---
66
  origins = ["https://previdengue.vercel.app", "http://localhost:3000", "*"]
 
69
  allow_origins=origins,
70
  allow_credentials=True,
71
  allow_methods=["*"],
72
+ allow_headers=["*"],
73
  )
74
 
75
+
76
  @app.get("/")
77
  def health_check():
78
+ return {
79
+ "status": "ok",
80
+ "message": "API de Dengue rodando!",
81
+ "mode": "online" if ONLINE else "offline",
82
+ "online": ONLINE,
83
+ }
84
+
85
 
86
  @app.post("/detect/")
87
  async def detect(file: UploadFile = File(...)):
 
89
  return JSONResponse(status_code=503, content={"error": "Detector ainda n茫o foi inicializado."})
90
  try:
91
  content = await file.read()
92
+ result = detector.detect_image(content)
93
  return JSONResponse(content=result)
94
  except Exception as e:
95
+ tb_str = traceback.format_exc()
96
+ print(tb_str)
97
  return JSONResponse(status_code=500, content={"error": str(e)})
98
 
99
 
 
105
  ibge_code_str = payload.get("ibge_code")
106
  if ibge_code_str is None:
107
  raise ValueError("O campo 'ibge_code' 茅 obrigat贸rio.")
108
+
109
  ibge_code = int(ibge_code_str)
 
110
  result = predictor.predict(ibge_code)
111
+
112
  json_content = json.dumps(result, default=default_json_serializer)
113
+
114
  return Response(content=json_content, media_type="application/json")
115
 
116
  except Exception as e:
117
  tb_str = traceback.format_exc()
118
+ print(tb_str)
119
  return JSONResponse(status_code=500, content={
120
  "error": str(e),
121
+ "traceback": tb_str,
122
  })
123
 
124
 
 
126
  async def predict_dengue_state_route(payload: dict = Body(...)):
127
  global state_predictor
128
  if state_predictor is None:
 
129
  try:
130
+ local_state_inf = None
131
+ state_predictor = StatePredictor(
132
+ offline=(not ONLINE),
133
+ local_inference_path=local_state_inf,
134
+ )
135
  except Exception as e:
136
  return JSONResponse(status_code=503, content={"error": f"Preditor estadual ainda n茫o foi inicializado: {str(e)}"})
137
  try:
 
141
  if not state_sigla:
142
  raise ValueError("O campo 'state' (sigla) 茅 obrigat贸rio.")
143
 
 
 
144
  result = state_predictor.predict(
145
  str(state_sigla).upper(),
146
  year=int(year) if year is not None else None,
 
155
  print(tb_str)
156
  return JSONResponse(status_code=500, content={
157
  "error": str(e),
158
+ "traceback": tb_str,
159
  })
detect.py CHANGED
@@ -131,7 +131,6 @@ class DengueDetector:
131
  all_scores.append(float(confidences[j]))
132
  all_classes.append(int(class_ids[j]))
133
 
134
- final_indices = []
135
  final_boxes = []
136
  final_scores = []
137
  final_classes = []
 
131
  all_scores.append(float(confidences[j]))
132
  all_classes.append(int(class_ids[j]))
133
 
 
134
  final_boxes = []
135
  final_scores = []
136
  final_classes = []
models/detect.pt CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:27a0e59e4f7573d93e1a4ead65ddbde30cc8f5b8db31fe089cfffda4baa2b43b
3
- size 52117266
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3d07392eada71e3c173a9626f6acd9706590c115dcf6eae48652fd10ea7c28b
3
+ size 155794973
municipal_predictor.py CHANGED
@@ -27,8 +27,10 @@ def asymmetric_mse(y_true, y_pred):
27
  return tf.reduce_mean(loss)
28
 
29
  class DenguePredictor:
30
- def __init__(self, project_root=None):
31
  self.project_root = Path(project_root) if project_root else Path(__file__).resolve().parent
 
 
32
  self.sequence_length = 12
33
  self.horizon = 6
34
  self.year_min_train = 2014
@@ -66,15 +68,38 @@ class DenguePredictor:
66
  else:
67
  self.city_to_idx = {}
68
 
69
- hf_token = os.environ.get("HF_TOKEN")
70
- inference_path = hf_hub_download(
71
- repo_id="previdengue/predict_inference_data",
72
- filename="inference_data.parquet",
73
- repo_type="dataset",
74
- token=hf_token
75
- )
76
-
77
- df = pd.read_parquet(inference_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  df["codigo_ibge"] = df["codigo_ibge"].astype(int)
79
  df["ano"] = df["ano"].astype(int)
80
  df["semana"] = df["semana"].astype(int)
 
27
  return tf.reduce_mean(loss)
28
 
29
  class DenguePredictor:
30
+ def __init__(self, project_root=None, offline: bool = False, local_inference_path: str | None = None):
31
  self.project_root = Path(project_root) if project_root else Path(__file__).resolve().parent
32
+ self.offline = bool(offline)
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
 
68
  else:
69
  self.city_to_idx = {}
70
 
71
+ # Load inference dataset (HF online or local offline)
72
+ df = None
73
+ if self.offline:
74
+ # Somente .parquet 茅 aceito no modo offline
75
+ candidate_paths = []
76
+ if self.local_inference_path:
77
+ candidate_paths.append(self.local_inference_path)
78
+ candidate_paths.append(models_dir / "inference_data.parquet")
79
+
80
+ found = None
81
+ for p in candidate_paths:
82
+ try:
83
+ if p and Path(p).exists() and str(p).lower().endswith(".parquet"):
84
+ found = Path(p)
85
+ break
86
+ except Exception:
87
+ continue
88
+ if not found:
89
+ raise FileNotFoundError(
90
+ "Offline mode enabled but no local Parquet dataset found. "
91
+ "Place 'inference_data.parquet' under models/ or pass a valid 'local_inference_path' (.parquet)."
92
+ )
93
+ df = pd.read_parquet(found)
94
+ else:
95
+ hf_token = os.environ.get("HF_TOKEN")
96
+ inference_path = hf_hub_download(
97
+ repo_id="previdengue/predict_inference_data",
98
+ filename="inference_data.parquet",
99
+ repo_type="dataset",
100
+ token=hf_token
101
+ )
102
+ df = pd.read_parquet(inference_path)
103
  df["codigo_ibge"] = df["codigo_ibge"].astype(int)
104
  df["ano"] = df["ano"].astype(int)
105
  df["semana"] = df["semana"].astype(int)
state_predictor.py CHANGED
@@ -20,8 +20,10 @@ def asymmetric_mse(y_true, y_pred):
20
  return tf.reduce_mean(loss)
21
 
22
  class StatePredictor:
23
- def __init__(self, project_root=None):
24
  self.project_root = Path(project_root) if project_root else Path(__file__).resolve().parent
 
 
25
  self.sequence_length = 12
26
  self.horizon = 6
27
  self.dynamic_features = [
@@ -64,26 +66,53 @@ class StatePredictor:
64
  else:
65
  self.state_peak_map = {}
66
 
67
- # inference dataset (HF only)
68
- hf_token = os.environ.get("HF_TOKEN")
69
- hf_repo = "previdengue/predict_inference_data_estadual"
70
- hf_filename = "inference_data_estadual.parquet"
71
- try:
72
- hf_path = hf_hub_download(
73
- repo_id=hf_repo,
74
- filename=hf_filename,
75
- repo_type="dataset",
76
- token=hf_token,
77
- )
78
- df_loaded = pd.read_parquet(hf_path)
79
- except Exception as e:
80
- raise FileNotFoundError(
81
- "Could not download 'inference_data_estadual.parquet' from HF repo 'previdengue/predict_inference_data_estadual'. "
82
- "Ensure the dataset exists and set HF_TOKEN if the repo requires authentication."
83
- ) from e
84
 
85
- # normalize
86
- df = df_loaded.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  required = ["estado_sigla", "year", "week", "casos_soma"]
88
  if any(col not in df.columns for col in required):
89
  raise ValueError("State dataset missing required columns: ['estado_sigla','year','week','casos_soma']")
 
20
  return tf.reduce_mean(loss)
21
 
22
  class StatePredictor:
23
+ def __init__(self, project_root=None, offline: bool = False, local_inference_path: str | None = None):
24
  self.project_root = Path(project_root) if project_root else Path(__file__).resolve().parent
25
+ self.offline = bool(offline)
26
+ self.local_inference_path = Path(local_inference_path) if local_inference_path else None
27
  self.sequence_length = 12
28
  self.horizon = 6
29
  self.dynamic_features = [
 
66
  else:
67
  self.state_peak_map = {}
68
 
69
+ # inference dataset: HF online or local offline (.parquet only)
70
+ if self.offline:
71
+ # Somente .parquet 茅 aceito no modo offline
72
+ candidate_paths = []
73
+ if self.local_inference_path:
74
+ candidate_paths.append(self.local_inference_path)
75
+ # Candidatos comuns no diret贸rio de modelos
76
+ candidate_paths.append(models_dir / "inference_data_state.parquet")
77
+ candidate_paths.append(models_dir / "inference_data_estadual.parquet")
 
 
 
 
 
 
 
 
78
 
79
+ found = None
80
+ for p in candidate_paths:
81
+ try:
82
+ if p and Path(p).exists() and str(p).lower().endswith(".parquet"):
83
+ found = Path(p)
84
+ break
85
+ except Exception:
86
+ continue
87
+ if not found:
88
+ raise FileNotFoundError(
89
+ "Offline mode enabled but no local Parquet state dataset found. "
90
+ "Place 'inference_data_state.parquet' or 'inference_data_estadual.parquet' under models/ or pass a valid 'local_inference_path' (.parquet)."
91
+ )
92
+ df = pd.read_parquet(found)
93
+ else:
94
+ # Tenta baixar do HF; se falhar, tenta arquivo local como fallback
95
+ df = None
96
+ try:
97
+ hf_token = os.environ.get("HF_TOKEN")
98
+ inference_path = hf_hub_download(
99
+ repo_id="previdengue/predict_inference_data",
100
+ filename="inference_data_estadual.parquet",
101
+ repo_type="dataset",
102
+ token=hf_token,
103
+ )
104
+ df = pd.read_parquet(inference_path)
105
+ except Exception:
106
+ # Fallback local
107
+ for p in [models_dir / "inference_data_state.parquet", models_dir / "inference_data_estadual.parquet"]:
108
+ if p.exists():
109
+ df = pd.read_parquet(p)
110
+ break
111
+ if df is None:
112
+ raise FileNotFoundError(
113
+ "Online state dataset not available from HF and no local fallback found. "
114
+ "Place 'inference_data_estadual.parquet' under models/ or switch APP_MODE to 'offline'."
115
+ )
116
  required = ["estado_sigla", "year", "week", "casos_soma"]
117
  if any(col not in df.columns for col in required):
118
  raise ValueError("State dataset missing required columns: ['estado_sigla','year','week','casos_soma']")