Upload 6 files
Browse files- .gitattributes +35 -35
- Dockerfile +33 -33
- README.md +11 -11
- app.py +608 -461
- requirements.txt +22 -21
- stopwords.txt +545 -545
.gitattributes
CHANGED
|
@@ -1,35 +1,35 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
CHANGED
|
@@ -1,33 +1,33 @@
|
|
| 1 |
-
# ==============================================================================
|
| 2 |
-
# Dockerfile — AetherMap API (versão profissional)
|
| 3 |
-
# ==============================================================================
|
| 4 |
-
|
| 5 |
-
# Imagem Python robusta (não slim → evita erros de build)
|
| 6 |
-
FROM python:3.10
|
| 7 |
-
|
| 8 |
-
# Define diretório da aplicação
|
| 9 |
-
WORKDIR /app
|
| 10 |
-
|
| 11 |
-
# --- INSTALAR TORCH CPU ANTES (CRÍTICO!) ---
|
| 12 |
-
# Isso garante que a versão certa (CPU) seja instalada
|
| 13 |
-
RUN pip install --no-cache-dir \
|
| 14 |
-
torch \
|
| 15 |
-
torchvision \
|
| 16 |
-
torchaudio \
|
| 17 |
-
--index-url https://download.pytorch.org/whl/cpu
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
# Copiar requirements
|
| 21 |
-
COPY requirements.txt .
|
| 22 |
-
|
| 23 |
-
# Instalar dependências restantes
|
| 24 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 25 |
-
|
| 26 |
-
# Copiar código da aplicação
|
| 27 |
-
COPY . .
|
| 28 |
-
|
| 29 |
-
# Expor porta usada pelo Hugging Face Spaces
|
| 30 |
-
EXPOSE 7860
|
| 31 |
-
|
| 32 |
-
# Comando padrão para executar FastAPI
|
| 33 |
-
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Dockerfile — AetherMap API (versão profissional)
|
| 3 |
+
# ==============================================================================
|
| 4 |
+
|
| 5 |
+
# Imagem Python robusta (não slim → evita erros de build)
|
| 6 |
+
FROM python:3.10
|
| 7 |
+
|
| 8 |
+
# Define diretório da aplicação
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# --- INSTALAR TORCH CPU ANTES (CRÍTICO!) ---
|
| 12 |
+
# Isso garante que a versão certa (CPU) seja instalada
|
| 13 |
+
RUN pip install --no-cache-dir \
|
| 14 |
+
torch \
|
| 15 |
+
torchvision \
|
| 16 |
+
torchaudio \
|
| 17 |
+
--index-url https://download.pytorch.org/whl/cpu
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Copiar requirements
|
| 21 |
+
COPY requirements.txt .
|
| 22 |
+
|
| 23 |
+
# Instalar dependências restantes
|
| 24 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 25 |
+
|
| 26 |
+
# Copiar código da aplicação
|
| 27 |
+
COPY . .
|
| 28 |
+
|
| 29 |
+
# Expor porta usada pelo Hugging Face Spaces
|
| 30 |
+
EXPOSE 7860
|
| 31 |
+
|
| 32 |
+
# Comando padrão para executar FastAPI
|
| 33 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: AetherMap
|
| 3 |
-
emoji: 🦀
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: pink
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
license: apache-2.0
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: AetherMap
|
| 3 |
+
emoji: 🦀
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: apache-2.0
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -1,462 +1,609 @@
|
|
| 1 |
-
# ==============================================================================
|
| 2 |
-
# API do AetherMap — VERSÃO 7.
|
| 3 |
-
# Backend com RAG Híbrido,
|
| 4 |
-
# ==============================================================================
|
| 5 |
-
|
| 6 |
-
import numpy as np
|
| 7 |
-
import pandas as pd
|
| 8 |
-
import torch
|
| 9 |
-
import gc
|
| 10 |
-
import uuid
|
| 11 |
-
import os
|
| 12 |
-
import
|
| 13 |
-
import
|
| 14 |
-
import
|
| 15 |
-
import
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
from fastapi
|
| 20 |
-
from
|
| 21 |
-
from
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
import
|
| 26 |
-
import
|
| 27 |
-
|
| 28 |
-
from sklearn.
|
| 29 |
-
from sklearn.
|
| 30 |
-
from
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
from
|
| 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 |
-
def
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
return
|
| 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 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
"""
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
)
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
logging.
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# API do AetherMap — VERSÃO 7.2 (CSV + TAVILY EDITION)
|
| 3 |
+
# Backend com RAG Híbrido, CSV Support, Web Search via Tavily
|
| 4 |
+
# ==============================================================================
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import torch
|
| 9 |
+
import gc
|
| 10 |
+
import uuid
|
| 11 |
+
import os
|
| 12 |
+
import io
|
| 13 |
+
import json
|
| 14 |
+
import logging
|
| 15 |
+
import time
|
| 16 |
+
import nltk
|
| 17 |
+
from nltk.corpus import stopwords
|
| 18 |
+
|
| 19 |
+
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 20 |
+
from fastapi.responses import JSONResponse
|
| 21 |
+
from typing import List, Dict, Any
|
| 22 |
+
from functools import lru_cache
|
| 23 |
+
|
| 24 |
+
# Ferramentas de Alquimia (ML & NLP)
|
| 25 |
+
from sentence_transformers import SentenceTransformer, CrossEncoder
|
| 26 |
+
import umap
|
| 27 |
+
import hdbscan
|
| 28 |
+
from sklearn.preprocessing import StandardScaler
|
| 29 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 30 |
+
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
|
| 31 |
+
from scipy.stats import entropy
|
| 32 |
+
|
| 33 |
+
# Monitoramento (O Toque da Berta)
|
| 34 |
+
from prometheus_fastapi_instrumentator import Instrumentator
|
| 35 |
+
from prometheus_client import Histogram
|
| 36 |
+
|
| 37 |
+
# A Conexão com o Oráculo
|
| 38 |
+
from groq import Groq
|
| 39 |
+
|
| 40 |
+
# ==============================================================================
|
| 41 |
+
# CONFIGURAÇÕES GERAIS E LOGGING
|
| 42 |
+
# ==============================================================================
|
| 43 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 44 |
+
|
| 45 |
+
# Modelos de IA
|
| 46 |
+
RETRIEVAL_MODEL = "all-MiniLM-L6-v2" # Rápido para varredura inicial
|
| 47 |
+
RERANKER_MODEL = "cross-encoder/ms-marco-MiniLM-L-6-v2" # Preciso para reordenação
|
| 48 |
+
|
| 49 |
+
# Parâmetros de Processamento
|
| 50 |
+
BATCH_SIZE = 256
|
| 51 |
+
UMAP_N_NEIGHBORS = 30
|
| 52 |
+
|
| 53 |
+
# Cache de Sessão (Na memória RAM)
|
| 54 |
+
cache: Dict[str, Any] = {}
|
| 55 |
+
|
| 56 |
+
# Definição de Métricas Customizadas do Prometheus
|
| 57 |
+
# Isso permite separar a latência da sua lógica vs a latência da API externa
|
| 58 |
+
GROQ_LATENCY = Histogram(
|
| 59 |
+
"groq_api_latency_seconds",
|
| 60 |
+
"Tempo de resposta da API externa Groq (LLM Generation)",
|
| 61 |
+
buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0]
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Inicialização do Cliente Groq
|
| 65 |
+
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 66 |
+
try:
|
| 67 |
+
if not GROQ_API_KEY:
|
| 68 |
+
logging.warning("GROQ_API_KEY não encontrada. Funcionalidades de LLM estarão indisponíveis.")
|
| 69 |
+
groq_client = None
|
| 70 |
+
else:
|
| 71 |
+
groq_client = Groq(api_key=GROQ_API_KEY)
|
| 72 |
+
logging.info("Cliente Groq inicializado com sucesso.")
|
| 73 |
+
except Exception as e:
|
| 74 |
+
logging.error(f"FALHA AO INICIALIZAR GROQ: {e}")
|
| 75 |
+
groq_client = None
|
| 76 |
+
|
| 77 |
+
# Inicialização do Cliente Tavily (Web Search)
|
| 78 |
+
TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY")
|
| 79 |
+
tavily_client = None
|
| 80 |
+
try:
|
| 81 |
+
if TAVILY_API_KEY:
|
| 82 |
+
from tavily import TavilyClient
|
| 83 |
+
tavily_client = TavilyClient(api_key=TAVILY_API_KEY)
|
| 84 |
+
logging.info("Cliente Tavily inicializado com sucesso.")
|
| 85 |
+
else:
|
| 86 |
+
logging.warning("TAVILY_API_KEY não encontrada. Busca web estará indisponível.")
|
| 87 |
+
except Exception as e:
|
| 88 |
+
logging.error(f"FALHA AO INICIALIZAR TAVILY: {e}")
|
| 89 |
+
tavily_client = None
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# ==============================================================================
|
| 93 |
+
# GERENCIAMENTO HÍBRIDO DE STOP WORDS (NLTK + ARQUIVO TXT)
|
| 94 |
+
# ==============================================================================
|
| 95 |
+
def carregar_stopwords():
|
| 96 |
+
"""
|
| 97 |
+
Carrega stop words do NLTK e combina com um arquivo externo 'stopwords.txt'.
|
| 98 |
+
"""
|
| 99 |
+
logging.info("Iniciando carregamento de Stop Words...")
|
| 100 |
+
|
| 101 |
+
# 1. Base Gramatical (NLTK - Inglês e Português)
|
| 102 |
+
try:
|
| 103 |
+
nltk.data.find('corpora/stopwords')
|
| 104 |
+
except LookupError:
|
| 105 |
+
logging.info("Baixando corpus de stopwords...")
|
| 106 |
+
nltk.download('stopwords')
|
| 107 |
+
|
| 108 |
+
# Cria um conjunto único com PT e EN
|
| 109 |
+
final_stops = set(stopwords.words('portuguese')) | set(stopwords.words('english'))
|
| 110 |
+
logging.info(f"Stopwords base (NLTK) carregadas: {len(final_stops)}")
|
| 111 |
+
|
| 112 |
+
# 2. Base Customizada
|
| 113 |
+
arquivo_custom = "stopwords.txt"
|
| 114 |
+
|
| 115 |
+
if os.path.exists(arquivo_custom):
|
| 116 |
+
logging.info(f"Arquivo '{arquivo_custom}' encontrado. Lendo palavras customizadas...")
|
| 117 |
+
try:
|
| 118 |
+
count_custom = 0
|
| 119 |
+
with open(arquivo_custom, "r", encoding="utf-8") as f:
|
| 120 |
+
for linha in f:
|
| 121 |
+
palavra = linha.split('#')[0].strip().lower()
|
| 122 |
+
if palavra and len(palavra) > 1:
|
| 123 |
+
final_stops.add(palavra)
|
| 124 |
+
count_custom += 1
|
| 125 |
+
logging.info(f"{count_custom} stop words customizadas importadas do arquivo.")
|
| 126 |
+
except Exception as e:
|
| 127 |
+
logging.error(f"Erro ao ler '{arquivo_custom}': {e}")
|
| 128 |
+
else:
|
| 129 |
+
logging.warning(f"Arquivo '{arquivo_custom}' não encontrado. Usando apenas NLTK.")
|
| 130 |
+
|
| 131 |
+
lista_final = list(final_stops)
|
| 132 |
+
logging.info(f"Total final de Stop Words ativas: {len(lista_final)}")
|
| 133 |
+
return lista_final
|
| 134 |
+
|
| 135 |
+
# Variável global carregada na inicialização
|
| 136 |
+
STOP_WORDS_MULTILINGUAL = carregar_stopwords()
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# ==============================================================================
|
| 140 |
+
# CARREGAMENTO DE MODELOS (COM CACHE)
|
| 141 |
+
# ==============================================================================
|
| 142 |
+
@lru_cache(maxsize=1)
|
| 143 |
+
def load_retriever():
|
| 144 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 145 |
+
logging.info(f"Carregando Retriever '{RETRIEVAL_MODEL}' em: {device}")
|
| 146 |
+
return SentenceTransformer(RETRIEVAL_MODEL, device=device)
|
| 147 |
+
|
| 148 |
+
@lru_cache(maxsize=1)
|
| 149 |
+
def load_reranker():
|
| 150 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 151 |
+
logging.info(f"Carregando Reranker '{RERANKER_MODEL}' em: {device}")
|
| 152 |
+
return CrossEncoder(RERANKER_MODEL, device=device)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# ==============================================================================
|
| 156 |
+
# PIPELINE DE PROCESSAMENTO DE DADOS
|
| 157 |
+
# ==============================================================================
|
| 158 |
+
def preparar_textos(file_bytes: bytes, n_samples: int) -> List[str]:
|
| 159 |
+
"""Prepara textos de arquivo TXT (uma linha por documento)."""
|
| 160 |
+
linhas = file_bytes.decode("utf-8", errors="ignore").splitlines()
|
| 161 |
+
textos = [s for line in linhas if (s := line.strip()) and len(s.split()) > 3]
|
| 162 |
+
return textos[:n_samples]
|
| 163 |
+
|
| 164 |
+
def preparar_textos_csv(file_bytes: bytes, text_column: str, n_samples: int) -> List[str]:
|
| 165 |
+
"""Prepara textos de arquivo CSV extraindo coluna especificada."""
|
| 166 |
+
try:
|
| 167 |
+
df = pd.read_csv(io.BytesIO(file_bytes), encoding="utf-8")
|
| 168 |
+
except UnicodeDecodeError:
|
| 169 |
+
df = pd.read_csv(io.BytesIO(file_bytes), encoding="latin-1")
|
| 170 |
+
|
| 171 |
+
if text_column not in df.columns:
|
| 172 |
+
available = ", ".join(df.columns.tolist()[:10])
|
| 173 |
+
raise ValueError(f"Coluna '{text_column}' não encontrada. Colunas disponíveis: {available}")
|
| 174 |
+
|
| 175 |
+
textos = df[text_column].dropna().astype(str).tolist()
|
| 176 |
+
# Filtrar textos muito curtos
|
| 177 |
+
textos = [t.strip() for t in textos if len(t.strip().split()) > 3]
|
| 178 |
+
return textos[:n_samples]
|
| 179 |
+
|
| 180 |
+
def get_csv_columns(file_bytes: bytes) -> List[str]:
|
| 181 |
+
"""Retorna lista de colunas de um arquivo CSV."""
|
| 182 |
+
try:
|
| 183 |
+
df = pd.read_csv(io.BytesIO(file_bytes), nrows=0, encoding="utf-8")
|
| 184 |
+
except UnicodeDecodeError:
|
| 185 |
+
df = pd.read_csv(io.BytesIO(file_bytes), nrows=0, encoding="latin-1")
|
| 186 |
+
return df.columns.tolist()
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def processar_pipeline(textos: List[str]) -> (pd.DataFrame, np.ndarray):
|
| 190 |
+
logging.info(f"Iniciando pipeline para {len(textos)} textos...")
|
| 191 |
+
model = load_retriever()
|
| 192 |
+
|
| 193 |
+
# 1. Embeddings
|
| 194 |
+
embeddings = model.encode(textos, batch_size=BATCH_SIZE, show_progress_bar=False, convert_to_numpy=True)
|
| 195 |
+
|
| 196 |
+
# 2. UMAP
|
| 197 |
+
reducer = umap.UMAP(n_components=3, n_neighbors=UMAP_N_NEIGHBORS, min_dist=0.0, metric="cosine", random_state=42)
|
| 198 |
+
emb_3d = reducer.fit_transform(embeddings)
|
| 199 |
+
emb_3d = StandardScaler().fit_transform(emb_3d)
|
| 200 |
+
|
| 201 |
+
# 3. HDBSCAN
|
| 202 |
+
num_textos = len(textos)
|
| 203 |
+
min_size = max(10, int(num_textos * 0.02))
|
| 204 |
+
logging.info(f"HDBSCAN min_cluster_size: {min_size}")
|
| 205 |
+
|
| 206 |
+
clusterer = hdbscan.HDBSCAN(min_cluster_size=min_size)
|
| 207 |
+
clusters = clusterer.fit_predict(emb_3d)
|
| 208 |
+
|
| 209 |
+
# 4. DataFrame
|
| 210 |
+
df = pd.DataFrame({
|
| 211 |
+
"x": emb_3d[:, 0], "y": emb_3d[:, 1], "z": emb_3d[:, 2],
|
| 212 |
+
"full_text": textos, "cluster": clusters.astype(str)
|
| 213 |
+
})
|
| 214 |
+
|
| 215 |
+
del reducer, clusterer, emb_3d; gc.collect()
|
| 216 |
+
return df, embeddings
|
| 217 |
+
|
| 218 |
+
def calcular_metricas(textos: List[str]) -> Dict[str, Any]:
|
| 219 |
+
logging.info("Calculando métricas globais...")
|
| 220 |
+
if not textos: return {}
|
| 221 |
+
|
| 222 |
+
vectorizer_count = CountVectorizer(stop_words=STOP_WORDS_MULTILINGUAL, max_features=1000)
|
| 223 |
+
vectorizer_tfidf = TfidfVectorizer(stop_words=STOP_WORDS_MULTILINGUAL, max_features=1000)
|
| 224 |
+
|
| 225 |
+
try:
|
| 226 |
+
counts_matrix = vectorizer_count.fit_transform(textos)
|
| 227 |
+
tfidf_matrix = vectorizer_tfidf.fit_transform(textos)
|
| 228 |
+
except ValueError:
|
| 229 |
+
return {"riqueza_lexical": 0, "top_tfidf_palavras": [], "entropia": 0.0}
|
| 230 |
+
|
| 231 |
+
vocab_count = vectorizer_count.get_feature_names_out()
|
| 232 |
+
contagens = counts_matrix.sum(axis=0).A1
|
| 233 |
+
|
| 234 |
+
vocab_tfidf = vectorizer_tfidf.get_feature_names_out()
|
| 235 |
+
soma_tfidf = tfidf_matrix.sum(axis=0).A1
|
| 236 |
+
top_idx_tfidf = np.argsort(soma_tfidf)[-10:][::-1]
|
| 237 |
+
top_tfidf = [{"palavra": vocab_tfidf[i], "score": round(float(soma_tfidf[i]), 4)} for i in top_idx_tfidf]
|
| 238 |
+
|
| 239 |
+
return {
|
| 240 |
+
"riqueza_lexical": len(vocab_count),
|
| 241 |
+
"top_tfidf_palavras": top_tfidf,
|
| 242 |
+
"entropia": float(entropy(contagens / contagens.sum(), base=2)) if contagens.sum() > 0 else 0.0
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
def encontrar_duplicados(df: pd.DataFrame, embeddings: np.ndarray) -> Dict[str, Any]:
|
| 246 |
+
logging.info("Detectando duplicados...")
|
| 247 |
+
mask = df["full_text"].duplicated(keep=False)
|
| 248 |
+
grupos_exatos = {t: [int(i) for i in idxs] for t, idxs in df[mask].groupby("full_text").groups.items()}
|
| 249 |
+
pares_semanticos = []
|
| 250 |
+
|
| 251 |
+
if 2 < len(embeddings) < 5000:
|
| 252 |
+
sim = cosine_similarity(embeddings)
|
| 253 |
+
triu_indices = np.triu_indices_from(sim, k=1)
|
| 254 |
+
sim_vetor = sim[triu_indices]
|
| 255 |
+
pares_idx = np.where(sim_vetor > 0.98)[0]
|
| 256 |
+
top_pares_idx = pares_idx[np.argsort(sim_vetor[pares_idx])[-5:][::-1]]
|
| 257 |
+
for i in top_pares_idx:
|
| 258 |
+
idx1, idx2 = triu_indices[0][i], triu_indices[1][i]
|
| 259 |
+
if df["full_text"].iloc[idx1] != df["full_text"].iloc[idx2]:
|
| 260 |
+
pares_semanticos.append({
|
| 261 |
+
"similaridade": float(sim[idx1, idx2]),
|
| 262 |
+
"texto1": df["full_text"].iloc[idx1],
|
| 263 |
+
"texto2": df["full_text"].iloc[idx2]
|
| 264 |
+
})
|
| 265 |
+
return {"grupos_exatos": grupos_exatos, "pares_semanticos": pares_semanticos}
|
| 266 |
+
|
| 267 |
+
def analisar_clusters(df: pd.DataFrame) -> Dict[str, Any]:
|
| 268 |
+
logging.info("Analisando clusters...")
|
| 269 |
+
analise = {}
|
| 270 |
+
ids_clusters_validos = sorted([c for c in df["cluster"].unique() if c != "-1"], key=int)
|
| 271 |
+
for cid in ids_clusters_validos:
|
| 272 |
+
textos_cluster = df[df["cluster"] == cid]["full_text"].tolist()
|
| 273 |
+
if len(textos_cluster) < 2: continue
|
| 274 |
+
try:
|
| 275 |
+
vectorizer = TfidfVectorizer(stop_words=STOP_WORDS_MULTILINGUAL, max_features=1000)
|
| 276 |
+
tfidf_matrix = vectorizer.fit_transform(textos_cluster)
|
| 277 |
+
vocab = vectorizer.get_feature_names_out()
|
| 278 |
+
soma = tfidf_matrix.sum(axis=0).A1
|
| 279 |
+
top_idx = np.argsort(soma)[-5:][::-1]
|
| 280 |
+
top_palavras = [{"palavra": vocab[i], "score": round(float(soma[i]), 4)} for i in top_idx]
|
| 281 |
+
except ValueError:
|
| 282 |
+
top_palavras = []
|
| 283 |
+
analise[cid] = {"num_documentos": len(textos_cluster), "top_palavras": top_palavras}
|
| 284 |
+
return analise
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
# ==============================================================================
|
| 288 |
+
# API FASTAPI & INSTRUMENTAÇÃO
|
| 289 |
+
# ==============================================================================
|
| 290 |
+
app = FastAPI(title="AetherMap API 7.2", version="7.2.0", description="Backend Semantic Search + CSV + Tavily Web Search")
|
| 291 |
+
|
| 292 |
+
# --- A MÁGICA ACONTECE AQUI ---
|
| 293 |
+
# Isso expõe automaticamente o endpoint /metrics para o Prometheus/Grafana
|
| 294 |
+
Instrumentator().instrument(app).expose(app)
|
| 295 |
+
# ------------------------------
|
| 296 |
+
|
| 297 |
+
@app.get("/")
|
| 298 |
+
async def root():
|
| 299 |
+
return {"status": "online", "message": "Aether Map API 7.2 (CSV + Tavily Ready)."}
|
| 300 |
+
|
| 301 |
+
@app.post("/csv_columns/")
|
| 302 |
+
async def get_columns_api(file: UploadFile = File(...)):
|
| 303 |
+
"""Retorna as colunas de um arquivo CSV para preview."""
|
| 304 |
+
if not file.filename.lower().endswith('.csv'):
|
| 305 |
+
raise HTTPException(status_code=400, detail="Arquivo deve ser CSV.")
|
| 306 |
+
try:
|
| 307 |
+
file_bytes = await file.read()
|
| 308 |
+
columns = get_csv_columns(file_bytes)
|
| 309 |
+
return {"columns": columns, "filename": file.filename}
|
| 310 |
+
except Exception as e:
|
| 311 |
+
raise HTTPException(status_code=400, detail=f"Erro ao ler CSV: {str(e)}")
|
| 312 |
+
|
| 313 |
+
@app.post("/process/")
|
| 314 |
+
async def process_api(
|
| 315 |
+
n_samples: int = Form(10000),
|
| 316 |
+
file: UploadFile = File(...),
|
| 317 |
+
text_column: str = Form(None) # Coluna de texto para CSV
|
| 318 |
+
):
|
| 319 |
+
logging.info(f"Processando arquivo: {file.filename}")
|
| 320 |
+
try:
|
| 321 |
+
file_bytes = await file.read()
|
| 322 |
+
|
| 323 |
+
# Detectar tipo de arquivo e processar
|
| 324 |
+
if file.filename.lower().endswith('.csv'):
|
| 325 |
+
if not text_column:
|
| 326 |
+
raise HTTPException(status_code=400, detail="Para CSV, informe 'text_column'.")
|
| 327 |
+
textos = preparar_textos_csv(file_bytes, text_column, n_samples)
|
| 328 |
+
else:
|
| 329 |
+
textos = preparar_textos(file_bytes, n_samples)
|
| 330 |
+
|
| 331 |
+
if not textos: raise HTTPException(status_code=400, detail="Nenhum texto válido encontrado.")
|
| 332 |
+
|
| 333 |
+
df, embeddings = processar_pipeline(textos)
|
| 334 |
+
|
| 335 |
+
job_id = str(uuid.uuid4())
|
| 336 |
+
cache[job_id] = {"embeddings": embeddings, "df": df}
|
| 337 |
+
logging.info(f"Job criado: {job_id}")
|
| 338 |
+
|
| 339 |
+
metricas_globais = calcular_metricas(df["full_text"].tolist())
|
| 340 |
+
analise_de_duplicados = encontrar_duplicados(df, embeddings)
|
| 341 |
+
analise_por_cluster_tfidf = analisar_clusters(df)
|
| 342 |
+
|
| 343 |
+
n_clusters = len(df["cluster"].unique()) - (1 if "-1" in df["cluster"].unique() else 0)
|
| 344 |
+
n_ruido = int((df["cluster"] == "-1").sum())
|
| 345 |
+
|
| 346 |
+
return {
|
| 347 |
+
"job_id": job_id,
|
| 348 |
+
"metadata": {
|
| 349 |
+
"filename": file.filename,
|
| 350 |
+
"num_documents_processed": len(df),
|
| 351 |
+
"num_clusters_found": n_clusters,
|
| 352 |
+
"num_noise_points": n_ruido
|
| 353 |
+
},
|
| 354 |
+
"metrics": metricas_globais,
|
| 355 |
+
"duplicates": analise_de_duplicados,
|
| 356 |
+
"cluster_analysis": analise_por_cluster_tfidf,
|
| 357 |
+
"plot_data": df[["x", "y", "z", "cluster", "full_text"]].to_dict("records"),
|
| 358 |
+
}
|
| 359 |
+
except Exception as e:
|
| 360 |
+
logging.error(f"ERRO EM /process/: {e}", exc_info=True)
|
| 361 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
@app.post("/search/")
|
| 365 |
+
async def search_api(query: str = Form(...), job_id: str = Form(...)):
|
| 366 |
+
"""
|
| 367 |
+
ENDPOINT DE BUSCA (RAG Híbrido) com Monitoramento de Latência
|
| 368 |
+
"""
|
| 369 |
+
logging.info(f"Busca: '{query}' [Job: {job_id}]")
|
| 370 |
+
if job_id not in cache:
|
| 371 |
+
raise HTTPException(status_code=404, detail="Job ID não encontrado.")
|
| 372 |
+
|
| 373 |
+
try:
|
| 374 |
+
model = load_retriever()
|
| 375 |
+
reranker = load_reranker()
|
| 376 |
+
|
| 377 |
+
cached_data = cache[job_id]
|
| 378 |
+
df = cached_data["df"]
|
| 379 |
+
corpus_embeddings = cached_data["embeddings"]
|
| 380 |
+
|
| 381 |
+
# FASE 1: Varredura Ampla
|
| 382 |
+
query_embedding = model.encode([query], convert_to_numpy=True)
|
| 383 |
+
similarities = cosine_similarity(query_embedding, corpus_embeddings)[0]
|
| 384 |
+
|
| 385 |
+
top_k_retrieval = 50
|
| 386 |
+
top_indices = np.argsort(similarities)[-top_k_retrieval:][::-1]
|
| 387 |
+
|
| 388 |
+
candidate_docs = []
|
| 389 |
+
candidate_indices = []
|
| 390 |
+
|
| 391 |
+
for idx in top_indices:
|
| 392 |
+
if similarities[idx] > 0.15:
|
| 393 |
+
doc_text = df.iloc[int(idx)]["full_text"]
|
| 394 |
+
candidate_docs.append([query, doc_text])
|
| 395 |
+
candidate_indices.append(int(idx))
|
| 396 |
+
|
| 397 |
+
if not candidate_docs:
|
| 398 |
+
return {"summary": "Não foram encontrados documentos relevantes.", "results": []}
|
| 399 |
+
|
| 400 |
+
# FASE 2: Reranking
|
| 401 |
+
logging.info(f"Reranking {len(candidate_docs)} documentos...")
|
| 402 |
+
rerank_scores = reranker.predict(candidate_docs)
|
| 403 |
+
|
| 404 |
+
rerank_results = sorted(
|
| 405 |
+
zip(candidate_indices, rerank_scores),
|
| 406 |
+
key=lambda x: x[1],
|
| 407 |
+
reverse=True
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
final_top_k = 5
|
| 411 |
+
final_results = []
|
| 412 |
+
context_parts = []
|
| 413 |
+
|
| 414 |
+
for rank, (idx, score) in enumerate(rerank_results[:final_top_k]):
|
| 415 |
+
doc_text = df.iloc[idx]["full_text"]
|
| 416 |
+
context_parts.append(f"[ID: {rank+1}] DOCUMENTO:\n{doc_text}\n---------------------")
|
| 417 |
+
|
| 418 |
+
final_results.append({
|
| 419 |
+
"index": idx,
|
| 420 |
+
"score": float(score),
|
| 421 |
+
"cosine_score": float(similarities[idx]),
|
| 422 |
+
"citation_id": rank + 1
|
| 423 |
+
})
|
| 424 |
+
|
| 425 |
+
# FASE 3: Geração (Groq) com TELEMETRIA
|
| 426 |
+
summary = ""
|
| 427 |
+
if groq_client:
|
| 428 |
+
context_str = "\n".join(context_parts)
|
| 429 |
+
rag_prompt = (
|
| 430 |
+
"INSTRUÇÃO DE SISTEMA:\n"
|
| 431 |
+
"Você é o Aetherius, um motor de busca semântica de alta precisão.\n"
|
| 432 |
+
"Sua missão é responder à pergunta do usuário baseando-se ESTRITAMENTE nos documentos fornecidos.\n\n"
|
| 433 |
+
"REGRAS OBRIGATÓRIAS:\n"
|
| 434 |
+
"1. CITAÇÕES: Toda afirmação deve ter fonte [ID: x]. Ex: 'O lucro subiu [ID: 1].'\n"
|
| 435 |
+
"2. HONESTIDADE: Se não estiver no texto, diga que não encontrou.\n"
|
| 436 |
+
"3. IDIOMA: Português do Brasil.\n\n"
|
| 437 |
+
f"CONTEXTO RECUPERADO:\n{context_str}\n\n"
|
| 438 |
+
f"PERGUNTA DO USUÁRIO: \"{query}\"\n\n"
|
| 439 |
+
"RESPOSTA:"
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
try:
|
| 443 |
+
# --- INÍCIO DA MEDIÇÃO DA API EXTERNA ---
|
| 444 |
+
start_time_groq = time.time()
|
| 445 |
+
|
| 446 |
+
chat_completion = groq_client.chat.completions.create(
|
| 447 |
+
messages=[{"role": "user", "content": rag_prompt}],
|
| 448 |
+
model="moonshotai/kimi-k2-instruct-0905",
|
| 449 |
+
temperature=0.1,
|
| 450 |
+
max_tokens=1024
|
| 451 |
+
)
|
| 452 |
+
|
| 453 |
+
# Registra o tempo gasto apenas na chamada da API
|
| 454 |
+
duration = time.time() - start_time_groq
|
| 455 |
+
GROQ_LATENCY.observe(duration)
|
| 456 |
+
# --- FIM DA MEDIÇÃO ---
|
| 457 |
+
|
| 458 |
+
summary = chat_completion.choices[0].message.content.strip()
|
| 459 |
+
except Exception as e:
|
| 460 |
+
logging.warning(f"Erro na geração do LLM: {e}")
|
| 461 |
+
summary = "Não foi possível gerar o resumo automático, mas os documentos estão listados abaixo."
|
| 462 |
+
|
| 463 |
+
return {"summary": summary, "results": final_results}
|
| 464 |
+
|
| 465 |
+
except Exception as e:
|
| 466 |
+
logging.error(f"ERRO EM /search/: {e}", exc_info=True)
|
| 467 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
@app.post("/describe_clusters/")
|
| 471 |
+
async def describe_clusters_api(job_id: str = Form(...)):
|
| 472 |
+
logging.info(f"Descrevendo clusters para Job: {job_id}")
|
| 473 |
+
if not groq_client: raise HTTPException(status_code=503, detail="Groq indisponível.")
|
| 474 |
+
if job_id not in cache: raise HTTPException(status_code=404, detail="Job não encontrado.")
|
| 475 |
+
|
| 476 |
+
try:
|
| 477 |
+
cached_data = cache[job_id]
|
| 478 |
+
df = cached_data["df"]
|
| 479 |
+
embeddings = cached_data["embeddings"]
|
| 480 |
+
|
| 481 |
+
champion_docs_by_cluster = {}
|
| 482 |
+
cluster_ids = sorted([c for c in df["cluster"].unique() if c != "-1"], key=int)
|
| 483 |
+
|
| 484 |
+
for cid in cluster_ids:
|
| 485 |
+
mask = df["cluster"] == cid
|
| 486 |
+
cluster_embeddings = embeddings[mask]
|
| 487 |
+
cluster_texts = df[mask]["full_text"].tolist()
|
| 488 |
+
if len(cluster_texts) < 3: continue
|
| 489 |
+
|
| 490 |
+
centroid = np.mean(cluster_embeddings, axis=0)
|
| 491 |
+
similarities = cosine_similarity([centroid], cluster_embeddings)[0]
|
| 492 |
+
top_indices = np.argsort(similarities)[-3:][::-1]
|
| 493 |
+
champion_docs_by_cluster[cid] = [cluster_texts[i] for i in top_indices]
|
| 494 |
+
|
| 495 |
+
if not champion_docs_by_cluster: return {"insights": {}}
|
| 496 |
+
|
| 497 |
+
prompt_sections = []
|
| 498 |
+
for cid, docs in champion_docs_by_cluster.items():
|
| 499 |
+
doc_list = "\n".join([f"- {doc[:300]}..." for doc in docs])
|
| 500 |
+
prompt_sections.append(f"Grupo {cid}:\n{doc_list}")
|
| 501 |
+
|
| 502 |
+
master_prompt = (
|
| 503 |
+
"Analise os grupos de texto abaixo. Para cada grupo, retorne um JSON com 'topic_name' e 'core_insight'.\n"
|
| 504 |
+
"Responda APENAS o JSON válido.\n\n" + "\n\n".join(prompt_sections)
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
# --- INÍCIO DA MEDIÇÃO DA API EXTERNA ---
|
| 508 |
+
start_time_groq = time.time()
|
| 509 |
+
|
| 510 |
+
chat_completion = groq_client.chat.completions.create(
|
| 511 |
+
messages=[
|
| 512 |
+
{"role": "system", "content": "JSON Output Only."},
|
| 513 |
+
{"role": "user", "content": master_prompt},
|
| 514 |
+
], model="meta-llama/llama-4-maverick-17b-128e-instruct", temperature=0.2,
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
duration = time.time() - start_time_groq
|
| 518 |
+
GROQ_LATENCY.observe(duration)
|
| 519 |
+
# --- FIM DA MEDIÇÃO ---
|
| 520 |
+
|
| 521 |
+
response_content = chat_completion.choices[0].message.content
|
| 522 |
+
insights = json.loads(response_content.strip().replace("```json", "").replace("```", ""))
|
| 523 |
+
return {"insights": insights}
|
| 524 |
+
|
| 525 |
+
except Exception as e:
|
| 526 |
+
logging.error(f"ERRO EM /describe_clusters/: {e}", exc_info=True)
|
| 527 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
# ==============================================================================
|
| 531 |
+
# ENDPOINT TAVILY WEB SEARCH
|
| 532 |
+
# ==============================================================================
|
| 533 |
+
@app.post("/search_web/")
|
| 534 |
+
async def search_web_api(
|
| 535 |
+
query: str = Form(...),
|
| 536 |
+
max_results: int = Form(20),
|
| 537 |
+
search_depth: str = Form("basic") # "basic" ou "advanced"
|
| 538 |
+
):
|
| 539 |
+
"""
|
| 540 |
+
Busca na web via Tavily e processa resultados para visualização.
|
| 541 |
+
"""
|
| 542 |
+
if not tavily_client:
|
| 543 |
+
raise HTTPException(status_code=503, detail="Tavily não configurado. Defina TAVILY_API_KEY.")
|
| 544 |
+
|
| 545 |
+
logging.info(f"Tavily Search: '{query}' (max: {max_results})")
|
| 546 |
+
|
| 547 |
+
try:
|
| 548 |
+
# Buscar via Tavily
|
| 549 |
+
search_result = tavily_client.search(
|
| 550 |
+
query=query,
|
| 551 |
+
max_results=max_results,
|
| 552 |
+
search_depth=search_depth,
|
| 553 |
+
include_answer=False
|
| 554 |
+
)
|
| 555 |
+
|
| 556 |
+
results = search_result.get("results", [])
|
| 557 |
+
if not results:
|
| 558 |
+
return {"error": "Nenhum resultado encontrado.", "results_count": 0}
|
| 559 |
+
|
| 560 |
+
# Extrair textos dos resultados
|
| 561 |
+
textos = []
|
| 562 |
+
sources = []
|
| 563 |
+
for r in results:
|
| 564 |
+
title = r.get("title", "")
|
| 565 |
+
content = r.get("content", "")
|
| 566 |
+
url = r.get("url", "")
|
| 567 |
+
|
| 568 |
+
# Combinar título + conteúdo
|
| 569 |
+
full_text = f"{title}: {content}" if title else content
|
| 570 |
+
if len(full_text.strip().split()) > 5:
|
| 571 |
+
textos.append(full_text.strip())
|
| 572 |
+
sources.append({"title": title, "url": url})
|
| 573 |
+
|
| 574 |
+
if not textos:
|
| 575 |
+
return {"error": "Resultados sem conteúdo válido.", "results_count": 0}
|
| 576 |
+
|
| 577 |
+
# Processar através do pipeline existente
|
| 578 |
+
df, embeddings = processar_pipeline(textos)
|
| 579 |
+
|
| 580 |
+
# Criar job e cachear
|
| 581 |
+
job_id = str(uuid.uuid4())
|
| 582 |
+
cache[job_id] = {"embeddings": embeddings, "df": df, "sources": sources}
|
| 583 |
+
logging.info(f"Tavily Job criado: {job_id}")
|
| 584 |
+
|
| 585 |
+
# Calcular métricas e análises
|
| 586 |
+
metricas_globais = calcular_metricas(df["full_text"].tolist())
|
| 587 |
+
analise_por_cluster_tfidf = analisar_clusters(df)
|
| 588 |
+
|
| 589 |
+
n_clusters = len(df["cluster"].unique()) - (1 if "-1" in df["cluster"].unique() else 0)
|
| 590 |
+
n_ruido = int((df["cluster"] == "-1").sum())
|
| 591 |
+
|
| 592 |
+
return {
|
| 593 |
+
"job_id": job_id,
|
| 594 |
+
"metadata": {
|
| 595 |
+
"query": query,
|
| 596 |
+
"source": "tavily_web_search",
|
| 597 |
+
"num_documents_processed": len(df),
|
| 598 |
+
"num_clusters_found": n_clusters,
|
| 599 |
+
"num_noise_points": n_ruido
|
| 600 |
+
},
|
| 601 |
+
"metrics": metricas_globais,
|
| 602 |
+
"cluster_analysis": analise_por_cluster_tfidf,
|
| 603 |
+
"plot_data": df[["x", "y", "z", "cluster", "full_text"]].to_dict("records"),
|
| 604 |
+
"sources": sources # URLs originais
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
except Exception as e:
|
| 608 |
+
logging.error(f"ERRO EM /search_web/: {e}", exc_info=True)
|
| 609 |
raise HTTPException(status_code=500, detail=str(e))
|
requirements.txt
CHANGED
|
@@ -1,22 +1,23 @@
|
|
| 1 |
-
# --- SERVIDOR E API ---
|
| 2 |
-
fastapi
|
| 3 |
-
uvicorn[standard]
|
| 4 |
-
python-multipart
|
| 5 |
-
groq
|
| 6 |
-
prometheus-fastapi-instrumentator
|
| 7 |
-
prometheus-client
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
| 22 |
torchaudio
|
|
|
|
| 1 |
+
# --- SERVIDOR E API ---
|
| 2 |
+
fastapi
|
| 3 |
+
uvicorn[standard]
|
| 4 |
+
python-multipart
|
| 5 |
+
groq
|
| 6 |
+
prometheus-fastapi-instrumentator
|
| 7 |
+
prometheus-client
|
| 8 |
+
tavily-python
|
| 9 |
+
|
| 10 |
+
# --- MACHINE LEARNING E NLP ---
|
| 11 |
+
sentence-transformers
|
| 12 |
+
numpy
|
| 13 |
+
pandas
|
| 14 |
+
scikit-learn
|
| 15 |
+
scipy
|
| 16 |
+
umap-learn
|
| 17 |
+
hdbscan
|
| 18 |
+
nltk
|
| 19 |
+
|
| 20 |
+
# --- TORCH CPU ---
|
| 21 |
+
torch
|
| 22 |
+
torchvision
|
| 23 |
torchaudio
|
stopwords.txt
CHANGED
|
@@ -1,546 +1,546 @@
|
|
| 1 |
-
# ==============================================================================
|
| 2 |
-
# STOPWORDS DO AETHER MAP - LISTA MESTRA (PT + EN)
|
| 3 |
-
# ==============================================================================
|
| 4 |
-
|
| 5 |
-
# --- TERMOS DE SISTEMA E WEB ---
|
| 6 |
-
http
|
| 7 |
-
https
|
| 8 |
-
www
|
| 9 |
-
com
|
| 10 |
-
br
|
| 11 |
-
org
|
| 12 |
-
net
|
| 13 |
-
html
|
| 14 |
-
php
|
| 15 |
-
jsp
|
| 16 |
-
asp
|
| 17 |
-
pdf
|
| 18 |
-
docx
|
| 19 |
-
xlsx
|
| 20 |
-
json
|
| 21 |
-
api
|
| 22 |
-
id
|
| 23 |
-
url
|
| 24 |
-
email
|
| 25 |
-
site
|
| 26 |
-
website
|
| 27 |
-
page
|
| 28 |
-
pagina
|
| 29 |
-
link
|
| 30 |
-
click
|
| 31 |
-
login
|
| 32 |
-
|
| 33 |
-
# --- TERMOS GENÉRICOS DE DOCUMENTOS ---
|
| 34 |
-
document
|
| 35 |
-
documento
|
| 36 |
-
texto
|
| 37 |
-
text
|
| 38 |
-
file
|
| 39 |
-
arquivo
|
| 40 |
-
data
|
| 41 |
-
dados
|
| 42 |
-
database
|
| 43 |
-
base
|
| 44 |
-
dataset
|
| 45 |
-
sample
|
| 46 |
-
amostra
|
| 47 |
-
example
|
| 48 |
-
exemplo
|
| 49 |
-
case
|
| 50 |
-
caso
|
| 51 |
-
study
|
| 52 |
-
estudo
|
| 53 |
-
analysis
|
| 54 |
-
analise
|
| 55 |
-
análise
|
| 56 |
-
report
|
| 57 |
-
relatorio
|
| 58 |
-
relatório
|
| 59 |
-
paper
|
| 60 |
-
artigo
|
| 61 |
-
results
|
| 62 |
-
resultados
|
| 63 |
-
conclusion
|
| 64 |
-
conclusão
|
| 65 |
-
introduction
|
| 66 |
-
introdução
|
| 67 |
-
abstract
|
| 68 |
-
resumo
|
| 69 |
-
chapter
|
| 70 |
-
capitulo
|
| 71 |
-
capítulo
|
| 72 |
-
section
|
| 73 |
-
seção
|
| 74 |
-
part
|
| 75 |
-
parte
|
| 76 |
-
figure
|
| 77 |
-
figura
|
| 78 |
-
fig
|
| 79 |
-
table
|
| 80 |
-
tabela
|
| 81 |
-
tab
|
| 82 |
-
chart
|
| 83 |
-
grafico
|
| 84 |
-
gráfico
|
| 85 |
-
image
|
| 86 |
-
imagem
|
| 87 |
-
source
|
| 88 |
-
fonte
|
| 89 |
-
reference
|
| 90 |
-
referencia
|
| 91 |
-
referência
|
| 92 |
-
bibliography
|
| 93 |
-
bibliografia
|
| 94 |
-
et
|
| 95 |
-
al
|
| 96 |
-
citation
|
| 97 |
-
citação
|
| 98 |
-
|
| 99 |
-
# --- INGLÊS ACADÊMICO E "FILLER WORDS" ---
|
| 100 |
-
the
|
| 101 |
-
be
|
| 102 |
-
to
|
| 103 |
-
of
|
| 104 |
-
and
|
| 105 |
-
a
|
| 106 |
-
in
|
| 107 |
-
that
|
| 108 |
-
have
|
| 109 |
-
i
|
| 110 |
-
it
|
| 111 |
-
for
|
| 112 |
-
not
|
| 113 |
-
on
|
| 114 |
-
with
|
| 115 |
-
he
|
| 116 |
-
as
|
| 117 |
-
you
|
| 118 |
-
do
|
| 119 |
-
at
|
| 120 |
-
this
|
| 121 |
-
but
|
| 122 |
-
his
|
| 123 |
-
by
|
| 124 |
-
from
|
| 125 |
-
they
|
| 126 |
-
we
|
| 127 |
-
say
|
| 128 |
-
her
|
| 129 |
-
she
|
| 130 |
-
or
|
| 131 |
-
an
|
| 132 |
-
will
|
| 133 |
-
my
|
| 134 |
-
one
|
| 135 |
-
all
|
| 136 |
-
would
|
| 137 |
-
there
|
| 138 |
-
their
|
| 139 |
-
what
|
| 140 |
-
so
|
| 141 |
-
up
|
| 142 |
-
out
|
| 143 |
-
if
|
| 144 |
-
about
|
| 145 |
-
who
|
| 146 |
-
get
|
| 147 |
-
which
|
| 148 |
-
go
|
| 149 |
-
me
|
| 150 |
-
when
|
| 151 |
-
make
|
| 152 |
-
can
|
| 153 |
-
like
|
| 154 |
-
time
|
| 155 |
-
no
|
| 156 |
-
just
|
| 157 |
-
him
|
| 158 |
-
know
|
| 159 |
-
take
|
| 160 |
-
people
|
| 161 |
-
into
|
| 162 |
-
year
|
| 163 |
-
your
|
| 164 |
-
good
|
| 165 |
-
some
|
| 166 |
-
could
|
| 167 |
-
them
|
| 168 |
-
see
|
| 169 |
-
other
|
| 170 |
-
than
|
| 171 |
-
then
|
| 172 |
-
now
|
| 173 |
-
look
|
| 174 |
-
only
|
| 175 |
-
come
|
| 176 |
-
its
|
| 177 |
-
over
|
| 178 |
-
think
|
| 179 |
-
also
|
| 180 |
-
back
|
| 181 |
-
after
|
| 182 |
-
use
|
| 183 |
-
two
|
| 184 |
-
how
|
| 185 |
-
our
|
| 186 |
-
work
|
| 187 |
-
first
|
| 188 |
-
well
|
| 189 |
-
way
|
| 190 |
-
even
|
| 191 |
-
new
|
| 192 |
-
want
|
| 193 |
-
because
|
| 194 |
-
any
|
| 195 |
-
these
|
| 196 |
-
give
|
| 197 |
-
day
|
| 198 |
-
most
|
| 199 |
-
us
|
| 200 |
-
is
|
| 201 |
-
are
|
| 202 |
-
was
|
| 203 |
-
were
|
| 204 |
-
been
|
| 205 |
-
has
|
| 206 |
-
had
|
| 207 |
-
did
|
| 208 |
-
does
|
| 209 |
-
may
|
| 210 |
-
might
|
| 211 |
-
should
|
| 212 |
-
must
|
| 213 |
-
shall
|
| 214 |
-
used
|
| 215 |
-
using
|
| 216 |
-
uses
|
| 217 |
-
based
|
| 218 |
-
found
|
| 219 |
-
show
|
| 220 |
-
shown
|
| 221 |
-
shows
|
| 222 |
-
suggest
|
| 223 |
-
suggests
|
| 224 |
-
however
|
| 225 |
-
therefore
|
| 226 |
-
thus
|
| 227 |
-
hence
|
| 228 |
-
although
|
| 229 |
-
though
|
| 230 |
-
whereas
|
| 231 |
-
while
|
| 232 |
-
meanwhile
|
| 233 |
-
furthermore
|
| 234 |
-
moreover
|
| 235 |
-
additionally
|
| 236 |
-
besides
|
| 237 |
-
indeed
|
| 238 |
-
fact
|
| 239 |
-
overall
|
| 240 |
-
general
|
| 241 |
-
specific
|
| 242 |
-
significantly
|
| 243 |
-
associated
|
| 244 |
-
related
|
| 245 |
-
various
|
| 246 |
-
several
|
| 247 |
-
many
|
| 248 |
-
much
|
| 249 |
-
less
|
| 250 |
-
more
|
| 251 |
-
high
|
| 252 |
-
low
|
| 253 |
-
increase
|
| 254 |
-
decrease
|
| 255 |
-
positive
|
| 256 |
-
negative
|
| 257 |
-
|
| 258 |
-
# --- PORTUGUÊS ACADÊMICO E "PALAVRAS VAZIAS" ---
|
| 259 |
-
de
|
| 260 |
-
a
|
| 261 |
-
o
|
| 262 |
-
que
|
| 263 |
-
e
|
| 264 |
-
do
|
| 265 |
-
da
|
| 266 |
-
em
|
| 267 |
-
um
|
| 268 |
-
para
|
| 269 |
-
é
|
| 270 |
-
com
|
| 271 |
-
não
|
| 272 |
-
uma
|
| 273 |
-
os
|
| 274 |
-
no
|
| 275 |
-
se
|
| 276 |
-
na
|
| 277 |
-
por
|
| 278 |
-
mais
|
| 279 |
-
as
|
| 280 |
-
dos
|
| 281 |
-
como
|
| 282 |
-
mas
|
| 283 |
-
foi
|
| 284 |
-
ao
|
| 285 |
-
ele
|
| 286 |
-
das
|
| 287 |
-
tem
|
| 288 |
-
à
|
| 289 |
-
seu
|
| 290 |
-
sua
|
| 291 |
-
ou
|
| 292 |
-
ser
|
| 293 |
-
quando
|
| 294 |
-
muito
|
| 295 |
-
há
|
| 296 |
-
nos
|
| 297 |
-
já
|
| 298 |
-
está
|
| 299 |
-
eu
|
| 300 |
-
também
|
| 301 |
-
só
|
| 302 |
-
pelo
|
| 303 |
-
pela
|
| 304 |
-
até
|
| 305 |
-
isso
|
| 306 |
-
ela
|
| 307 |
-
entre
|
| 308 |
-
era
|
| 309 |
-
depois
|
| 310 |
-
sem
|
| 311 |
-
mesmo
|
| 312 |
-
aos
|
| 313 |
-
ter
|
| 314 |
-
seus
|
| 315 |
-
quem
|
| 316 |
-
nas
|
| 317 |
-
me
|
| 318 |
-
esse
|
| 319 |
-
eles
|
| 320 |
-
estão
|
| 321 |
-
você
|
| 322 |
-
tinha
|
| 323 |
-
foram
|
| 324 |
-
essa
|
| 325 |
-
num
|
| 326 |
-
nem
|
| 327 |
-
suas
|
| 328 |
-
meu
|
| 329 |
-
às
|
| 330 |
-
minha
|
| 331 |
-
numa
|
| 332 |
-
pelos
|
| 333 |
-
elas
|
| 334 |
-
havia
|
| 335 |
-
seja
|
| 336 |
-
qual
|
| 337 |
-
será
|
| 338 |
-
nós
|
| 339 |
-
tenho
|
| 340 |
-
lhe
|
| 341 |
-
deles
|
| 342 |
-
essas
|
| 343 |
-
esses
|
| 344 |
-
pelas
|
| 345 |
-
este
|
| 346 |
-
fosse
|
| 347 |
-
dele
|
| 348 |
-
tu
|
| 349 |
-
te
|
| 350 |
-
vocês
|
| 351 |
-
vos
|
| 352 |
-
lhes
|
| 353 |
-
meus
|
| 354 |
-
minhas
|
| 355 |
-
teu
|
| 356 |
-
tua
|
| 357 |
-
teus
|
| 358 |
-
tuas
|
| 359 |
-
nosso
|
| 360 |
-
nossa
|
| 361 |
-
nossos
|
| 362 |
-
nossas
|
| 363 |
-
dela
|
| 364 |
-
delas
|
| 365 |
-
esta
|
| 366 |
-
estes
|
| 367 |
-
estas
|
| 368 |
-
aquele
|
| 369 |
-
aquela
|
| 370 |
-
aqueles
|
| 371 |
-
aquelas
|
| 372 |
-
isto
|
| 373 |
-
aquilo
|
| 374 |
-
estou
|
| 375 |
-
está
|
| 376 |
-
estamos
|
| 377 |
-
estão
|
| 378 |
-
estive
|
| 379 |
-
esteve
|
| 380 |
-
estivemos
|
| 381 |
-
estiveram
|
| 382 |
-
estava
|
| 383 |
-
estávamos
|
| 384 |
-
estavam
|
| 385 |
-
estivera
|
| 386 |
-
estivéramos
|
| 387 |
-
esteja
|
| 388 |
-
estejamos
|
| 389 |
-
estejam
|
| 390 |
-
estivesse
|
| 391 |
-
estivéssemos
|
| 392 |
-
estivessem
|
| 393 |
-
estiver
|
| 394 |
-
estivermos
|
| 395 |
-
estiverem
|
| 396 |
-
hei
|
| 397 |
-
há
|
| 398 |
-
havemos
|
| 399 |
-
hão
|
| 400 |
-
houve
|
| 401 |
-
houvemos
|
| 402 |
-
houveram
|
| 403 |
-
houvera
|
| 404 |
-
houvéramos
|
| 405 |
-
haja
|
| 406 |
-
hajamos
|
| 407 |
-
hajam
|
| 408 |
-
houvesse
|
| 409 |
-
houvéssemos
|
| 410 |
-
houvessem
|
| 411 |
-
houver
|
| 412 |
-
houvermos
|
| 413 |
-
houverem
|
| 414 |
-
houverei
|
| 415 |
-
houverá
|
| 416 |
-
houveremos
|
| 417 |
-
houverão
|
| 418 |
-
houveria
|
| 419 |
-
houveríamos
|
| 420 |
-
houveriam
|
| 421 |
-
sou
|
| 422 |
-
somos
|
| 423 |
-
são
|
| 424 |
-
era
|
| 425 |
-
éramos
|
| 426 |
-
eram
|
| 427 |
-
fui
|
| 428 |
-
foi
|
| 429 |
-
fomos
|
| 430 |
-
foram
|
| 431 |
-
fora
|
| 432 |
-
fôramos
|
| 433 |
-
seja
|
| 434 |
-
sejamos
|
| 435 |
-
sejam
|
| 436 |
-
fosse
|
| 437 |
-
fôssemos
|
| 438 |
-
fossem
|
| 439 |
-
for
|
| 440 |
-
formos
|
| 441 |
-
forem
|
| 442 |
-
serei
|
| 443 |
-
será
|
| 444 |
-
seremos
|
| 445 |
-
serão
|
| 446 |
-
seria
|
| 447 |
-
seríamos
|
| 448 |
-
seriam
|
| 449 |
-
tenho
|
| 450 |
-
tem
|
| 451 |
-
temos
|
| 452 |
-
tém
|
| 453 |
-
tinha
|
| 454 |
-
tínhamos
|
| 455 |
-
tinham
|
| 456 |
-
tive
|
| 457 |
-
teve
|
| 458 |
-
tivemos
|
| 459 |
-
tiveram
|
| 460 |
-
tivera
|
| 461 |
-
tivéramos
|
| 462 |
-
tenha
|
| 463 |
-
tenhamos
|
| 464 |
-
tenham
|
| 465 |
-
tivesse
|
| 466 |
-
tivéssemos
|
| 467 |
-
tivessem
|
| 468 |
-
tiver
|
| 469 |
-
tivermos
|
| 470 |
-
tiverem
|
| 471 |
-
terei
|
| 472 |
-
terá
|
| 473 |
-
teremos
|
| 474 |
-
terão
|
| 475 |
-
teria
|
| 476 |
-
teríamos
|
| 477 |
-
teriam
|
| 478 |
-
dá
|
| 479 |
-
pode
|
| 480 |
-
poder
|
| 481 |
-
podem
|
| 482 |
-
poderia
|
| 483 |
-
poderiam
|
| 484 |
-
fazer
|
| 485 |
-
feito
|
| 486 |
-
faz
|
| 487 |
-
fazem
|
| 488 |
-
dizer
|
| 489 |
-
diz
|
| 490 |
-
disse
|
| 491 |
-
dizem
|
| 492 |
-
coisa
|
| 493 |
-
coisas
|
| 494 |
-
tudo
|
| 495 |
-
todo
|
| 496 |
-
toda
|
| 497 |
-
todos
|
| 498 |
-
todas
|
| 499 |
-
algo
|
| 500 |
-
alguém
|
| 501 |
-
algum
|
| 502 |
-
alguma
|
| 503 |
-
alguns
|
| 504 |
-
algumas
|
| 505 |
-
nada
|
| 506 |
-
ninguém
|
| 507 |
-
nenhum
|
| 508 |
-
nenhuma
|
| 509 |
-
cada
|
| 510 |
-
onde
|
| 511 |
-
aonde
|
| 512 |
-
qualquer
|
| 513 |
-
vários
|
| 514 |
-
várias
|
| 515 |
-
apenas
|
| 516 |
-
somente
|
| 517 |
-
através
|
| 518 |
-
mediante
|
| 519 |
-
conforme
|
| 520 |
-
segundo
|
| 521 |
-
visto
|
| 522 |
-
dado
|
| 523 |
-
sendo
|
| 524 |
-
tendo
|
| 525 |
-
havendo
|
| 526 |
-
ficando
|
| 527 |
-
geral
|
| 528 |
-
grande
|
| 529 |
-
pequeno
|
| 530 |
-
novo
|
| 531 |
-
nova
|
| 532 |
-
velho
|
| 533 |
-
velha
|
| 534 |
-
bom
|
| 535 |
-
boa
|
| 536 |
-
mau
|
| 537 |
-
má
|
| 538 |
-
alto
|
| 539 |
-
baixo
|
| 540 |
-
primeiro
|
| 541 |
-
segundo
|
| 542 |
-
terceiro
|
| 543 |
-
último
|
| 544 |
-
próximo
|
| 545 |
-
anterior
|
| 546 |
seguinte
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# STOPWORDS DO AETHER MAP - LISTA MESTRA (PT + EN)
|
| 3 |
+
# ==============================================================================
|
| 4 |
+
|
| 5 |
+
# --- TERMOS DE SISTEMA E WEB ---
|
| 6 |
+
http
|
| 7 |
+
https
|
| 8 |
+
www
|
| 9 |
+
com
|
| 10 |
+
br
|
| 11 |
+
org
|
| 12 |
+
net
|
| 13 |
+
html
|
| 14 |
+
php
|
| 15 |
+
jsp
|
| 16 |
+
asp
|
| 17 |
+
pdf
|
| 18 |
+
docx
|
| 19 |
+
xlsx
|
| 20 |
+
json
|
| 21 |
+
api
|
| 22 |
+
id
|
| 23 |
+
url
|
| 24 |
+
email
|
| 25 |
+
site
|
| 26 |
+
website
|
| 27 |
+
page
|
| 28 |
+
pagina
|
| 29 |
+
link
|
| 30 |
+
click
|
| 31 |
+
login
|
| 32 |
+
|
| 33 |
+
# --- TERMOS GENÉRICOS DE DOCUMENTOS ---
|
| 34 |
+
document
|
| 35 |
+
documento
|
| 36 |
+
texto
|
| 37 |
+
text
|
| 38 |
+
file
|
| 39 |
+
arquivo
|
| 40 |
+
data
|
| 41 |
+
dados
|
| 42 |
+
database
|
| 43 |
+
base
|
| 44 |
+
dataset
|
| 45 |
+
sample
|
| 46 |
+
amostra
|
| 47 |
+
example
|
| 48 |
+
exemplo
|
| 49 |
+
case
|
| 50 |
+
caso
|
| 51 |
+
study
|
| 52 |
+
estudo
|
| 53 |
+
analysis
|
| 54 |
+
analise
|
| 55 |
+
análise
|
| 56 |
+
report
|
| 57 |
+
relatorio
|
| 58 |
+
relatório
|
| 59 |
+
paper
|
| 60 |
+
artigo
|
| 61 |
+
results
|
| 62 |
+
resultados
|
| 63 |
+
conclusion
|
| 64 |
+
conclusão
|
| 65 |
+
introduction
|
| 66 |
+
introdução
|
| 67 |
+
abstract
|
| 68 |
+
resumo
|
| 69 |
+
chapter
|
| 70 |
+
capitulo
|
| 71 |
+
capítulo
|
| 72 |
+
section
|
| 73 |
+
seção
|
| 74 |
+
part
|
| 75 |
+
parte
|
| 76 |
+
figure
|
| 77 |
+
figura
|
| 78 |
+
fig
|
| 79 |
+
table
|
| 80 |
+
tabela
|
| 81 |
+
tab
|
| 82 |
+
chart
|
| 83 |
+
grafico
|
| 84 |
+
gráfico
|
| 85 |
+
image
|
| 86 |
+
imagem
|
| 87 |
+
source
|
| 88 |
+
fonte
|
| 89 |
+
reference
|
| 90 |
+
referencia
|
| 91 |
+
referência
|
| 92 |
+
bibliography
|
| 93 |
+
bibliografia
|
| 94 |
+
et
|
| 95 |
+
al
|
| 96 |
+
citation
|
| 97 |
+
citação
|
| 98 |
+
|
| 99 |
+
# --- INGLÊS ACADÊMICO E "FILLER WORDS" ---
|
| 100 |
+
the
|
| 101 |
+
be
|
| 102 |
+
to
|
| 103 |
+
of
|
| 104 |
+
and
|
| 105 |
+
a
|
| 106 |
+
in
|
| 107 |
+
that
|
| 108 |
+
have
|
| 109 |
+
i
|
| 110 |
+
it
|
| 111 |
+
for
|
| 112 |
+
not
|
| 113 |
+
on
|
| 114 |
+
with
|
| 115 |
+
he
|
| 116 |
+
as
|
| 117 |
+
you
|
| 118 |
+
do
|
| 119 |
+
at
|
| 120 |
+
this
|
| 121 |
+
but
|
| 122 |
+
his
|
| 123 |
+
by
|
| 124 |
+
from
|
| 125 |
+
they
|
| 126 |
+
we
|
| 127 |
+
say
|
| 128 |
+
her
|
| 129 |
+
she
|
| 130 |
+
or
|
| 131 |
+
an
|
| 132 |
+
will
|
| 133 |
+
my
|
| 134 |
+
one
|
| 135 |
+
all
|
| 136 |
+
would
|
| 137 |
+
there
|
| 138 |
+
their
|
| 139 |
+
what
|
| 140 |
+
so
|
| 141 |
+
up
|
| 142 |
+
out
|
| 143 |
+
if
|
| 144 |
+
about
|
| 145 |
+
who
|
| 146 |
+
get
|
| 147 |
+
which
|
| 148 |
+
go
|
| 149 |
+
me
|
| 150 |
+
when
|
| 151 |
+
make
|
| 152 |
+
can
|
| 153 |
+
like
|
| 154 |
+
time
|
| 155 |
+
no
|
| 156 |
+
just
|
| 157 |
+
him
|
| 158 |
+
know
|
| 159 |
+
take
|
| 160 |
+
people
|
| 161 |
+
into
|
| 162 |
+
year
|
| 163 |
+
your
|
| 164 |
+
good
|
| 165 |
+
some
|
| 166 |
+
could
|
| 167 |
+
them
|
| 168 |
+
see
|
| 169 |
+
other
|
| 170 |
+
than
|
| 171 |
+
then
|
| 172 |
+
now
|
| 173 |
+
look
|
| 174 |
+
only
|
| 175 |
+
come
|
| 176 |
+
its
|
| 177 |
+
over
|
| 178 |
+
think
|
| 179 |
+
also
|
| 180 |
+
back
|
| 181 |
+
after
|
| 182 |
+
use
|
| 183 |
+
two
|
| 184 |
+
how
|
| 185 |
+
our
|
| 186 |
+
work
|
| 187 |
+
first
|
| 188 |
+
well
|
| 189 |
+
way
|
| 190 |
+
even
|
| 191 |
+
new
|
| 192 |
+
want
|
| 193 |
+
because
|
| 194 |
+
any
|
| 195 |
+
these
|
| 196 |
+
give
|
| 197 |
+
day
|
| 198 |
+
most
|
| 199 |
+
us
|
| 200 |
+
is
|
| 201 |
+
are
|
| 202 |
+
was
|
| 203 |
+
were
|
| 204 |
+
been
|
| 205 |
+
has
|
| 206 |
+
had
|
| 207 |
+
did
|
| 208 |
+
does
|
| 209 |
+
may
|
| 210 |
+
might
|
| 211 |
+
should
|
| 212 |
+
must
|
| 213 |
+
shall
|
| 214 |
+
used
|
| 215 |
+
using
|
| 216 |
+
uses
|
| 217 |
+
based
|
| 218 |
+
found
|
| 219 |
+
show
|
| 220 |
+
shown
|
| 221 |
+
shows
|
| 222 |
+
suggest
|
| 223 |
+
suggests
|
| 224 |
+
however
|
| 225 |
+
therefore
|
| 226 |
+
thus
|
| 227 |
+
hence
|
| 228 |
+
although
|
| 229 |
+
though
|
| 230 |
+
whereas
|
| 231 |
+
while
|
| 232 |
+
meanwhile
|
| 233 |
+
furthermore
|
| 234 |
+
moreover
|
| 235 |
+
additionally
|
| 236 |
+
besides
|
| 237 |
+
indeed
|
| 238 |
+
fact
|
| 239 |
+
overall
|
| 240 |
+
general
|
| 241 |
+
specific
|
| 242 |
+
significantly
|
| 243 |
+
associated
|
| 244 |
+
related
|
| 245 |
+
various
|
| 246 |
+
several
|
| 247 |
+
many
|
| 248 |
+
much
|
| 249 |
+
less
|
| 250 |
+
more
|
| 251 |
+
high
|
| 252 |
+
low
|
| 253 |
+
increase
|
| 254 |
+
decrease
|
| 255 |
+
positive
|
| 256 |
+
negative
|
| 257 |
+
|
| 258 |
+
# --- PORTUGUÊS ACADÊMICO E "PALAVRAS VAZIAS" ---
|
| 259 |
+
de
|
| 260 |
+
a
|
| 261 |
+
o
|
| 262 |
+
que
|
| 263 |
+
e
|
| 264 |
+
do
|
| 265 |
+
da
|
| 266 |
+
em
|
| 267 |
+
um
|
| 268 |
+
para
|
| 269 |
+
é
|
| 270 |
+
com
|
| 271 |
+
não
|
| 272 |
+
uma
|
| 273 |
+
os
|
| 274 |
+
no
|
| 275 |
+
se
|
| 276 |
+
na
|
| 277 |
+
por
|
| 278 |
+
mais
|
| 279 |
+
as
|
| 280 |
+
dos
|
| 281 |
+
como
|
| 282 |
+
mas
|
| 283 |
+
foi
|
| 284 |
+
ao
|
| 285 |
+
ele
|
| 286 |
+
das
|
| 287 |
+
tem
|
| 288 |
+
à
|
| 289 |
+
seu
|
| 290 |
+
sua
|
| 291 |
+
ou
|
| 292 |
+
ser
|
| 293 |
+
quando
|
| 294 |
+
muito
|
| 295 |
+
há
|
| 296 |
+
nos
|
| 297 |
+
já
|
| 298 |
+
está
|
| 299 |
+
eu
|
| 300 |
+
também
|
| 301 |
+
só
|
| 302 |
+
pelo
|
| 303 |
+
pela
|
| 304 |
+
até
|
| 305 |
+
isso
|
| 306 |
+
ela
|
| 307 |
+
entre
|
| 308 |
+
era
|
| 309 |
+
depois
|
| 310 |
+
sem
|
| 311 |
+
mesmo
|
| 312 |
+
aos
|
| 313 |
+
ter
|
| 314 |
+
seus
|
| 315 |
+
quem
|
| 316 |
+
nas
|
| 317 |
+
me
|
| 318 |
+
esse
|
| 319 |
+
eles
|
| 320 |
+
estão
|
| 321 |
+
você
|
| 322 |
+
tinha
|
| 323 |
+
foram
|
| 324 |
+
essa
|
| 325 |
+
num
|
| 326 |
+
nem
|
| 327 |
+
suas
|
| 328 |
+
meu
|
| 329 |
+
às
|
| 330 |
+
minha
|
| 331 |
+
numa
|
| 332 |
+
pelos
|
| 333 |
+
elas
|
| 334 |
+
havia
|
| 335 |
+
seja
|
| 336 |
+
qual
|
| 337 |
+
será
|
| 338 |
+
nós
|
| 339 |
+
tenho
|
| 340 |
+
lhe
|
| 341 |
+
deles
|
| 342 |
+
essas
|
| 343 |
+
esses
|
| 344 |
+
pelas
|
| 345 |
+
este
|
| 346 |
+
fosse
|
| 347 |
+
dele
|
| 348 |
+
tu
|
| 349 |
+
te
|
| 350 |
+
vocês
|
| 351 |
+
vos
|
| 352 |
+
lhes
|
| 353 |
+
meus
|
| 354 |
+
minhas
|
| 355 |
+
teu
|
| 356 |
+
tua
|
| 357 |
+
teus
|
| 358 |
+
tuas
|
| 359 |
+
nosso
|
| 360 |
+
nossa
|
| 361 |
+
nossos
|
| 362 |
+
nossas
|
| 363 |
+
dela
|
| 364 |
+
delas
|
| 365 |
+
esta
|
| 366 |
+
estes
|
| 367 |
+
estas
|
| 368 |
+
aquele
|
| 369 |
+
aquela
|
| 370 |
+
aqueles
|
| 371 |
+
aquelas
|
| 372 |
+
isto
|
| 373 |
+
aquilo
|
| 374 |
+
estou
|
| 375 |
+
está
|
| 376 |
+
estamos
|
| 377 |
+
estão
|
| 378 |
+
estive
|
| 379 |
+
esteve
|
| 380 |
+
estivemos
|
| 381 |
+
estiveram
|
| 382 |
+
estava
|
| 383 |
+
estávamos
|
| 384 |
+
estavam
|
| 385 |
+
estivera
|
| 386 |
+
estivéramos
|
| 387 |
+
esteja
|
| 388 |
+
estejamos
|
| 389 |
+
estejam
|
| 390 |
+
estivesse
|
| 391 |
+
estivéssemos
|
| 392 |
+
estivessem
|
| 393 |
+
estiver
|
| 394 |
+
estivermos
|
| 395 |
+
estiverem
|
| 396 |
+
hei
|
| 397 |
+
há
|
| 398 |
+
havemos
|
| 399 |
+
hão
|
| 400 |
+
houve
|
| 401 |
+
houvemos
|
| 402 |
+
houveram
|
| 403 |
+
houvera
|
| 404 |
+
houvéramos
|
| 405 |
+
haja
|
| 406 |
+
hajamos
|
| 407 |
+
hajam
|
| 408 |
+
houvesse
|
| 409 |
+
houvéssemos
|
| 410 |
+
houvessem
|
| 411 |
+
houver
|
| 412 |
+
houvermos
|
| 413 |
+
houverem
|
| 414 |
+
houverei
|
| 415 |
+
houverá
|
| 416 |
+
houveremos
|
| 417 |
+
houverão
|
| 418 |
+
houveria
|
| 419 |
+
houveríamos
|
| 420 |
+
houveriam
|
| 421 |
+
sou
|
| 422 |
+
somos
|
| 423 |
+
são
|
| 424 |
+
era
|
| 425 |
+
éramos
|
| 426 |
+
eram
|
| 427 |
+
fui
|
| 428 |
+
foi
|
| 429 |
+
fomos
|
| 430 |
+
foram
|
| 431 |
+
fora
|
| 432 |
+
fôramos
|
| 433 |
+
seja
|
| 434 |
+
sejamos
|
| 435 |
+
sejam
|
| 436 |
+
fosse
|
| 437 |
+
fôssemos
|
| 438 |
+
fossem
|
| 439 |
+
for
|
| 440 |
+
formos
|
| 441 |
+
forem
|
| 442 |
+
serei
|
| 443 |
+
será
|
| 444 |
+
seremos
|
| 445 |
+
serão
|
| 446 |
+
seria
|
| 447 |
+
seríamos
|
| 448 |
+
seriam
|
| 449 |
+
tenho
|
| 450 |
+
tem
|
| 451 |
+
temos
|
| 452 |
+
tém
|
| 453 |
+
tinha
|
| 454 |
+
tínhamos
|
| 455 |
+
tinham
|
| 456 |
+
tive
|
| 457 |
+
teve
|
| 458 |
+
tivemos
|
| 459 |
+
tiveram
|
| 460 |
+
tivera
|
| 461 |
+
tivéramos
|
| 462 |
+
tenha
|
| 463 |
+
tenhamos
|
| 464 |
+
tenham
|
| 465 |
+
tivesse
|
| 466 |
+
tivéssemos
|
| 467 |
+
tivessem
|
| 468 |
+
tiver
|
| 469 |
+
tivermos
|
| 470 |
+
tiverem
|
| 471 |
+
terei
|
| 472 |
+
terá
|
| 473 |
+
teremos
|
| 474 |
+
terão
|
| 475 |
+
teria
|
| 476 |
+
teríamos
|
| 477 |
+
teriam
|
| 478 |
+
dá
|
| 479 |
+
pode
|
| 480 |
+
poder
|
| 481 |
+
podem
|
| 482 |
+
poderia
|
| 483 |
+
poderiam
|
| 484 |
+
fazer
|
| 485 |
+
feito
|
| 486 |
+
faz
|
| 487 |
+
fazem
|
| 488 |
+
dizer
|
| 489 |
+
diz
|
| 490 |
+
disse
|
| 491 |
+
dizem
|
| 492 |
+
coisa
|
| 493 |
+
coisas
|
| 494 |
+
tudo
|
| 495 |
+
todo
|
| 496 |
+
toda
|
| 497 |
+
todos
|
| 498 |
+
todas
|
| 499 |
+
algo
|
| 500 |
+
alguém
|
| 501 |
+
algum
|
| 502 |
+
alguma
|
| 503 |
+
alguns
|
| 504 |
+
algumas
|
| 505 |
+
nada
|
| 506 |
+
ninguém
|
| 507 |
+
nenhum
|
| 508 |
+
nenhuma
|
| 509 |
+
cada
|
| 510 |
+
onde
|
| 511 |
+
aonde
|
| 512 |
+
qualquer
|
| 513 |
+
vários
|
| 514 |
+
várias
|
| 515 |
+
apenas
|
| 516 |
+
somente
|
| 517 |
+
através
|
| 518 |
+
mediante
|
| 519 |
+
conforme
|
| 520 |
+
segundo
|
| 521 |
+
visto
|
| 522 |
+
dado
|
| 523 |
+
sendo
|
| 524 |
+
tendo
|
| 525 |
+
havendo
|
| 526 |
+
ficando
|
| 527 |
+
geral
|
| 528 |
+
grande
|
| 529 |
+
pequeno
|
| 530 |
+
novo
|
| 531 |
+
nova
|
| 532 |
+
velho
|
| 533 |
+
velha
|
| 534 |
+
bom
|
| 535 |
+
boa
|
| 536 |
+
mau
|
| 537 |
+
má
|
| 538 |
+
alto
|
| 539 |
+
baixo
|
| 540 |
+
primeiro
|
| 541 |
+
segundo
|
| 542 |
+
terceiro
|
| 543 |
+
último
|
| 544 |
+
próximo
|
| 545 |
+
anterior
|
| 546 |
seguinte
|